summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp3
-rw-r--r--apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java5
-rw-r--r--apex/appsearch/framework/java/external/android/app/appsearch/AppSearchBatchResult.java11
-rw-r--r--apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java2
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java358
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java45
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLogger.java4
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java44
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/SearchStats.java2
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/SetSchemaStats.java17
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java6
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStoreImpl.java3
-rw-r--r--apex/appsearch/synced_jetpack_changeid.txt2
-rw-r--r--apex/appsearch/testing/Android.bp1
-rw-r--r--apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java56
-rw-r--r--core/api/current.txt102
-rw-r--r--core/api/system-current.txt51
-rw-r--r--core/api/test-current.txt105
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java107
-rw-r--r--core/java/android/accessibilityservice/AccessibilityTrace.java216
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl4
-rw-r--r--core/java/android/app/Activity.java47
-rw-r--r--core/java/android/app/ActivityClient.java14
-rw-r--r--core/java/android/app/ActivityManagerInternal.java24
-rw-r--r--core/java/android/app/ActivityOptions.java46
-rw-r--r--core/java/android/app/ActivityTaskManager.java13
-rw-r--r--core/java/android/app/ActivityThread.java222
-rw-r--r--core/java/android/app/ActivityThreadInternal.java4
-rw-r--r--core/java/android/app/ActivityTransitionCoordinator.java1
-rw-r--r--core/java/android/app/Application.java17
-rw-r--r--core/java/android/app/ClientTransactionHandler.java7
-rw-r--r--core/java/android/app/ConfigurationController.java38
-rw-r--r--core/java/android/app/ContextImpl.java8
-rw-r--r--core/java/android/app/DirectAction.java3
-rw-r--r--core/java/android/app/DisabledWallpaperManager.java31
-rw-r--r--core/java/android/app/IActivityClientController.aidl1
-rw-r--r--core/java/android/app/IActivityTaskManager.aidl14
-rw-r--r--core/java/android/app/Instrumentation.java42
-rw-r--r--core/java/android/app/LoadedApk.java34
-rw-r--r--core/java/android/app/ResourcesManager.java2
-rw-r--r--core/java/android/app/StatusBarManager.java3
-rw-r--r--core/java/android/app/TaskInfo.java41
-rw-r--r--core/java/android/app/WallpaperInfo.java28
-rw-r--r--core/java/android/app/WallpaperManager.java29
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java74
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl3
-rw-r--r--core/java/android/app/compat/PackageOverride.java17
-rw-r--r--core/java/android/app/servertransaction/ActivityResultItem.java19
-rw-r--r--core/java/android/app/servertransaction/ActivityTransactionItem.java31
-rw-r--r--core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java38
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java18
-rw-r--r--core/java/android/companion/ICompanionDeviceDiscoveryService.aidl2
-rw-r--r--core/java/android/content/ClipDescription.java10
-rw-r--r--core/java/android/content/Context.java6
-rw-r--r--core/java/android/content/pm/ActivityInfo.java26
-rw-r--r--core/java/android/content/pm/PackageManager.java11
-rw-r--r--core/java/android/hardware/biometrics/BiometricOverlayConstants.java50
-rw-r--r--core/java/android/hardware/biometrics/SensorLocationInternal.aidl19
-rw-r--r--core/java/android/hardware/biometrics/SensorLocationInternal.java112
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java116
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java95
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java25
-rw-r--r--core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java156
-rw-r--r--core/java/android/hardware/devicestate/DeviceStateManager.java22
-rw-r--r--core/java/android/hardware/devicestate/DeviceStateManagerInternal.java (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt)17
-rw-r--r--core/java/android/hardware/display/AmbientDisplayConfiguration.java25
-rw-r--r--core/java/android/hardware/display/DisplayManager.java39
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java32
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java32
-rw-r--r--core/java/android/hardware/display/IDisplayManager.aidl10
-rw-r--r--core/java/android/hardware/display/VirtualDisplay.java10
-rw-r--r--core/java/android/hardware/display/VirtualDisplayConfig.java56
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java83
-rw-r--r--core/java/android/hardware/fingerprint/ISidefpsController.aidl6
-rw-r--r--core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl9
-rw-r--r--core/java/android/inputmethodservice/AbstractInputMethodService.java50
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java11
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java26
-rw-r--r--core/java/android/net/NetworkTemplate.java52
-rw-r--r--core/java/android/os/BatteryStats.java97
-rwxr-xr-xcore/java/android/os/Build.java11
-rw-r--r--core/java/android/os/Environment.java16
-rw-r--r--core/java/android/os/PowerManager.java8
-rw-r--r--core/java/android/provider/DeviceConfig.java8
-rw-r--r--core/java/android/provider/Settings.java118
-rw-r--r--core/java/android/provider/Telephony.java9
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java27
-rw-r--r--core/java/android/service/voice/IVoiceInteractionSession.aidl2
-rw-r--r--core/java/android/service/voice/VisibleActivityInfo.aidl19
-rw-r--r--core/java/android/service/voice/VisibleActivityInfo.java205
-rw-r--r--core/java/android/service/voice/VoiceInteractionManagerInternal.java1
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java171
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java189
-rw-r--r--core/java/android/util/DisplayUtils.java54
-rw-r--r--core/java/android/util/FeatureFlagUtils.java5
-rw-r--r--core/java/android/util/MathUtils.java4
-rw-r--r--core/java/android/util/imetracing/ImeTracingServerImpl.java6
-rw-r--r--core/java/android/view/AttachedSurfaceControl.java71
-rw-r--r--core/java/android/view/Display.java6
-rw-r--r--core/java/android/view/DisplayCutout.java124
-rw-r--r--core/java/android/view/DisplayInfo.java15
-rw-r--r--core/java/android/view/IDisplayWindowListener.aidl3
-rw-r--r--core/java/android/view/IWindowManager.aidl54
-rw-r--r--core/java/android/view/IWindowSession.aidl15
-rw-r--r--core/java/android/view/InputWindowHandle.java36
-rw-r--r--core/java/android/view/InsetsAnimationControlCallbacks.java10
-rw-r--r--core/java/android/view/InsetsAnimationControlImpl.java9
-rw-r--r--core/java/android/view/InsetsAnimationThreadControlRunner.java4
-rw-r--r--core/java/android/view/InsetsController.java134
-rw-r--r--core/java/android/view/InsetsResizeAnimationRunner.java235
-rw-r--r--core/java/android/view/InsetsSourceControl.java9
-rw-r--r--core/java/android/view/InsetsState.java21
-rw-r--r--core/java/android/view/InsetsVisibilities.aidl19
-rw-r--r--core/java/android/view/InsetsVisibilities.java134
-rw-r--r--core/java/android/view/InternalInsetsAnimationController.java41
-rw-r--r--core/java/android/view/MotionEvent.java30
-rw-r--r--core/java/android/view/RemoteAnimationAdapter.java22
-rw-r--r--core/java/android/view/RemoteAnimationTarget.java31
-rw-r--r--core/java/android/view/RoundedCorners.java174
-rw-r--r--core/java/android/view/Surface.java15
-rw-r--r--core/java/android/view/SurfaceControl.java142
-rw-r--r--core/java/android/view/SurfaceControlViewHost.java13
-rw-r--r--core/java/android/view/SurfaceView.java90
-rw-r--r--core/java/android/view/TaskTransitionSpec.aidl20
-rw-r--r--core/java/android/view/TaskTransitionSpec.java91
-rw-r--r--core/java/android/view/View.java147
-rw-r--r--core/java/android/view/ViewConfiguration.java5
-rw-r--r--core/java/android/view/ViewParent.java3
-rw-r--r--core/java/android/view/ViewRootImpl.java161
-rw-r--r--core/java/android/view/ViewRootInsetsControllerHost.java4
-rw-r--r--core/java/android/view/WindowInfo.java7
-rw-r--r--core/java/android/view/WindowInsets.java10
-rw-r--r--core/java/android/view/WindowManager.java160
-rw-r--r--core/java/android/view/WindowManagerGlobal.java11
-rw-r--r--core/java/android/view/WindowManagerImpl.java117
-rw-r--r--core/java/android/view/WindowManagerPolicyConstants.java37
-rw-r--r--core/java/android/view/WindowlessWindowManager.java13
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java39
-rw-r--r--core/java/android/view/accessibility/AccessibilityInteractionClient.java202
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java65
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java49
-rw-r--r--core/java/android/view/accessibility/AccessibilityWindowInfo.java29
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureContext.java27
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureEvent.java42
-rw-r--r--core/java/android/view/contentcapture/MainContentCaptureSession.java10
-rw-r--r--core/java/android/view/inputmethod/InputMethod.java15
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java1
-rw-r--r--core/java/android/view/translation/UiTranslationController.java27
-rw-r--r--core/java/android/widget/RemoteViews.java9
-rw-r--r--core/java/android/widget/RemoteViewsAdapter.java2
-rw-r--r--core/java/android/window/ConfigurationHelper.java132
-rw-r--r--core/java/android/window/DisplayAreaInfo.java13
-rw-r--r--core/java/android/window/DisplayAreaOrganizer.java11
-rw-r--r--core/java/android/window/IRemoteTransitionFinishedCallback.aidl6
-rw-r--r--core/java/android/window/ITaskFragmentOrganizer.aidl52
-rw-r--r--core/java/android/window/ITaskFragmentOrganizerController.aidl47
-rw-r--r--core/java/android/window/ITaskOrganizer.aidl8
-rw-r--r--core/java/android/window/ITransitionMetricsReporter.aidl33
-rw-r--r--core/java/android/window/IWindowOrganizerController.aidl20
-rw-r--r--core/java/android/window/RemoteTransition.aidl19
-rw-r--r--core/java/android/window/RemoteTransition.java158
-rw-r--r--core/java/android/window/SizeConfigurationBuckets.java156
-rw-r--r--core/java/android/window/SplashScreen.java5
-rw-r--r--core/java/android/window/SplashScreenView.java74
-rw-r--r--core/java/android/window/StartingWindowInfo.java17
-rw-r--r--core/java/android/window/StartingWindowRemovalInfo.aidl20
-rw-r--r--core/java/android/window/StartingWindowRemovalInfo.java111
-rw-r--r--core/java/android/window/TaskFragmentAppearedInfo.aidl23
-rw-r--r--core/java/android/window/TaskFragmentAppearedInfo.java93
-rw-r--r--core/java/android/window/TaskFragmentCreationParams.aidl23
-rw-r--r--core/java/android/window/TaskFragmentCreationParams.java193
-rw-r--r--core/java/android/window/TaskFragmentInfo.aidl23
-rw-r--r--core/java/android/window/TaskFragmentInfo.java226
-rw-r--r--core/java/android/window/TaskFragmentOrganizer.java220
-rw-r--r--core/java/android/window/TaskFragmentOrganizerToken.java97
-rw-r--r--core/java/android/window/TaskOrganizer.java19
-rw-r--r--core/java/android/window/TransitionFilter.java119
-rw-r--r--core/java/android/window/TransitionInfo.java250
-rw-r--r--core/java/android/window/TransitionMetrics.java57
-rw-r--r--core/java/android/window/TransitionRequestInfo.java20
-rw-r--r--core/java/android/window/WindowContainerTransaction.java591
-rw-r--r--core/java/android/window/WindowContext.java51
-rw-r--r--core/java/android/window/WindowContextController.java3
-rw-r--r--core/java/android/window/WindowInfosListener.java63
-rw-r--r--core/java/android/window/WindowOrganizer.java55
-rw-r--r--core/java/android/window/WindowProvider.java39
-rw-r--r--core/java/android/window/WindowProviderService.java88
-rw-r--r--core/java/android/window/WindowTokenClient.java86
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java18
-rw-r--r--core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java20
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl10
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodDebug.java2
-rw-r--r--core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java10
-rw-r--r--core/java/com/android/internal/jank/FrameTracker.java379
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java317
-rw-r--r--core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java18
-rw-r--r--core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java43
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java463
-rw-r--r--core/java/com/android/internal/os/PowerCalculator.java26
-rw-r--r--core/java/com/android/internal/os/PowerProfile.java122
-rw-r--r--core/java/com/android/internal/os/ScreenPowerCalculator.java53
-rw-r--r--core/java/com/android/internal/policy/IKeyguardStateCallback.aidl1
-rw-r--r--core/java/com/android/internal/policy/ScreenDecorationsUtils.java23
-rw-r--r--core/java/com/android/internal/policy/SystemBarUtils.java94
-rw-r--r--core/java/com/android/internal/policy/TransitionAnimation.java273
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogGroup.java5
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogImpl.java2
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl8
-rw-r--r--core/java/com/android/internal/statusbar/RegisterStatusBarResult.java20
-rw-r--r--core/java/com/android/internal/util/LatencyTracker.java166
-rw-r--r--core/java/com/android/internal/view/IInputMethod.aidl2
-rw-r--r--core/java/com/android/internal/view/ScrollCaptureInternal.java38
-rw-r--r--core/java/com/android/internal/view/WebViewCaptureHelper.java100
-rw-r--r--core/java/com/android/internal/widget/PointerLocationView.java153
-rw-r--r--core/jni/Android.bp6
-rw-r--r--core/jni/AndroidRuntime.cpp3
-rw-r--r--core/jni/OWNERS1
-rw-r--r--core/jni/android_hardware_input_InputApplicationHandle.cpp19
-rw-r--r--core/jni/android_hardware_input_InputApplicationHandle.h5
-rw-r--r--core/jni/android_hardware_input_InputWindowHandle.cpp127
-rw-r--r--core/jni/android_hardware_input_InputWindowHandle.h8
-rw-r--r--core/jni/android_media_AudioDeviceAttributes.cpp27
-rw-r--r--core/jni/android_media_AudioDeviceAttributes.h3
-rw-r--r--core/jni/android_media_AudioSystem.cpp89
-rw-r--r--core/jni/android_util_AssetManager.cpp4
-rw-r--r--core/jni/android_view_InputEventSender.cpp1
-rw-r--r--core/jni/android_view_MotionEvent.cpp25
-rw-r--r--core/jni/android_view_SurfaceControl.cpp46
-rw-r--r--core/jni/android_window_WindowInfosListener.cpp134
-rw-r--r--core/proto/android/providers/settings/secure.proto2
-rw-r--r--core/proto/android/server/accessibilitytrace.proto18
-rw-r--r--core/proto/android/server/windowmanagerservice.proto30
-rw-r--r--core/proto/android/view/surfacecontrol.proto1
-rw-r--r--core/res/AndroidManifest.xml20
-rw-r--r--core/res/OWNERS6
-rw-r--r--core/res/res/drawable-nodpi/default_wallpaper.pngbin489912 -> 738385 bytes
-rw-r--r--core/res/res/drawable-sw600dp-nodpi/default_wallpaper.pngbin1197339 -> 2774036 bytes
-rw-r--r--core/res/res/drawable-sw720dp-nodpi/default_wallpaper.pngbin1930496 -> 4958722 bytes
-rw-r--r--core/res/res/drawable/ic_corp_badge.xml5
-rw-r--r--core/res/res/drawable/ic_corp_badge_case.xml6
-rw-r--r--core/res/res/drawable/ic_corp_badge_no_background.xml2
-rw-r--r--core/res/res/drawable/ic_corp_icon.xml2
-rw-r--r--core/res/res/drawable/ic_corp_icon_badge_case.xml12
-rw-r--r--core/res/res/drawable/ic_corp_statusbar_icon.xml2
-rw-r--r--core/res/res/drawable/ic_corp_user_badge.xml3
-rw-r--r--core/res/res/layout-car/car_alert_dialog.xml2
-rw-r--r--core/res/res/layout-car/car_alert_dialog_button_bar.xml9
-rw-r--r--core/res/res/layout/accessibility_shortcut_chooser_item.xml17
-rw-r--r--core/res/res/layout/alert_dialog.xml6
-rw-r--r--core/res/res/layout/transient_notification.xml1
-rw-r--r--core/res/res/values-af/strings.xml2
-rw-r--r--core/res/res/values-am/strings.xml2
-rw-r--r--core/res/res/values-ar/strings.xml2
-rw-r--r--core/res/res/values-as/strings.xml2
-rw-r--r--core/res/res/values-az/strings.xml2
-rw-r--r--core/res/res/values-b+sr+Latn/strings.xml2
-rw-r--r--core/res/res/values-be/strings.xml2
-rw-r--r--core/res/res/values-bg/strings.xml2
-rw-r--r--core/res/res/values-bn/strings.xml2
-rw-r--r--core/res/res/values-bs/strings.xml2
-rw-r--r--core/res/res/values-ca/strings.xml2
-rw-r--r--core/res/res/values-cs/strings.xml2
-rw-r--r--core/res/res/values-da/strings.xml2
-rw-r--r--core/res/res/values-de/strings.xml2
-rw-r--r--core/res/res/values-el/strings.xml2
-rw-r--r--core/res/res/values-en-rAU/strings.xml2
-rw-r--r--core/res/res/values-en-rCA/strings.xml2
-rw-r--r--core/res/res/values-en-rGB/strings.xml2
-rw-r--r--core/res/res/values-en-rIN/strings.xml2
-rw-r--r--core/res/res/values-en-rXC/strings.xml2
-rw-r--r--core/res/res/values-es-rUS/strings.xml2
-rw-r--r--core/res/res/values-es/strings.xml2
-rw-r--r--core/res/res/values-et/strings.xml2
-rw-r--r--core/res/res/values-eu/strings.xml2
-rw-r--r--core/res/res/values-fa/strings.xml2
-rw-r--r--core/res/res/values-fi/strings.xml2
-rw-r--r--core/res/res/values-fr-rCA/strings.xml2
-rw-r--r--core/res/res/values-fr/strings.xml2
-rw-r--r--core/res/res/values-gl/strings.xml2
-rw-r--r--core/res/res/values-gu/strings.xml2
-rw-r--r--core/res/res/values-hi/strings.xml2
-rw-r--r--core/res/res/values-hr/strings.xml2
-rw-r--r--core/res/res/values-hu/strings.xml2
-rw-r--r--core/res/res/values-hy/strings.xml2
-rw-r--r--core/res/res/values-in/strings.xml2
-rw-r--r--core/res/res/values-is/strings.xml2
-rw-r--r--core/res/res/values-it/strings.xml2
-rw-r--r--core/res/res/values-iw/strings.xml2
-rw-r--r--core/res/res/values-ja/strings.xml2
-rw-r--r--core/res/res/values-ka/strings.xml2
-rw-r--r--core/res/res/values-kk/strings.xml2
-rw-r--r--core/res/res/values-km/strings.xml2
-rw-r--r--core/res/res/values-kn/strings.xml2
-rw-r--r--core/res/res/values-ko/strings.xml2
-rw-r--r--core/res/res/values-ky/strings.xml2
-rw-r--r--core/res/res/values-land/dimens.xml2
-rw-r--r--core/res/res/values-lo/strings.xml2
-rw-r--r--core/res/res/values-lt/strings.xml2
-rw-r--r--core/res/res/values-lv/strings.xml2
-rw-r--r--core/res/res/values-mk/strings.xml2
-rw-r--r--core/res/res/values-ml/strings.xml2
-rw-r--r--core/res/res/values-mn/strings.xml2
-rw-r--r--core/res/res/values-mr/strings.xml2
-rw-r--r--core/res/res/values-ms/strings.xml2
-rw-r--r--core/res/res/values-my/strings.xml2
-rw-r--r--core/res/res/values-nb/strings.xml2
-rw-r--r--core/res/res/values-ne/strings.xml2
-rw-r--r--core/res/res/values-nl/strings.xml2
-rw-r--r--core/res/res/values-or/strings.xml2
-rw-r--r--core/res/res/values-pa/strings.xml2
-rw-r--r--core/res/res/values-pl/strings.xml2
-rw-r--r--core/res/res/values-pt-rBR/strings.xml2
-rw-r--r--core/res/res/values-pt-rPT/strings.xml2
-rw-r--r--core/res/res/values-pt/strings.xml2
-rw-r--r--core/res/res/values-ro/strings.xml2
-rw-r--r--core/res/res/values-ru/strings.xml2
-rw-r--r--core/res/res/values-si/strings.xml2
-rw-r--r--core/res/res/values-sk/strings.xml2
-rw-r--r--core/res/res/values-sl/strings.xml2
-rw-r--r--core/res/res/values-sq/strings.xml2
-rw-r--r--core/res/res/values-sr/strings.xml2
-rw-r--r--core/res/res/values-sv/strings.xml2
-rw-r--r--core/res/res/values-sw/strings.xml2
-rw-r--r--core/res/res/values-sw600dp/config.xml5
-rw-r--r--core/res/res/values-ta/strings.xml2
-rw-r--r--core/res/res/values-te/strings.xml2
-rw-r--r--core/res/res/values-th/strings.xml2
-rw-r--r--core/res/res/values-tl/strings.xml2
-rw-r--r--core/res/res/values-tr/strings.xml2
-rw-r--r--core/res/res/values-uk/strings.xml2
-rw-r--r--core/res/res/values-ur/strings.xml2
-rw-r--r--core/res/res/values-uz/strings.xml2
-rw-r--r--core/res/res/values-vi/strings.xml2
-rw-r--r--core/res/res/values-zh-rCN/strings.xml2
-rw-r--r--core/res/res/values-zh-rHK/strings.xml2
-rw-r--r--core/res/res/values-zh-rTW/strings.xml2
-rw-r--r--core/res/res/values-zu/strings.xml2
-rw-r--r--core/res/res/values/attrs.xml13
-rw-r--r--core/res/res/values/colors.xml110
-rw-r--r--core/res/res/values/config.xml218
-rw-r--r--core/res/res/values/dimens.xml40
-rw-r--r--core/res/res/values/dimens_car.xml8
-rw-r--r--core/res/res/values/ids.xml9
-rw-r--r--core/res/res/values/public.xml25
-rw-r--r--core/res/res/values/strings.xml2
-rw-r--r--core/res/res/values/styles.xml1
-rw-r--r--core/res/res/values/symbols.xml45
-rw-r--r--core/res/res/xml/power_profile.xml30
-rw-r--r--core/tests/BroadcastRadioTests/OWNERS3
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java3
-rw-r--r--core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java126
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java1
-rw-r--r--core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java14
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java3
-rw-r--r--core/tests/coretests/src/android/content/ContextTest.java8
-rw-r--r--core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java4
-rw-r--r--core/tests/coretests/src/android/view/InsetsControllerTest.java117
-rw-r--r--core/tests/coretests/src/android/view/InsetsStateTest.java16
-rw-r--r--core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java121
-rw-r--r--core/tests/coretests/src/android/view/WindowInfoTest.java3
-rw-r--r--core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java3
-rw-r--r--core/tests/coretests/src/android/view/contentcapture/ContentCaptureContextTest.java4
-rw-r--r--core/tests/coretests/src/android/window/WindowContextControllerTest.java6
-rw-r--r--core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java280
-rw-r--r--core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java108
-rw-r--r--core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java117
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java474
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java14
-rw-r--r--core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java16
-rw-r--r--core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java11
-rw-r--r--core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java225
-rw-r--r--core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java7
-rw-r--r--core/tests/mockingcoretests/src/android/window/ConfigurationHelperTest.java164
-rw-r--r--core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java379
-rw-r--r--data/etc/car/Android.bp7
-rw-r--r--data/etc/car/com.android.car.carlauncher.xml2
-rw-r--r--data/etc/car/com.google.android.car.adaslocation.xml (renamed from packages/overlays/NoCutoutOverlay/res/values-nb/strings.xml)16
-rw-r--r--data/etc/car/com.google.android.car.kitchensink.xml4
-rw-r--r--data/etc/services.core.protolog.json671
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java41
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java72
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java78
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsProvider.java38
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java304
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java86
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java802
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java412
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java189
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java60
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java264
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java215
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java270
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java (renamed from libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java)75
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java4
-rw-r--r--libs/WindowManager/Jetpack/window-extensions-release.aarbin7613 -> 18462 bytes
-rw-r--r--libs/WindowManager/Shell/Android.bp8
-rw-r--r--libs/WindowManager/Shell/res/color/split_divider_background.xml (renamed from packages/overlays/NoCutoutOverlay/res/values-ca/strings.xml)14
-rw-r--r--libs/WindowManager/Shell/res/color/unfold_transition_background.xml (renamed from packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml)17
-rw-r--r--libs/WindowManager/Shell/res/drawable/bubble_manage_btn_bg.xml2
-rw-r--r--libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml (renamed from packages/overlays/NoCutoutOverlay/res/values-et/strings.xml)16
-rw-r--r--libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml (renamed from packages/overlays/AvoidAppsInCutoutOverlay/res/values-as/strings.xml)20
-rw-r--r--libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml25
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_manage_button.xml10
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml30
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml1
-rw-r--r--libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml19
-rw-r--r--libs/WindowManager/Shell/res/layout/docked_stack_divider.xml2
-rw-r--r--libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml35
-rw-r--r--libs/WindowManager/Shell/res/layout/size_compat_ui.xml23
-rw-r--r--libs/WindowManager/Shell/res/layout/split_decor.xml (renamed from packages/overlays/AvoidAppsInCutoutOverlay/res/values-ar/strings.xml)23
-rw-r--r--libs/WindowManager/Shell/res/layout/split_divider.xml28
-rw-r--r--libs/WindowManager/Shell/res/layout/split_outline.xml (renamed from packages/overlays/AvoidAppsInCutoutOverlay/res/values-az/strings.xml)20
-rw-r--r--libs/WindowManager/Shell/res/values-land/dimens.xml4
-rw-r--r--libs/WindowManager/Shell/res/values-land/styles.xml3
-rw-r--r--libs/WindowManager/Shell/res/values/colors.xml2
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml42
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml3
-rw-r--r--libs/WindowManager/Shell/res/values/styles.xml5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java114
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java74
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java90
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java352
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java345
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java166
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java88
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java92
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java265
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java122
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SurfaceUtils.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java69
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java160
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java121
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java184
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java491
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelper.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelperController.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java132
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java147
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java)91
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java229
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java43
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java181
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java244
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java97
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java296
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java324
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java664
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java109
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java224
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl103
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java104
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java181
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java82
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java144
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java99
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java595
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java298
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java324
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java1330
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java288
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java224
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java117
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java536
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java124
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java158
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java485
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java110
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/WindowThumbnail.java71
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java89
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java87
-rw-r--r--libs/WindowManager/Shell/tests/flicker/Android.bp8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt72
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt17
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt25
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt15
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt36
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt39
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt43
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt45
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt18
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt118
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt65
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt59
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithDismissButtonTest.kt)44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt66
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt17
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt9
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt78
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt17
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt49
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt30
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt61
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt27
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt25
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt59
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt53
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt111
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt64
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt81
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt65
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt69
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt27
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt55
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt60
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt48
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt68
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt61
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt122
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt119
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt130
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt)49
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt92
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt)85
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt78
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt)59
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt174
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt95
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt116
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipShelfHeightTest.kt)67
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt56
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt120
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt105
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt32
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt45
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt9
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml15
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.pngbin0 -> 1966 bytes
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml31
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml26
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml48
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml48
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java77
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java178
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java82
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java37
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java15
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java57
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java35
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java193
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java43
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java65
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java85
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java151
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java53
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java47
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java155
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java84
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java40
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java281
-rw-r--r--libs/hwui/Android.bp2
-rw-r--r--libs/hwui/Properties.cpp3
-rw-r--r--libs/hwui/apex/android_matrix.cpp7
-rw-r--r--libs/hwui/apex/include/android/graphics/matrix.h10
-rw-r--r--libs/hwui/jni/android_graphics_Matrix.cpp7
-rw-r--r--libs/hwui/jni/android_graphics_Matrix.h3
-rw-r--r--libs/hwui/libhwui.map.txt1
-rw-r--r--libs/hwui/renderthread/EglManager.cpp7
-rw-r--r--libs/hwui/renderthread/RenderEffectCapabilityQuery.cpp39
-rw-r--r--libs/hwui/renderthread/RenderEffectCapabilityQuery.h35
-rw-r--r--libs/hwui/tests/unit/EglManagerTests.cpp14
-rw-r--r--libs/hwui/tests/unit/RenderEffectCapabilityQueryTests.cpp52
-rw-r--r--libs/input/SpriteController.cpp3
-rw-r--r--media/java/android/media/AudioAttributes.java99
-rw-r--r--media/java/android/media/AudioFormat.java89
-rw-r--r--media/java/android/media/AudioManager.java15
-rw-r--r--media/java/android/media/AudioSystem.java41
-rw-r--r--media/java/android/media/AudioTrack.java18
-rwxr-xr-xmedia/java/android/media/IAudioService.aidl55
-rw-r--r--media/java/android/media/IMediaRouterClient.aidl1
-rw-r--r--media/java/android/media/ISpatializerCallback.aidl29
-rw-r--r--media/java/android/media/ISpatializerHeadToSoundStagePoseCallback.aidl31
-rw-r--r--media/java/android/media/ISpatializerHeadTrackingModeCallback.aidl29
-rw-r--r--media/java/android/media/ISpatializerOutputCallback.aidl27
-rw-r--r--media/java/android/media/MediaFormat.java13
-rw-r--r--media/java/android/media/MediaMetrics.java10
-rw-r--r--media/java/android/media/MediaRouter.java35
-rw-r--r--media/java/android/media/Spatializer.java1089
-rw-r--r--media/java/android/media/projection/MediaProjection.java60
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java8
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java10
-rw-r--r--packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java7
-rw-r--r--packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml10
-rw-r--r--packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java65
-rw-r--r--packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java13
-rw-r--r--packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java7
-rw-r--r--packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target_divider.xml2
-rw-r--r--packages/SettingsLib/Utils/src/com/android/settingslib/utils/BuildCompatUtils.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/SignalIcon.java124
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java (renamed from packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java)96
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java16
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java122
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java36
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileStateTest.java133
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java (renamed from packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java)61
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java10
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java31
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java31
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java4
-rw-r--r--packages/SystemUI/Android.bp28
-rw-r--r--packages/SystemUI/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/TEST_MAPPING28
-rw-r--r--packages/SystemUI/animation/res/anim/launch_host_dialog_enter.xml23
-rw-r--r--packages/SystemUI/animation/res/anim/launch_host_dialog_exit.xml22
-rw-r--r--packages/SystemUI/animation/res/values/ids.xml19
-rw-r--r--packages/SystemUI/animation/res/values/styles.xml29
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt411
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt16
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt651
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt99
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java128
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt355
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt30
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt37
-rw-r--r--packages/SystemUI/docs/keyguard.md46
-rw-r--r--packages/SystemUI/docs/keyguard/aod.md1
-rw-r--r--packages/SystemUI/docs/keyguard/bouncer.md18
-rw-r--r--packages/SystemUI/monet/Android.bp31
-rw-r--r--packages/SystemUI/monet/AndroidManifest.xml20
-rw-r--r--packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt261
-rw-r--r--packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java69
-rw-r--r--packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java10
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/flags/Flag.kt52
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java129
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java10
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java19
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt9
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java6
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java3
-rw-r--r--packages/SystemUI/proguard.flags3
-rw-r--r--packages/SystemUI/res-keyguard/color/numpad_key_color_secondary.xml5
-rw-r--r--packages/SystemUI/res-keyguard/drawable/face_auth_wallpaper.pngbin709067 -> 0 bytes
-rw-r--r--packages/SystemUI/res-keyguard/drawable/fp_to_unlock.xml (renamed from packages/SystemUI/res/anim/fp_to_unlock.xml)10
-rw-r--r--packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml2
-rw-r--r--packages/SystemUI/res-keyguard/drawable/ic_fingerprint.xml48
-rw-r--r--packages/SystemUI/res-keyguard/drawable/ic_keyboard_tab_36dp.xml3
-rw-r--r--packages/SystemUI/res-keyguard/drawable/ic_lock.xml95
-rw-r--r--packages/SystemUI/res-keyguard/drawable/ic_lock_aod.xml41
-rw-r--r--packages/SystemUI/res-keyguard/drawable/ic_unlocked.xml48
-rw-r--r--packages/SystemUI/res-keyguard/drawable/lock_aod_to_ls.xml151
-rw-r--r--packages/SystemUI/res-keyguard/drawable/lock_ls_to_aod.xml151
-rw-r--r--packages/SystemUI/res-keyguard/drawable/lock_to_unlock.xml (renamed from packages/SystemUI/res/anim/lock_to_unlock.xml)6
-rw-r--r--packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml82
-rw-r--r--packages/SystemUI/res-keyguard/drawable/unlock_to_fp.xml298
-rw-r--r--packages/SystemUI/res-keyguard/layout/footer_actions.xml105
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml31
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml71
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml315
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml (renamed from packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml)7
-rw-r--r--packages/SystemUI/res-keyguard/values-land/donottranslate.xml19
-rw-r--r--packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml4
-rw-r--r--packages/SystemUI/res-keyguard/values-sw600dp-land/donottranslate.xml (renamed from packages/overlays/NoCutoutOverlay/res/values-is/strings.xml)12
-rw-r--r--packages/SystemUI/res-keyguard/values-sw720dp/bools.xml (renamed from packages/overlays/NoCutoutOverlay/res/values-vi/strings.xml)11
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml13
-rw-r--r--packages/SystemUI/res-keyguard/values/donottranslate.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values/styles.xml12
-rw-r--r--packages/SystemUI/res/color/docked_divider_background.xml18
-rw-r--r--packages/SystemUI/res/color/prv_color_surface.xml20
-rw-r--r--packages/SystemUI/res/color/prv_text_color_on_accent.xml20
-rw-r--r--packages/SystemUI/res/drawable/ic_kg_fingerprint.xml (renamed from packages/SystemUI/res/drawable/ic_fingerprint.xml)0
-rw-r--r--packages/SystemUI/res/drawable/ic_qs_drag_handle.xml (renamed from packages/overlays/AvoidAppsInCutoutOverlay/res/values-ca/strings.xml)21
-rw-r--r--packages/SystemUI/res/drawable/internet_dialog_background.xml23
-rw-r--r--packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml3
-rw-r--r--packages/SystemUI/res/drawable/media_output_dialog_background.xml23
-rw-r--r--packages/SystemUI/res/drawable/media_output_dialog_button_background.xml (renamed from packages/overlays/AvoidAppsInCutoutOverlay/res/values-be/strings.xml)24
-rw-r--r--packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml39
-rw-r--r--packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml42
-rw-r--r--packages/SystemUI/res/drawable/rounded_corner_bottom_secondary.xml16
-rw-r--r--packages/SystemUI/res/drawable/rounded_corner_top_secondary.xml16
-rw-r--r--packages/SystemUI/res/drawable/rounded_secondary.xml24
-rw-r--r--packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml3
-rw-r--r--packages/SystemUI/res/layout/global_screenshot_static.xml9
-rw-r--r--packages/SystemUI/res/layout/internet_connectivity_dialog.xml8
-rw-r--r--packages/SystemUI/res/layout/media_output_dialog.xml43
-rw-r--r--packages/SystemUI/res/layout/media_output_list_item.xml89
-rw-r--r--packages/SystemUI/res/layout/media_view.xml1
-rw-r--r--packages/SystemUI/res/layout/ongoing_call_chip.xml1
-rw-r--r--packages/SystemUI/res/layout/qs_customize_panel_content.xml2
-rw-r--r--packages/SystemUI/res/layout/qs_detail.xml2
-rw-r--r--packages/SystemUI/res/layout/qs_detail_header.xml2
-rw-r--r--packages/SystemUI/res/layout/qs_footer_impl.xml88
-rw-r--r--packages/SystemUI/res/layout/qs_panel.xml28
-rw-r--r--packages/SystemUI/res/layout/qs_user_dialog_content.xml93
-rw-r--r--packages/SystemUI/res/layout/quick_qs_status_icons.xml8
-rw-r--r--packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml41
-rw-r--r--packages/SystemUI/res/layout/rotate_suggestion.xml23
-rw-r--r--packages/SystemUI/res/layout/rounded_corners_bottom.xml7
-rw-r--r--packages/SystemUI/res/layout/rounded_corners_top.xml12
-rw-r--r--packages/SystemUI/res/layout/sidefps_view.xml16
-rw-r--r--packages/SystemUI/res/layout/split_shade_header.xml89
-rw-r--r--packages/SystemUI/res/layout/status_bar_expanded.xml22
-rw-r--r--packages/SystemUI/res/layout/super_notification_shade.xml14
-rw-r--r--packages/SystemUI/res/layout/system_icons.xml2
-rw-r--r--packages/SystemUI/res/layout/text_toast.xml1
-rw-r--r--packages/SystemUI/res/layout/udfps_aod_lock_icon.xml (renamed from packages/SystemUI/res/layout/global_actions_change_panel.xml)26
-rw-r--r--packages/SystemUI/res/raw/sfps_pulse.json1
-rw-r--r--packages/SystemUI/res/raw/sfps_pulse_landscape.json1
-rw-r--r--packages/SystemUI/res/values-af/strings.xml5
-rw-r--r--packages/SystemUI/res/values-am/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ar/strings.xml5
-rw-r--r--packages/SystemUI/res/values-as-land/strings.xml2
-rw-r--r--packages/SystemUI/res/values-as/strings.xml5
-rw-r--r--packages/SystemUI/res/values-az/strings.xml5
-rw-r--r--packages/SystemUI/res/values-b+sr+Latn/strings.xml5
-rw-r--r--packages/SystemUI/res/values-be/strings.xml5
-rw-r--r--packages/SystemUI/res/values-bg/strings.xml5
-rw-r--r--packages/SystemUI/res/values-bn/strings.xml5
-rw-r--r--packages/SystemUI/res/values-bs/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ca/strings.xml5
-rw-r--r--packages/SystemUI/res/values-cs/strings.xml5
-rw-r--r--packages/SystemUI/res/values-da/strings.xml5
-rw-r--r--packages/SystemUI/res/values-de/strings.xml5
-rw-r--r--packages/SystemUI/res/values-el/strings.xml5
-rw-r--r--packages/SystemUI/res/values-en-rAU/strings.xml5
-rw-r--r--packages/SystemUI/res/values-en-rCA/strings.xml5
-rw-r--r--packages/SystemUI/res/values-en-rGB/strings.xml5
-rw-r--r--packages/SystemUI/res/values-en-rIN/strings.xml5
-rw-r--r--packages/SystemUI/res/values-en-rXC/strings.xml5
-rw-r--r--packages/SystemUI/res/values-es-rUS/strings.xml5
-rw-r--r--packages/SystemUI/res/values-es/strings.xml5
-rw-r--r--packages/SystemUI/res/values-et/strings.xml5
-rw-r--r--packages/SystemUI/res/values-eu/strings.xml5
-rw-r--r--packages/SystemUI/res/values-fa/strings.xml5
-rw-r--r--packages/SystemUI/res/values-fi/strings.xml5
-rw-r--r--packages/SystemUI/res/values-fr-rCA/strings.xml5
-rw-r--r--packages/SystemUI/res/values-fr/strings.xml5
-rw-r--r--packages/SystemUI/res/values-fr/tiles_states_strings.xml2
-rw-r--r--packages/SystemUI/res/values-gl/strings.xml5
-rw-r--r--packages/SystemUI/res/values-gu/strings.xml5
-rw-r--r--packages/SystemUI/res/values-hi/strings.xml5
-rw-r--r--packages/SystemUI/res/values-hr/strings.xml5
-rw-r--r--packages/SystemUI/res/values-hu/strings.xml5
-rw-r--r--packages/SystemUI/res/values-hy/strings.xml5
-rw-r--r--packages/SystemUI/res/values-in/strings.xml5
-rw-r--r--packages/SystemUI/res/values-is/strings.xml5
-rw-r--r--packages/SystemUI/res/values-it/strings.xml5
-rw-r--r--packages/SystemUI/res/values-it/tiles_states_strings.xml50
-rw-r--r--packages/SystemUI/res/values-iw/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ja/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ka/strings.xml5
-rw-r--r--packages/SystemUI/res/values-kk/strings.xml5
-rw-r--r--packages/SystemUI/res/values-km/strings.xml5
-rw-r--r--packages/SystemUI/res/values-kn/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ko/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ky/strings.xml5
-rw-r--r--packages/SystemUI/res/values-land/config.xml3
-rw-r--r--packages/SystemUI/res/values-land/dimens.xml6
-rw-r--r--packages/SystemUI/res/values-land/styles.xml6
-rw-r--r--packages/SystemUI/res/values-lo/strings.xml5
-rw-r--r--packages/SystemUI/res/values-lt/strings.xml5
-rw-r--r--packages/SystemUI/res/values-lv/strings.xml5
-rw-r--r--packages/SystemUI/res/values-mk/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ml/strings.xml5
-rw-r--r--packages/SystemUI/res/values-mn/strings.xml5
-rw-r--r--packages/SystemUI/res/values-mr/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ms/strings.xml5
-rw-r--r--packages/SystemUI/res/values-my/strings.xml6
-rw-r--r--packages/SystemUI/res/values-nb/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ne/strings.xml5
-rw-r--r--packages/SystemUI/res/values-night/styles.xml4
-rw-r--r--packages/SystemUI/res/values-nl/strings.xml5
-rw-r--r--packages/SystemUI/res/values-or/strings.xml5
-rw-r--r--packages/SystemUI/res/values-pa/strings.xml5
-rw-r--r--packages/SystemUI/res/values-pl/strings.xml5
-rw-r--r--packages/SystemUI/res/values-pt-rBR/strings.xml5
-rw-r--r--packages/SystemUI/res/values-pt-rPT/strings.xml5
-rw-r--r--packages/SystemUI/res/values-pt/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ro/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ru/strings.xml5
-rw-r--r--packages/SystemUI/res/values-si/strings.xml5
-rw-r--r--packages/SystemUI/res/values-sk/strings.xml5
-rw-r--r--packages/SystemUI/res/values-sl/strings.xml5
-rw-r--r--packages/SystemUI/res/values-sq/strings.xml5
-rw-r--r--packages/SystemUI/res/values-sr/strings.xml5
-rw-r--r--packages/SystemUI/res/values-sv/strings.xml5
-rw-r--r--packages/SystemUI/res/values-sw/strings.xml5
-rw-r--r--packages/SystemUI/res/values-sw600dp-land/config.xml14
-rw-r--r--packages/SystemUI/res/values-sw600dp-port/config.xml (renamed from packages/SystemUI/res/values-h560dp-xhdpi/config.xml)16
-rw-r--r--packages/SystemUI/res/values-sw600dp-port/dimens.xml2
-rw-r--r--packages/SystemUI/res/values-sw600dp/config.xml6
-rw-r--r--packages/SystemUI/res/values-sw600dp/dimens.xml13
-rw-r--r--packages/SystemUI/res/values-sw720dp-land/config.xml35
-rw-r--r--packages/SystemUI/res/values-sw720dp-port/config.xml33
-rw-r--r--packages/SystemUI/res/values-sw720dp/config.xml3
-rw-r--r--packages/SystemUI/res/values-sw900dp/dimens.xml9
-rw-r--r--packages/SystemUI/res/values-ta/strings.xml5
-rw-r--r--packages/SystemUI/res/values-te/strings.xml5
-rw-r--r--packages/SystemUI/res/values-th/strings.xml5
-rw-r--r--packages/SystemUI/res/values-tl/strings.xml5
-rw-r--r--packages/SystemUI/res/values-tr/strings.xml5
-rw-r--r--packages/SystemUI/res/values-uk/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ur/strings.xml5
-rw-r--r--packages/SystemUI/res/values-uz/strings.xml5
-rw-r--r--packages/SystemUI/res/values-vi/strings.xml5
-rw-r--r--packages/SystemUI/res/values-zh-rCN/strings.xml5
-rw-r--r--packages/SystemUI/res/values-zh-rHK/strings.xml5
-rw-r--r--packages/SystemUI/res/values-zh-rTW/strings.xml5
-rw-r--r--packages/SystemUI/res/values-zu/strings.xml5
-rw-r--r--packages/SystemUI/res/values/attrs.xml1
-rw-r--r--packages/SystemUI/res/values/colors.xml4
-rw-r--r--packages/SystemUI/res/values/config.xml84
-rw-r--r--packages/SystemUI/res/values/dimens.xml68
-rw-r--r--packages/SystemUI/res/values/flags.xml30
-rw-r--r--packages/SystemUI/res/values/strings.xml14
-rw-r--r--packages/SystemUI/res/values/styles.xml42
-rw-r--r--packages/SystemUI/shared/Android.bp5
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java (renamed from packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java)23
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt175
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java (renamed from packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java)57
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java432
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java45
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java228
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java462
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java16
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java262
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/plugins/VersionInfo.java12
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl20
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl12
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java78
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/ViewRippler.java55
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java257
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt65
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java86
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java59
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java (renamed from packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java)300
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java3
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java23
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java69
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java22
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java134
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java5
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/statusbar/policy/CallbackController.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java)0
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt76
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt38
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt47
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt21
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt117
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt218
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt142
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt57
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt17
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt18
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt42
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt29
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt73
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java142
-rw-r--r--packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java215
-rw-r--r--packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java64
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java120
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java60
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java26
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java90
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java21
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java16
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java207
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java96
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java89
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java13
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java13
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java118
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java16
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconView.java84
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java160
-rw-r--r--packages/SystemUI/src/com/android/keyguard/NumPadButton.java15
-rw-r--r--packages/SystemUI/src/com/android/keyguard/NumPadKey.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java13
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java79
-rw-r--r--packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/ImageWallpaper.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/LatencyTester.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java338
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java102
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIFactory.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java113
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java (renamed from packages/SystemUI/src/com/android/systemui/BatteryMeterView.java)189
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java203
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt89
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/BiometricOrientationEventListener.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/OWNERS1
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java239
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt219
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/SidefpsView.java107
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java93
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java99
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLog.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java127
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java363
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeUi.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java196
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagReader.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagWriter.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java599
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt120
-rw-r--r--packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessController.kt195
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java273
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java78
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java (renamed from tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt)29
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java502
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java90
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java210
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/RotationButton.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java277
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButton.java186
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java130
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerUI.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt218
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt158
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java139
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDetail.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooter.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java126
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java187
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java116
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java72
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt102
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/TileLayout.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java70
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java63
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt95
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java399
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java108
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java74
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java (renamed from packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java)38
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt210
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java115
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt100
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java655
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java101
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java201
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java141
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java139
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java)9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java)20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/EthernetIcons.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetIcons.java)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/EthernetSignalController.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java)8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java)361
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java)52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java)47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java)20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java)3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java)17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java100
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt152
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt97
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java126
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java115
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt126
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java122
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt126
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt225
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt105
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt79
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt109
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java100
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Invalidator.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/InternalNotifUpdater.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt113
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt74
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java131
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java171
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java269
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java126
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLogger.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java88
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java137
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java177
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java423
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java801
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt141
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java126
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java166
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java129
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java154
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt135
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java164
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java95
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt115
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java2128
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java648
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt144
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java143
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java133
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java146
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java108
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java87
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIHostDialogProvider.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java105
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java91
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt144
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java79
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java94
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java149
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt230
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java239
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/ShellUnfoldProgressProvider.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt270
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt101
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/AlphaTintDrawableWrapper.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java120
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/Utils.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/MessageRouter.java198
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/MessageRouterImpl.java186
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java65
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/leak/LeakDetector.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java108
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/sensors/ProximityCheck.java90
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java395
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java361
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java171
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensor.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorEvent.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/wrapper/UtilWrapperModule.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardCarousel.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java157
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java41
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java36
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java157
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java114
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java24
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/clock/SmallClockPositionTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java918
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java110
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java81
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipViewTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompatTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt186
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java133
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt71
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java190
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java172
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java109
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java108
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java119
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java119
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java575
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsInfoProviderTest.kt134
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessControllerTest.kt137
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java209
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt174
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java93
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java67
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java124
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt128
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt125
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java91
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java176
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt167
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt62
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSBrightnessControllerTest.kt118
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt64
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java209
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt62
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt207
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderTest.kt)15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt239
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java308
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java338
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java138
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java119
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt188
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java196
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java174
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/AccessPointControllerImplTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java)20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java)21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java)22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java)20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java)233
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java)27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt96
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java92
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java66
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt117
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt145
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt209
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt106
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt230
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt323
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java114
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java)64
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLoggerTest.kt55
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java103
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java384
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java203
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt254
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt133
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt174
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java209
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt87
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java172
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt196
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java48
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java175
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt92
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt167
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt241
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java239
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java113
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt178
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt139
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java48
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt135
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt145
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java371
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java115
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java114
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java)8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java)10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java158
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java178
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java13
-rw-r--r--packages/VpnDialogs/res/values-ar/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-as/strings.xml7
-rw-r--r--packages/VpnDialogs/res/values-az/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-be/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-bn/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-ca/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-et/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-eu/strings.xml4
-rw-r--r--packages/VpnDialogs/res/values-fa/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-fr/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-gu/strings.xml5
-rw-r--r--packages/VpnDialogs/res/values-hi/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-hu/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-hy/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-in/strings.xml10
-rw-r--r--packages/VpnDialogs/res/values-is/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-iw/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-kk/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-kn/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-ky/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-lt/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-lv/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-ml/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-mr/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-my/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-nb/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-ne/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-or/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-pl/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-sl/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-sq/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-sv/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-ta/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-te/strings.xml5
-rw-r--r--packages/VpnDialogs/res/values-ur/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-vi/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-zh-rCN/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-zh-rTW/strings.xml3
-rw-r--r--packages/VpnDialogs/res/values-zu/strings.xml3
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-af/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-am/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-b+sr+Latn/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-bg/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-bn/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-bs/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-cs/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-da/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-de/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-el/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rAU/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rCA/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rGB/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rIN/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rXC/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-es-rUS/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-es/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-et/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-eu/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-fa/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-fi/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-fr-rCA/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-fr/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-gl/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-gu/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-hi/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-hr/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-hu/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-hy/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-in/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-is/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-it/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-iw/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-ja/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-ka/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-kk/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-km/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-kn/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-ko/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-ky/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-lo/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-lt/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-lv/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-mk/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-ml/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-mn/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-mr/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-ms/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-my/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-nb/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-ne/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-nl/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-or/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-pa/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-pl/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-pt-rBR/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-pt-rPT/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-pt/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-ro/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-ru/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-si/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-sk/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-sl/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-sq/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-sr/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-sv/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-sw/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-ta/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-te/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-th/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-tl/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-tr/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-uk/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-ur/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-uz/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-vi/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-zh-rCN/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-zh-rHK/strings.xml2
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-zh-rTW/strings.xml21
-rw-r--r--packages/overlays/AvoidAppsInCutoutOverlay/res/values-zu/strings.xml21
-rw-r--r--packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-land/config.xml22
-rw-r--r--packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values/config.xml8
-rw-r--r--packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-land/config.xml22
-rw-r--r--packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values/config.xml8
-rw-r--r--packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-land/config.xml22
-rw-r--r--packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values/config.xml8
-rw-r--r--packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values-land/config.xml22
-rw-r--r--packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values/config.xml8
-rw-r--r--packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-land/config.xml22
-rw-r--r--packages/overlays/DisplayCutoutEmulationTallOverlay/res/values/config.xml8
-rw-r--r--packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-land/config.xml22
-rw-r--r--packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml10
-rw-r--r--packages/overlays/DisplayCutoutEmulationWideOverlay/res/values-land/config.xml22
-rw-r--r--packages/overlays/DisplayCutoutEmulationWideOverlay/res/values/config.xml8
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-ar/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-as/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-az/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-be/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-bn/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-fa/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-fr/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-gu/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-hi/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-hu/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-hy/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-iw/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-kk/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-kn/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-ky/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-lt/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-lv/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-ml/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-mr/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-my/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-ne/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-or/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-pl/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-sl/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-sq/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-sv/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-ta/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-te/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-ur/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-zh-rCN/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-zh-rTW/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values-zu/strings.xml21
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values/config.xml8
-rw-r--r--packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java36
-rw-r--r--packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java57
-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/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/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/TelephonyRegistry.java183
-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/ActivityManagerService.java35
-rw-r--r--services/core/java/com/android/server/am/AppProfiler.java59
-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/MeasuredEnergySnapshot.java11
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java305
-rw-r--r--services/core/java/com/android/server/audio/AudioSystemAdapter.java23
-rw-r--r--services/core/java/com/android/server/audio/RotationHelper.java35
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java957
-rw-r--r--services/core/java/com/android/server/biometrics/AuthService.java6
-rw-r--r--services/core/java/com/android/server/biometrics/Utils.java12
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java48
-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.java2
-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/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.java343
-rw-r--r--services/core/java/com/android/server/compat/CompatChange.java79
-rw-r--r--services/core/java/com/android/server/compat/CompatConfig.java387
-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/devicestate/DeviceState.java33
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateManagerService.java548
-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.java296
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerShellCommand.java18
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java22
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java17
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java9
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java102
-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.java56
-rw-r--r--services/core/java/com/android/server/location/provider/LocationProviderManager.java5
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java18
-rw-r--r--services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java9
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java2
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java3
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java4
-rw-r--r--services/core/java/com/android/server/notification/VibratorHelper.java29
-rw-r--r--services/core/java/com/android/server/os/NativeTombstoneManager.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java14
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java5
-rw-r--r--services/core/java/com/android/server/policy/AppOpsPolicy.java5
-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/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/soundtrigger_middleware/ConversionUtil.java10
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java34
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java172
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java3
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java25
-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/wm/AccessibilityController.java609
-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.java245
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java1107
-rw-r--r--services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java26
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartInterceptor.java29
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java274
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java67
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java235
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java297
-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.java265
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java419
-rw-r--r--services/core/java/com/android/server/wm/BLASTSyncEngine.java35
-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.java677
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java864
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java31
-rw-r--r--services/core/java/com/android/server/wm/DisplayWindowListenerController.java16
-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.java96
-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.java60
-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.java115
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java35
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java4
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java80
-rw-r--r--services/core/java/com/android/server/wm/Letterbox.java121
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfiguration.java327
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java185
-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/NavBarFadeAnimationController.java3
-rw-r--r--services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java5
-rw-r--r--services/core/java/com/android/server/wm/PackageConfigPersister.java87
-rw-r--r--services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java126
-rw-r--r--services/core/java/com/android/server/wm/PinnedTaskController.java10
-rw-r--r--services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java138
-rw-r--r--services/core/java/com/android/server/wm/RecentTasks.java24
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimation.java24
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimationController.java46
-rw-r--r--services/core/java/com/android/server/wm/RemoteAnimationController.java80
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java157
-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.java29
-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.java47
-rw-r--r--services/core/java/com/android/server/wm/SurfaceFreezer.java68
-rw-r--r--services/core/java/com/android/server/wm/Task.java2602
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java215
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java2422
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java550
-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.java77
-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.java286
-rw-r--r--services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java21
-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.java344
-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.java530
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerShellCommand.java472
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java546
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java91
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java357
-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/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.java104
-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/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/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/WallpaperManagerServiceTests.java2
-rw-r--r--services/tests/servicestests/AndroidManifest.xml1
-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/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/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/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/sensors/SensorOverlaysTest.java129
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java13
-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/devicestate/DeviceStateManagerServiceTest.java93
-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/LogicalDisplayMapperTest.java98
-rw-r--r--services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java94
-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.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java40
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java67
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java8
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java21
-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.java335
-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.java452
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java11
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java420
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java192
-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/DisplayPolicyLayoutTests.java43
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java117
-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.java40
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java42
-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.java7
-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/PossibleDisplayInfoMapperTests.java182
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java86
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java20
-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.java253
-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.java421
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java105
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java112
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java41
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java15
-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/WindowAnimationSpecTest.java64
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java102
-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.java55
-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.java231
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java74
-rw-r--r--services/uwb/java/com/android/server/uwb/UwbInjector.java22
-rw-r--r--services/uwb/java/com/android/server/uwb/UwbServiceImpl.java46
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java14
-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
-rw-r--r--telephony/common/com/android/internal/telephony/TelephonyPermissions.java21
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java38
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java9
-rw-r--r--telephony/java/android/telephony/TelephonyScanManager.java35
-rw-r--r--tests/FlickerTests/Android.bp22
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt255
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt43
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt44
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt114
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt1
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt6
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt11
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt52
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt34
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt6
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt6
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt52
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt83
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt95
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt50
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt59
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt100
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt153
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt41
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt127
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt106
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt161
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt67
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt66
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt252
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt134
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt53
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt248
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt330
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt347
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt346
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt119
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt114
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt154
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml31
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml27
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml1
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_non_resizeable.xml32
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml27
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java15
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java41
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java44
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableActivity.java37
-rw-r--r--tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java5
-rw-r--r--tools/aapt2/SdkConstants.cpp2
-rw-r--r--tools/aapt2/format/binary/BinaryResourceParser.cpp18
-rw-r--r--tools/aapt2/format/binary/BinaryResourceParser.h4
-rwxr-xr-xtools/finalize_res/finalize_res.py35
2154 files changed, 102559 insertions, 32814 deletions
diff --git a/Android.bp b/Android.bp
index 3af2f07884e5..a99cef879384 100644
--- a/Android.bp
+++ b/Android.bp
@@ -112,6 +112,7 @@ filegroup {
":framework_native_aidl",
":gatekeeper_aidl",
":gsiservice_aidl",
+ ":guiconstants_aidl",
":idmap2_aidl",
":idmap2_core_aidl",
":incidentcompanion_aidl",
@@ -340,6 +341,8 @@ java_defaults {
"modules-utils-preconditions",
"modules-utils-os",
"framework-permission-aidl-java",
+ "spatializer-aidl-java",
+ "audiopolicy-types-aidl-java",
],
}
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
index a2dc1c29f026..452bb0ab5909 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
@@ -29,6 +29,7 @@ import android.view.IWindowSession;
import android.view.InputChannel;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -82,7 +83,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase
private static class TestWindow extends BaseIWindow {
final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
- final InsetsState mRequestedVisibility = new InsetsState();
+ final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
final InsetsState mOutInsetsState = new InsetsState();
final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0];
@@ -102,7 +103,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase
long startTime = SystemClock.elapsedRealtimeNanos();
session.addToDisplay(this, mLayoutParams, View.VISIBLE,
- Display.DEFAULT_DISPLAY, mRequestedVisibility, inputChannel,
+ Display.DEFAULT_DISPLAY, mRequestedVisibilities, inputChannel,
mOutInsetsState, mOutControls);
final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime;
state.addExtraResult("add", elapsedTimeNsOfAdd);
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchBatchResult.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchBatchResult.java
index d493a1c28a3a..272e12db0124 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchBatchResult.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchBatchResult.java
@@ -96,6 +96,17 @@ public final class AppSearchBatchResult<KeyType, ValueType> {
return Collections.unmodifiableMap(mAll);
}
+ /**
+ * Asserts that this {@link AppSearchBatchResult} has no failures.
+ *
+ * @hide
+ */
+ public void checkSuccess() {
+ if (!isSuccess()) {
+ throw new IllegalStateException("AppSearchBatchResult has failures: " + this);
+ }
+ }
+
@Override
@NonNull
public String toString() {
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java b/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java
index 10e014bf9c9a..d6d53155b131 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java
@@ -71,7 +71,7 @@ public final class SchemaMigrationUtil {
/**
* Checks the setSchema() call won't delete any types or has incompatible types after all {@link
- * Migrator} has been triggered..
+ * Migrator} has been triggered.
*/
public static void checkDeletedAndIncompatibleAfterMigration(
@NonNull SetSchemaResponse setSchemaResponse, @NonNull Set<String> activeMigrators)
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index db23a6dc3047..d4e32396187d 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -332,17 +332,20 @@ public class AppSearchManagerService extends SystemService {
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
AppSearchUserInstance instance = null;
int operationSuccessCount = 0;
int operationFailureCount = 0;
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
List<AppSearchSchema> schemas = new ArrayList<>(schemaBundles.size());
for (int i = 0; i < schemaBundles.size(); i++) {
schemas.add(new AppSearchSchema(schemaBundles.get(i)));
@@ -359,7 +362,8 @@ public class AppSearchManagerService extends SystemService {
}
schemasVisibleToPackages.put(entry.getKey(), packageIdentifiers);
}
- instance = mAppSearchUserInstanceManager.getUserInstance(callingUser);
+ instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
+ // TODO(b/173532925): Implement logging for statsBuilder
SetSchemaResponse setSchemaResponse = instance.getAppSearchImpl().setSchema(
packageName,
databaseName,
@@ -368,7 +372,8 @@ public class AppSearchManagerService extends SystemService {
schemasNotDisplayedBySystem,
schemasVisibleToPackages,
forceOverride,
- schemaVersion);
+ schemaVersion,
+ /*setSchemaStatsBuilder=*/ null);
++operationSuccessCount;
invokeCallbackOnResult(callback,
AppSearchResult.newSuccessfulResult(setSchemaResponse.getBundle()));
@@ -418,15 +423,18 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
EXECUTOR.execute(() -> {
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
AppSearchUserInstance instance =
- mAppSearchUserInstanceManager.getUserInstance(callingUser);
+ mAppSearchUserInstanceManager.getUserInstance(targetUser);
GetSchemaResponse response =
instance.getAppSearchImpl().getSchema(packageName, databaseName);
invokeCallbackOnResult(
@@ -450,15 +458,18 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
EXECUTOR.execute(() -> {
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
AppSearchUserInstance instance =
- mAppSearchUserInstanceManager.getUserInstance(callingUser);
+ mAppSearchUserInstanceManager.getUserInstance(targetUser);
List<String> namespaces =
instance.getAppSearchImpl().getNamespaces(packageName, databaseName);
invokeCallbackOnResult(
@@ -485,20 +496,23 @@ public class AppSearchManagerService extends SystemService {
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
AppSearchUserInstance instance = null;
int operationSuccessCount = 0;
int operationFailureCount = 0;
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
AppSearchBatchResult.Builder<String, Void> resultBuilder =
new AppSearchBatchResult.Builder<>();
- instance = mAppSearchUserInstanceManager.getUserInstance(callingUser);
+ instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
for (int i = 0; i < documentBundles.size(); i++) {
GenericDocument document = new GenericDocument(documentBundles.get(i));
try {
@@ -571,20 +585,23 @@ public class AppSearchManagerService extends SystemService {
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
AppSearchUserInstance instance = null;
int operationSuccessCount = 0;
int operationFailureCount = 0;
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
AppSearchBatchResult.Builder<String, Bundle> resultBuilder =
new AppSearchBatchResult.Builder<>();
- instance = mAppSearchUserInstanceManager.getUserInstance(callingUser);
+ instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
for (int i = 0; i < ids.size(); i++) {
String id = ids.get(i);
try {
@@ -652,18 +669,21 @@ public class AppSearchManagerService extends SystemService {
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
AppSearchUserInstance instance = null;
int operationSuccessCount = 0;
int operationFailureCount = 0;
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
- instance = mAppSearchUserInstanceManager.getUserInstance(callingUser);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
+ instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
SearchResultPage searchResultPage = instance.getAppSearchImpl().query(
packageName,
databaseName,
@@ -718,18 +738,21 @@ public class AppSearchManagerService extends SystemService {
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
AppSearchUserInstance instance = null;
int operationSuccessCount = 0;
int operationFailureCount = 0;
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
- instance = mAppSearchUserInstanceManager.getUserInstance(callingUser);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
+ instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
boolean callerHasSystemAccess =
instance.getVisibilityStore().doesCallerHaveSystemAccess(packageName);
@@ -783,19 +806,22 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
- // TODO(b/162450968) check nextPageToken is being advanced by the same uid as originally
- // opened it
EXECUTOR.execute(() -> {
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
AppSearchUserInstance instance =
- mAppSearchUserInstanceManager.getUserInstance(callingUser);
+ mAppSearchUserInstanceManager.getUserInstance(targetUser);
+ // TODO(b/173532925): Implement logging for statsBuilder
SearchResultPage searchResultPage =
- instance.getAppSearchImpl().getNextPage(packageName, nextPageToken);
+ instance.getAppSearchImpl().getNextPage(
+ packageName, nextPageToken, /*statsBuilder=*/ null);
invokeCallbackOnResult(
callback,
AppSearchResult.newSuccessfulResult(searchResultPage.getBundle()));
@@ -812,15 +838,18 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(userHandle);
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
EXECUTOR.execute(() -> {
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
AppSearchUserInstance instance =
- mAppSearchUserInstanceManager.getUserInstance(callingUser);
+ mAppSearchUserInstanceManager.getUserInstance(targetUser);
instance.getAppSearchImpl().invalidateNextPageToken(packageName, nextPageToken);
} catch (Throwable t) {
Log.e(TAG, "Unable to invalidate the query page token", t);
@@ -846,15 +875,18 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
EXECUTOR.execute(() -> {
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
AppSearchUserInstance instance =
- mAppSearchUserInstanceManager.getUserInstance(callingUser);
+ mAppSearchUserInstanceManager.getUserInstance(targetUser);
// we don't need to append the file. The file is always brand new.
try (DataOutputStream outputStream = new DataOutputStream(
new FileOutputStream(fileDescriptor.getFileDescriptor()))) {
@@ -870,8 +902,11 @@ public class AppSearchManagerService extends SystemService {
outputStream, searchResultPage.getResults().get(i)
.getGenericDocument().getBundle());
}
+ // TODO(b/173532925): Implement logging for statsBuilder
searchResultPage = instance.getAppSearchImpl().getNextPage(
- packageName, searchResultPage.getNextPageToken());
+ packageName,
+ searchResultPage.getNextPageToken(),
+ /*statsBuilder=*/ null);
}
}
invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
@@ -895,15 +930,18 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
EXECUTOR.execute(() -> {
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
AppSearchUserInstance instance =
- mAppSearchUserInstanceManager.getUserInstance(callingUser);
+ mAppSearchUserInstanceManager.getUserInstance(targetUser);
GenericDocument document;
ArrayList<Bundle> migrationFailureBundles = new ArrayList<>();
@@ -957,15 +995,18 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
EXECUTOR.execute(() -> {
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
AppSearchUserInstance instance =
- mAppSearchUserInstanceManager.getUserInstance(callingUser);
+ mAppSearchUserInstanceManager.getUserInstance(targetUser);
if (systemUsage
&& !instance.getVisibilityStore()
@@ -1004,20 +1045,23 @@ public class AppSearchManagerService extends SystemService {
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
AppSearchUserInstance instance = null;
int operationSuccessCount = 0;
int operationFailureCount = 0;
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
AppSearchBatchResult.Builder<String, Void> resultBuilder =
new AppSearchBatchResult.Builder<>();
- instance = mAppSearchUserInstanceManager.getUserInstance(callingUser);
+ instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
for (int i = 0; i < ids.size(); i++) {
String id = ids.get(i);
try {
@@ -1090,18 +1134,21 @@ public class AppSearchManagerService extends SystemService {
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
AppSearchUserInstance instance = null;
int operationSuccessCount = 0;
int operationFailureCount = 0;
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
- instance = mAppSearchUserInstanceManager.getUserInstance(callingUser);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
+ instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
instance.getAppSearchImpl().removeByQuery(
packageName,
databaseName,
@@ -1154,15 +1201,18 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
EXECUTOR.execute(() -> {
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
AppSearchUserInstance instance =
- mAppSearchUserInstanceManager.getUserInstance(callingUser);
+ mAppSearchUserInstanceManager.getUserInstance(targetUser);
StorageInfo storageInfo = instance.getAppSearchImpl()
.getStorageInfoForDatabase(packageName, databaseName);
Bundle storageInfoBundle = storageInfo.getBundle();
@@ -1184,18 +1234,21 @@ public class AppSearchManagerService extends SystemService {
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
AppSearchUserInstance instance = null;
int operationSuccessCount = 0;
int operationFailureCount = 0;
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
- instance = mAppSearchUserInstanceManager.getUserInstance(callingUser);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
+ instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
instance.getAppSearchImpl().persistToDisk(PersistType.Code.FULL);
++operationSuccessCount;
} catch (Throwable t) {
@@ -1236,7 +1289,6 @@ public class AppSearchManagerService extends SystemService {
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
int callingUid = Binder.getCallingUid();
- UserHandle callingUser = handleIncomingUser(userHandle, callingUid);
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -1244,12 +1296,18 @@ public class AppSearchManagerService extends SystemService {
int operationSuccessCount = 0;
int operationFailureCount = 0;
try {
- Context userContext = mContext.createContextAsUser(callingUser, /*flags=*/ 0);
- verifyUserUnlocked(callingUser);
- verifyCallingPackage(userContext, callingUser, callingUid, packageName);
- verifyNotInstantApp(userContext, packageName);
+ verifyCaller(callingUid, packageName);
+
+ // Obtain the user where the client wants to run the operations in. This should
+ // end up being the same as userHandle, assuming it is not a special user and
+ // the client is allowed to run operations in that user.
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
+ verifyUserUnlocked(targetUser);
+
+ Context targetUserContext = mContext.createContextAsUser(targetUser,
+ /*flags=*/ 0);
instance = mAppSearchUserInstanceManager.getOrCreateUserInstance(
- userContext, callingUser, AppSearchConfig.getInstance(EXECUTOR));
+ targetUserContext, targetUser, AppSearchConfig.getInstance(EXECUTOR));
++operationSuccessCount;
invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
} catch (Throwable t) {
@@ -1278,29 +1336,6 @@ public class AppSearchManagerService extends SystemService {
});
}
- private void verifyCallingPackage(
- @NonNull Context userContext,
- @NonNull UserHandle actualCallingUser,
- int actualCallingUid,
- @NonNull String claimedCallingPackage) {
- Objects.requireNonNull(actualCallingUser);
- Objects.requireNonNull(claimedCallingPackage);
-
- int claimedCallingUid = PackageUtil.getPackageUid(
- userContext, claimedCallingPackage);
- if (claimedCallingUid == INVALID_UID) {
- throw new SecurityException(
- "Specified calling package [" + claimedCallingPackage + "] not found");
- }
- if (claimedCallingUid != actualCallingUid) {
- throw new SecurityException(
- "Specified calling package ["
- + claimedCallingPackage
- + "] does not match the calling uid "
- + actualCallingUid);
- }
- }
-
/** Invokes the {@link IAppSearchResultCallback} with the result. */
private void invokeCallbackOnResult(
IAppSearchResultCallback callback, AppSearchResult<?> result) {
@@ -1354,33 +1389,72 @@ public class AppSearchManagerService extends SystemService {
/**
* Helper for dealing with incoming user arguments to system service calls.
*
- * @param requestedUser The user which the caller is requesting to execute as.
+ * @param targetUserHandle The user which the caller is requesting to execute as.
* @param callingUid The actual uid of the caller as determined by Binder.
* @return the user handle that the call should run as. Will always be a concrete user.
*/
@NonNull
- private UserHandle handleIncomingUser(@NonNull UserHandle requestedUser, int callingUid) {
- UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
- if (callingUser.equals(requestedUser)) {
- return requestedUser;
+ private UserHandle handleIncomingUser(@NonNull UserHandle targetUserHandle, int callingUid) {
+ UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid);
+ if (callingUserHandle.equals(targetUserHandle)) {
+ return targetUserHandle;
}
// Duplicates UserController#ensureNotSpecialUser
- if (requestedUser.getIdentifier() < 0) {
+ if (targetUserHandle.getIdentifier() < 0) {
throw new IllegalArgumentException(
- "Call does not support special user " + requestedUser);
+ "Call does not support special user " + targetUserHandle);
}
throw new SecurityException(
- "Requested user, " + requestedUser + ", is not the same as the calling user, "
- + callingUser + ".");
+ "Requested user, " + targetUserHandle + ", is not the same as the calling user, "
+ + callingUserHandle + ".");
+ }
+
+ /**
+ * Verify various aspects of the calling user.
+ *
+ * @param callingUid Uid of the caller, usually retrieved from Binder for authenticity.
+ * @param claimedCallingPackage Package name the caller claims to be.
+ */
+ private void verifyCaller(int callingUid, @NonNull String claimedCallingPackage) {
+ // Obtain the user where the client is running in. Note that this could be different from
+ // the userHandle where the client wants to run the AppSearch operation in.
+ UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid);
+ Context callingUserContext = mContext.createContextAsUser(callingUserHandle,
+ /*flags=*/ 0);
+
+ verifyCallingPackage(callingUserContext, callingUid, claimedCallingPackage);
+ verifyNotInstantApp(callingUserContext, claimedCallingPackage);
+ }
+
+ /**
+ * Check that the caller's supposed package name matches the uid making the call.
+ *
+ * @throws SecurityException if the package name and uid don't match.
+ */
+ private void verifyCallingPackage(
+ @NonNull Context actualCallingUserContext,
+ int actualCallingUid,
+ @NonNull String claimedCallingPackage) {
+ int claimedCallingUid = PackageUtil.getPackageUid(
+ actualCallingUserContext, claimedCallingPackage);
+ if (claimedCallingUid == INVALID_UID) {
+ throw new SecurityException(
+ "Specified calling package [" + claimedCallingPackage + "] not found");
+ }
+ if (claimedCallingUid != actualCallingUid) {
+ throw new SecurityException(
+ "Specified calling package ["
+ + claimedCallingPackage
+ + "] does not match the calling uid "
+ + actualCallingUid);
+ }
}
/**
- * Helper for ensuring instant apps can't make calls to AppSearch.
+ * Ensure instant apps can't make calls to AppSearch.
*
- * @param userContext Context of the user making the call.
- * @param packageName Package name of the caller.
* @throws SecurityException if the caller is an instant app.
*/
private void verifyNotInstantApp(@NonNull Context userContext, @NonNull String packageName) {
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index 15916cc23c0f..324163f49fd3 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
@@ -59,6 +59,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.external.localstorage.visibilitystore.VisibilityStore;
import com.google.android.icing.IcingSearchEngine;
@@ -393,6 +394,7 @@ public final class AppSearchImpl implements Closeable {
* @param forceOverride Whether to force-apply the schema even if it is incompatible. Documents
* which do not comply with the new schema will be deleted.
* @param version The overall version number of the request.
+ * @param setSchemaStatsBuilder Builder for {@link SetSchemaStats} to hold stats for setSchema
* @return The response contains deleted schema types and incompatible schema types of this
* call.
* @throws AppSearchException On IcingSearchEngine error. If the status code is
@@ -408,7 +410,8 @@ public final class AppSearchImpl implements Closeable {
@NonNull List<String> schemasNotDisplayedBySystem,
@NonNull Map<String, List<PackageIdentifier>> schemasVisibleToPackages,
boolean forceOverride,
- int version)
+ int version,
+ @Nullable SetSchemaStats.Builder setSchemaStatsBuilder)
throws AppSearchException {
mReadWriteLock.writeLock().lock();
try {
@@ -438,6 +441,12 @@ public final class AppSearchImpl implements Closeable {
mLogUtil.piiTrace(
"setSchema, response", setSchemaResultProto.getStatus(), setSchemaResultProto);
+ if (setSchemaStatsBuilder != null) {
+ setSchemaStatsBuilder.setStatusCode(
+ statusProtoToResultCode(setSchemaResultProto.getStatus()));
+ AppSearchLoggerHelper.copyNativeStats(setSchemaResultProto, setSchemaStatsBuilder);
+ }
+
// Determine whether it succeeded.
try {
checkSuccess(setSchemaResultProto.getStatus());
@@ -1127,8 +1136,13 @@ public final class AppSearchImpl implements Closeable {
* @throws AppSearchException on IcingSearchEngine error or if can't advance on nextPageToken.
*/
@NonNull
- public SearchResultPage getNextPage(@NonNull String packageName, long nextPageToken)
+ public SearchResultPage getNextPage(
+ @NonNull String packageName,
+ long nextPageToken,
+ @Nullable SearchStats.Builder statsBuilder)
throws AppSearchException {
+ long totalLatencyStartMillis = SystemClock.elapsedRealtime();
+
mReadWriteLock.readLock().lock();
try {
throwIfClosedLocked();
@@ -1137,6 +1151,13 @@ public final class AppSearchImpl implements Closeable {
checkNextPageToken(packageName, nextPageToken);
SearchResultProto searchResultProto =
mIcingSearchEngineLocked.getNextPage(nextPageToken);
+
+ if (statsBuilder != null) {
+ statsBuilder.setStatusCode(statusProtoToResultCode(searchResultProto.getStatus()));
+ AppSearchLoggerHelper.copyNativeStats(
+ searchResultProto.getQueryStats(), statsBuilder);
+ }
+
mLogUtil.piiTrace(
"getNextPage, response",
searchResultProto.getResultsCount(),
@@ -1152,9 +1173,22 @@ public final class AppSearchImpl implements Closeable {
mNextPageTokensLocked.get(packageName).remove(nextPageToken);
}
}
- return rewriteSearchResultProto(searchResultProto, mSchemaMapLocked);
+ long rewriteSearchResultLatencyStartMillis = SystemClock.elapsedRealtime();
+ SearchResultPage resultPage =
+ rewriteSearchResultProto(searchResultProto, mSchemaMapLocked);
+ if (statsBuilder != null) {
+ statsBuilder.setRewriteSearchResultLatencyMillis(
+ (int)
+ (SystemClock.elapsedRealtime()
+ - rewriteSearchResultLatencyStartMillis));
+ }
+ return resultPage;
} finally {
mReadWriteLock.readLock().unlock();
+ if (statsBuilder != null) {
+ statsBuilder.setTotalLatencyMillis(
+ (int) (SystemClock.elapsedRealtime() - totalLatencyStartMillis));
+ }
}
}
@@ -1334,7 +1368,7 @@ public final class AppSearchImpl implements Closeable {
statusProtoToResultCode(deleteResultProto.getStatus()));
// TODO(b/187206766) also log query stats here once IcingLib returns it
AppSearchLoggerHelper.copyNativeStats(
- deleteResultProto.getDeleteStats(), removeStatsBuilder);
+ deleteResultProto.getDeleteByQueryStats(), removeStatsBuilder);
}
// It seems that the caller wants to get success if the data matching the query is
@@ -1343,7 +1377,8 @@ public final class AppSearchImpl implements Closeable {
deleteResultProto.getStatus(), StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
// Update derived maps
- int numDocumentsDeleted = deleteResultProto.getDeleteStats().getNumDocumentsDeleted();
+ int numDocumentsDeleted =
+ deleteResultProto.getDeleteByQueryStats().getNumDocumentsDeleted();
updateDocumentCountAfterRemovalLocked(packageName, numDocumentsDeleted);
} finally {
mReadWriteLock.writeLock().unlock();
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLogger.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLogger.java
index 98cedc7e6b54..1f7d44e3b75a 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLogger.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLogger.java
@@ -24,6 +24,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;
/**
* An interface for implementing client-defined logging AppSearch operations stats.
@@ -54,5 +55,8 @@ public interface AppSearchLogger {
/** Logs {@link OptimizeStats} */
void logStats(@NonNull OptimizeStats stats);
+ /** Logs {@link SetSchemaStats} */
+ void logStats(@NonNull SetSchemaStats stats);
+
// TODO(b/173532925) Add remaining logStats once we add all the stats.
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java
index cd653e569f11..c19ba1408ec9 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java
@@ -23,12 +23,15 @@ 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.google.android.icing.proto.DeleteByQueryStatsProto;
import com.google.android.icing.proto.DeleteStatsProto;
import com.google.android.icing.proto.InitializeStatsProto;
import com.google.android.icing.proto.OptimizeStatsProto;
import com.google.android.icing.proto.PutDocumentStatsProto;
import com.google.android.icing.proto.QueryStatsProto;
+import com.google.android.icing.proto.SetSchemaResultProto;
import java.util.Objects;
@@ -142,6 +145,26 @@ public final class AppSearchLoggerHelper {
}
/**
+ * Copies native DeleteByQuery stats to builder.
+ *
+ * @param fromNativeStats Stats copied from.
+ * @param toStatsBuilder Stats copied to.
+ */
+ static void copyNativeStats(
+ @NonNull DeleteByQueryStatsProto fromNativeStats,
+ @NonNull RemoveStats.Builder toStatsBuilder) {
+ Objects.requireNonNull(fromNativeStats);
+ Objects.requireNonNull(toStatsBuilder);
+
+ @SuppressWarnings("deprecation")
+ int deleteType = DeleteStatsProto.DeleteType.Code.DEPRECATED_QUERY.getNumber();
+ toStatsBuilder
+ .setNativeLatencyMillis(fromNativeStats.getLatencyMs())
+ .setDeleteType(deleteType)
+ .setDeletedDocumentCount(fromNativeStats.getNumDocumentsDeleted());
+ }
+
+ /**
* Copies native {@link OptimizeStatsProto} to builder.
*
* @param fromNativeStats Stats copied from.
@@ -164,4 +187,25 @@ public final class AppSearchLoggerHelper {
.setStorageSizeAfterBytes(fromNativeStats.getStorageSizeAfter())
.setTimeSinceLastOptimizeMillis(fromNativeStats.getTimeSinceLastOptimizeMs());
}
+
+ /*
+ * Copy SetSchema result stats to builder.
+ *
+ * @param fromProto Stats copied from.
+ * @param toStatsBuilder Stats copied to.
+ */
+ static void copyNativeStats(
+ @NonNull SetSchemaResultProto fromProto,
+ @NonNull SetSchemaStats.Builder toStatsBuilder) {
+ Objects.requireNonNull(fromProto);
+ Objects.requireNonNull(toStatsBuilder);
+ toStatsBuilder
+ .setNewTypeCount(fromProto.getNewSchemaTypesCount())
+ .setDeletedTypeCount(fromProto.getDeletedSchemaTypesCount())
+ .setCompatibleTypeChangeCount(fromProto.getFullyCompatibleChangedSchemaTypesCount())
+ .setIndexIncompatibleTypeChangeCount(
+ fromProto.getIndexIncompatibleChangedSchemaTypesCount())
+ .setBackwardsIncompatibleTypeChangeCount(
+ fromProto.getIncompatibleSchemaTypesCount());
+ }
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/SearchStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/SearchStats.java
index d7904f3ca49f..75ae2d0accfd 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/SearchStats.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/SearchStats.java
@@ -40,6 +40,7 @@ public final class SearchStats {
VISIBILITY_SCOPE_LOCAL,
// Searches the global documents. Including platform surfaceable and 3p-access.
VISIBILITY_SCOPE_GLOBAL,
+ VISIBILITY_SCOPE_UNKNOWN,
// TODO(b/173532925) Add THIRD_PARTY_ACCESS once we can distinguish platform
// surfaceable from 3p access(right both of them are categorized as
// VISIBILITY_SCOPE_GLOBAL)
@@ -51,6 +52,7 @@ public final class SearchStats {
public static final int VISIBILITY_SCOPE_LOCAL = 1;
// Searches the global documents. Including platform surfaceable and 3p-access.
public static final int VISIBILITY_SCOPE_GLOBAL = 2;
+ public static final int VISIBILITY_SCOPE_UNKNOWN = 3;
// TODO(b/173532925): Add a field searchType to indicate where the search is used(normal
// query vs in removeByQuery vs during migration)
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/SetSchemaStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/SetSchemaStats.java
index 9d789a894855..3e5a80f88f5f 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/SetSchemaStats.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/SetSchemaStats.java
@@ -47,9 +47,6 @@ public final class SetSchemaStats {
private final int mTotalLatencyMillis;
- /** Overall time used for the native function call. */
- private final int mNativeLatencyMillis;
-
/** Number of newly added schema types. */
private final int mNewTypeCount;
@@ -72,7 +69,6 @@ public final class SetSchemaStats {
mStatusCode = builder.mStatusCode;
mSchemaMigrationStats = builder.mSchemaMigrationStats;
mTotalLatencyMillis = builder.mTotalLatencyMillis;
- mNativeLatencyMillis = builder.mNativeLatencyMillis;
mNewTypeCount = builder.mNewTypeCount;
mDeletedTypeCount = builder.mDeletedTypeCount;
mCompatibleTypeChangeCount = builder.mCompatibleTypeChangeCount;
@@ -112,11 +108,6 @@ public final class SetSchemaStats {
return mTotalLatencyMillis;
}
- /** Returns overall time used for the native function call. */
- public int getNativeLatencyMillis() {
- return mNativeLatencyMillis;
- }
-
/** Returns number of newly added schema types. */
public int getNewTypeCount() {
return mNewTypeCount;
@@ -159,7 +150,6 @@ public final class SetSchemaStats {
@AppSearchResult.ResultCode int mStatusCode;
@Nullable SchemaMigrationStats mSchemaMigrationStats;
int mTotalLatencyMillis;
- int mNativeLatencyMillis;
int mNewTypeCount;
int mDeletedTypeCount;
int mCompatibleTypeChangeCount;
@@ -193,13 +183,6 @@ public final class SetSchemaStats {
return this;
}
- /** Sets native latency in milliseconds. */
- @NonNull
- public Builder setNativeLatencyMillis(int nativeLatencyMillis) {
- mNativeLatencyMillis = nativeLatencyMillis;
- return this;
- }
-
/** Sets number of new types. */
@NonNull
public Builder setNewTypeCount(int newTypeCount) {
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
index fdf6a008b10c..4c29ece3dd03 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
@@ -36,6 +36,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.util.PackageUtil;
import java.io.UnsupportedEncodingException;
@@ -180,6 +181,11 @@ public final class PlatformLogger implements AppSearchLogger {
}
}
+ @Override
+ public void logStats(@NonNull SetSchemaStats stats) {
+ // TODO(b/173532925): Log stats
+ }
+
/**
* Removes cached UID for package.
*
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStoreImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStoreImpl.java
index ce142d646d1c..c4d10169f9fc 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStoreImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/visibilitystore/VisibilityStoreImpl.java
@@ -126,7 +126,8 @@ public class VisibilityStoreImpl implements VisibilityStore {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ SCHEMA_VERSION);
+ /*version=*/ SCHEMA_VERSION,
+ /*setSchemaStatsBuilder=*/ null);
}
// Populate visibility settings set
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index a81d7d8022b2..4db8355076cf 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-Ie04f1ecc033faae8085afcb51eb9e40a298998d5
+bd53b062816070b64feb992c2bf58f3afa3d420e
diff --git a/apex/appsearch/testing/Android.bp b/apex/appsearch/testing/Android.bp
index 5407cb4ccec7..f78d98ae115a 100644
--- a/apex/appsearch/testing/Android.bp
+++ b/apex/appsearch/testing/Android.bp
@@ -28,6 +28,7 @@ java_library {
"framework",
"framework-appsearch",
"guava",
+ "service-appsearch",
"truth-prebuilt",
],
visibility: [
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
index ec9a42eaa276..4d8e8e9e7b1a 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java
@@ -19,6 +19,8 @@ package com.android.server.appsearch.testing;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchSessionShim;
import android.app.appsearch.GenericDocument;
@@ -26,12 +28,66 @@ import android.app.appsearch.GetByDocumentIdRequest;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResultsShim;
+import com.android.server.appsearch.external.localstorage.AppSearchLogger;
+import com.android.server.appsearch.external.localstorage.stats.CallStats;
+import com.android.server.appsearch.external.localstorage.stats.InitializeStats;
+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 java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Future;
public class AppSearchTestUtils {
+ // Non-thread-safe logger implementation for testing
+ public static class TestLogger implements AppSearchLogger {
+ @Nullable public CallStats mCallStats;
+ @Nullable public PutDocumentStats mPutDocumentStats;
+ @Nullable public InitializeStats mInitializeStats;
+ @Nullable public SearchStats mSearchStats;
+ @Nullable public RemoveStats mRemoveStats;
+ @Nullable public OptimizeStats mOptimizeStats;
+ @Nullable public SetSchemaStats mSetSchemaStats;
+
+ @Override
+ public void logStats(@NonNull CallStats stats) {
+ mCallStats = stats;
+ }
+
+ @Override
+ public void logStats(@NonNull PutDocumentStats stats) {
+ mPutDocumentStats = stats;
+ }
+
+ @Override
+ public void logStats(@NonNull InitializeStats stats) {
+ mInitializeStats = stats;
+ }
+
+ @Override
+ public void logStats(@NonNull SearchStats stats) {
+ mSearchStats = stats;
+ }
+
+ @Override
+ public void logStats(@NonNull RemoveStats stats) {
+ mRemoveStats = stats;
+ }
+
+ @Override
+ public void logStats(@NonNull OptimizeStats stats) {
+ mOptimizeStats = stats;
+ }
+
+ @Override
+ public void logStats(@NonNull SetSchemaStats stats) {
+ mSetSchemaStats = stats;
+ }
+ }
public static <K, V> AppSearchBatchResult<K, V> checkIsBatchResultSuccess(
Future<AppSearchBatchResult<K, V>> future) throws Exception {
diff --git a/core/api/current.txt b/core/api/current.txt
index 1de47b548a5c..1dd401d04e2b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -99,6 +99,7 @@ package android {
field public static final String INTERACT_ACROSS_PROFILES = "android.permission.INTERACT_ACROSS_PROFILES";
field public static final String INTERNET = "android.permission.INTERNET";
field public static final String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES";
+ field public static final String LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK = "android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK";
field public static final String LOADER_USAGE_STATS = "android.permission.LOADER_USAGE_STATS";
field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
@@ -1297,6 +1298,7 @@ package android {
field public static final int shortcutLongLabel = 16844074; // 0x101052a
field public static final int shortcutShortLabel = 16844073; // 0x1010529
field public static final int shouldDisableView = 16843246; // 0x10101ee
+ field public static final int shouldUseDefaultUnfoldTransition = 16844364; // 0x101064c
field public static final int showAsAction = 16843481; // 0x10102d9
field public static final int showDefault = 16843258; // 0x10101fa
field public static final int showDividers = 16843561; // 0x1010329
@@ -2012,6 +2014,9 @@ package android {
public static final class R.id {
ctor public R.id();
field public static final int accessibilityActionContextClick = 16908348; // 0x102003c
+ field public static final int accessibilityActionDragCancel = 16908375; // 0x1020057
+ field public static final int accessibilityActionDragDrop = 16908374; // 0x1020056
+ field public static final int accessibilityActionDragStart = 16908373; // 0x1020055
field public static final int accessibilityActionHideTooltip = 16908357; // 0x1020045
field public static final int accessibilityActionImeEnter = 16908372; // 0x1020054
field public static final int accessibilityActionMoveWindow = 16908354; // 0x1020042
@@ -6707,6 +6712,7 @@ package android.app {
}
public class TaskInfo {
+ method public boolean isVisible();
field @Nullable public android.content.ComponentName baseActivity;
field @NonNull public android.content.Intent baseIntent;
field public boolean isRunning;
@@ -6922,6 +6928,7 @@ package android.app {
method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
method public CharSequence loadLabel(android.content.pm.PackageManager);
method public android.graphics.drawable.Drawable loadThumbnail(android.content.pm.PackageManager);
+ method public boolean shouldUseDefaultUnfoldTransition();
method public boolean supportsMultipleDisplays();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.WallpaperInfo> CREATOR;
@@ -17943,6 +17950,7 @@ package android.hardware.camera2 {
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> EDGE_AVAILABLE_EDGE_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> FLASH_INFO_AVAILABLE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap> INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> INFO_SUPPORTED_HARDWARE_LEVEL;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.String> INFO_VERSION;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES;
@@ -18637,6 +18645,12 @@ package android.hardware.camera2.params {
method public android.util.Rational getElement(int, int);
}
+ public final class DeviceStateSensorOrientationMap {
+ method public int getSensorOrientation(long);
+ field public static final long FOLDED = 4L; // 0x4L
+ field public static final long NORMAL = 0L; // 0x0L
+ }
+
public final class ExtensionSessionConfiguration {
ctor public ExtensionSessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.StateCallback);
method @NonNull public java.util.concurrent.Executor getExecutor();
@@ -20178,8 +20192,10 @@ package android.media {
method public int getAllowedCapturePolicy();
method public int getContentType();
method public int getFlags();
+ method public int getSpatializationBehavior();
method public int getUsage();
method public int getVolumeControlStream();
+ method public boolean isContentSpatialized();
method public void writeToParcel(android.os.Parcel, int);
field public static final int ALLOW_CAPTURE_BY_ALL = 1; // 0x1
field public static final int ALLOW_CAPTURE_BY_NONE = 3; // 0x3
@@ -20193,6 +20209,8 @@ package android.media {
field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
field public static final int FLAG_HW_AV_SYNC = 16; // 0x10
field @Deprecated public static final int FLAG_LOW_LATENCY = 256; // 0x100
+ field public static final int SPATIALIZATION_BEHAVIOR_AUTO = 0; // 0x0
+ field public static final int SPATIALIZATION_BEHAVIOR_NEVER = 1; // 0x1
field public static final int USAGE_ALARM = 4; // 0x4
field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
@@ -20219,7 +20237,9 @@ package android.media {
method public android.media.AudioAttributes.Builder setContentType(int);
method public android.media.AudioAttributes.Builder setFlags(int);
method @NonNull public android.media.AudioAttributes.Builder setHapticChannelsMuted(boolean);
+ method @NonNull public android.media.AudioAttributes.Builder setIsContentSpatialized(boolean);
method public android.media.AudioAttributes.Builder setLegacyStreamType(int);
+ method @NonNull public android.media.AudioAttributes.Builder setSpatializationBehavior(int);
method public android.media.AudioAttributes.Builder setUsage(int);
}
@@ -20336,24 +20356,45 @@ package android.media {
field public static final int CHANNEL_IN_Y_AXIS = 4096; // 0x1000
field public static final int CHANNEL_IN_Z_AXIS = 8192; // 0x2000
field public static final int CHANNEL_OUT_5POINT1 = 252; // 0xfc
+ field public static final int CHANNEL_OUT_5POINT1POINT2 = 3145980; // 0x3000fc
+ field public static final int CHANNEL_OUT_5POINT1POINT4 = 737532; // 0xb40fc
field @Deprecated public static final int CHANNEL_OUT_7POINT1 = 1020; // 0x3fc
+ field public static final int CHANNEL_OUT_7POINT1POINT2 = 3152124; // 0x3018fc
+ field public static final int CHANNEL_OUT_7POINT1POINT4 = 743676; // 0xb58fc
field public static final int CHANNEL_OUT_7POINT1_SURROUND = 6396; // 0x18fc
+ field public static final int CHANNEL_OUT_9POINT1POINT4 = 202070268; // 0xc0b58fc
+ field public static final int CHANNEL_OUT_9POINT1POINT6 = 205215996; // 0xc3b58fc
field public static final int CHANNEL_OUT_BACK_CENTER = 1024; // 0x400
field public static final int CHANNEL_OUT_BACK_LEFT = 64; // 0x40
field public static final int CHANNEL_OUT_BACK_RIGHT = 128; // 0x80
+ field public static final int CHANNEL_OUT_BOTTOM_FRONT_CENTER = 8388608; // 0x800000
+ field public static final int CHANNEL_OUT_BOTTOM_FRONT_LEFT = 4194304; // 0x400000
+ field public static final int CHANNEL_OUT_BOTTOM_FRONT_RIGHT = 16777216; // 0x1000000
field public static final int CHANNEL_OUT_DEFAULT = 1; // 0x1
field public static final int CHANNEL_OUT_FRONT_CENTER = 16; // 0x10
field public static final int CHANNEL_OUT_FRONT_LEFT = 4; // 0x4
field public static final int CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 256; // 0x100
field public static final int CHANNEL_OUT_FRONT_RIGHT = 8; // 0x8
field public static final int CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 512; // 0x200
+ field public static final int CHANNEL_OUT_FRONT_WIDE_LEFT = 67108864; // 0x4000000
+ field public static final int CHANNEL_OUT_FRONT_WIDE_RIGHT = 134217728; // 0x8000000
field public static final int CHANNEL_OUT_LOW_FREQUENCY = 32; // 0x20
+ field public static final int CHANNEL_OUT_LOW_FREQUENCY_2 = 33554432; // 0x2000000
field public static final int CHANNEL_OUT_MONO = 4; // 0x4
field public static final int CHANNEL_OUT_QUAD = 204; // 0xcc
field public static final int CHANNEL_OUT_SIDE_LEFT = 2048; // 0x800
field public static final int CHANNEL_OUT_SIDE_RIGHT = 4096; // 0x1000
field public static final int CHANNEL_OUT_STEREO = 12; // 0xc
field public static final int CHANNEL_OUT_SURROUND = 1052; // 0x41c
+ field public static final int CHANNEL_OUT_TOP_BACK_CENTER = 262144; // 0x40000
+ field public static final int CHANNEL_OUT_TOP_BACK_LEFT = 131072; // 0x20000
+ field public static final int CHANNEL_OUT_TOP_BACK_RIGHT = 524288; // 0x80000
+ field public static final int CHANNEL_OUT_TOP_CENTER = 8192; // 0x2000
+ field public static final int CHANNEL_OUT_TOP_FRONT_CENTER = 32768; // 0x8000
+ field public static final int CHANNEL_OUT_TOP_FRONT_LEFT = 16384; // 0x4000
+ field public static final int CHANNEL_OUT_TOP_FRONT_RIGHT = 65536; // 0x10000
+ field public static final int CHANNEL_OUT_TOP_SIDE_LEFT = 1048576; // 0x100000
+ field public static final int CHANNEL_OUT_TOP_SIDE_RIGHT = 2097152; // 0x200000
field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioFormat> CREATOR;
field public static final int ENCODING_AAC_ELD = 15; // 0xf
field public static final int ENCODING_AAC_HE_V1 = 11; // 0xb
@@ -20423,6 +20464,7 @@ package android.media {
method public String getProperty(String);
method public int getRingerMode();
method @Deprecated public int getRouting(int);
+ method @NonNull public android.media.Spatializer getSpatializer();
method public int getStreamMaxVolume(int);
method public int getStreamMinVolume(int);
method public int getStreamVolume(int);
@@ -22540,6 +22582,7 @@ package android.media {
field public static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder";
field public static final String KEY_MAX_HEIGHT = "max-height";
field public static final String KEY_MAX_INPUT_SIZE = "max-input-size";
+ field public static final String KEY_MAX_OUTPUT_CHANNEL_COUNT = "max-output-channel-count";
field public static final String KEY_MAX_PTS_GAP_TO_ENCODER = "max-pts-gap-to-encoder";
field public static final String KEY_MAX_WIDTH = "max-width";
field public static final String KEY_MIME = "mime";
@@ -23819,6 +23862,23 @@ package android.media {
method public void onLoadComplete(android.media.SoundPool, int, int);
}
+ public class Spatializer {
+ method public void addOnSpatializerStateChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerStateChangedListener);
+ method public boolean canBeSpatialized(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioFormat);
+ method public int getImmersiveAudioLevel();
+ method public boolean isAvailable();
+ method public boolean isEnabled();
+ method public void removeOnSpatializerStateChangedListener(@NonNull android.media.Spatializer.OnSpatializerStateChangedListener);
+ field public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1; // 0x1
+ field public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0; // 0x0
+ field public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1; // 0xffffffff
+ }
+
+ public static interface Spatializer.OnSpatializerStateChangedListener {
+ method public void onSpatializerAvailableChanged(@NonNull android.media.Spatializer, boolean);
+ method public void onSpatializerEnabledChanged(@NonNull android.media.Spatializer, boolean);
+ }
+
public final class SubtitleData {
ctor public SubtitleData(int, long, long, @NonNull byte[]);
method @NonNull public byte[] getData();
@@ -30797,6 +30857,7 @@ package android.os {
field public static final int Q = 29; // 0x1d
field public static final int R = 30; // 0x1e
field public static final int S = 31; // 0x1f
+ field public static final int S_V2 = 32; // 0x20
}
public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
@@ -31070,8 +31131,8 @@ package android.os {
ctor public Environment();
method public static java.io.File getDataDirectory();
method public static java.io.File getDownloadCacheDirectory();
- method @Deprecated public static java.io.File getExternalStorageDirectory();
- method @Deprecated public static java.io.File getExternalStoragePublicDirectory(String);
+ method public static java.io.File getExternalStorageDirectory();
+ method public static java.io.File getExternalStoragePublicDirectory(String);
method public static String getExternalStorageState();
method public static String getExternalStorageState(java.io.File);
method @NonNull public static java.io.File getRootDirectory();
@@ -35182,6 +35243,7 @@ package android.provider {
field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
field public static final String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
field public static final String ACTION_SETTINGS = "android.settings.SETTINGS";
+ field public static final String ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY = "android.settings.SETTINGS_EMBED_DEEP_LINK_ACTIVITY";
field public static final String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
field public static final String ACTION_SHOW_WORK_POLICY_INFO = "android.settings.SHOW_WORK_POLICY_INFO";
field public static final String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS";
@@ -35222,6 +35284,8 @@ package android.provider {
field public static final String EXTRA_EASY_CONNECT_ERROR_CODE = "android.provider.extra.EASY_CONNECT_ERROR_CODE";
field public static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
field public static final String EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME = "android.provider.extra.NOTIFICATION_LISTENER_COMPONENT_NAME";
+ field public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY = "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY";
+ field public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI = "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI";
field public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
field public static final String EXTRA_WIFI_NETWORK_LIST = "android.provider.extra.WIFI_NETWORK_LIST";
field public static final String EXTRA_WIFI_NETWORK_RESULT_LIST = "android.provider.extra.WIFI_NETWORK_RESULT_LIST";
@@ -38947,6 +39011,13 @@ package android.service.textservice {
package android.service.voice {
+ public final class VisibleActivityInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.service.voice.VoiceInteractionSession.ActivityId getActivityId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.VisibleActivityInfo> CREATOR;
+ }
+
public class VoiceInteractionService extends android.app.Service {
ctor public VoiceInteractionService();
method public int getDisabledShowContext();
@@ -39008,6 +39079,7 @@ package android.service.voice {
method public void onTaskStarted(android.content.Intent, int);
method public void onTrimMemory(int);
method public final void performDirectAction(@NonNull android.app.DirectAction, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.os.Bundle>);
+ method public final void registerVisibleActivityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.service.voice.VoiceInteractionSession.VisibleActivityCallback);
method public final void requestDirectActions(@NonNull android.service.voice.VoiceInteractionSession.ActivityId, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.DirectAction>>);
method public void setContentView(android.view.View);
method public void setDisabledShowContext(int);
@@ -39017,6 +39089,7 @@ package android.service.voice {
method public void show(android.os.Bundle, int);
method public void startAssistantActivity(android.content.Intent);
method public void startVoiceActivity(android.content.Intent);
+ method public final void unregisterVisibleActivityCallback(@NonNull android.service.voice.VoiceInteractionSession.VisibleActivityCallback);
field public static final int SHOW_SOURCE_ACTIVITY = 16; // 0x10
field public static final int SHOW_SOURCE_APPLICATION = 8; // 0x8
field public static final int SHOW_SOURCE_ASSIST_GESTURE = 4; // 0x4
@@ -39090,6 +39163,11 @@ package android.service.voice {
method public boolean isActive();
}
+ public static interface VoiceInteractionSession.VisibleActivityCallback {
+ method public default void onInvisible(@NonNull android.service.voice.VoiceInteractionSession.ActivityId);
+ method public default void onVisible(@NonNull android.service.voice.VisibleActivityInfo);
+ }
+
public abstract class VoiceInteractionSessionService extends android.app.Service {
ctor public VoiceInteractionSessionService();
method public android.os.IBinder onBind(android.content.Intent);
@@ -46837,8 +46915,15 @@ package android.view {
}
@UiThread public interface AttachedSurfaceControl {
+ method public default void addOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
method public boolean applyTransactionOnDraw(@NonNull android.view.SurfaceControl.Transaction);
method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl);
+ method public default int getBufferTransformHint();
+ method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
+ }
+
+ @UiThread public static interface AttachedSurfaceControl.OnBufferTransformHintChangedListener {
+ method public void onBufferTransformHintChanged(int);
}
public final class Choreographer {
@@ -48328,6 +48413,12 @@ package android.view {
method public void readFromParcel(android.os.Parcel);
method public void release();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int BUFFER_TRANSFORM_IDENTITY = 0; // 0x0
+ field public static final int BUFFER_TRANSFORM_MIRROR_HORIZONTAL = 1; // 0x1
+ field public static final int BUFFER_TRANSFORM_MIRROR_VERTICAL = 2; // 0x2
+ field public static final int BUFFER_TRANSFORM_ROTATE_180 = 3; // 0x3
+ field public static final int BUFFER_TRANSFORM_ROTATE_270 = 7; // 0x7
+ field public static final int BUFFER_TRANSFORM_ROTATE_90 = 4; // 0x4
field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl> CREATOR;
}
@@ -49190,6 +49281,7 @@ package android.view {
field public static final int AUTOFILL_TYPE_NONE = 0; // 0x0
field public static final int AUTOFILL_TYPE_TEXT = 1; // 0x1
field public static final int AUTOFILL_TYPE_TOGGLE = 2; // 0x2
+ field public static final int DRAG_FLAG_ACCESSIBILITY_ACTION = 1024; // 0x400
field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
@@ -50616,6 +50708,9 @@ package android.view.accessibility {
method public void setPackageName(CharSequence);
method public void writeToParcel(android.os.Parcel, int);
field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4
+ field public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 512; // 0x200
+ field public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 256; // 0x100
+ field public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 128; // 0x80
field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10
field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20
field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
@@ -50914,6 +51009,9 @@ package android.view.accessibility {
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_COPY;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_CUT;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DISMISS;
+ field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DRAG_CANCEL;
+ field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DRAG_DROP;
+ field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DRAG_START;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_EXPAND;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_FOCUS;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_HIDE_TOOLTIP;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2d73aa67ed1a..3aa17f0865b4 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -26,6 +26,7 @@ package android {
field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY";
field public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE";
field public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK";
+ field public static final String ALLOW_PLACE_IN_MULTI_PANE_SETTINGS = "android.permission.ALLOW_PLACE_IN_MULTI_PANE_SETTINGS";
field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
field public static final String ASSOCIATE_COMPANION_DEVICES = "android.permission.ASSOCIATE_COMPANION_DEVICES";
@@ -436,6 +437,11 @@ package android.app {
method public void onUidImportance(int, int);
}
+ public class ActivityOptions {
+ method public int getLaunchTaskId();
+ method @RequiresPermission("android.permission.START_TASKS_FROM_RECENTS") public void setLaunchTaskId(int);
+ }
+
public class AlarmManager {
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.PendingIntent, android.os.WorkSource);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.AlarmManager.OnAlarmListener, android.os.Handler, android.os.WorkSource);
@@ -3262,11 +3268,13 @@ package android.hardware.display {
public final class DisplayManager {
method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_LIGHT_STATS) public java.util.List<android.hardware.display.AmbientBrightnessDayStats> getAmbientBrightnessStats();
method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getBrightnessConfiguration();
+ method @Nullable @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getBrightnessConfigurationForDisplay(@NonNull String);
method @RequiresPermission(android.Manifest.permission.BRIGHTNESS_SLIDER_USAGE) public java.util.List<android.hardware.display.BrightnessChangeEvent> getBrightnessEvents();
method @Nullable @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getDefaultBrightnessConfiguration();
method public android.util.Pair<float[],float[]> getMinimumBrightnessCurve();
method public android.graphics.Point getStableDisplaySize();
method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
+ method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String);
method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float);
}
@@ -5370,6 +5378,46 @@ package android.media {
field public static final android.media.RouteDiscoveryPreference EMPTY;
}
+ public class Spatializer {
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void addCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void addOnHeadTrackingModeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadTrackingModeChangedListener);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void clearOnHeadToSoundstagePoseUpdatedListener();
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void clearOnSpatializerOutputChangedListener();
+ method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<android.media.AudioDeviceAttributes> getCompatibleAudioDevices();
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getDesiredHeadTrackingMode();
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void getEffectParameter(int, @NonNull byte[]);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getHeadTrackingMode();
+ method @IntRange(from=0) @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getOutput();
+ method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<java.lang.Integer> getSupportedHeadTrackingModes();
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void recenterHeadTracker();
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void removeCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void removeOnHeadTrackingModeChangedListener(@NonNull android.media.Spatializer.OnHeadTrackingModeChangedListener);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setDesiredHeadTrackingMode(int);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setEffectParameter(int, @NonNull byte[]);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setEnabled(boolean);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setGlobalTransform(@NonNull float[]);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnHeadToSoundstagePoseUpdatedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadToSoundstagePoseUpdatedListener);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnSpatializerOutputChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerOutputChangedListener);
+ field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_DISABLED = -1; // 0xffffffff
+ field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_OTHER = 0; // 0x0
+ field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2; // 0x2
+ field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_RELATIVE_WORLD = 1; // 0x1
+ field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_UNSUPPORTED = -2; // 0xfffffffe
+ }
+
+ public static interface Spatializer.OnHeadToSoundstagePoseUpdatedListener {
+ method public void onHeadToSoundstagePoseUpdated(@NonNull android.media.Spatializer, @NonNull float[]);
+ }
+
+ public static interface Spatializer.OnHeadTrackingModeChangedListener {
+ method public void onDesiredHeadTrackingModeChanged(@NonNull android.media.Spatializer, int);
+ method public void onHeadTrackingModeChanged(@NonNull android.media.Spatializer, int);
+ }
+
+ public static interface Spatializer.OnSpatializerOutputChangedListener {
+ method public void onSpatializerOutputChanged(@NonNull android.media.Spatializer, @IntRange(from=0) int);
+ }
+
}
package android.media.audiofx {
@@ -14414,6 +14462,7 @@ package android.view.contentcapture {
method public int getFlags();
method @Nullable public android.view.contentcapture.ContentCaptureSessionId getParentSessionId();
method public int getTaskId();
+ method @Nullable public android.os.IBinder getWindowToken();
field public static final int FLAG_DISABLED_BY_APP = 1; // 0x1
field public static final int FLAG_DISABLED_BY_FLAG_SECURE = 2; // 0x2
field public static final int FLAG_RECONNECTED = 4; // 0x4
@@ -14421,6 +14470,7 @@ package android.view.contentcapture {
public final class ContentCaptureEvent implements android.os.Parcelable {
method public int describeContents();
+ method @Nullable public android.graphics.Rect getBounds();
method @Nullable public android.view.contentcapture.ContentCaptureContext getContentCaptureContext();
method public long getEventTime();
method @Nullable public android.view.autofill.AutofillId getId();
@@ -14440,6 +14490,7 @@ package android.view.contentcapture {
field public static final int TYPE_VIEW_TEXT_CHANGED = 3; // 0x3
field public static final int TYPE_VIEW_TREE_APPEARED = 5; // 0x5
field public static final int TYPE_VIEW_TREE_APPEARING = 4; // 0x4
+ field public static final int TYPE_WINDOW_BOUNDS_CHANGED = 10; // 0xa
}
public final class ContentCaptureManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ea6d0cecfd73..488f8b145e52 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -144,7 +144,6 @@ package android.app {
method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
method public static void setExitTransitionTimeout(long);
method public void setLaunchActivityType(int);
- method public void setLaunchTaskId(int);
method public void setLaunchWindowingMode(int);
method public void setLaunchedFromBubble(boolean);
method public void setTaskAlwaysOnTop(boolean);
@@ -425,6 +424,7 @@ package android.app.admin {
public class DevicePolicyManager {
method public int checkProvisioningPreCondition(@Nullable String, @NonNull String);
+ method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void clearOrganizationId();
method @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord();
method @Nullable public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs();
@@ -447,6 +447,7 @@ package android.app.admin {
method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void resetDefaultCrossProfileIntentFilters(int);
method @RequiresPermission(allOf={"android.permission.MANAGE_DEVICE_ADMINS", android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int);
method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int);
+ method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, @Nullable String, int);
method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int);
field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED";
@@ -1133,10 +1134,10 @@ package android.hardware.camera2 {
package android.hardware.devicestate {
public final class DeviceStateManager {
- method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest);
+ method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest);
method @NonNull public int[] getSupportedStates();
method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
- method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
+ method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
method public void unregisterCallback(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff
field public static final int MINIMUM_DEVICE_STATE = 0; // 0x0
@@ -1281,6 +1282,12 @@ package android.hardware.soundtrigger {
package android.inputmethodservice {
+ public abstract class AbstractInputMethodService extends android.window.WindowProviderService implements android.view.KeyEvent.Callback {
+ method public final int getInitialDisplayId();
+ method @Nullable public final android.os.Bundle getWindowContextOptions();
+ method public final int getWindowType();
+ }
+
@UiContext public class InputMethodService extends android.inputmethodservice.AbstractInputMethodService {
field public static final long FINISH_INPUT_NO_FALLBACK_CONNECTION = 156215187L; // 0x94fa793L
}
@@ -2121,6 +2128,7 @@ package android.provider {
public final class DeviceConfig {
field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
field public static final String NAMESPACE_ANDROID = "android";
+ field public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
field public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
field public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
@@ -2383,6 +2391,10 @@ package android.service.voice {
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public void triggerHardwareRecognitionEventForTest(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[]);
}
+ public final class VisibleActivityInfo implements android.os.Parcelable {
+ ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder);
+ }
+
}
package android.service.watchdog {
@@ -3178,13 +3190,6 @@ package android.window {
method @Nullable public android.view.View getBrandingView();
}
- public final class StartingWindowInfo implements android.os.Parcelable {
- ctor public StartingWindowInfo();
- method public int describeContents();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.window.StartingWindowInfo> CREATOR;
- }
-
public final class TaskAppearedInfo implements android.os.Parcelable {
ctor public TaskAppearedInfo(@NonNull android.app.ActivityManager.RunningTaskInfo, @NonNull android.view.SurfaceControl);
method public int describeContents();
@@ -3194,9 +3199,63 @@ package android.window {
field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskAppearedInfo> CREATOR;
}
+ public final class TaskFragmentAppearedInfo implements android.os.Parcelable {
+ method @NonNull public android.view.SurfaceControl getLeash();
+ method @NonNull public android.window.TaskFragmentInfo getTaskFragmentInfo();
+ field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentAppearedInfo> CREATOR;
+ }
+
+ public final class TaskFragmentCreationParams implements android.os.Parcelable {
+ method @NonNull public android.os.IBinder getFragmentToken();
+ method @NonNull public android.graphics.Rect getInitialBounds();
+ method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizer();
+ method @NonNull public android.os.IBinder getOwnerToken();
+ method public int getWindowingMode();
+ field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentCreationParams> CREATOR;
+ }
+
+ public static final class TaskFragmentCreationParams.Builder {
+ ctor public TaskFragmentCreationParams.Builder(@NonNull android.window.TaskFragmentOrganizerToken, @NonNull android.os.IBinder, @NonNull android.os.IBinder);
+ method @NonNull public android.window.TaskFragmentCreationParams build();
+ method @NonNull public android.window.TaskFragmentCreationParams.Builder setInitialBounds(@NonNull android.graphics.Rect);
+ method @NonNull public android.window.TaskFragmentCreationParams.Builder setWindowingMode(int);
+ }
+
+ public final class TaskFragmentInfo implements android.os.Parcelable {
+ method public boolean equalsForTaskFragmentOrganizer(@Nullable android.window.TaskFragmentInfo);
+ method @NonNull public java.util.List<android.os.IBinder> getActivities();
+ method @NonNull public android.content.res.Configuration getConfiguration();
+ method @NonNull public android.os.IBinder getFragmentToken();
+ method @NonNull public android.graphics.Point getPositionInParent();
+ method public int getRunningActivityCount();
+ method @NonNull public android.window.WindowContainerToken getToken();
+ method public int getWindowingMode();
+ method public boolean hasRunningActivity();
+ method public boolean isEmpty();
+ method public boolean isTaskClearedForReuse();
+ method public boolean isVisible();
+ field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentInfo> CREATOR;
+ }
+
+ public class TaskFragmentOrganizer extends android.window.WindowOrganizer {
+ ctor public TaskFragmentOrganizer(@NonNull java.util.concurrent.Executor);
+ method @NonNull public java.util.concurrent.Executor getExecutor();
+ method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizerToken();
+ method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentAppearedInfo);
+ method public void onTaskFragmentError(@NonNull android.os.IBinder, @NonNull Throwable);
+ method public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo);
+ method public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration);
+ method public void onTaskFragmentVanished(@NonNull android.window.TaskFragmentInfo);
+ method @CallSuper public void registerOrganizer();
+ method @CallSuper public void unregisterOrganizer();
+ }
+
+ public final class TaskFragmentOrganizerToken implements android.os.Parcelable {
+ field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR;
+ }
+
public class TaskOrganizer extends android.window.WindowOrganizer {
ctor public TaskOrganizer();
- method @BinderThread public void addStartingWindow(@NonNull android.window.StartingWindowInfo, @NonNull android.os.IBinder);
method @BinderThread public void copySplashScreenView(int);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void createRootTask(int, int, @Nullable android.os.IBinder);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean deleteRootTask(@NonNull android.window.WindowContainerToken);
@@ -3209,7 +3268,6 @@ package android.window {
method @BinderThread public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo);
method @BinderThread public void onTaskVanished(@NonNull android.app.ActivityManager.RunningTaskInfo);
method @CallSuper @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.window.TaskAppearedInfo> registerOrganizer();
- method @BinderThread public void removeStartingWindow(int, @Nullable android.view.SurfaceControl, @Nullable android.graphics.Rect, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void setInterceptBackPressedOnTaskRoot(@NonNull android.window.WindowContainerToken, boolean);
method @CallSuper @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void unregisterOrganizer();
}
@@ -3222,26 +3280,42 @@ package android.window {
public final class WindowContainerTransaction implements android.os.Parcelable {
ctor public WindowContainerTransaction();
+ method @NonNull public android.window.WindowContainerTransaction createTaskFragment(@NonNull android.window.TaskFragmentCreationParams);
+ method @NonNull public android.window.WindowContainerTransaction deleteTaskFragment(@NonNull android.window.WindowContainerToken);
method public int describeContents();
method @NonNull public android.window.WindowContainerTransaction reorder(@NonNull android.window.WindowContainerToken, boolean);
method @NonNull public android.window.WindowContainerTransaction reparent(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, boolean);
+ method @NonNull public android.window.WindowContainerTransaction reparentActivityToTaskFragment(@NonNull android.os.IBinder, @NonNull android.os.IBinder);
+ method @NonNull public android.window.WindowContainerTransaction reparentChildren(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken);
method @NonNull public android.window.WindowContainerTransaction reparentTasks(@Nullable android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, @Nullable int[], @Nullable int[], boolean);
method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int);
method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken);
+ method @NonNull public android.window.WindowContainerTransaction setAdjacentTaskFragments(@NonNull android.os.IBinder, @Nullable android.os.IBinder, @Nullable android.window.WindowContainerTransaction.TaskFragmentAdjacentParams);
method @NonNull public android.window.WindowContainerTransaction setAppBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
method @NonNull public android.window.WindowContainerTransaction setBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
method @NonNull public android.window.WindowContainerTransaction setBoundsChangeTransaction(@NonNull android.window.WindowContainerToken, @NonNull android.view.SurfaceControl.Transaction);
+ method @NonNull public android.window.WindowContainerTransaction setErrorCallbackToken(@NonNull android.os.IBinder);
method @NonNull public android.window.WindowContainerTransaction setFocusable(@NonNull android.window.WindowContainerToken, boolean);
method @NonNull public android.window.WindowContainerTransaction setHidden(@NonNull android.window.WindowContainerToken, boolean);
method @NonNull public android.window.WindowContainerTransaction setLaunchRoot(@NonNull android.window.WindowContainerToken, @Nullable int[], @Nullable int[]);
method @NonNull public android.window.WindowContainerTransaction setScreenSizeDp(@NonNull android.window.WindowContainerToken, int, int);
method @NonNull public android.window.WindowContainerTransaction setSmallestScreenWidthDp(@NonNull android.window.WindowContainerToken, int);
method @NonNull public android.window.WindowContainerTransaction setWindowingMode(@NonNull android.window.WindowContainerToken, int);
+ method @NonNull public android.window.WindowContainerTransaction startActivityInTaskFragment(@NonNull android.os.IBinder, @NonNull android.os.IBinder, @NonNull android.content.Intent, @Nullable android.os.Bundle);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.window.WindowContainerTransaction> CREATOR;
}
+ public static class WindowContainerTransaction.TaskFragmentAdjacentParams {
+ ctor public WindowContainerTransaction.TaskFragmentAdjacentParams();
+ ctor public WindowContainerTransaction.TaskFragmentAdjacentParams(@NonNull android.os.Bundle);
+ method public void setShouldDelayPrimaryLastActivityRemoval(boolean);
+ method public void setShouldDelaySecondaryLastActivityRemoval(boolean);
+ method public boolean shouldDelayPrimaryLastActivityRemoval();
+ method public boolean shouldDelaySecondaryLastActivityRemoval();
+ }
+
public abstract class WindowContainerTransactionCallback {
ctor public WindowContainerTransactionCallback();
method public abstract void onTransactionReady(int, @NonNull android.view.SurfaceControl.Transaction);
@@ -3249,14 +3323,15 @@ package android.window {
public class WindowOrganizer {
ctor public WindowOrganizer();
- method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback);
- method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void applyTransaction(@NonNull android.window.WindowContainerTransaction);
+ method @RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback);
+ method @RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true) public void applyTransaction(@NonNull android.window.WindowContainerTransaction);
}
@UiContext public abstract class WindowProviderService extends android.app.Service {
ctor public WindowProviderService();
method public final void attachToWindowToken(@NonNull android.os.IBinder);
- method @Nullable public android.os.Bundle getWindowContextOptions();
+ method @NonNull public int getInitialDisplayId();
+ method @CallSuper @Nullable public android.os.Bundle getWindowContextOptions();
method public abstract int getWindowType();
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index e91209c1a273..806283229d98 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -16,6 +16,8 @@
package android.accessibilityservice;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+
import android.accessibilityservice.GestureDescription.MotionEventGenerator;
import android.annotation.CallbackExecutor;
import android.annotation.ColorInt;
@@ -27,6 +29,7 @@ import android.annotation.TestApi;
import android.app.Service;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.Bitmap;
@@ -36,6 +39,7 @@ import android.graphics.Region;
import android.hardware.HardwareBuffer;
import android.hardware.display.DisplayManager;
import android.os.Build;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -961,30 +965,31 @@ public abstract class AccessibilityService extends Service {
}
}
+ @NonNull
@Override
public Context createDisplayContext(Display display) {
- final Context context = super.createDisplayContext(display);
- final int displayId = display.getDisplayId();
- setDefaultTokenInternal(context, displayId);
- return context;
+ return new AccessibilityContext(super.createDisplayContext(display), mConnectionId);
}
- private void setDefaultTokenInternal(Context context, int displayId) {
- final WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService(WINDOW_SERVICE);
- final IAccessibilityServiceConnection connection =
- AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
- IBinder token = null;
- if (connection != null) {
- synchronized (mLock) {
- try {
- token = connection.getOverlayWindowToken(displayId);
- } catch (RemoteException re) {
- Log.w(LOG_TAG, "Failed to get window token", re);
- re.rethrowFromSystemServer();
- }
- }
- wm.setDefaultToken(token);
+ @NonNull
+ @Override
+ public Context createWindowContext(int type, @Nullable Bundle options) {
+ final Context context = super.createWindowContext(type, options);
+ if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+ return context;
+ }
+ return new AccessibilityContext(context, mConnectionId);
+ }
+
+ @NonNull
+ @Override
+ public Context createWindowContext(@NonNull Display display, int type,
+ @Nullable Bundle options) {
+ final Context context = super.createWindowContext(display, type, options);
+ if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+ return context;
}
+ return new AccessibilityContext(context, mConnectionId);
}
/**
@@ -2069,6 +2074,10 @@ public abstract class AccessibilityService extends Service {
if (WINDOW_SERVICE.equals(name)) {
if (mWindowManager == null) {
mWindowManager = (WindowManager) getBaseContext().getSystemService(name);
+ final WindowManagerImpl wm = (WindowManagerImpl) mWindowManager;
+ // Set e default token obtained from the connection to ensure client could use
+ // accessibility overlay.
+ wm.setDefaultToken(mWindowToken);
}
return mWindowManager;
}
@@ -2177,8 +2186,10 @@ public abstract class AccessibilityService extends Service {
// The client may have already obtained the window manager, so
// update the default token on whatever manager we gave them.
- final WindowManagerImpl wm = (WindowManagerImpl) getSystemService(WINDOW_SERVICE);
- wm.setDefaultToken(windowToken);
+ if (mWindowManager != null) {
+ final WindowManagerImpl wm = (WindowManagerImpl) mWindowManager;
+ wm.setDefaultToken(mWindowToken);
+ }
}
@Override
@@ -2675,4 +2686,58 @@ public abstract class AccessibilityService extends Service {
}
}
}
+
+ private static class AccessibilityContext extends ContextWrapper {
+ private final int mConnectionId;
+
+ private AccessibilityContext(Context base, int connectionId) {
+ super(base);
+ mConnectionId = connectionId;
+ setDefaultTokenInternal(this, getDisplayId());
+ }
+
+ @NonNull
+ @Override
+ public Context createDisplayContext(Display display) {
+ return new AccessibilityContext(super.createDisplayContext(display), mConnectionId);
+ }
+
+ @NonNull
+ @Override
+ public Context createWindowContext(int type, @Nullable Bundle options) {
+ final Context context = super.createWindowContext(type, options);
+ if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+ return context;
+ }
+ return new AccessibilityContext(context, mConnectionId);
+ }
+
+ @NonNull
+ @Override
+ public Context createWindowContext(@NonNull Display display, int type,
+ @Nullable Bundle options) {
+ final Context context = super.createWindowContext(display, type, options);
+ if (type != TYPE_ACCESSIBILITY_OVERLAY) {
+ return context;
+ }
+ return new AccessibilityContext(context, mConnectionId);
+ }
+
+ private void setDefaultTokenInternal(Context context, int displayId) {
+ final WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService(
+ WINDOW_SERVICE);
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getConnection(mConnectionId);
+ IBinder token = null;
+ if (connection != null) {
+ try {
+ token = connection.getOverlayWindowToken(displayId);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to get window token", re);
+ re.rethrowFromSystemServer();
+ }
+ wm.setDefaultToken(token);
+ }
+ }
+ }
}
diff --git a/core/java/android/accessibilityservice/AccessibilityTrace.java b/core/java/android/accessibilityservice/AccessibilityTrace.java
new file mode 100644
index 000000000000..f28015ab4685
--- /dev/null
+++ b/core/java/android/accessibilityservice/AccessibilityTrace.java
@@ -0,0 +1,216 @@
+/**
+ * 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 android.accessibilityservice;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Interface to log accessibility trace.
+ *
+ * @hide
+ */
+public interface AccessibilityTrace {
+ String NAME_ACCESSIBILITY_SERVICE_CONNECTION = "IAccessibilityServiceConnection";
+ String NAME_ACCESSIBILITY_SERVICE_CLIENT = "IAccessibilityServiceClient";
+ String NAME_ACCESSIBILITY_MANAGER = "IAccessibilityManager";
+ String NAME_ACCESSIBILITY_MANAGER_CLIENT = "IAccessibilityManagerClient";
+ String NAME_ACCESSIBILITY_INTERACTION_CONNECTION = "IAccessibilityInteractionConnection";
+ String NAME_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK =
+ "IAccessibilityInteractionConnectionCallback";
+ String NAME_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = "IRemoteMagnificationAnimationCallback";
+ String NAME_WINDOW_MAGNIFICATION_CONNECTION = "IWindowMagnificationConnection";
+ String NAME_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK = "IWindowMagnificationConnectionCallback";
+ String NAME_WINDOW_MANAGER_INTERNAL = "WindowManagerInternal";
+ String NAME_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = "WindowsForAccessibilityCallback";
+ String NAME_MAGNIFICATION_CALLBACK = "MagnificationCallbacks";
+ String NAME_INPUT_FILTER = "InputFilter";
+ String NAME_GESTURE = "Gesture";
+ String NAME_ACCESSIBILITY_SERVICE = "AccessibilityService";
+ String NAME_PACKAGE_BROADCAST_RECEIVER = "PMBroadcastReceiver";
+ String NAME_USER_BROADCAST_RECEIVER = "UserBroadcastReceiver";
+ String NAME_FINGERPRINT = "FingerprintGesture";
+ String NAME_ACCESSIBILITY_INTERACTION_CLIENT = "AccessibilityInteractionClient";
+
+ String NAME_ALL_LOGGINGS = "AllLoggings";
+ String NAME_NONE = "None";
+
+ long FLAGS_ACCESSIBILITY_SERVICE_CONNECTION = 0x0000000000000001L;
+ long FLAGS_ACCESSIBILITY_SERVICE_CLIENT = 0x0000000000000002L;
+ long FLAGS_ACCESSIBILITY_MANAGER = 0x0000000000000004L;
+ long FLAGS_ACCESSIBILITY_MANAGER_CLIENT = 0x0000000000000008L;
+ long FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION = 0x0000000000000010L;
+ long FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK = 0x0000000000000020L;
+ long FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = 0x0000000000000040L;
+ long FLAGS_WINDOW_MAGNIFICATION_CONNECTION = 0x0000000000000080L;
+ long FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK = 0x0000000000000100L;
+ long FLAGS_WINDOW_MANAGER_INTERNAL = 0x0000000000000200L;
+ long FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = 0x0000000000000400L;
+ long FLAGS_MAGNIFICATION_CALLBACK = 0x0000000000000800L;
+ long FLAGS_INPUT_FILTER = 0x0000000000001000L;
+ long FLAGS_GESTURE = 0x0000000000002000L;
+ long FLAGS_ACCESSIBILITY_SERVICE = 0x0000000000004000L;
+ long FLAGS_PACKAGE_BROADCAST_RECEIVER = 0x0000000000008000L;
+ long FLAGS_USER_BROADCAST_RECEIVER = 0x0000000000010000L;
+ long FLAGS_FINGERPRINT = 0x0000000000020000L;
+ long FLAGS_ACCESSIBILITY_INTERACTION_CLIENT = 0x0000000000040000L;
+
+ long FLAGS_LOGGING_NONE = 0x0000000000000000L;
+ long FLAGS_LOGGING_ALL = 0xFFFFFFFFFFFFFFFFL;
+
+ long FLAGS_ACCESSIBILITY_MANAGER_CLIENT_STATES = FLAGS_ACCESSIBILITY_INTERACTION_CLIENT
+ | FLAGS_ACCESSIBILITY_SERVICE
+ | FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION
+ | FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK;
+
+ Map<String, Long> sNamesToFlags = Map.ofEntries(
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_SERVICE_CONNECTION, FLAGS_ACCESSIBILITY_SERVICE_CONNECTION),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_SERVICE_CLIENT, FLAGS_ACCESSIBILITY_SERVICE_CLIENT),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_MANAGER, FLAGS_ACCESSIBILITY_MANAGER),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_MANAGER_CLIENT, FLAGS_ACCESSIBILITY_MANAGER_CLIENT),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_INTERACTION_CONNECTION,
+ FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK,
+ FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK,
+ FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_WINDOW_MAGNIFICATION_CONNECTION, FLAGS_WINDOW_MAGNIFICATION_CONNECTION),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_WINDOW_MANAGER_INTERNAL, FLAGS_WINDOW_MANAGER_INTERNAL),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
+ FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_MAGNIFICATION_CALLBACK, FLAGS_MAGNIFICATION_CALLBACK),
+ new AbstractMap.SimpleEntry<String, Long>(NAME_INPUT_FILTER, FLAGS_INPUT_FILTER),
+ new AbstractMap.SimpleEntry<String, Long>(NAME_GESTURE, FLAGS_GESTURE),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_SERVICE, FLAGS_ACCESSIBILITY_SERVICE),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_PACKAGE_BROADCAST_RECEIVER, FLAGS_PACKAGE_BROADCAST_RECEIVER),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_USER_BROADCAST_RECEIVER, FLAGS_USER_BROADCAST_RECEIVER),
+ new AbstractMap.SimpleEntry<String, Long>(NAME_FINGERPRINT, FLAGS_FINGERPRINT),
+ new AbstractMap.SimpleEntry<String, Long>(
+ NAME_ACCESSIBILITY_INTERACTION_CLIENT, FLAGS_ACCESSIBILITY_INTERACTION_CLIENT),
+ new AbstractMap.SimpleEntry<String, Long>(NAME_NONE, FLAGS_LOGGING_NONE),
+ new AbstractMap.SimpleEntry<String, Long>(NAME_ALL_LOGGINGS, FLAGS_LOGGING_ALL));
+
+ /**
+ * Get the flags of the logging types by the given names.
+ * The names list contains logging type names in lower case.
+ */
+ static long getLoggingFlagsFromNames(List<String> names) {
+ long types = FLAGS_LOGGING_NONE;
+ for (String name : names) {
+ long flag = sNamesToFlags.get(name);
+ types |= flag;
+ }
+ return types;
+ }
+
+ /**
+ * Get the list of the names of logging types by the given flags.
+ */
+ static List<String> getNamesOfLoggingTypes(long flags) {
+ List<String> list = new ArrayList<String>();
+
+ for (Map.Entry<String, Long> entry : sNamesToFlags.entrySet()) {
+ if ((entry.getValue() & flags) != FLAGS_LOGGING_NONE) {
+ list.add(entry.getKey());
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * Whether the trace is enabled for any logging type.
+ */
+ boolean isA11yTracingEnabled();
+
+ /**
+ * Whether the trace is enabled for any of the given logging type.
+ */
+ boolean isA11yTracingEnabledForTypes(long typeIdFlags);
+
+ /**
+ * Get trace state to be sent to AccessibilityManager.
+ */
+ int getTraceStateForAccessibilityManagerClientState();
+
+ /**
+ * Start tracing for the given logging types.
+ */
+ void startTrace(long flagss);
+
+ /**
+ * Stop tracing.
+ */
+ void stopTrace();
+
+ /**
+ * Log one trace entry.
+ * @param where A string to identify this log entry, which can be used to search through the
+ * tracing file.
+ * @param loggingFlags Flags to identify which logging types this entry belongs to. This
+ * can be used to filter the log entries when generating tracing file.
+ */
+ void logTrace(String where, long loggingFlags);
+
+ /**
+ * 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 loggingFlags Flags to identify which logging types this entry belongs to. This
+ * can be used to filter the log entries when generating tracing file.
+ * @param callingParams The parameters for the method to be logged.
+ */
+ void logTrace(String where, long loggingFlags, 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 loggingFlags Flags to identify which logging types this entry belongs to. This
+ * can be used to filter the log entries when generating 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.
+ * @param ignoreStackElements ignore these call stack element
+ */
+ void logTrace(long timestamp, String where, long loggingFlags, String callingParams,
+ int processId, long threadId, int callingUid, StackTraceElement[] callStack,
+ Set<String> ignoreStackElements);
+}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 923b6f41414a..1e76bbf43370 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -118,6 +118,6 @@ interface IAccessibilityServiceConnection {
void setFocusAppearance(int strokeWidth, int color);
- oneway void logTrace(long timestamp, String where, String callingParams, int processId,
- long threadId, int callingUid, in Bundle serializedCallingStackInBundle);
+ oneway void logTrace(long timestamp, String where, long loggingTypes, String callingParams,
+ int processId, long threadId, int callingUid, in Bundle serializedCallingStackInBundle);
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index db5dcc5c264b..f453ba16043c 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -77,7 +77,6 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Parcelable;
import android.os.PersistableBundle;
-import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager.ServiceNotFoundException;
@@ -141,7 +140,6 @@ import android.widget.AdapterView;
import android.widget.Toast;
import android.widget.Toolbar;
import android.window.SplashScreen;
-import android.window.SplashScreenView;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -970,7 +968,6 @@ public class Activity extends ContextThemeWrapper
private UiTranslationController mUiTranslationController;
private SplashScreen mSplashScreen;
- private SplashScreenView mSplashScreenView;
private final WindowControllerCallback mWindowControllerCallback =
new WindowControllerCallback() {
@@ -1539,6 +1536,17 @@ public class Activity extends ContextThemeWrapper
getApplication().dispatchActivityPostDestroyed(this);
}
+ private void dispatchActivityConfigurationChanged() {
+ getApplication().dispatchActivityConfigurationChanged(this);
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = 0; i < callbacks.length; i++) {
+ ((Application.ActivityLifecycleCallbacks) callbacks[i])
+ .onActivityConfigurationChanged(this);
+ }
+ }
+ }
+
private Object[] collectActivityLifecycleCallbacks() {
Object[] callbacks = null;
synchronized (mActivityLifecycleCallbacks) {
@@ -1630,16 +1638,6 @@ public class Activity extends ContextThemeWrapper
}
}
- /** @hide */
- public void setSplashScreenView(SplashScreenView v) {
- mSplashScreenView = v;
- }
-
- /** @hide */
- SplashScreenView getSplashScreenView() {
- return mSplashScreenView;
- }
-
/**
* Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with
* the attribute {@link android.R.attr#persistableMode} set to
@@ -1923,10 +1921,14 @@ public class Activity extends ContextThemeWrapper
}
/**
- * Called after {@link #onRestoreInstanceState}, {@link #onRestart}, or
- * {@link #onPause}, for your activity to start interacting with the user. This is an indicator
- * that the activity became active and ready to receive input. It is on top of an activity stack
- * and visible to user.
+ * Called after {@link #onRestoreInstanceState}, {@link #onRestart}, or {@link #onPause}. This
+ * is usually a hint for your activity to start interacting with the user, which is a good
+ * indicator that the activity became active and ready to receive input. This sometimes could
+ * also be a transit state toward another resting state. For instance, an activity may be
+ * relaunched to {@link #onPause} due to configuration changes and the activity was visible,
+ * but wasn’t the top-most activity of an activity task. {@link #onResume} is guaranteed to be
+ * called before {@link #onPause} in this case which honors the activity lifecycle policy and
+ * the activity eventually rests in {@link #onPause}.
*
* <p>On platform versions prior to {@link android.os.Build.VERSION_CODES#Q} this is also a good
* place to try to open exclusive-access devices or to get access to singleton resources.
@@ -2492,12 +2494,11 @@ public class Activity extends ContextThemeWrapper
*
* <p>To get the voice interactor you need to call {@link #getVoiceInteractor()}
* which would return non <code>null</code> only if there is an ongoing voice
- * interaction session. You an also detect when the voice interactor is no
+ * interaction session. You can also detect when the voice interactor is no
* longer valid because the voice interaction session that is backing is finished
* by calling {@link VoiceInteractor#registerOnDestroyedCallback(Executor, Runnable)}.
*
- * <p>This method will be called only after {@link #onStart()} is being called and
- * before {@link #onStop()} is being called.
+ * <p>This method will be called only after {@link #onStart()} and before {@link #onStop()}.
*
* <p>You should pass to the callback the currently supported direct actions which
* cannot be <code>null</code> or contain <code>null</code> elements.
@@ -3028,6 +3029,8 @@ public class Activity extends ContextThemeWrapper
// view changes from above.
mActionBar.onConfigurationChanged(newConfig);
}
+
+ dispatchActivityConfigurationChanged();
}
/**
@@ -8788,9 +8791,7 @@ public class Activity extends ContextThemeWrapper
* the activity is visible after the screen is turned on when the lockscreen is up. In addition,
* if this flag is set and the activity calls {@link
* KeyguardManager#requestDismissKeyguard(Activity, KeyguardManager.KeyguardDismissCallback)}
- * the screen will turn on. If the screen is off and device is not secured, this flag can turn
- * screen on and dismiss keyguard to make this activity visible and resume, which can be used to
- * replace {@link PowerManager#ACQUIRE_CAUSES_WAKEUP}
+ * the screen will turn on.
*
* @param turnScreenOn {@code true} to turn on the screen; {@code false} otherwise.
*
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index bd4386885dd6..db7ab1a6f379 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -16,6 +16,7 @@
package android.app;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Intent;
import android.content.res.Configuration;
@@ -205,6 +206,19 @@ public class ActivityClient {
}
}
+ /**
+ * Returns the non-finishing activity token below in the same task if it belongs to the same
+ * process.
+ */
+ @Nullable
+ public IBinder getActivityTokenBelow(IBinder activityToken) {
+ try {
+ return getActivityClientController().getActivityTokenBelow(activityToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
ComponentName getCallingActivity(IBinder token) {
try {
return getActivityClientController().getCallingActivity(token);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 4e8480c4e113..f8c8aa32a26e 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -647,4 +647,28 @@ public abstract class ActivityManagerInternal {
*/
@Nullable
public abstract List<Integer> getIsolatedProcesses(int uid);
+
+ /** @see ActivityManagerService#sendIntentSender */
+ public abstract int sendIntentSender(IIntentSender target, IBinder allowlistToken, int code,
+ Intent intent, String resolvedType,
+ IIntentReceiver finishedReceiver, String requiredPermission, Bundle options);
+
+ /**
+ * Sets the provider to communicate between voice interaction manager service and
+ * ActivityManagerService.
+ */
+ public abstract void setVoiceInteractionManagerProvider(
+ @Nullable VoiceInteractionManagerProvider provider);
+
+ /**
+ * Provides the interface to communicate between voice interaction manager service and
+ * ActivityManagerService.
+ */
+ public interface VoiceInteractionManagerProvider {
+ /**
+ * Notifies the service when a high-level activity event has been changed, for example,
+ * an activity was resumed or stopped.
+ */
+ void notifyActivityEventChanged();
+ }
}
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 8e1f263ebf03..edcab29dec94 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -26,6 +26,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ExitTransitionCoordinator.ActivityExitTransitionCallbacks;
import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks;
@@ -55,7 +56,7 @@ import android.view.RemoteAnimationAdapter;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
-import android.window.IRemoteTransition;
+import android.window.RemoteTransition;
import android.window.SplashScreen;
import android.window.WindowContainerToken;
@@ -215,6 +216,14 @@ public class ActivityOptions {
"android.activity.launchRootTaskToken";
/**
+ * The {@link com.android.server.wm.TaskFragment} token the activity should be launched into.
+ * @see #setLaunchTaskFragmentToken(IBinder)
+ * @hide
+ */
+ public static final String KEY_LAUNCH_TASK_FRAGMENT_TOKEN =
+ "android.activity.launchTaskFragmentToken";
+
+ /**
* The windowing mode the activity should be launched into.
* @hide
*/
@@ -396,6 +405,7 @@ public class ActivityOptions {
private int mCallerDisplayId = INVALID_DISPLAY;
private WindowContainerToken mLaunchTaskDisplayArea;
private WindowContainerToken mLaunchRootTask;
+ private IBinder mLaunchTaskFragmentToken;
@WindowConfiguration.WindowingMode
private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED;
@WindowConfiguration.ActivityType
@@ -417,7 +427,7 @@ public class ActivityOptions {
private IAppTransitionAnimationSpecsFuture mSpecsFuture;
private RemoteAnimationAdapter mRemoteAnimationAdapter;
private IBinder mLaunchCookie;
- private IRemoteTransition mRemoteTransition;
+ private RemoteTransition mRemoteTransition;
private boolean mOverrideTaskTransition;
private String mSplashScreenThemeResName;
@SplashScreen.SplashScreenStyle
@@ -1045,7 +1055,7 @@ public class ActivityOptions {
*/
@RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
public static ActivityOptions makeRemoteAnimation(RemoteAnimationAdapter remoteAnimationAdapter,
- IRemoteTransition remoteTransition) {
+ RemoteTransition remoteTransition) {
final ActivityOptions opts = new ActivityOptions();
opts.mRemoteAnimationAdapter = remoteAnimationAdapter;
opts.mAnimationType = ANIM_REMOTE_ANIMATION;
@@ -1055,11 +1065,11 @@ public class ActivityOptions {
/**
* Create an {@link ActivityOptions} instance that lets the application control the entire
- * transition using a {@link IRemoteTransition}.
+ * transition using a {@link RemoteTransition}.
* @hide
*/
@RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
- public static ActivityOptions makeRemoteTransition(IRemoteTransition remoteTransition) {
+ public static ActivityOptions makeRemoteTransition(RemoteTransition remoteTransition) {
final ActivityOptions opts = new ActivityOptions();
opts.mRemoteTransition = remoteTransition;
return opts;
@@ -1138,6 +1148,7 @@ public class ActivityOptions {
mCallerDisplayId = opts.getInt(KEY_CALLER_DISPLAY_ID, INVALID_DISPLAY);
mLaunchTaskDisplayArea = opts.getParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN);
mLaunchRootTask = opts.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN);
+ mLaunchTaskFragmentToken = opts.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED);
mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED);
mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
@@ -1171,8 +1182,7 @@ public class ActivityOptions {
}
mRemoteAnimationAdapter = opts.getParcelable(KEY_REMOTE_ANIMATION_ADAPTER);
mLaunchCookie = opts.getBinder(KEY_LAUNCH_COOKIE);
- mRemoteTransition = IRemoteTransition.Stub.asInterface(opts.getBinder(
- KEY_REMOTE_TRANSITION));
+ mRemoteTransition = opts.getParcelable(KEY_REMOTE_TRANSITION);
mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION);
mSplashScreenThemeResName = opts.getString(KEY_SPLASH_SCREEN_THEME);
mRemoveWithTaskOrganizer = opts.getBoolean(KEY_REMOVE_WITH_TASK_ORGANIZER);
@@ -1338,7 +1348,7 @@ public class ActivityOptions {
}
/** @hide */
- public IRemoteTransition getRemoteTransition() {
+ public RemoteTransition getRemoteTransition() {
return mRemoteTransition;
}
@@ -1473,6 +1483,17 @@ public class ActivityOptions {
}
/** @hide */
+ public IBinder getLaunchTaskFragmentToken() {
+ return mLaunchTaskFragmentToken;
+ }
+
+ /** @hide */
+ public ActivityOptions setLaunchTaskFragmentToken(IBinder taskFragmentToken) {
+ mLaunchTaskFragmentToken = taskFragmentToken;
+ return this;
+ }
+
+ /** @hide */
public int getLaunchWindowingMode() {
return mLaunchWindowingMode;
}
@@ -1501,7 +1522,8 @@ public class ActivityOptions {
* Sets the task the activity will be launched in.
* @hide
*/
- @TestApi
+ @RequiresPermission(START_TASKS_FROM_RECENTS)
+ @SystemApi
public void setLaunchTaskId(int taskId) {
mLaunchTaskId = taskId;
}
@@ -1509,6 +1531,7 @@ public class ActivityOptions {
/**
* @hide
*/
+ @SystemApi
public int getLaunchTaskId() {
return mLaunchTaskId;
}
@@ -1882,6 +1905,9 @@ public class ActivityOptions {
if (mLaunchRootTask != null) {
b.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, mLaunchRootTask);
}
+ if (mLaunchTaskFragmentToken != null) {
+ b.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, mLaunchTaskFragmentToken);
+ }
if (mLaunchWindowingMode != WINDOWING_MODE_UNDEFINED) {
b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode);
}
@@ -1941,7 +1967,7 @@ public class ActivityOptions {
b.putBinder(KEY_LAUNCH_COOKIE, mLaunchCookie);
}
if (mRemoteTransition != null) {
- b.putBinder(KEY_REMOTE_TRANSITION, mRemoteTransition.asBinder());
+ b.putParcelable(KEY_REMOTE_TRANSITION, mRemoteTransition);
}
if (mOverrideTaskTransition) {
b.putBoolean(KEY_OVERRIDE_TASK_TRANSITION, mOverrideTaskTransition);
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 4a7fcd232ce9..a83662592513 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -476,6 +476,19 @@ public class ActivityTaskManager {
}
/**
+ * Detaches the navigation bar from the app it was attached to during a transition.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+ public void detachNavigationBarFromApp(@NonNull IBinder transition) {
+ try {
+ getService().detachNavigationBarFromApp(transition);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Information you can retrieve about a root task in the system.
* @hide
*/
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index fc4bb1a00da5..431755e092e3 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -18,7 +18,6 @@ package android.app;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
import static android.app.ConfigurationController.createNewConfigAndUpdateIfNotNull;
-import static android.app.ConfigurationController.freeTextLayoutCachesIfNeeded;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE;
@@ -31,6 +30,10 @@ import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS;
import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
import static android.view.Display.INVALID_DISPLAY;
+import static android.window.ConfigurationHelper.diffPublicWithSizeBuckets;
+import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
+import static android.window.ConfigurationHelper.isDifferentDisplay;
+import static android.window.ConfigurationHelper.shouldUpdateResources;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
@@ -88,10 +91,8 @@ import android.database.sqlite.SQLiteDebug.DbStats;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.HardwareRenderer;
-import android.graphics.Rect;
import android.graphics.Typeface;
import android.hardware.display.DisplayManagerGlobal;
-import android.inputmethodservice.InputMethodService;
import android.media.MediaFrameworkInitializer;
import android.media.MediaFrameworkPlatformInitializer;
import android.media.MediaServiceManager;
@@ -165,6 +166,7 @@ import android.view.Choreographer;
import android.view.Display;
import android.view.DisplayAdjustments;
import android.view.DisplayAdjustments.FixedRotationAdjustments;
+import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewDebug;
@@ -183,6 +185,7 @@ import android.webkit.WebView;
import android.window.SizeConfigurationBuckets;
import android.window.SplashScreen;
import android.window.SplashScreenView;
+import android.window.WindowProviderService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -233,7 +236,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;
import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
/**
@@ -3557,6 +3559,13 @@ public final class ActivityThread extends ClientTransactionHandler
+ ", comp=" + r.intent.getComponent().toShortString()
+ ", dir=" + r.packageInfo.getAppDir());
+ // updatePendingActivityConfiguration() reads from mActivities to update
+ // ActivityClientRecord which runs in a different thread. Protect modifications to
+ // mActivities to avoid race.
+ synchronized (mResourcesManager) {
+ mActivities.put(r.token, r);
+ }
+
if (activity != null) {
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config =
@@ -3602,6 +3611,11 @@ public final class ActivityThread extends ClientTransactionHandler
}
activity.mLaunchedFromBubble = r.mLaunchedFromBubble;
activity.mCalled = false;
+ // Assigning the activity to the record before calling onCreate() allows
+ // ActivityThread#getActivity() lookup for the callbacks triggered from
+ // ActivityLifecycleCallbacks#onActivityCreated() or
+ // ActivityLifecycleCallback#onActivityPostCreated().
+ r.activity = activity;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
@@ -3612,19 +3626,11 @@ public final class ActivityThread extends ClientTransactionHandler
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onCreate()");
}
- r.activity = activity;
mLastReportedWindowingMode.put(activity.getActivityToken(),
config.windowConfiguration.getWindowingMode());
}
r.setState(ON_CREATE);
- // updatePendingActivityConfiguration() reads from mActivities to update
- // ActivityClientRecord which runs in a different thread. Protect modifications to
- // mActivities to avoid race.
- synchronized (mResourcesManager) {
- mActivities.put(r.token, r);
- }
-
} catch (SuperNotCalledException e) {
throw e;
@@ -4068,10 +4074,11 @@ public final class ActivityThread extends ClientTransactionHandler
@Override
public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
- @Nullable SplashScreenView.SplashScreenViewParcelable parcelable) {
+ @Nullable SplashScreenView.SplashScreenViewParcelable parcelable,
+ @NonNull SurfaceControl startingWindowLeash) {
final DecorView decorView = (DecorView) r.window.peekDecorView();
if (parcelable != null && decorView != null) {
- createSplashScreen(r, decorView, parcelable);
+ createSplashScreen(r, decorView, parcelable, startingWindowLeash);
} else {
// shouldn't happen!
Slog.e(TAG, "handleAttachSplashScreenView failed, unable to attach");
@@ -4079,63 +4086,52 @@ public final class ActivityThread extends ClientTransactionHandler
}
private void createSplashScreen(ActivityClientRecord r, DecorView decorView,
- SplashScreenView.SplashScreenViewParcelable parcelable) {
+ SplashScreenView.SplashScreenViewParcelable parcelable,
+ @NonNull SurfaceControl startingWindowLeash) {
final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity);
final SplashScreenView view = builder.createFromParcel(parcelable).build();
decorView.addView(view);
view.attachHostActivityAndSetSystemUIColors(r.activity, r.window);
view.requestLayout();
- // Ensure splash screen view is shown before remove the splash screen window.
- final ViewRootImpl impl = decorView.getViewRootImpl();
- final boolean hardwareEnabled = impl != null && impl.isHardwareEnabled();
- final AtomicBoolean notified = new AtomicBoolean();
- if (hardwareEnabled) {
- final Runnable frameCommit = new Runnable() {
- @Override
- public void run() {
- view.post(() -> {
- if (!notified.get()) {
- view.getViewTreeObserver().unregisterFrameCommitCallback(this);
- ActivityClient.getInstance().reportSplashScreenAttached(
- r.token);
- notified.set(true);
- }
- });
- }
- };
- view.getViewTreeObserver().registerFrameCommitCallback(frameCommit);
- } else {
- final ViewTreeObserver.OnDrawListener onDrawListener =
- new ViewTreeObserver.OnDrawListener() {
- @Override
- public void onDraw() {
- view.post(() -> {
- if (!notified.get()) {
- view.getViewTreeObserver().removeOnDrawListener(this);
- ActivityClient.getInstance().reportSplashScreenAttached(
- r.token);
- notified.set(true);
- }
- });
- }
- };
- view.getViewTreeObserver().addOnDrawListener(onDrawListener);
- }
+
+ view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
+ @Override
+ public void onDraw() {
+ // Transfer the splash screen view from shell to client.
+ // Call syncTransferSplashscreenViewTransaction at the first onDraw so we can ensure
+ // the client view is ready to show and we can use applyTransactionOnDraw to make
+ // all transitions happen at the same frame.
+ syncTransferSplashscreenViewTransaction(
+ view, r.token, decorView, startingWindowLeash);
+ view.postOnAnimation(() -> view.getViewTreeObserver().removeOnDrawListener(this));
+ }
+ });
}
- @Override
- public void handOverSplashScreenView(@NonNull ActivityClientRecord r) {
- final SplashScreenView v = r.activity.getSplashScreenView();
- if (v == null) {
- return;
- }
+ private void reportSplashscreenViewShown(IBinder token, SplashScreenView view) {
+ ActivityClient.getInstance().reportSplashScreenAttached(token);
synchronized (this) {
if (mSplashScreenGlobal != null) {
- mSplashScreenGlobal.handOverSplashScreenView(r.token, v);
+ mSplashScreenGlobal.handOverSplashScreenView(token, view);
}
}
}
+ private void syncTransferSplashscreenViewTransaction(SplashScreenView view, IBinder token,
+ View decorView, @NonNull SurfaceControl startingWindowLeash) {
+ // Ensure splash screen view is shown before remove the splash screen window.
+ // Once the copied splash screen view is onDrawn on decor view, use applyTransactionOnDraw
+ // to ensure the transfer of surface view and hide starting window are happen at the same
+ // frame.
+ final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ transaction.hide(startingWindowLeash);
+
+ decorView.getViewRootImpl().applyTransactionOnDraw(transaction);
+ view.syncTransferSurfaceOnDraw();
+ // Tell server we can remove the starting window
+ decorView.postOnAnimation(() -> reportSplashscreenViewShown(token, view));
+ }
+
/**
* Cycle activity through onPause and onUserLeaveHint so that PIP is entered if supported, then
* return to its previous state. This allows activities that rely on onUserLeaveHint instead of
@@ -4907,8 +4903,7 @@ public final class ActivityThread extends ClientTransactionHandler
Slog.w(TAG, "Activity top position already set to onTop=" + onTop);
return;
}
- // TODO(b/197484331): Remove this short-term workaround while fixing the binder failure.
- Slog.e(TAG, "Activity top position already set to onTop=" + onTop);
+ throw new IllegalStateException("Activity top position already set to onTop=" + onTop);
}
r.isTopResumedActivity = onTop;
@@ -5436,6 +5431,12 @@ public final class ActivityThread extends ClientTransactionHandler
// behave properly when activity is relaunching.
r.window.clearContentView();
} else {
+ final ViewRootImpl viewRoot = v.getViewRootImpl();
+ if (viewRoot != null) {
+ // Clear the callback to avoid the destroyed activity from receiving
+ // configuration changes that are no longer effective.
+ viewRoot.setActivityConfigCallback(null);
+ }
wm.removeViewImmediate(v);
}
}
@@ -5759,7 +5760,7 @@ public final class ActivityThread extends ClientTransactionHandler
}
@Override
- public ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeActivities) {
+ public ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeUiContexts) {
ArrayList<ComponentCallbacks2> callbacks
= new ArrayList<ComponentCallbacks2>();
@@ -5768,7 +5769,7 @@ public final class ActivityThread extends ClientTransactionHandler
for (int i=0; i<NAPP; i++) {
callbacks.add(mAllApplications.get(i));
}
- if (includeActivities) {
+ if (includeUiContexts) {
for (int i = mActivities.size() - 1; i >= 0; i--) {
final Activity a = mActivities.valueAt(i).activity;
if (a != null && !a.mFinished) {
@@ -5778,11 +5779,12 @@ public final class ActivityThread extends ClientTransactionHandler
}
final int NSVC = mServices.size();
for (int i=0; i<NSVC; i++) {
- final ComponentCallbacks2 serviceComp = mServices.valueAt(i);
- if (serviceComp instanceof InputMethodService) {
- mHasImeComponent = true;
+ final Service service = mServices.valueAt(i);
+ // If {@code includeUiContext} is set to false, WindowProviderService should not be
+ // collected because WindowProviderService is a UI Context.
+ if (includeUiContexts || !(service instanceof WindowProviderService)) {
+ callbacks.add(service);
}
- callbacks.add(serviceComp);
}
}
synchronized (mProviderMap) {
@@ -5837,35 +5839,25 @@ public final class ActivityThread extends ClientTransactionHandler
// change callback, see also PinnedStackTests#testConfigurationChangeOrderDuringTransition
handleWindowingModeChangeIfNeeded(activity, newConfig);
- final boolean movedToDifferentDisplay = isDifferentDisplay(activity, displayId);
- boolean shouldReportChange = false;
- if (activity.mCurrentConfig == null) {
- shouldReportChange = true;
- } else {
- // If the new config is the same as the config this Activity is already running with and
- // the override config also didn't change, then don't bother calling
- // onConfigurationChanged.
- // TODO(b/173090263): Use diff instead after the improvement of AssetManager and
- // ResourcesImpl constructions.
- int diff = activity.mCurrentConfig.diffPublicOnly(newConfig);
- final ActivityClientRecord cr = getActivityClient(activityToken);
- diff = SizeConfigurationBuckets.filterDiff(diff, activity.mCurrentConfig, newConfig,
- cr != null ? cr.mSizeConfigurations : null);
-
- if (diff == 0) {
- if (!shouldUpdateWindowMetricsBounds(activity.mCurrentConfig, newConfig)
- && !movedToDifferentDisplay
- && mResourcesManager.isSameResourcesOverrideConfig(
- activityToken, amOverrideConfig)) {
- // Nothing significant, don't proceed with updating and reporting.
- return null;
- }
- } else if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0) {
+ final boolean movedToDifferentDisplay = isDifferentDisplay(activity.getDisplayId(),
+ displayId);
+ final ActivityClientRecord r = mActivities.get(activityToken);
+ final int diff = diffPublicWithSizeBuckets(activity.mCurrentConfig,
+ newConfig, r != null ? r.mSizeConfigurations : null);
+ final boolean hasPublicConfigChange = diff != 0;
+ // TODO(b/173090263): Use diff instead after the improvement of AssetManager and
+ // ResourcesImpl constructions.
+ final boolean shouldUpdateResources = hasPublicConfigChange
+ || shouldUpdateResources(activityToken, activity.mCurrentConfig, newConfig,
+ amOverrideConfig, movedToDifferentDisplay, hasPublicConfigChange);
+ final boolean shouldReportChange = hasPublicConfigChange
// If this activity doesn't handle any of the config changes, then don't bother
// calling onConfigurationChanged. Otherwise, report to the activity for the
// changes.
- shouldReportChange = true;
- }
+ && (~activity.mActivityInfo.getRealConfigChanged() & diff) == 0;
+ // Nothing significant, don't proceed with updating and reporting.
+ if (!shouldUpdateResources) {
+ return null;
}
// Propagate the configuration change to ResourcesManager and Activity.
@@ -5916,26 +5908,6 @@ public final class ActivityThread extends ClientTransactionHandler
return configToReport;
}
- // TODO(b/173090263): Remove this method after the improvement of AssetManager and ResourcesImpl
- // constructions.
- /**
- * Returns {@code true} if the metrics reported by {@link android.view.WindowMetrics} APIs
- * should be updated.
- *
- * @see WindowManager#getCurrentWindowMetrics()
- * @see WindowManager#getMaximumWindowMetrics()
- */
- private static boolean shouldUpdateWindowMetricsBounds(@NonNull Configuration currentConfig,
- @NonNull Configuration newConfig) {
- final Rect currentBounds = currentConfig.windowConfiguration.getBounds();
- final Rect newBounds = newConfig.windowConfiguration.getBounds();
-
- final Rect currentMaxBounds = currentConfig.windowConfiguration.getMaxBounds();
- final Rect newMaxBounds = newConfig.windowConfiguration.getMaxBounds();
-
- return !currentBounds.equals(newBounds) || !currentMaxBounds.equals(newMaxBounds);
- }
-
public final void applyConfigurationToResources(Configuration config) {
synchronized (mResourcesManager) {
mResourcesManager.applyConfigurationToResources(config, null);
@@ -6079,7 +6051,8 @@ public final class ActivityThread extends ClientTransactionHandler
// display.
displayId = r.activity.getDisplayId();
}
- final boolean movedToDifferentDisplay = isDifferentDisplay(r.activity, displayId);
+ final boolean movedToDifferentDisplay = isDifferentDisplay(
+ r.activity.getDisplayId(), displayId);
if (r.overrideConfig != null && !r.overrideConfig.isOtherSeqNewer(overrideConfig)
&& !movedToDifferentDisplay) {
if (DEBUG_CONFIGURATION) {
@@ -6115,14 +6088,6 @@ public final class ActivityThread extends ClientTransactionHandler
mSomeActivitiesChanged = true;
}
- /**
- * Checks if the display id of activity is different from the given one. Note that
- * {@link Display#INVALID_DISPLAY} means no difference.
- */
- private static boolean isDifferentDisplay(@NonNull Activity activity, int displayId) {
- return displayId != INVALID_DISPLAY && displayId != activity.getDisplayId();
- }
-
final void handleProfilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
if (start) {
try {
@@ -6302,7 +6267,7 @@ public final class ActivityThread extends ClientTransactionHandler
final void handleLowMemory() {
final ArrayList<ComponentCallbacks2> callbacks =
- collectComponentCallbacks(true /* includeActivities */);
+ collectComponentCallbacks(true /* includeUiContexts */);
final int N = callbacks.size();
for (int i=0; i<N; i++) {
@@ -6335,7 +6300,7 @@ public final class ActivityThread extends ClientTransactionHandler
}
final ArrayList<ComponentCallbacks2> callbacks =
- collectComponentCallbacks(true /* includeActivities */);
+ collectComponentCallbacks(true /* includeUiContexts */);
final int N = callbacks.size();
for (int i = 0; i < N; i++) {
@@ -7563,12 +7528,6 @@ public final class ActivityThread extends ClientTransactionHandler
ViewRootImpl.ConfigChangedCallback configChangedCallback = (Configuration globalConfig) -> {
synchronized (mResourcesManager) {
- // TODO (b/135719017): Temporary log for debugging IME service.
- if (Build.IS_DEBUGGABLE && mHasImeComponent) {
- Log.d(TAG, "ViewRootImpl.ConfigChangedCallback for IME, "
- + "config=" + globalConfig);
- }
-
// We need to apply this change to the resources immediately, because upon returning
// the view hierarchy will be informed about it.
if (mResourcesManager.applyConfigurationToResources(globalConfig,
@@ -7923,11 +7882,6 @@ public final class ActivityThread extends ClientTransactionHandler
return mDensityCompatMode;
}
- @Override
- public boolean hasImeComponent() {
- return mHasImeComponent;
- }
-
// ------------------ Regular JNI ------------------------
private native void nPurgePendingResources();
private native void nDumpGraphicsInfo(FileDescriptor fd);
diff --git a/core/java/android/app/ActivityThreadInternal.java b/core/java/android/app/ActivityThreadInternal.java
index d91933c0f817..bc698f657305 100644
--- a/core/java/android/app/ActivityThreadInternal.java
+++ b/core/java/android/app/ActivityThreadInternal.java
@@ -32,11 +32,9 @@ interface ActivityThreadInternal {
boolean isInDensityCompatMode();
- boolean hasImeComponent();
-
boolean isCachedProcessState();
Application getApplication();
- ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeActivities);
+ ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeUiContexts);
}
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index 4b87a647a80b..f5b3b40d88d6 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -871,6 +871,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
if (view.isAttachedToWindow()) {
tempMatrix.reset();
mSharedElementParentMatrices.get(i).invert(tempMatrix);
+ decor.transformMatrixToLocal(tempMatrix);
GhostView.addGhost(view, decor, tempMatrix);
ViewGroup parent = (ViewGroup) view.getParent();
if (moveWithParent && !isInTransitionGroup(parent, decor)) {
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 618eda8c84e8..a1eab6553cff 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -205,6 +205,13 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
*/
default void onActivityPostDestroyed(@NonNull Activity activity) {
}
+
+ /**
+ * Called when the Activity configuration was changed.
+ * @hide
+ */
+ default void onActivityConfigurationChanged(@NonNull Activity activity) {
+ }
}
/**
@@ -554,6 +561,16 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
}
}
+ /* package */ void dispatchActivityConfigurationChanged(@NonNull Activity activity) {
+ Object[] callbacks = collectActivityLifecycleCallbacks();
+ if (callbacks != null) {
+ for (int i = 0; i < callbacks.length; i++) {
+ ((ActivityLifecycleCallbacks) callbacks[i]).onActivityConfigurationChanged(
+ activity);
+ }
+ }
+ }
+
@UnsupportedAppUsage
private Object[] collectActivityLifecycleCallbacks() {
Object[] callbacks = null;
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 115101c0bff6..c743f6572d5e 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -28,6 +28,7 @@ import android.content.res.Configuration;
import android.os.IBinder;
import android.util.MergedConfiguration;
import android.view.DisplayAdjustments.FixedRotationAdjustments;
+import android.view.SurfaceControl;
import android.window.SplashScreenView.SplashScreenViewParcelable;
import com.android.internal.annotations.VisibleForTesting;
@@ -165,10 +166,8 @@ public abstract class ClientTransactionHandler {
/** Attach a splash screen window view to the top of the activity */
public abstract void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
- @NonNull SplashScreenViewParcelable parcelable);
-
- /** Hand over the splash screen window view to the activity */
- public abstract void handOverSplashScreenView(@NonNull ActivityClientRecord r);
+ @NonNull SplashScreenViewParcelable parcelable,
+ @NonNull SurfaceControl startingWindowLeash);
/** Perform activity launch. */
public abstract Activity handleLaunchActivity(@NonNull ActivityClientRecord r,
diff --git a/core/java/android/app/ConfigurationController.java b/core/java/android/app/ConfigurationController.java
index f79e0780ecae..8637e31eb122 100644
--- a/core/java/android/app/ConfigurationController.java
+++ b/core/java/android/app/ConfigurationController.java
@@ -17,24 +17,20 @@
package android.app;
import static android.app.ActivityThread.DEBUG_CONFIGURATION;
+import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentCallbacks2;
import android.content.Context;
-import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.HardwareRenderer;
-import android.inputmethodservice.InputMethodService;
-import android.os.Build;
import android.os.LocaleList;
import android.os.Trace;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.util.Slog;
import android.view.ContextThemeWrapper;
import android.view.WindowManagerGlobal;
@@ -169,12 +165,7 @@ class ConfigurationController {
mPendingConfiguration = null;
}
- final boolean hasIme = mActivityThread.hasImeComponent();
if (config == null) {
- // TODO (b/135719017): Temporary log for debugging IME service.
- if (Build.IS_DEBUGGABLE && hasIme) {
- Log.w(TAG, "handleConfigurationChanged for IME app but config is null");
- }
return;
}
@@ -205,12 +196,6 @@ class ConfigurationController {
mConfiguration = new Configuration();
}
if (!mConfiguration.isOtherSeqNewer(config) && compat == null) {
- // TODO (b/135719017): Temporary log for debugging IME service.
- if (Build.IS_DEBUGGABLE && hasIme) {
- Log.w(TAG, "handleConfigurationChanged for IME app but config seq is obsolete "
- + ", config=" + config
- + ", mConfiguration=" + mConfiguration);
- }
return;
}
@@ -228,7 +213,7 @@ class ConfigurationController {
}
final ArrayList<ComponentCallbacks2> callbacks =
- mActivityThread.collectComponentCallbacks(false /* includeActivities */);
+ mActivityThread.collectComponentCallbacks(false /* includeUiContexts */);
freeTextLayoutCachesIfNeeded(configDiff);
@@ -238,13 +223,6 @@ class ConfigurationController {
ComponentCallbacks2 cb = callbacks.get(i);
if (!equivalent) {
performConfigurationChanged(cb, config);
- } else {
- // TODO (b/135719017): Temporary log for debugging IME service.
- if (Build.IS_DEBUGGABLE && cb instanceof InputMethodService) {
- Log.w(TAG, "performConfigurationChanged didn't callback to IME "
- + ", configDiff=" + configDiff
- + ", mConfiguration=" + mConfiguration);
- }
}
}
}
@@ -326,16 +304,4 @@ class ConfigurationController {
return newConfig;
}
- /** Ask test layout engine to free its caches if there is a locale change. */
- static void freeTextLayoutCachesIfNeeded(int configDiff) {
- if (configDiff != 0) {
- boolean hasLocaleConfigChange = ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0);
- if (hasLocaleConfigChange) {
- Canvas.freeTextLayoutCaches();
- if (DEBUG_CONFIGURATION) {
- Slog.v(TAG, "Cleared TextLayout Caches");
- }
- }
- }
- }
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 1397f5ea10dc..b5ed1717496e 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2743,7 +2743,7 @@ class ContextImpl extends Context {
*/
private static Resources createWindowContextResources(@NonNull ContextImpl windowContextBase) {
final LoadedApk packageInfo = windowContextBase.mPackageInfo;
- final ClassLoader classLoader = windowContextBase.mClassLoader;
+ final ClassLoader classLoader = windowContextBase.getClassLoader();
final IBinder token = windowContextBase.getWindowContextToken();
final String resDir = packageInfo.getResDir();
@@ -3191,12 +3191,6 @@ class ContextImpl extends Context {
@UnsupportedAppUsage
final void setOuterContext(@NonNull Context context) {
mOuterContext = context;
- // TODO(b/149463653): check if we still need this method after migrating IMS to
- // WindowContext.
- if (mOuterContext.isUiContext() && mContextType <= CONTEXT_TYPE_DISPLAY_CONTEXT) {
- mContextType = CONTEXT_TYPE_WINDOW_CONTEXT;
- mIsConfigurationBasedContext = true;
- }
}
@UnsupportedAppUsage
diff --git a/core/java/android/app/DirectAction.java b/core/java/android/app/DirectAction.java
index b0ed490369ad..ac3868b2ece9 100644
--- a/core/java/android/app/DirectAction.java
+++ b/core/java/android/app/DirectAction.java
@@ -22,14 +22,13 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
-import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.util.Preconditions;
import java.util.Objects;
/**
- * Represents a abstract action that can be perform on this app. This are requested from
+ * Represents an abstract action that can be perform on this app. This are requested from
* outside the app's UI (eg by SystemUI or assistant). The semantics of these actions are
* not specified by the OS. This allows open-ended and scalable approach for defining how
* an app interacts with components that expose alternative interaction models to the user
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index 1f323c378093..ae3a9e6668ab 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -74,6 +74,11 @@ final class DisabledWallpaperManager extends WallpaperManager {
return false;
}
+ private static int unsupportedInt() {
+ if (DEBUG) Log.w(TAG, "unsupported method called; returning -1", new Exception());
+ return -1;
+ }
+
@Override
public Drawable getDrawable() {
return unsupported();
@@ -189,12 +194,12 @@ final class DisabledWallpaperManager extends WallpaperManager {
@Override
public int getWallpaperId(int which) {
- return unsupported();
+ return unsupportedInt();
}
@Override
public int getWallpaperIdForUser(int which, int userId) {
- return unsupported();
+ return unsupportedInt();
}
@Override
@@ -209,7 +214,8 @@ final class DisabledWallpaperManager extends WallpaperManager {
@Override
public int setResource(int resid, int which) throws IOException {
- return unsupported();
+ unsupported();
+ return 0;
}
@Override
@@ -220,19 +226,22 @@ final class DisabledWallpaperManager extends WallpaperManager {
@Override
public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup)
throws IOException {
- return unsupported();
+ unsupported();
+ return 0;
}
@Override
public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup, int which)
throws IOException {
- return unsupported();
+ unsupported();
+ return 0;
}
@Override
public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup, int which,
int userId) throws IOException {
- return unsupported();
+ unsupported();
+ return 0;
}
@Override
@@ -243,13 +252,15 @@ final class DisabledWallpaperManager extends WallpaperManager {
@Override
public int setStream(InputStream bitmapData, Rect visibleCropHint, boolean allowBackup)
throws IOException {
- return unsupported();
+ unsupported();
+ return 0;
}
@Override
public int setStream(InputStream bitmapData, Rect visibleCropHint, boolean allowBackup,
int which) throws IOException {
- return unsupported();
+ unsupported();
+ return 0;
}
@Override
@@ -259,12 +270,12 @@ final class DisabledWallpaperManager extends WallpaperManager {
@Override
public int getDesiredMinimumWidth() {
- return unsupported();
+ return unsupportedInt();
}
@Override
public int getDesiredMinimumHeight() {
- return unsupported();
+ return unsupportedInt();
}
@Override
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index c6649692d848..aba6eb9229f2 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -70,6 +70,7 @@ interface IActivityClientController {
boolean willActivityBeVisible(in IBinder token);
int getDisplayId(in IBinder activityToken);
int getTaskForActivity(in IBinder token, in boolean onlyRoot);
+ IBinder getActivityTokenBelow(IBinder token);
ComponentName getCallingActivity(in IBinder token);
String getCallingPackage(in IBinder token);
int getLaunchedFromUid(in IBinder token);
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 74d51a0bcf63..2be78033ddf7 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -330,4 +330,18 @@ interface IActivityTaskManager {
* When the Picture-in-picture state has changed.
*/
void onPictureInPictureStateChanged(in PictureInPictureUiState pipState);
+
+ /**
+ * Re-attach navbar to the display during a recents transition.
+ * TODO(188595497): Remove this once navbar attachment is in shell.
+ */
+ void detachNavigationBarFromApp(in IBinder transition);
+
+ /**
+ * Marks a process as a delegate for the currently playing remote transition animation. This
+ * must be called from a process that is already a remote transition player or delegate. Any
+ * marked delegates are cleaned-up automatically at the end of the transition.
+ * @param caller is the IApplicationThread representing the calling process.
+ */
+ void setRunningRemoteTransitionDelegate(in IApplicationThread caller);
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index b2184fe65887..fd6fa57b9e8d 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -743,6 +743,18 @@ public class Instrumentation {
}
/**
+ * This overload is used for notifying the {@link android.window.TaskFragmentOrganizer}
+ * implementation internally about started activities.
+ *
+ * @see #onStartActivity(Intent)
+ * @hide
+ */
+ public ActivityResult onStartActivity(@NonNull Context who, @NonNull Intent intent,
+ @NonNull Bundle options) {
+ return onStartActivity(intent);
+ }
+
+ /**
* Used for intercepting any started activity.
*
* <p> A non-null return value here will be considered a hit for this monitor.
@@ -1722,7 +1734,10 @@ public class Instrumentation {
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
- result = am.onStartActivity(intent);
+ if (options == null) {
+ options = ActivityOptions.makeBasic().toBundle();
+ }
+ result = am.onStartActivity(who, intent, options);
}
if (result != null) {
am.mHits++;
@@ -1790,7 +1805,10 @@ public class Instrumentation {
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
- result = am.onStartActivity(intents[0]);
+ if (options == null) {
+ options = ActivityOptions.makeBasic().toBundle();
+ }
+ result = am.onStartActivity(who, intents[0], options);
}
if (result != null) {
am.mHits++;
@@ -1861,7 +1879,10 @@ public class Instrumentation {
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
- result = am.onStartActivity(intent);
+ if (options == null) {
+ options = ActivityOptions.makeBasic().toBundle();
+ }
+ result = am.onStartActivity(who, intent, options);
}
if (result != null) {
am.mHits++;
@@ -1928,7 +1949,10 @@ public class Instrumentation {
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
- result = am.onStartActivity(intent);
+ if (options == null) {
+ options = ActivityOptions.makeBasic().toBundle();
+ }
+ result = am.onStartActivity(who, intent, options);
}
if (result != null) {
am.mHits++;
@@ -1974,7 +1998,10 @@ public class Instrumentation {
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
- result = am.onStartActivity(intent);
+ if (options == null) {
+ options = ActivityOptions.makeBasic().toBundle();
+ }
+ result = am.onStartActivity(who, intent, options);
}
if (result != null) {
am.mHits++;
@@ -2021,7 +2048,10 @@ public class Instrumentation {
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
- result = am.onStartActivity(intent);
+ if (options == null) {
+ options = ActivityOptions.makeBasic().toBundle();
+ }
+ result = am.onStartActivity(who, intent, options);
}
if (result != null) {
am.mHits++;
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index a2c9795204ad..74208c3a4aff 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -2134,4 +2134,38 @@ public final class LoadedApk {
final IBinder mService;
}
}
+
+ /**
+ * Check if the Apk paths in the cache are correct, and update them if they are not.
+ * @hide
+ */
+ public static void checkAndUpdateApkPaths(ApplicationInfo expectedAppInfo) {
+ // Get the LoadedApk from the cache
+ ActivityThread activityThread = ActivityThread.currentActivityThread();
+ if (activityThread == null) {
+ Log.e(TAG, "Cannot find activity thread");
+ return;
+ }
+ checkAndUpdateApkPaths(activityThread, expectedAppInfo, /* cacheWithCode */ true);
+ checkAndUpdateApkPaths(activityThread, expectedAppInfo, /* cacheWithCode */ false);
+ }
+
+ private static void checkAndUpdateApkPaths(ActivityThread activityThread,
+ ApplicationInfo expectedAppInfo, boolean cacheWithCode) {
+ String expectedCodePath = expectedAppInfo.getCodePath();
+ LoadedApk loadedApk = activityThread.peekPackageInfo(
+ expectedAppInfo.packageName, /* includeCode= */ cacheWithCode);
+ // If there is load apk cached, or if the cache is valid, don't do anything.
+ if (loadedApk == null || loadedApk.getApplicationInfo() == null
+ || loadedApk.getApplicationInfo().getCodePath().equals(expectedCodePath)) {
+ return;
+ }
+ // Duplicate framework logic
+ List<String> oldPaths = new ArrayList<>();
+ LoadedApk.makePaths(activityThread, expectedAppInfo, oldPaths);
+
+ // Force update the LoadedApk instance, which should update the reference in the cache
+ loadedApk.updateApplicationInfo(expectedAppInfo, oldPaths);
+ }
+
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 198c33e83707..bf3778dfeecc 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -693,7 +693,7 @@ public class ResourcesManager {
* @return true if activity resources override config matches the provided one or they are both
* null, false otherwise.
*/
- boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken,
+ public boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken,
@Nullable Configuration overrideConfig) {
synchronized (mLock) {
final ActivityResources activityResources
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 77bcef3ae009..be702c2a1756 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -46,7 +46,7 @@ import java.lang.annotation.RetentionPolicy;
*/
@SystemService(Context.STATUS_BAR_SERVICE)
public class StatusBarManager {
-
+ // LINT.IfChange
/** @hide */
public static final int DISABLE_EXPAND = View.STATUS_BAR_DISABLE_EXPAND;
/** @hide */
@@ -144,6 +144,7 @@ public class StatusBarManager {
})
@Retention(RetentionPolicy.SOURCE)
public @interface Disable2Flags {}
+ // LINT.ThenChange(frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt)
/**
* Default disable flags for setup
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 85758a92fa98..bd9b6e952118 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -17,6 +17,7 @@
package android.app;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -119,6 +120,12 @@ public class TaskInfo {
public int displayId;
/**
+ * The feature id of {@link com.android.server.wm.TaskDisplayArea} this task is associated with.
+ * @hide
+ */
+ public int displayAreaFeatureId = FEATURE_UNDEFINED;
+
+ /**
* The recent activity values for the highest activity in the stack to have set the values.
* {@link Activity#setTaskDescription(android.app.ActivityManager.TaskDescription)}.
*/
@@ -243,6 +250,12 @@ public class TaskInfo {
*/
public boolean isVisible;
+ /**
+ * Whether this task is sleeping due to sleeping display.
+ * @hide
+ */
+ public boolean isSleeping;
+
TaskInfo() {
// Do nothing
}
@@ -252,6 +265,13 @@ public class TaskInfo {
}
/**
+ * Whether this task is visible.
+ */
+ public boolean isVisible() {
+ return isVisible;
+ }
+
+ /**
* @param isLowResolution
* @return
* @hide
@@ -329,11 +349,10 @@ public class TaskInfo {
}
/**
- * Returns {@code true} if parameters that are important for task organizers have changed
- * and {@link com.android.server.wm.TaskOrginizerController} needs to notify listeners
- * about that.
- * @hide
- */
+ * Returns {@code true} if the parameters that are important for task organizers are equal
+ * between this {@link TaskInfo} and {@param that}.
+ * @hide
+ */
public boolean equalsForTaskOrganizer(@Nullable TaskInfo that) {
if (that == null) {
return false;
@@ -341,13 +360,15 @@ public class TaskInfo {
return topActivityType == that.topActivityType
&& isResizeable == that.isResizeable
&& supportsMultiWindow == that.supportsMultiWindow
+ && displayAreaFeatureId == that.displayAreaFeatureId
&& Objects.equals(positionInParent, that.positionInParent)
&& Objects.equals(pictureInPictureParams, that.pictureInPictureParams)
&& Objects.equals(displayCutoutInsets, that.displayCutoutInsets)
&& getWindowingMode() == that.getWindowingMode()
&& Objects.equals(taskDescription, that.taskDescription)
&& isFocused == that.isFocused
- && isVisible == that.isVisible;
+ && isVisible == that.isVisible
+ && isSleeping == that.isSleeping;
}
/**
@@ -402,8 +423,10 @@ public class TaskInfo {
parentTaskId = source.readInt();
isFocused = source.readBoolean();
isVisible = source.readBoolean();
+ isSleeping = source.readBoolean();
topActivityInSizeCompat = source.readBoolean();
mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
+ displayAreaFeatureId = source.readInt();
}
/**
@@ -440,8 +463,10 @@ public class TaskInfo {
dest.writeInt(parentTaskId);
dest.writeBoolean(isFocused);
dest.writeBoolean(isVisible);
+ dest.writeBoolean(isSleeping);
dest.writeBoolean(topActivityInSizeCompat);
dest.writeTypedObject(mTopActivityLocusId, flags);
+ dest.writeInt(displayAreaFeatureId);
}
@Override
@@ -468,8 +493,10 @@ public class TaskInfo {
+ " parentTaskId=" + parentTaskId
+ " isFocused=" + isFocused
+ " isVisible=" + isVisible
+ + " isSleeping=" + isSleeping
+ " topActivityInSizeCompat=" + topActivityInSizeCompat
- + " locusId= " + mTopActivityLocusId
+ + " locusId=" + mTopActivityLocusId
+ + " displayAreaFeatureId=" + displayAreaFeatureId
+ "}";
}
}
diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java
index e9b01750b3b1..99d406446dae 100644
--- a/core/java/android/app/WallpaperInfo.java
+++ b/core/java/android/app/WallpaperInfo.java
@@ -81,6 +81,7 @@ public final class WallpaperInfo implements Parcelable {
final int mContextDescriptionResource;
final boolean mShowMetadataInPreview;
final boolean mSupportsAmbientMode;
+ final boolean mShouldUseDefaultUnfoldTransition;
final String mSettingsSliceUri;
final boolean mSupportMultipleDisplays;
@@ -145,6 +146,9 @@ public final class WallpaperInfo implements Parcelable {
mSupportsAmbientMode = sa.getBoolean(
com.android.internal.R.styleable.Wallpaper_supportsAmbientMode,
false);
+ mShouldUseDefaultUnfoldTransition = sa.getBoolean(
+ com.android.internal.R.styleable
+ .Wallpaper_shouldUseDefaultUnfoldTransition, true);
mSettingsSliceUri = sa.getString(
com.android.internal.R.styleable.Wallpaper_settingsSliceUri);
mSupportMultipleDisplays = sa.getBoolean(
@@ -171,6 +175,7 @@ public final class WallpaperInfo implements Parcelable {
mSupportsAmbientMode = source.readInt() != 0;
mSettingsSliceUri = source.readString();
mSupportMultipleDisplays = source.readInt() != 0;
+ mShouldUseDefaultUnfoldTransition = source.readInt() != 0;
mService = ResolveInfo.CREATOR.createFromParcel(source);
}
@@ -393,6 +398,28 @@ public final class WallpaperInfo implements Parcelable {
return mSupportMultipleDisplays;
}
+ /**
+ * Returns whether this wallpaper should receive default zooming updates when the device
+ * changes its state (e.g. when folding or unfolding a foldable device).
+ * If set to false the wallpaper will not receive zoom events when changing the device state,
+ * so it can implement its own transition instead.
+ * <p>
+ * This corresponds to the value {@link
+ * android.R.styleable#Wallpaper_shouldUseDefaultUnfoldTransition} in the
+ * XML description of the wallpaper.
+ * <p>
+ * The default value is {@code true}.
+ *
+ * @see android.R.styleable#Wallpaper_shouldUseDefaultUnfoldTransition
+ * @return {@code true} if wallpaper should receive default device state change
+ * transition updates
+ *
+ * @attr ref android.R.styleable#Wallpaper_shouldUseDefaultUnfoldTransition
+ */
+ public boolean shouldUseDefaultUnfoldTransition() {
+ return mShouldUseDefaultUnfoldTransition;
+ }
+
public void dump(Printer pw, String prefix) {
pw.println(prefix + "Service:");
mService.dump(pw, prefix + " ");
@@ -423,6 +450,7 @@ public final class WallpaperInfo implements Parcelable {
dest.writeInt(mSupportsAmbientMode ? 1 : 0);
dest.writeString(mSettingsSliceUri);
dest.writeInt(mSupportMultipleDisplays ? 1 : 0);
+ dest.writeInt(mShouldUseDefaultUnfoldTransition ? 1 : 0);
mService.writeToParcel(dest, flags);
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 6cfa39cd2337..11c01e61911c 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -221,6 +221,20 @@ public class WallpaperManager {
public static final String COMMAND_REAPPLY = "android.wallpaper.reapply";
/**
+ * Command for {@link #sendWallpaperCommand}: reported when the live wallpaper needs to be
+ * frozen.
+ * @hide
+ */
+ public static final String COMMAND_FREEZE = "android.wallpaper.freeze";
+
+ /**
+ * Command for {@link #sendWallpaperCommand}: reported when the live wallapper doesn't need
+ * to be frozen anymore.
+ * @hide
+ */
+ public static final String COMMAND_UNFREEZE = "android.wallpaper.unfreeze";
+
+ /**
* Extra passed back from setWallpaper() giving the new wallpaper's assigned ID.
* @hide
*/
@@ -548,7 +562,7 @@ public class WallpaperManager {
}
if (returnDefault) {
Bitmap defaultWallpaper = mDefaultWallpaper;
- if (defaultWallpaper == null) {
+ if (defaultWallpaper == null || defaultWallpaper.isRecycled()) {
defaultWallpaper = getDefaultWallpaper(context, which);
synchronized (this) {
mDefaultWallpaper = defaultWallpaper;
@@ -572,12 +586,12 @@ public class WallpaperManager {
Rect dimensions = null;
synchronized (this) {
+ ParcelFileDescriptor pfd = null;
try {
Bundle params = new Bundle();
+ pfd = mService.getWallpaperWithFeature(context.getOpPackageName(),
+ context.getAttributionTag(), this, FLAG_SYSTEM, params, userId);
// Let's peek user wallpaper first.
- ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
- context.getOpPackageName(), context.getAttributionTag(), this,
- FLAG_SYSTEM, params, userId);
if (pfd != null) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
@@ -586,6 +600,13 @@ public class WallpaperManager {
}
} catch (RemoteException ex) {
Log.w(TAG, "peek wallpaper dimensions failed", ex);
+ } finally {
+ if (pfd != null) {
+ try {
+ pfd.close();
+ } catch (IOException ignored) {
+ }
+ }
}
}
// If user wallpaper is unavailable, may be the default one instead.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0fe80c45ad2a..5b01a7d2de66 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -7726,27 +7726,64 @@ public class DevicePolicyManager {
}
/**
- * @hide
- * Sets the given package as the device owner. The package must already be installed. There
- * must not already be a device owner.
- * Only apps with the MANAGE_PROFILE_AND_DEVICE_OWNERS permission and the shell uid can call
- * this method.
- * Calling this after the setup phase of the primary user has completed is allowed only if
- * the caller is the shell uid, and there are no additional users and no accounts.
+ * Sets the given package as the device owner.
+ *
+ * <p>Preconditions:
+ * <ul>
+ * <li>The package must already be installed.
+ * <li>There must not already be a device owner.
+ * <li>Only apps with the {@code MANAGE_PROFILE_AND_DEVICE_OWNERS} permission or the
+ * {@link Process#SHELL_UID Shell UID} can call this method.
+ * </ul>
+ *
+ * <p>Calling this after the setup phase of the device owner user has completed is allowed only
+ * if the caller is the {@link Process#SHELL_UID Shell UID}, and there are no additional users
+ * (except when the device runs on headless system user mode, in which case it could have exact
+ * one extra user, which is the current user - the device owner will be set in the
+ * {@link UserHandle#SYSTEM system} user and a profile owner will be set in the current user)
+ * and no accounts.
+ *
* @param who the component name to be registered as device owner.
* @param ownerName the human readable name of the institution that owns this device.
* @param userId ID of the user on which the device owner runs.
+ *
* @return whether the package was successfully registered as the device owner.
- * @throws IllegalArgumentException if the package name is null or invalid
+ *
+ * @throws IllegalArgumentException if the package name is {@code null} or invalid.
* @throws IllegalStateException If the preconditions mentioned are not met.
+ *
+ * @hide
*/
@TestApi
@RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
- public boolean setDeviceOwner(
+ public boolean setDeviceOwner(@NonNull ComponentName who, @Nullable String ownerName,
+ @UserIdInt int userId) {
+ if (mService != null) {
+ try {
+ return mService.setDeviceOwner(who, ownerName, userId,
+ /* setProfileOwnerOnCurrentUserIfNecessary= */ true);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Same as {@link #setDeviceOwner(ComponentName, String, int)}, but without setting the profile
+ * owner on current user when running on headless system user mode - should be used only by
+ * testing infra.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ public boolean setDeviceOwnerOnly(
@NonNull ComponentName who, @Nullable String ownerName, @UserIdInt int userId) {
if (mService != null) {
try {
- return mService.setDeviceOwner(who, ownerName, userId);
+ return mService.setDeviceOwner(who, ownerName, userId,
+ /* setProfileOwnerOnCurrentUserIfNecessary= */ false);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -13761,6 +13798,23 @@ public class DevicePolicyManager {
}
/**
+ * Clears organization ID set by the DPC and resets the precomputed enrollment specific ID.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ public void clearOrganizationId() {
+ if (mService == null) {
+ return;
+ }
+ try {
+ mService.clearOrganizationIdForUser(myUserId());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Creates and provisions a managed profile and sets the
* {@link ManagedProfileProvisioningParams#getProfileAdminComponentName()} as the profile
* owner.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index b5b3934035e0..1c9187d8d22c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -159,7 +159,7 @@ interface IDevicePolicyManager {
void reportKeyguardDismissed(int userHandle);
void reportKeyguardSecured(int userHandle);
- boolean setDeviceOwner(in ComponentName who, String ownerName, int userId);
+ boolean setDeviceOwner(in ComponentName who, String ownerName, int userId, boolean setProfileOwnerOnCurrentUserIfNecessary);
ComponentName getDeviceOwnerComponent(boolean callingUserOnly);
boolean hasDeviceOwner();
String getDeviceOwnerName();
@@ -378,6 +378,7 @@ interface IDevicePolicyManager {
void setOrganizationColor(in ComponentName admin, in int color);
void setOrganizationColorForUser(in int color, in int userId);
+ void clearOrganizationIdForUser(int userHandle);
int getOrganizationColor(in ComponentName admin);
int getOrganizationColorForUser(int userHandle);
diff --git a/core/java/android/app/compat/PackageOverride.java b/core/java/android/app/compat/PackageOverride.java
index fad6cd311021..ebc2945fb1a0 100644
--- a/core/java/android/app/compat/PackageOverride.java
+++ b/core/java/android/app/compat/PackageOverride.java
@@ -24,6 +24,7 @@ import android.os.Parcel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
/**
* An app compat override applied to a given package and change id pairing.
@@ -139,6 +140,22 @@ public final class PackageOverride {
/** @hide */
@Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PackageOverride that = (PackageOverride) o;
+ return mMinVersionCode == that.mMinVersionCode && mMaxVersionCode == that.mMaxVersionCode
+ && mEnabled == that.mEnabled;
+ }
+
+ /** @hide */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mMinVersionCode, mMaxVersionCode, mEnabled);
+ }
+
+ /** @hide */
+ @Override
public String toString() {
if (mMinVersionCode == Long.MIN_VALUE && mMaxVersionCode == Long.MAX_VALUE) {
return Boolean.toString(mEnabled);
diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java
index e059f177e344..27d104b59284 100644
--- a/core/java/android/app/servertransaction/ActivityResultItem.java
+++ b/core/java/android/app/servertransaction/ActivityResultItem.java
@@ -16,6 +16,8 @@
package android.app.servertransaction;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME;
+import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import android.annotation.NonNull;
@@ -23,6 +25,9 @@ import android.annotation.Nullable;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.app.ResultInfo;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
@@ -41,11 +46,19 @@ public class ActivityResultItem extends ActivityTransactionItem {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private List<ResultInfo> mResultInfoList;
- /* TODO(b/78294732)
+ /**
+ * Correct the lifecycle of activity result after {@link android.os.Build.VERSION_CODES#S} to
+ * guarantee that an activity gets activity result just before resume.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
+ public static final long CALL_ACTIVITY_RESULT_BEFORE_RESUME = 78294732L;
+
@Override
public int getPostExecutionState() {
- return ON_RESUME;
- }*/
+ return CompatChanges.isChangeEnabled(CALL_ACTIVITY_RESULT_BEFORE_RESUME)
+ ? ON_RESUME : UNDEFINED;
+ }
@Override
public void execute(ClientTransactionHandler client, ActivityClientRecord r,
diff --git a/core/java/android/app/servertransaction/ActivityTransactionItem.java b/core/java/android/app/servertransaction/ActivityTransactionItem.java
index a539812fa8c6..186f25deab67 100644
--- a/core/java/android/app/servertransaction/ActivityTransactionItem.java
+++ b/core/java/android/app/servertransaction/ActivityTransactionItem.java
@@ -59,36 +59,37 @@ public abstract class ActivityTransactionItem extends ClientTransactionItem {
}
/**
- * Get the {@link ActivityClientRecord} instance that corresponds to the provided token.
+ * Gets the {@link ActivityClientRecord} instance that corresponds to the provided token.
* @param client Target client handler.
* @param token Target activity token.
- * @param includeLaunching Indicate to also find the {@link ActivityClientRecord} in launching
- * activity list. It should be noted that there is no activity in
+ * @param includeLaunching Indicate to find the {@link ActivityClientRecord} in launching
+ * activity list.
+ * <p>Note that there is no {@link android.app.Activity} instance in
* {@link ActivityClientRecord} from the launching activity list.
* @return The {@link ActivityClientRecord} instance that corresponds to the provided token.
*/
@NonNull ActivityClientRecord getActivityClientRecord(
@NonNull ClientTransactionHandler client, IBinder token, boolean includeLaunching) {
- ActivityClientRecord r = client.getActivityClient(token);
- if (r != null) {
- if (client.getActivity(token) == null) {
+ ActivityClientRecord r = null;
+ // Check launching Activity first to prevent race condition that activity instance has not
+ // yet set to ActivityClientRecord.
+ if (includeLaunching) {
+ r = client.getLaunchingActivity(token);
+ }
+ // Then if we don't want to find launching Activity or the ActivityClientRecord doesn't
+ // exist in launching Activity list. The ActivityClientRecord should have been initialized
+ // and put in the Activity list.
+ if (r == null) {
+ r = client.getActivityClient(token);
+ if (r != null && client.getActivity(token) == null) {
throw new IllegalArgumentException("Activity must not be null to execute "
+ "transaction item");
}
- return r;
- }
- // The activity may not be launched yet. Fallback to check launching activity.
- if (includeLaunching) {
- r = client.getLaunchingActivity(token);
}
if (r == null) {
throw new IllegalArgumentException("Activity client record must not be null to execute "
+ "transaction item");
}
-
- // We don't need to check the activity of launching activity client records because they
- // have not been launched yet.
-
return r;
}
}
diff --git a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
index 5374984d31d0..767fd28b8a2a 100644
--- a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
+++ b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
@@ -16,17 +16,14 @@
package android.app.servertransaction;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
import android.app.ClientTransactionHandler;
import android.os.Parcel;
+import android.view.SurfaceControl;
import android.window.SplashScreenView.SplashScreenViewParcelable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
/**
* Transfer a splash screen view to an Activity.
* @hide
@@ -34,31 +31,13 @@ import java.lang.annotation.RetentionPolicy;
public class TransferSplashScreenViewStateItem extends ActivityTransactionItem {
private SplashScreenViewParcelable mSplashScreenViewParcelable;
- private @TransferRequest int mRequest;
-
- @IntDef(value = {
- ATTACH_TO,
- HANDOVER_TO
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface TransferRequest {}
- // request client to attach the view on it.
- public static final int ATTACH_TO = 0;
- // tell client that you can handle the splash screen view.
- public static final int HANDOVER_TO = 1;
+ private SurfaceControl mStartingWindowLeash;
@Override
public void execute(@NonNull ClientTransactionHandler client,
@NonNull ActivityThread.ActivityClientRecord r,
PendingTransactionActions pendingActions) {
- switch (mRequest) {
- case ATTACH_TO:
- client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable);
- break;
- case HANDOVER_TO:
- client.handOverSplashScreenView(r);
- break;
- }
+ client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable, mStartingWindowLeash);
}
@Override
@@ -68,26 +47,27 @@ public class TransferSplashScreenViewStateItem extends ActivityTransactionItem {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mRequest);
dest.writeTypedObject(mSplashScreenViewParcelable, flags);
+ dest.writeTypedObject(mStartingWindowLeash, flags);
}
private TransferSplashScreenViewStateItem() {}
private TransferSplashScreenViewStateItem(Parcel in) {
- mRequest = in.readInt();
mSplashScreenViewParcelable = in.readTypedObject(SplashScreenViewParcelable.CREATOR);
+ mStartingWindowLeash = in.readTypedObject(SurfaceControl.CREATOR);
}
/** Obtain an instance initialized with provided params. */
- public static TransferSplashScreenViewStateItem obtain(@TransferRequest int state,
- @Nullable SplashScreenViewParcelable parcelable) {
+ public static TransferSplashScreenViewStateItem obtain(
+ @Nullable SplashScreenViewParcelable parcelable,
+ @Nullable SurfaceControl startingWindowLeash) {
TransferSplashScreenViewStateItem instance =
ObjectPool.obtain(TransferSplashScreenViewStateItem.class);
if (instance == null) {
instance = new TransferSplashScreenViewStateItem();
}
- instance.mRequest = state;
instance.mSplashScreenViewParcelable = parcelable;
+ instance.mStartingWindowLeash = startingWindowLeash;
return instance;
}
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 8aa27853b462..7c2b1b72fbf1 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityOptions;
+import android.app.LoadedApk;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -552,6 +553,10 @@ public class AppWidgetHostView extends FrameLayout {
inflateAsync(rvToApply);
return;
}
+ // Prepare a local reference to the remote Context so we're ready to
+ // inflate any requested LayoutParams.
+ mRemoteContext = getRemoteContextEnsuringCorrectCachedApkPath();
+
int layoutId = rvToApply.getLayoutId();
if (rvToApply.canRecycleView(mView)) {
try {
@@ -612,7 +617,7 @@ public class AppWidgetHostView extends FrameLayout {
private void inflateAsync(@NonNull RemoteViews remoteViews) {
// Prepare a local reference to the remote Context so we're ready to
// inflate any requested LayoutParams.
- mRemoteContext = getRemoteContext();
+ mRemoteContext = getRemoteContextEnsuringCorrectCachedApkPath();
int layoutId = remoteViews.getLayoutId();
if (mLastExecutionSignal != null) {
@@ -621,7 +626,7 @@ public class AppWidgetHostView extends FrameLayout {
// If our stale view has been prepared to match active, and the new
// layout matches, try recycling it
- if (layoutId == mLayoutId && mView != null) {
+ if (remoteViews.canRecycleView(mView)) {
try {
mLastExecutionSignal = remoteViews.reapplyAsync(mContext,
mView,
@@ -714,8 +719,10 @@ public class AppWidgetHostView extends FrameLayout {
* purposes of reading remote resources.
* @hide
*/
- protected Context getRemoteContext() {
+ protected Context getRemoteContextEnsuringCorrectCachedApkPath() {
try {
+ ApplicationInfo expectedAppInfo = mInfo.providerInfo.applicationInfo;
+ LoadedApk.checkAndUpdateApkPaths(expectedAppInfo);
// Return if cloned successfully, otherwise default
Context newContext = mContext.createApplicationContext(
mInfo.providerInfo.applicationInfo,
@@ -727,6 +734,9 @@ public class AppWidgetHostView extends FrameLayout {
} catch (NameNotFoundException e) {
Log.e(TAG, "Package name " + mInfo.providerInfo.packageName + " not found");
return mContext;
+ } catch (NullPointerException e) {
+ Log.e(TAG, "Error trying to create the remote context.", e);
+ return mContext;
}
}
@@ -758,7 +768,7 @@ public class AppWidgetHostView extends FrameLayout {
try {
if (mInfo != null) {
- Context theirContext = getRemoteContext();
+ Context theirContext = getRemoteContextEnsuringCorrectCachedApkPath();
mRemoteContext = theirContext;
LayoutInflater inflater = (LayoutInflater)
theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
diff --git a/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl
index a630873c7f67..e1c13f7fc9e1 100644
--- a/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl
+++ b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl
@@ -29,4 +29,6 @@ oneway interface ICompanionDeviceDiscoveryService {
in String callingPackage,
in IFindDeviceCallback findCallback,
in AndroidFuture<Association> serviceCallback);
+
+ void onAssociationCreated();
}
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index f49362e9300e..2ecd71bc1f06 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -124,6 +124,16 @@ public class ClipDescription implements Parcelable {
*/
public static final String EXTRA_ACTIVITY_OPTIONS = "android.intent.extra.ACTIVITY_OPTIONS";
+ /**
+ * An instance id used for logging.
+ * <p>
+ * Type: {@link com.android.internal.logging.InstanceId}
+ * </p>
+ * @hide
+ */
+ public static final String EXTRA_LOGGING_INSTANCE_ID =
+ "android.intent.extra.LOGGING_INSTANCE_ID";
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(value =
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index c3ec09466de6..7f2a740d3228 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3246,7 +3246,11 @@ public abstract class Context {
* Service will call {@link android.app.Service#startForeground(int, android.app.Notification)
* startForeground(int, android.app.Notification)} once it begins running. The service is given
* an amount of time comparable to the ANR interval to do this, otherwise the system
- * will automatically stop the service and declare the app ANR.
+ * will automatically crash the process, in which case an internal exception
+ * {@code ForegroundServiceDidNotStartInTimeException} is logged on logcat on devices
+ * running SDK Version {@link android.os.Build.VERSION_CODES#S} or later. On older Android
+ * versions, an internal exception {@code RemoteServiceException} is logged instead, with
+ * a corresponding message.
*
* <p>Unlike the ordinary {@link #startService(Intent)}, this method can be used
* at any time, regardless of whether the app hosting the service is in a foreground
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 95c5612aeee4..e28e4ad9ae9f 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -994,9 +994,10 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
* OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
* OVERRIDE_MIN_ASPECT_RATIO_LARGE
*
- * If OVERRIDE_MIN_ASPECT_RATIO is applied, the min aspect ratio given in the app's
- * manifest will be overridden to the largest enabled aspect ratio treatment unless the app's
- * manifest value is higher.
+ * If OVERRIDE_MIN_ASPECT_RATIO is applied, and the activity's orientation is fixed to
+ * portrait, the min aspect ratio given in the app's manifest will be overridden to the
+ * largest enabled aspect ratio treatment unless the app's manifest value is higher.
+ * TODO(b/203647190): add OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY instead of portrait by default
* @hide
*/
@ChangeId
@@ -1232,8 +1233,8 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
* Returns true if the activity has maximum or minimum aspect ratio.
* @hide
*/
- public boolean hasFixedAspectRatio() {
- return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
+ public boolean hasFixedAspectRatio(@ScreenOrientation int orientation) {
+ return getMaxAspectRatio() != 0 || getMinAspectRatio(orientation) != 0;
}
/**
@@ -1392,10 +1393,14 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
* {@code getManifestMinAspectRatio}.
* @hide
*/
- public float getMinAspectRatio() {
+ public float getMinAspectRatio(@ScreenOrientation int orientation) {
+ // TODO(b/203647190): check orientation only if OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY
+ // In case the activity's orientation isn't fixed to portrait, OVERRIDE_MIN_ASPECT_RATIO
+ // shouldn't be applied.
if (applicationInfo == null || !CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO,
applicationInfo.packageName,
- UserHandle.getUserHandleForUid(applicationInfo.uid))) {
+ UserHandle.getUserHandleForUid(applicationInfo.uid))
+ || !isFixedOrientationPortrait(orientation)) {
return mMinAspectRatio;
}
@@ -1521,9 +1526,10 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
if (getMaxAspectRatio() != 0) {
pw.println(prefix + "maxAspectRatio=" + getMaxAspectRatio());
}
- if (getMinAspectRatio() != 0) {
- pw.println(prefix + "minAspectRatio=" + getMinAspectRatio());
- if (getManifestMinAspectRatio() != getMinAspectRatio()) {
+ final float minAspectRatio = getMinAspectRatio(screenOrientation);
+ if (minAspectRatio != 0) {
+ pw.println(prefix + "minAspectRatio=" + minAspectRatio);
+ if (getManifestMinAspectRatio() != minAspectRatio) {
pw.println(prefix + "getManifestMinAspectRatio=" + getManifestMinAspectRatio());
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 2ed00b5d2982..d4fa2d54bd8c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3699,6 +3699,17 @@ public abstract class PackageManager {
public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY =
"android.hardware.keystore.app_attest_key";
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+ * is opted-in to receive per-app compatibility overrides that are applied in
+ * {@link com.android.server.compat.overrides.AppCompatOverridesService}.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_APP_COMPAT_OVERRIDES =
+ "android.software.app_compat_overrides";
+
/** @hide */
public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
diff --git a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java b/core/java/android/hardware/biometrics/BiometricOverlayConstants.java
new file mode 100644
index 000000000000..603b06ddabaa
--- /dev/null
+++ b/core/java/android/hardware/biometrics/BiometricOverlayConstants.java
@@ -0,0 +1,50 @@
+/*
+ * 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 android.hardware.biometrics;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Common constants for biometric overlays.
+ * @hide
+ */
+public interface BiometricOverlayConstants {
+ /** Unknown usage. */
+ int REASON_UNKNOWN = 0;
+ /** User is about to enroll. */
+ int REASON_ENROLL_FIND_SENSOR = 1;
+ /** User is enrolling. */
+ int REASON_ENROLL_ENROLLING = 2;
+ /** Usage from BiometricPrompt. */
+ int REASON_AUTH_BP = 3;
+ /** Usage from Keyguard. */
+ int REASON_AUTH_KEYGUARD = 4;
+ /** Non-specific usage (from FingerprintManager). */
+ int REASON_AUTH_OTHER = 5;
+
+ @IntDef({REASON_UNKNOWN,
+ REASON_ENROLL_FIND_SENSOR,
+ REASON_ENROLL_ENROLLING,
+ REASON_AUTH_BP,
+ REASON_AUTH_KEYGUARD,
+ REASON_AUTH_OTHER})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ShowReason {}
+}
diff --git a/core/java/android/hardware/biometrics/SensorLocationInternal.aidl b/core/java/android/hardware/biometrics/SensorLocationInternal.aidl
new file mode 100644
index 000000000000..098190449d53
--- /dev/null
+++ b/core/java/android/hardware/biometrics/SensorLocationInternal.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.hardware.biometrics;
+
+// @hide
+parcelable SensorLocationInternal;
diff --git a/core/java/android/hardware/biometrics/SensorLocationInternal.java b/core/java/android/hardware/biometrics/SensorLocationInternal.java
new file mode 100644
index 000000000000..fb25a2fcd823
--- /dev/null
+++ b/core/java/android/hardware/biometrics/SensorLocationInternal.java
@@ -0,0 +1,112 @@
+/*
+ * 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 android.hardware.biometrics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The location of a sensor relative to a physical display.
+ *
+ * Note that the location may change depending on other attributes of the device, such as
+ * fold status, which are not yet included in this class.
+ * @hide
+ */
+public class SensorLocationInternal implements Parcelable {
+
+ /** Default value to use when the sensor's location is unknown or undefined. */
+ public static final SensorLocationInternal DEFAULT = new SensorLocationInternal("", 0, 0, 0);
+
+ /**
+ * The stable display id.
+ */
+ @NonNull
+ public final String displayId;
+
+ /**
+ * The location of the center of the sensor if applicable. For example, sensors of type
+ * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the
+ * distance in pixels, measured from the left edge of the screen.
+ */
+ public final int sensorLocationX;
+
+ /**
+ * The location of the center of the sensor if applicable. For example, sensors of type
+ * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the
+ * distance in pixels, measured from the top edge of the screen.
+ *
+ */
+ public final int sensorLocationY;
+
+ /**
+ * The radius of the sensor if applicable. For example, sensors of type
+ * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the radius
+ * of the sensor, in pixels.
+ */
+ public final int sensorRadius;
+
+ public SensorLocationInternal(@Nullable String displayId,
+ int sensorLocationX, int sensorLocationY, int sensorRadius) {
+ this.displayId = displayId != null ? displayId : "";
+ this.sensorLocationX = sensorLocationX;
+ this.sensorLocationY = sensorLocationY;
+ this.sensorRadius = sensorRadius;
+ }
+
+ protected SensorLocationInternal(Parcel in) {
+ displayId = in.readString16NoHelper();
+ sensorLocationX = in.readInt();
+ sensorLocationY = in.readInt();
+ sensorRadius = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(displayId);
+ dest.writeInt(sensorLocationX);
+ dest.writeInt(sensorLocationY);
+ dest.writeInt(sensorRadius);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<SensorLocationInternal> CREATOR =
+ new Creator<SensorLocationInternal>() {
+ @Override
+ public SensorLocationInternal createFromParcel(Parcel in) {
+ return new SensorLocationInternal(in);
+ }
+
+ @Override
+ public SensorLocationInternal[] newArray(int size) {
+ return new SensorLocationInternal[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "[id: " + displayId
+ + ", x: " + sensorLocationX
+ + ", y: " + sensorLocationY
+ + ", r: " + sensorRadius + "]";
+ }
+}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index e0138c5db178..9f77a7e72e70 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -22,15 +22,18 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
+import android.hardware.camera2.params.DeviceStateSensorOrientationMap;
import android.hardware.camera2.params.RecommendedStreamConfigurationMap;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.utils.TypeReference;
import android.os.Build;
+import android.util.Log;
import android.util.Rational;
+import com.android.internal.annotations.GuardedBy;
+
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -202,8 +205,25 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
private List<CaptureResult.Key<?>> mAvailableResultKeys;
private ArrayList<RecommendedStreamConfigurationMap> mRecommendedConfigurations;
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private boolean mFoldedDeviceState;
+
+ private final CameraManager.DeviceStateListener mFoldStateListener =
+ new CameraManager.DeviceStateListener() {
+ @Override
+ public final void onDeviceStateChanged(boolean folded) {
+ synchronized (mLock) {
+ mFoldedDeviceState = folded;
+ }
+ }};
+
+ private static final String TAG = "CameraCharacteristics";
+
/**
* Takes ownership of the passed-in properties object
+ *
+ * @param properties Camera properties.
* @hide
*/
public CameraCharacteristics(CameraMetadataNative properties) {
@@ -220,6 +240,42 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
}
/**
+ * Return the device state listener for this Camera characteristics instance
+ */
+ CameraManager.DeviceStateListener getDeviceStateListener() { return mFoldStateListener; }
+
+ /**
+ * Overrides the property value
+ *
+ * <p>Check whether a given property value needs to be overridden in some specific
+ * case.</p>
+ *
+ * @param key The characteristics field to override.
+ * @return The value of overridden property, or {@code null} if the property doesn't need an
+ * override.
+ */
+ @Nullable
+ private <T> T overrideProperty(Key<T> key) {
+ if (CameraCharacteristics.SENSOR_ORIENTATION.equals(key) && (mFoldStateListener != null) &&
+ (mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS) != null)) {
+ DeviceStateSensorOrientationMap deviceStateSensorOrientationMap =
+ mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP);
+ synchronized (mLock) {
+ Integer ret = deviceStateSensorOrientationMap.getSensorOrientation(
+ mFoldedDeviceState ? DeviceStateSensorOrientationMap.FOLDED :
+ DeviceStateSensorOrientationMap.NORMAL);
+ if (ret >= 0) {
+ return (T) ret;
+ } else {
+ Log.w(TAG, "No valid device state to orientation mapping! Using default!");
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
* Get a camera characteristics field value.
*
* <p>The field definitions can be
@@ -235,7 +291,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
*/
@Nullable
public <T> T get(Key<T> key) {
- return mProperties.get(key);
+ T propertyOverride = overrideProperty(key);
+ return (propertyOverride != null) ? propertyOverride : mProperties.get(key);
}
/**
@@ -3993,11 +4050,26 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* upright on the device screen in its native orientation.</p>
* <p>Also defines the direction of rolling shutter readout, which is from top to bottom in
* the sensor's coordinate system.</p>
+ * <p>Starting with Android API level 32, camera clients that query the orientation via
+ * {@link android.hardware.camera2.CameraCharacteristics#get } on foldable devices which
+ * include logical cameras can receive a value that can dynamically change depending on the
+ * device/fold state.
+ * Clients are advised to not cache or store the orientation value of such logical sensors.
+ * In case repeated queries to CameraCharacteristics are not preferred, then clients can
+ * also access the entire mapping from device state to sensor orientation in
+ * {@link android.hardware.camera2.params.DeviceStateSensorOrientationMap }.
+ * Do note that a dynamically changing sensor orientation value in camera characteristics
+ * will not be the best way to establish the orientation per frame. Clients that want to
+ * know the sensor orientation of a particular captured frame should query the
+ * {@link CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID android.logicalMultiCamera.activePhysicalId} from the corresponding capture result and
+ * check the respective physical camera orientation.</p>
* <p><b>Units</b>: Degrees of clockwise rotation; always a multiple of
* 90</p>
* <p><b>Range of valid values:</b><br>
* 0, 90, 180, 270</p>
* <p>This key is available on all devices.</p>
+ *
+ * @see CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID
*/
@PublicKey
@NonNull
@@ -4307,6 +4379,46 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<String>("android.info.version", String.class);
/**
+ * <p>This lists the mapping between a device folding state and
+ * specific camera sensor orientation for logical cameras on a foldable device.</p>
+ * <p>Logical cameras on foldable devices can support sensors with different orientation
+ * values. The orientation value may need to change depending on the specific folding
+ * state. Information about the mapping between the device folding state and the
+ * sensor orientation can be obtained in
+ * {@link android.hardware.camera2.params.DeviceStateSensorOrientationMap }.
+ * Device state orientation maps are optional and maybe present on devices that support
+ * {@link CaptureRequest#SCALER_ROTATE_AND_CROP android.scaler.rotateAndCrop}.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#SCALER_ROTATE_AND_CROP
+ */
+ @PublicKey
+ @NonNull
+ @SyntheticKey
+ public static final Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap> INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP =
+ new Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap>("android.info.deviceStateSensorOrientationMap", android.hardware.camera2.params.DeviceStateSensorOrientationMap.class);
+
+ /**
+ * <p>HAL must populate the array with
+ * (hardware::camera::provider::V2_5::DeviceState, sensorOrientation) pairs for each
+ * supported device state bitwise combination.</p>
+ * <p><b>Units</b>: (device fold state, sensor orientation) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @hide
+ */
+ public static final Key<long[]> INFO_DEVICE_STATE_ORIENTATIONS =
+ new Key<long[]>("android.info.deviceStateOrientations", long[].class);
+
+ /**
* <p>The maximum number of frames that can occur after a request
* (different than the previous) has been submitted, and before the
* result's state becomes synchronized.</p>
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 5833b3dbef86..b7c5644df107 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -35,10 +35,13 @@ import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfiguration;
import android.hardware.camera2.utils.CameraIdAndSessionConfiguration;
import android.hardware.camera2.utils.ConcurrentCameraIdCombination;
+import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.DeadObjectException;
import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -50,6 +53,10 @@ import android.util.Log;
import android.util.Size;
import android.view.Display;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -97,6 +104,90 @@ public final class CameraManager {
synchronized(mLock) {
mContext = context;
}
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mFoldStateListener = new FoldStateListener(context);
+ try {
+ context.getSystemService(DeviceStateManager.class)
+ .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
+ } catch (IllegalStateException e) {
+ Log.v(TAG, "Failed to register device state listener!");
+ Log.v(TAG, "Device state dependent characteristics updates will not be functional!");
+ mHandlerThread.quitSafely();
+ mHandler = null;
+ mFoldStateListener = null;
+ }
+ }
+
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private FoldStateListener mFoldStateListener;
+ @GuardedBy("mLock")
+ private ArrayList<WeakReference<DeviceStateListener>> mDeviceStateListeners = new ArrayList<>();
+ private boolean mFoldedDeviceState;
+
+ /**
+ * @hide
+ */
+ public interface DeviceStateListener {
+ void onDeviceStateChanged(boolean folded);
+ }
+
+ private final class FoldStateListener implements DeviceStateManager.DeviceStateCallback {
+ private final int[] mFoldedDeviceStates;
+
+ public FoldStateListener(Context context) {
+ mFoldedDeviceStates = context.getResources().getIntArray(
+ com.android.internal.R.array.config_foldedDeviceStates);
+ }
+
+ private void handleStateChange(int state) {
+ boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
+ synchronized (mLock) {
+ mFoldedDeviceState = folded;
+ ArrayList<WeakReference<DeviceStateListener>> invalidListeners = new ArrayList<>();
+ for (WeakReference<DeviceStateListener> listener : mDeviceStateListeners) {
+ DeviceStateListener callback = listener.get();
+ if (callback != null) {
+ callback.onDeviceStateChanged(folded);
+ } else {
+ invalidListeners.add(listener);
+ }
+ }
+ if (!invalidListeners.isEmpty()) {
+ mDeviceStateListeners.removeAll(invalidListeners);
+ }
+ }
+ }
+
+ @Override
+ public final void onBaseStateChanged(int state) {
+ handleStateChange(state);
+ }
+
+ @Override
+ public final void onStateChanged(int state) {
+ handleStateChange(state);
+ }
+ }
+
+ /**
+ * Register a {@link CameraCharacteristics} device state listener
+ *
+ * @param chars Camera characteristics that need to receive device state updates
+ *
+ * @hide
+ */
+ public void registerDeviceStateListener(@NonNull CameraCharacteristics chars) {
+ synchronized (mLock) {
+ DeviceStateListener listener = chars.getDeviceStateListener();
+ listener.onDeviceStateChanged(mFoldedDeviceState);
+ if (mFoldStateListener != null) {
+ mDeviceStateListeners.add(new WeakReference<>(listener));
+ }
+ }
}
/**
@@ -504,6 +595,7 @@ public final class CameraManager {
"Camera service is currently unavailable", e);
}
}
+ registerDeviceStateListener(characteristics);
return characteristics;
}
@@ -1327,8 +1419,7 @@ public final class CameraManager {
private ICameraService mCameraService;
// Singleton, don't allow construction
- private CameraManagerGlobal() {
- }
+ private CameraManagerGlobal() { }
public static final boolean sCameraServiceDisabled =
SystemProperties.getBoolean("config.disable_cameraservice", false);
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 196134b397cb..e393a66eb733 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -50,10 +50,10 @@ import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfiguration
import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfigurationDuration;
import android.hardware.camera2.marshal.impl.MarshalQueryableString;
import android.hardware.camera2.params.Capability;
+import android.hardware.camera2.params.DeviceStateSensorOrientationMap;
import android.hardware.camera2.params.Face;
import android.hardware.camera2.params.HighSpeedVideoConfiguration;
import android.hardware.camera2.params.LensShadingMap;
-import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.params.MandatoryStreamCombination;
import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap;
import android.hardware.camera2.params.OisSample;
@@ -754,7 +754,7 @@ public class CameraMetadataNative implements Parcelable {
});
sGetCommandMap.put(
CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP.getNativeKey(),
- new GetCommand() {
+ new GetCommand() {
@Override
@SuppressWarnings("unchecked")
public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
@@ -762,6 +762,15 @@ public class CameraMetadataNative implements Parcelable {
}
});
sGetCommandMap.put(
+ CameraCharacteristics.INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP.getNativeKey(),
+ new GetCommand() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+ return (T) metadata.getDeviceStateOrientationMap();
+ }
+ });
+ sGetCommandMap.put(
CaptureResult.STATISTICS_OIS_SAMPLES.getNativeKey(),
new GetCommand() {
@Override
@@ -994,6 +1003,18 @@ public class CameraMetadataNative implements Parcelable {
return map;
}
+ private DeviceStateSensorOrientationMap getDeviceStateOrientationMap() {
+ long[] mapArray = getBase(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS);
+
+ // Do not warn if map is null while s is not. This is valid.
+ if (mapArray == null) {
+ return null;
+ }
+
+ DeviceStateSensorOrientationMap map = new DeviceStateSensorOrientationMap(mapArray);
+ return map;
+ }
+
private Location getGpsLocation() {
String processingMethod = get(CaptureResult.JPEG_GPS_PROCESSING_METHOD);
double[] coords = get(CaptureResult.JPEG_GPS_COORDINATES);
diff --git a/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
new file mode 100644
index 000000000000..200409e9e000
--- /dev/null
+++ b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
@@ -0,0 +1,156 @@
+/*
+ * 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 android.hardware.camera2.params;
+
+import android.annotation.LongDef;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.utils.HashCodeHelpers;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Objects;
+
+/**
+ * Immutable class that maps the device fold state to sensor orientation.
+ *
+ * <p>Some {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA logical}
+ * cameras on foldables can include physical sensors with different sensor orientation
+ * values. As a result, the values of the logical camera device can potentially change depending
+ * on the device fold state.</p>
+ *
+ * <p>The device fold state to sensor orientation map will contain information about the
+ * respective logical camera sensor orientation given a device state. Clients
+ * can query the mapping for all possible supported folded states.
+ *
+ * @see CameraCharacteristics#SENSOR_ORIENTATION
+ */
+public final class DeviceStateSensorOrientationMap {
+ /**
+ * Needs to be kept in sync with the HIDL/AIDL DeviceState
+ */
+
+ /**
+ * The device is in its normal physical configuration. This is the default if the
+ * device does not support multiple different states.
+ */
+ public static final long NORMAL = 0;
+
+ /**
+ * The device is folded. If not set, the device is unfolded or does not
+ * support folding.
+ *
+ * The exact point when this status change happens during the folding
+ * operation is device-specific.
+ */
+ public static final long FOLDED = 1 << 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @LongDef(prefix = {"DEVICE_STATE"}, value =
+ {NORMAL,
+ FOLDED })
+ public @interface DeviceState {};
+
+ private final HashMap<Long, Integer> mDeviceStateOrientationMap = new HashMap<>();
+
+ /**
+ * Create a new immutable DeviceStateOrientationMap instance.
+ *
+ * <p>This constructor takes over the array; do not write to the array afterwards.</p>
+ *
+ * @param elements
+ * An array of elements describing the map
+ *
+ * @throws IllegalArgumentException
+ * if the {@code elements} array length is invalid, not divisible by 2 or contains
+ * invalid element values
+ * @throws NullPointerException
+ * if {@code elements} is {@code null}
+ *
+ * @hide
+ */
+ public DeviceStateSensorOrientationMap(final long[] elements) {
+ mElements = Objects.requireNonNull(elements, "elements must not be null");
+ if ((elements.length % 2) != 0) {
+ throw new IllegalArgumentException("Device state sensor orientation map length " +
+ elements.length + " is not even!");
+ }
+
+ for (int i = 0; i < elements.length; i += 2) {
+ if ((elements[i+1] % 90) != 0) {
+ throw new IllegalArgumentException("Sensor orientation not divisible by 90: " +
+ elements[i+1]);
+ }
+
+ mDeviceStateOrientationMap.put(elements[i], Math.toIntExact(elements[i + 1]));
+ }
+ }
+
+ /**
+ * Return the logical camera sensor orientation given a specific device fold state.
+ *
+ * @param deviceState Device fold state
+ *
+ * @return Valid {@link android.hardware.camera2.CameraCharacteristics#SENSOR_ORIENTATION} for
+ * any supported device fold state
+ *
+ * @throws IllegalArgumentException if the given device state is invalid
+ */
+ public int getSensorOrientation(@DeviceState long deviceState) {
+ if (!mDeviceStateOrientationMap.containsKey(deviceState)) {
+ throw new IllegalArgumentException("Invalid device state: " + deviceState);
+ }
+
+ return mDeviceStateOrientationMap.get(deviceState);
+ }
+
+ /**
+ * Check if this DeviceStateSensorOrientationMap is equal to another
+ * DeviceStateSensorOrientationMap.
+ *
+ * <p>Two device state orientation maps are equal if and only if all of their elements are
+ * {@link Object#equals equal}.</p>
+ *
+ * @return {@code true} if the objects were equal, {@code false} otherwise
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DeviceStateSensorOrientationMap) {
+ final DeviceStateSensorOrientationMap other = (DeviceStateSensorOrientationMap) obj;
+ return Arrays.equals(mElements, other.mElements);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return HashCodeHelpers.hashCodeGeneric(mElements);
+ }
+
+ private final long[] mElements;
+}
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index 52dad3efefb8..95892aa7af81 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -75,23 +75,24 @@ public final class DeviceStateManager {
/**
* Submits a {@link DeviceStateRequest request} to modify the device state.
* <p>
- * By default, the request is kept active until a call to
- * {@link #cancelRequest(DeviceStateRequest)} or until one of the following occurs:
+ * By default, the request is kept active until one of the following occurs:
* <ul>
+ * <li>The system deems the request can no longer be honored, for example if the requested
+ * state becomes unsupported.
+ * <li>A call to {@link #cancelRequest(DeviceStateRequest)}.
* <li>Another processes submits a request succeeding this request in which case the request
* will be suspended until the interrupting request is canceled.
- * <li>The requested state has become unsupported.
- * <li>The process submitting the request dies.
* </ul>
* However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}.
*
* @throws IllegalArgumentException if the requested state is unsupported.
- * @throws SecurityException if the {@link android.Manifest.permission#CONTROL_DEVICE_STATE}
- * permission is not held.
+ * @throws SecurityException if the caller is neither the current top-focused activity nor if
+ * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held.
*
* @see DeviceStateRequest
*/
- @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+ @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
+ conditional = true)
public void requestState(@NonNull DeviceStateRequest request,
@Nullable @CallbackExecutor Executor executor,
@Nullable DeviceStateRequest.Callback callback) {
@@ -105,10 +106,11 @@ public final class DeviceStateManager {
* This method is noop if the {@code request} has not been submitted with a call to
* {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
*
- * @throws SecurityException if the {@link android.Manifest.permission#CONTROL_DEVICE_STATE}
- * permission is not held.
+ * @throws SecurityException if the caller is neither the current top-focused activity nor if
+ * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held.
*/
- @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+ @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
+ conditional = true)
public void cancelRequest(@NonNull DeviceStateRequest request) {
mGlobal.cancelRequest(request);
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt b/core/java/android/hardware/devicestate/DeviceStateManagerInternal.java
index 0037059e2c51..4c91c160f891 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerInternal.java
@@ -14,16 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.pip
-
-import android.content.ComponentName
-import com.android.server.wm.traces.common.windowmanager.WindowManagerState
-import com.android.server.wm.traces.parser.toWindowName
+package android.hardware.devicestate;
/**
- * Checks that an activity [activity] is in PIP mode
+ * Device state manager local system service interface.
+ *
+ * @hide Only for use within the system server.
*/
-fun WindowManagerState.isInPipMode(activity: ComponentName): Boolean {
- val windowName = activity.toWindowName()
- return isInPipMode(windowName)
+public abstract class DeviceStateManagerInternal {
+
+ /** Returns the list of currently supported device state identifiers. */
+ public abstract int[] getSupportedStateIdentifiers();
}
diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
index a1f7aa12264b..2b52e967ce54 100644
--- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java
+++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
@@ -24,6 +24,7 @@ import android.provider.Settings;
import android.text.TextUtils;
import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
/**
* AmbientDisplayConfiguration encapsulates reading access to the configuration of ambient display.
@@ -32,7 +33,7 @@ import com.android.internal.R;
*/
@TestApi
public class AmbientDisplayConfiguration {
-
+ private static final String TAG = "AmbientDisplayConfig";
private final Context mContext;
private final boolean mAlwaysOnByDefault;
@@ -87,7 +88,12 @@ public class AmbientDisplayConfiguration {
/** {@hide} */
public boolean tapSensorAvailable() {
- return !TextUtils.isEmpty(tapSensorType());
+ for (String tapType : tapSensorTypeMapping()) {
+ if (!TextUtils.isEmpty(tapType)) {
+ return true;
+ }
+ }
+ return false;
}
/** {@hide} */
@@ -140,9 +146,18 @@ public class AmbientDisplayConfiguration {
return mContext.getResources().getString(R.string.config_dozeDoubleTapSensorType);
}
- /** {@hide} */
- public String tapSensorType() {
- return mContext.getResources().getString(R.string.config_dozeTapSensorType);
+ /** {@hide}
+ * May support multiple postures.
+ */
+ public String[] tapSensorTypeMapping() {
+ String[] postureMapping =
+ mContext.getResources().getStringArray(R.array.config_dozeTapSensorPostureMapping);
+ if (ArrayUtils.isEmpty(postureMapping)) {
+ return new String[] {
+ mContext.getResources().getString(R.string.config_dozeTapSensorType)
+ };
+ }
+ return postureMapping;
}
/** {@hide} */
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index fc8337ac3155..d8f20391098c 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -864,7 +864,8 @@ public final class DisplayManager {
if (surface != null) {
builder.setSurface(surface);
}
- return createVirtualDisplay(null /* projection */, builder.build(), callback, handler);
+ return createVirtualDisplay(null /* projection */, builder.build(), callback, handler,
+ null /* windowContext */);
}
// TODO : Remove this hidden API after remove all callers. (Refer to MultiDisplayService)
@@ -882,15 +883,17 @@ public final class DisplayManager {
if (surface != null) {
builder.setSurface(surface);
}
- return createVirtualDisplay(projection, builder.build(), callback, handler);
+ return createVirtualDisplay(projection, builder.build(), callback, handler,
+ null /* windowContext */);
}
/** @hide */
public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection,
@NonNull VirtualDisplayConfig virtualDisplayConfig,
- @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
+ @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler,
+ @Nullable Context windowContext) {
return mGlobal.createVirtualDisplay(mContext, projection, virtualDisplayConfig, callback,
- handler);
+ handler, windowContext);
}
/**
@@ -940,6 +943,34 @@ public final class DisplayManager {
}
/**
+ * Sets the brightness configuration for the specified display.
+ * If the specified display doesn't exist, then this will return and do nothing.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
+ public void setBrightnessConfigurationForDisplay(@NonNull BrightnessConfiguration c,
+ @NonNull String uniqueId) {
+ mGlobal.setBrightnessConfigurationForDisplay(c, uniqueId, mContext.getUserId(),
+ mContext.getPackageName());
+ }
+
+ /**
+ * Gets the brightness configuration for the specified display and default user.
+ * Returns the default configuration if unset or display is invalid.
+ *
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ @RequiresPermission(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
+ public BrightnessConfiguration getBrightnessConfigurationForDisplay(
+ @NonNull String uniqueId) {
+ return mGlobal.getBrightnessConfigurationForDisplay(uniqueId, mContext.getUserId());
+ }
+
+ /**
* Sets the global display brightness configuration for a specific user.
*
* Note this requires the INTERACT_ACROSS_USERS permission if setting the configuration for a
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 6c3936569c28..1fec3c9ff50a 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -583,7 +583,7 @@ public final class DisplayManagerGlobal {
public VirtualDisplay createVirtualDisplay(@NonNull Context context, MediaProjection projection,
@NonNull VirtualDisplayConfig virtualDisplayConfig, VirtualDisplay.Callback callback,
- Handler handler) {
+ Handler handler, @Nullable Context windowContext) {
VirtualDisplayCallback callbackWrapper = new VirtualDisplayCallback(callback, handler);
IMediaProjection projectionToken = projection != null ? projection.getProjection() : null;
int displayId;
@@ -609,7 +609,7 @@ public final class DisplayManagerGlobal {
return null;
}
return new VirtualDisplay(this, display, callbackWrapper,
- virtualDisplayConfig.getSurface());
+ virtualDisplayConfig.getSurface(), windowContext);
}
public void setVirtualDisplaySurface(IVirtualDisplayCallback token, Surface surface) {
@@ -710,6 +710,34 @@ public final class DisplayManagerGlobal {
}
/**
+ * Sets the brightness configuration for a given display.
+ *
+ * @hide
+ */
+ public void setBrightnessConfigurationForDisplay(BrightnessConfiguration c,
+ String uniqueDisplayId, int userId, String packageName) {
+ try {
+ mDm.setBrightnessConfigurationForDisplay(c, uniqueDisplayId, userId, packageName);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the brightness configuration for a given display or null if one hasn't been set.
+ *
+ * @hide
+ */
+ public BrightnessConfiguration getBrightnessConfigurationForDisplay(String uniqueDisplayId,
+ int userId) {
+ try {
+ return mDm.getBrightnessConfigurationForDisplay(uniqueDisplayId, userId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets the global brightness configuration for a given user or null if one hasn't been set.
*
* @hide
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index abcc33c43c74..5bb51c19342d 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.graphics.Point;
import android.hardware.SensorManager;
import android.os.Handler;
+import android.os.IBinder;
import android.os.PowerManager;
import android.util.IntArray;
import android.util.Slog;
@@ -34,6 +35,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Display manager local system service interface.
@@ -128,6 +130,14 @@ public abstract class DisplayManagerInternal {
public abstract DisplayInfo getDisplayInfo(int displayId);
/**
+ * Returns a set of DisplayInfo, for the states that may be assumed by either the given display,
+ * or any other display within that display's group.
+ *
+ * @param displayId The logical display id to fetch DisplayInfo for.
+ */
+ public abstract Set<DisplayInfo> getPossibleDisplayInfo(int displayId);
+
+ /**
* Returns the position of the display's projection.
*
* @param displayId The logical display id.
@@ -340,6 +350,28 @@ public abstract class DisplayManagerInternal {
public abstract List<RefreshRateLimitation> getRefreshRateLimitations(int displayId);
/**
+ * Returns the window token of the level of the WindowManager hierarchy to mirror. Returns null
+ * if layer mirroring by SurfaceFlinger should not be performed for the given displayId.
+ * For now, only used for mirroring started from MediaProjection.
+ */
+ public abstract IBinder getWindowTokenClientToMirror(int displayId);
+
+ /**
+ * For the given displayId, updates the window token of the level of the WindowManager hierarchy
+ * to mirror. If windowToken is null, then SurfaceFlinger performs no layer mirroring to the
+ * given display.
+ * For now, only used for mirroring started from MediaProjection.
+ */
+ public abstract void setWindowTokenClientToMirror(int displayId, 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.
+ */
+ public abstract Point getDisplaySurfaceDefaultSize(int displayId);
+
+ /**
* Describes the requested power state of the display.
*
* This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 2303353ad101..116214625e26 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -118,6 +118,16 @@ interface IDisplayManager {
void setBrightnessConfigurationForUser(in BrightnessConfiguration c, int userId,
String packageName);
+ // Sets the global brightness configuration for a given display. Requires
+ // CONFIGURE_DISPLAY_BRIGHTNESS.
+ void setBrightnessConfigurationForDisplay(in BrightnessConfiguration c, String uniqueDisplayId,
+ int userId, String packageName);
+
+ // Gets the brightness configuration for a given display. Requires
+ // CONFIGURE_DISPLAY_BRIGHTNESS.
+ BrightnessConfiguration getBrightnessConfigurationForDisplay(String uniqueDisplayId,
+ int userId);
+
// Gets the global brightness configuration for a given user. Requires
// CONFIGURE_DISPLAY_BRIGHTNESS, and INTERACT_ACROSS_USER if the user is not
// the same as the calling user.
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index bf62c95cf6db..71cbd20e8005 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -15,6 +15,8 @@
*/
package android.hardware.display;
+import android.annotation.Nullable;
+import android.content.Context;
import android.view.Display;
import android.view.Surface;
@@ -36,13 +38,19 @@ public final class VirtualDisplay {
private final Display mDisplay;
private IVirtualDisplayCallback mToken;
private Surface mSurface;
+ /**
+ * Store the WindowContext in a field. If it is a local variable, and it is garbage collected
+ * during a MediaProjection session, the WindowContainer listener no longer exists.
+ */
+ @Nullable private final Context mWindowContext;
VirtualDisplay(DisplayManagerGlobal global, Display display,
- IVirtualDisplayCallback token, Surface surface) {
+ IVirtualDisplayCallback token, Surface surface, Context windowContext) {
mGlobal = global;
mDisplay = display;
mToken = token;
mSurface = surface;
+ mWindowContext = windowContext;
}
/**
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 71688c7cc7e8..0e86f43207aa 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -23,6 +23,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.media.projection.MediaProjection;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.Surface;
@@ -91,9 +92,16 @@ public final class VirtualDisplayConfig implements Parcelable {
*/
private int mDisplayIdToMirror = DEFAULT_DISPLAY;
+ /**
+ * The window token of the level of the WindowManager hierarchy to mirror, or null if mirroring
+ * should not be performed.
+ */
+ @Nullable
+ private IBinder mWindowTokenClientToMirror = null;
- // Code below generated by codegen v1.0.20.
+
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -115,7 +123,8 @@ public final class VirtualDisplayConfig implements Parcelable {
int flags,
@Nullable Surface surface,
@Nullable String uniqueId,
- int displayIdToMirror) {
+ int displayIdToMirror,
+ @Nullable IBinder windowTokenClientToMirror) {
this.mName = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mName);
@@ -135,6 +144,7 @@ public final class VirtualDisplayConfig implements Parcelable {
this.mSurface = surface;
this.mUniqueId = uniqueId;
this.mDisplayIdToMirror = displayIdToMirror;
+ this.mWindowTokenClientToMirror = windowTokenClientToMirror;
// onConstructed(); // You can define this method to get a callback
}
@@ -212,6 +222,15 @@ public final class VirtualDisplayConfig implements Parcelable {
return mDisplayIdToMirror;
}
+ /**
+ * The window token of the level of the WindowManager hierarchy to mirror, or null if mirroring
+ * should not be performed.
+ */
+ @DataClass.Generated.Member
+ public @Nullable IBinder getWindowTokenClientToMirror() {
+ return mWindowTokenClientToMirror;
+ }
+
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -221,6 +240,7 @@ public final class VirtualDisplayConfig implements Parcelable {
int flg = 0;
if (mSurface != null) flg |= 0x20;
if (mUniqueId != null) flg |= 0x40;
+ if (mWindowTokenClientToMirror != null) flg |= 0x100;
dest.writeInt(flg);
dest.writeString(mName);
dest.writeInt(mWidth);
@@ -230,6 +250,7 @@ public final class VirtualDisplayConfig implements Parcelable {
if (mSurface != null) dest.writeTypedObject(mSurface, flags);
if (mUniqueId != null) dest.writeString(mUniqueId);
dest.writeInt(mDisplayIdToMirror);
+ if (mWindowTokenClientToMirror != null) dest.writeStrongBinder(mWindowTokenClientToMirror);
}
@Override
@@ -252,6 +273,7 @@ public final class VirtualDisplayConfig implements Parcelable {
Surface surface = (flg & 0x20) == 0 ? null : (Surface) in.readTypedObject(Surface.CREATOR);
String uniqueId = (flg & 0x40) == 0 ? null : in.readString();
int displayIdToMirror = in.readInt();
+ IBinder windowTokenClientToMirror = (flg & 0x100) == 0 ? null : (IBinder) in.readStrongBinder();
this.mName = name;
com.android.internal.util.AnnotationValidations.validate(
@@ -272,6 +294,7 @@ public final class VirtualDisplayConfig implements Parcelable {
this.mSurface = surface;
this.mUniqueId = uniqueId;
this.mDisplayIdToMirror = displayIdToMirror;
+ this.mWindowTokenClientToMirror = windowTokenClientToMirror;
// onConstructed(); // You can define this method to get a callback
}
@@ -305,6 +328,7 @@ public final class VirtualDisplayConfig implements Parcelable {
private @Nullable Surface mSurface;
private @Nullable String mUniqueId;
private int mDisplayIdToMirror;
+ private @Nullable IBinder mWindowTokenClientToMirror;
private long mBuilderFieldsSet = 0L;
@@ -439,10 +463,22 @@ public final class VirtualDisplayConfig implements Parcelable {
return this;
}
+ /**
+ * The window token of the level of the WindowManager hierarchy to mirror, or null if mirroring
+ * should not be performed.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setWindowTokenClientToMirror(@NonNull IBinder value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x100;
+ mWindowTokenClientToMirror = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull VirtualDisplayConfig build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x100; // Mark builder used
+ mBuilderFieldsSet |= 0x200; // Mark builder used
if ((mBuilderFieldsSet & 0x10) == 0) {
mFlags = 0;
@@ -456,6 +492,9 @@ public final class VirtualDisplayConfig implements Parcelable {
if ((mBuilderFieldsSet & 0x80) == 0) {
mDisplayIdToMirror = DEFAULT_DISPLAY;
}
+ if ((mBuilderFieldsSet & 0x100) == 0) {
+ mWindowTokenClientToMirror = null;
+ }
VirtualDisplayConfig o = new VirtualDisplayConfig(
mName,
mWidth,
@@ -464,12 +503,13 @@ public final class VirtualDisplayConfig implements Parcelable {
mFlags,
mSurface,
mUniqueId,
- mDisplayIdToMirror);
+ mDisplayIdToMirror,
+ mWindowTokenClientToMirror);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x100) != 0) {
+ if ((mBuilderFieldsSet & 0x200) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -477,10 +517,10 @@ public final class VirtualDisplayConfig implements Parcelable {
}
@DataClass.Generated(
- time = 1604456298440L,
- codegenVersion = "1.0.20",
+ time = 1620657851981L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/hardware/display/VirtualDisplayConfig.java",
- inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate int mDisplayIdToMirror\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
+ inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.IntRange int mWidth\nprivate @android.annotation.IntRange int mHeight\nprivate @android.annotation.IntRange int mDensityDpi\nprivate int mFlags\nprivate @android.annotation.Nullable android.view.Surface mSurface\nprivate @android.annotation.Nullable java.lang.String mUniqueId\nprivate int mDisplayIdToMirror\nprivate @android.annotation.Nullable android.os.IBinder mWindowTokenClientToMirror\nclass VirtualDisplayConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
index 45c6b290be11..4bf9a740f971 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
@@ -21,7 +21,9 @@ import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFP
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.hardware.biometrics.ComponentInfoInternal;
+import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.SensorProperties;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.os.Parcel;
@@ -38,34 +40,14 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
*/
public final @FingerprintSensorProperties.SensorType int sensorType;
- /**
- * The location of the center of the sensor if applicable. For example, sensors of type
- * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the
- * distance in pixels, measured from the left edge of the screen.
- */
- public final int sensorLocationX;
-
- /**
- * The location of the center of the sensor if applicable. For example, sensors of type
- * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the
- * distance in pixels, measured from the top edge of the screen.
- *
- */
- public final int sensorLocationY;
-
- /**
- * The radius of the sensor if applicable. For example, sensors of type
- * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the radius
- * of the sensor, in pixels.
- */
- public final int sensorRadius;
+ private final List<SensorLocationInternal> mSensorLocations;
public FingerprintSensorPropertiesInternal(int sensorId,
@SensorProperties.Strength int strength, int maxEnrollmentsPerUser,
@NonNull List<ComponentInfoInternal> componentInfo,
@FingerprintSensorProperties.SensorType int sensorType,
- boolean resetLockoutRequiresHardwareAuthToken, int sensorLocationX, int sensorLocationY,
- int sensorRadius) {
+ boolean resetLockoutRequiresHardwareAuthToken,
+ @NonNull List<SensorLocationInternal> sensorLocations) {
// IBiometricsFingerprint@2.1 handles lockout in the framework, so the challenge is not
// required as it can only be generated/attested/verified by TEE components.
// IFingerprint@1.0 handles lockout below the HAL, but does not require a challenge. See
@@ -73,9 +55,7 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
super(sensorId, strength, maxEnrollmentsPerUser, componentInfo,
resetLockoutRequiresHardwareAuthToken, false /* resetLockoutRequiresChallenge */);
this.sensorType = sensorType;
- this.sensorLocationX = sensorLocationX;
- this.sensorLocationY = sensorLocationY;
- this.sensorRadius = sensorRadius;
+ this.mSensorLocations = List.copyOf(sensorLocations);
}
/**
@@ -88,16 +68,15 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
boolean resetLockoutRequiresHardwareAuthToken) {
// TODO(b/179175438): Value should be provided from the HAL
this(sensorId, strength, maxEnrollmentsPerUser, componentInfo, sensorType,
- resetLockoutRequiresHardwareAuthToken, 540 /* sensorLocationX */,
- 1636 /* sensorLocationY */, 130 /* sensorRadius */);
+ resetLockoutRequiresHardwareAuthToken, List.of(new SensorLocationInternal(
+ "" /* displayId */, 540 /* sensorLocationX */, 1636 /* sensorLocationY */,
+ 130 /* sensorRadius */)));
}
protected FingerprintSensorPropertiesInternal(Parcel in) {
super(in);
sensorType = in.readInt();
- sensorLocationX = in.readInt();
- sensorLocationY = in.readInt();
- sensorRadius = in.readInt();
+ mSensorLocations = in.createTypedArrayList(SensorLocationInternal.CREATOR);
}
public static final Creator<FingerprintSensorPropertiesInternal> CREATOR =
@@ -122,9 +101,7 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(sensorType);
- dest.writeInt(sensorLocationX);
- dest.writeInt(sensorLocationY);
- dest.writeInt(sensorRadius);
+ dest.writeTypedList(mSensorLocations);
}
public boolean isAnyUdfpsType() {
@@ -150,6 +127,44 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
}
}
+ /**
+ * Get the default location.
+ *
+ * Use this method when the sensor's relationship to the displays on the device do not
+ * matter.
+ * @return
+ */
+ @NonNull
+ public SensorLocationInternal getLocation() {
+ final SensorLocationInternal location = getLocation("" /* displayId */);
+ return location != null ? location : SensorLocationInternal.DEFAULT;
+ }
+
+ /**
+ * Get the location of a sensor relative to a physical display layout.
+ *
+ * @param displayId stable display id
+ * @return location or null if none is specified
+ */
+ @Nullable
+ public SensorLocationInternal getLocation(String displayId) {
+ for (SensorLocationInternal location : mSensorLocations) {
+ if (location.displayId.equals(displayId)) {
+ return location;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets all locations relative to all supported display layouts.
+ * @return supported locations
+ */
+ @NonNull
+ public List<SensorLocationInternal> getAllLocations() {
+ return mSensorLocations;
+ }
+
@Override
public String toString() {
return "ID: " + sensorId + ", Strength: " + sensorStrength + ", Type: " + sensorType;
diff --git a/core/java/android/hardware/fingerprint/ISidefpsController.aidl b/core/java/android/hardware/fingerprint/ISidefpsController.aidl
index 00f40048cbae..684f18f3fd94 100644
--- a/core/java/android/hardware/fingerprint/ISidefpsController.aidl
+++ b/core/java/android/hardware/fingerprint/ISidefpsController.aidl
@@ -21,9 +21,9 @@ package android.hardware.fingerprint;
*/
oneway interface ISidefpsController {
- // Shows the overlay.
- void show();
+ // Shows the overlay for the given sensor with a reason from BiometricOverlayConstants.
+ void show(int sensorId, int reason);
// Hides the overlay.
- void hide();
+ void hide(int sensorId);
}
diff --git a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
index f18360ff4108..648edda62171 100644
--- a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
+++ b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
@@ -22,14 +22,7 @@ import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
* @hide
*/
oneway interface IUdfpsOverlayController {
- const int REASON_UNKNOWN = 0;
- const int REASON_ENROLL_FIND_SENSOR = 1;
- const int REASON_ENROLL_ENROLLING = 2;
- const int REASON_AUTH_BP = 3; // BiometricPrompt
- const int REASON_AUTH_FPM_KEYGUARD = 4; // FingerprintManager usage from Keyguard
- const int REASON_AUTH_FPM_OTHER = 5; // Other FingerprintManager usage
-
- // Shows the overlay.
+ // Shows the overlay for the given sensor with a reason from BiometricOverlayConstants.
void showUdfpsOverlay(int sensorId, int reason, IUdfpsOverlayControllerCallback callback);
// Hides the overlay.
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index 3cd13a212a4b..f8f0970ecfe4 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -18,16 +18,23 @@ package android.inputmethodservice;
import android.annotation.MainThread;
import android.annotation.NonNull;
-import android.app.Service;
+import android.annotation.Nullable;
+import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteException;
import android.util.proto.ProtoOutputStream;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodSession;
+import android.window.WindowProviderService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -44,9 +51,22 @@ import java.io.PrintWriter;
* implement. This base class takes care of reporting your InputMethod from
* the service when clients bind to it, but provides no standard implementation
* of the InputMethod interface itself. Derived classes must implement that
- * interface.
+ * interface.</p>
+ *
+ * <p>After {@link android.os.Build.VERSION_CODES#S}, the maximum possible area to show the soft
+ * input may not be the entire screen. For example, some devices may support to show the soft input
+ * on only half of screen.</p>
+ *
+ * <p>In that case, moving the soft input from one half screen to another will trigger a
+ * {@link android.content.res.Resources} update to match the new {@link Configuration} and
+ * this {@link AbstractInputMethodService} may also receive a
+ * {@link #onConfigurationChanged(Configuration)} callback if there's notable configuration changes
+ * </p>
+ *
+ * @see android.content.ComponentCallbacks#onConfigurationChanged(Configuration)
+ * @see Context#isUiContext Context#isUiContext to see the concept of UI Context.
*/
-public abstract class AbstractInputMethodService extends Service
+public abstract class AbstractInputMethodService extends WindowProviderService
implements KeyEvent.Callback {
private InputMethod mInputMethod;
@@ -272,9 +292,33 @@ public abstract class AbstractInputMethodService extends Service
public void notifyUserActionIfNecessary() {
}
+ // TODO(b/149463653): remove it in T. We missed the API deadline in S.
/** @hide */
@Override
public final boolean isUiContext() {
return true;
}
+
+ /** @hide */
+ @Override
+ public final int getWindowType() {
+ return WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+ }
+
+ /** @hide */
+ @Override
+ @Nullable
+ public final Bundle getWindowContextOptions() {
+ return super.getWindowContextOptions();
+ }
+
+ /** @hide */
+ @Override
+ public final int getInitialDisplayId() {
+ try {
+ return WindowManagerGlobal.getWindowManagerService().getImeDisplayId();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 9198eb74d1f8..89612fe753df 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -170,8 +170,8 @@ class IInputMethodWrapper extends IInputMethod.Stub
case DO_INITIALIZE_INTERNAL: {
SomeArgs args = (SomeArgs) msg.obj;
try {
- inputMethod.initializeInternal((IBinder) args.arg1, msg.arg1,
- (IInputMethodPrivilegedOperations) args.arg2, (int) args.arg3);
+ inputMethod.initializeInternal((IBinder) args.arg1,
+ (IInputMethodPrivilegedOperations) args.arg2, msg.arg1);
} finally {
args.recycle();
}
@@ -279,11 +279,10 @@ class IInputMethodWrapper extends IInputMethod.Stub
@BinderThread
@Override
- public void initializeInternal(IBinder token, int displayId,
- IInputMethodPrivilegedOperations privOps, int configChanges) {
+ public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
+ int configChanges) {
mCaller.executeOrSendMessage(
- mCaller.obtainMessageIOOO(DO_INITIALIZE_INTERNAL, displayId, token, privOps,
- configChanges));
+ mCaller.obtainMessageIOO(DO_INITIALIZE_INTERNAL, configChanges, token, privOps));
}
@BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 74cb42db7858..9721279bcd24 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -589,7 +589,7 @@ public class InputMethodService extends AbstractInputMethodService {
*/
@MainThread
@Override
- public final void initializeInternal(@NonNull IBinder token, int displayId,
+ public final void initializeInternal(@NonNull IBinder token,
IInputMethodPrivilegedOperations privilegedOperations, int configChanges) {
if (InputMethodPrivilegedOperationsRegistry.isRegistered(token)) {
Log.w(TAG, "The token has already registered, ignore this initialization.");
@@ -599,7 +599,6 @@ public class InputMethodService extends AbstractInputMethodService {
mConfigTracker.onInitialize(configChanges);
mPrivOps.set(privilegedOperations);
InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps);
- updateInputMethodDisplay(displayId);
attachToken(token);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@@ -629,29 +628,13 @@ public class InputMethodService extends AbstractInputMethodService {
throw new IllegalStateException(
"attachToken() must be called at most once. token=" + token);
}
+ attachToWindowToken(token);
mToken = token;
mWindow.setToken(token);
}
/**
* {@inheritDoc}
- * @hide
- */
- @MainThread
- @Override
- public void updateInputMethodDisplay(int displayId) {
- if (getDisplayId() == displayId) {
- return;
- }
- // Update display for adding IME window to the right display.
- // TODO(b/111364446) Need to address context lifecycle issue if need to re-create
- // for update resources & configuration correctly when show soft input
- // in non-default display.
- updateDisplay(displayId);
- }
-
- /**
- * {@inheritDoc}
*
* <p>Calls {@link InputMethodService#onBindInput()} when done.</p>
*/
@@ -807,11 +790,6 @@ public class InputMethodService extends AbstractInputMethodService {
return;
}
- if (Trace.isEnabled()) {
- Binder.enableTracing();
- } else {
- Binder.disableTracing();
- }
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showSoftInput");
ImeTracing.getInstance().triggerServiceDump(
"InputMethodService.InputMethodImpl#showSoftInput", InputMethodService.this,
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 249154aa9129..fee2e2b27457 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -47,7 +47,6 @@ import android.text.TextUtils;
import android.util.BackupUtils;
import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.net.module.util.NetworkIdentityUtils;
@@ -151,24 +150,6 @@ public class NetworkTemplate implements Parcelable {
}
}
- private static boolean sForceAllNetworkTypes = false;
-
- /**
- * Results in matching against all mobile network types.
- *
- * <p>See {@link #matchesMobile} and {@link matchesMobileWildcard}.
- */
- @VisibleForTesting
- public static void forceAllNetworkTypes() {
- sForceAllNetworkTypes = true;
- }
-
- /** Resets the affect of {@link #forceAllNetworkTypes}. */
- @VisibleForTesting
- public static void resetForceAllNetworkTypes() {
- sForceAllNetworkTypes = false;
- }
-
/**
* Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
* the given IMSI.
@@ -179,19 +160,19 @@ public class NetworkTemplate implements Parcelable {
}
/**
- * Template to match cellular networks with the given IMSI and {@code ratType}.
- * Use {@link #NETWORK_TYPE_ALL} to include all network types when filtering.
- * See {@code TelephonyManager.NETWORK_TYPE_*}.
+ * Template to match cellular networks with the given IMSI, {@code ratType} and
+ * {@code metered}. Use {@link #NETWORK_TYPE_ALL} to include all network types when
+ * filtering. See {@code TelephonyManager.NETWORK_TYPE_*}.
*/
public static NetworkTemplate buildTemplateMobileWithRatType(@Nullable String subscriberId,
- @NetworkType int ratType) {
+ @NetworkType int ratType, int metered) {
if (TextUtils.isEmpty(subscriberId)) {
return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null, null, null,
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
+ metered, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
SUBSCRIBER_ID_MATCH_RULE_EXACT);
}
return new NetworkTemplate(MATCH_MOBILE, subscriberId, new String[]{subscriberId}, null,
- METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
+ metered, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL,
SUBSCRIBER_ID_MATCH_RULE_EXACT);
}
@@ -324,6 +305,7 @@ public class NetworkTemplate implements Parcelable {
}
}
+ // TODO: Deprecate this constructor, mark it @UnsupportedAppUsage(maxTargetSdk = S)
@UnsupportedAppUsage
public NetworkTemplate(int matchRule, String subscriberId, String networkId) {
this(matchRule, subscriberId, new String[] { subscriberId }, networkId);
@@ -331,9 +313,14 @@ public class NetworkTemplate implements Parcelable {
public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
String networkId) {
- this(matchRule, subscriberId, matchSubscriberIds, networkId, METERED_ALL, ROAMING_ALL,
- DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
- SUBSCRIBER_ID_MATCH_RULE_EXACT);
+ // Older versions used to only match MATCH_MOBILE and MATCH_MOBILE_WILDCARD templates
+ // to metered networks. It is now possible to match mobile with any meteredness, but
+ // in order to preserve backward compatibility of @UnsupportedAppUsage methods, this
+ //constructor passes METERED_YES for these types.
+ this(matchRule, subscriberId, matchSubscriberIds, networkId,
+ (matchRule == MATCH_MOBILE || matchRule == MATCH_MOBILE_WILDCARD) ? METERED_YES
+ : METERED_ALL , ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+ OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT);
}
// TODO: Remove it after updating all of the caller.
@@ -608,11 +595,7 @@ public class NetworkTemplate implements Parcelable {
// TODO: consider matching against WiMAX subscriber identity
return true;
} else {
- // Only metered mobile network would be matched regardless of metered filter.
- // This is used to exclude non-metered APNs, e.g. IMS. See ag/908650.
- // TODO: Respect metered filter and remove mMetered condition.
- return (sForceAllNetworkTypes || (ident.mType == TYPE_MOBILE && ident.mMetered))
- && !ArrayUtils.isEmpty(mMatchSubscriberIds)
+ return ident.mType == TYPE_MOBILE && !ArrayUtils.isEmpty(mMatchSubscriberIds)
&& ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId)
&& matchesCollapsedRatType(ident);
}
@@ -726,8 +709,7 @@ public class NetworkTemplate implements Parcelable {
if (ident.mType == TYPE_WIMAX) {
return true;
} else {
- return (sForceAllNetworkTypes || (ident.mType == TYPE_MOBILE && ident.mMetered))
- && matchesCollapsedRatType(ident);
+ return ident.mType == TYPE_MOBILE && matchesCollapsedRatType(ident);
}
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index fb9911811da9..79ec55482c50 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2303,6 +2303,38 @@ public abstract class BatteryStats implements Parcelable {
public abstract Timer getScreenBrightnessTimer(int brightnessBin);
/**
+ * Returns the number of physical displays on the device.
+ *
+ * {@hide}
+ */
+ public abstract int getDisplayCount();
+
+ /**
+ * Returns the time in microseconds that the screen has been on for a display while the
+ * device was running on battery.
+ *
+ * {@hide}
+ */
+ public abstract long getDisplayScreenOnTime(int display, long elapsedRealtimeUs);
+
+ /**
+ * Returns the time in microseconds that a display has been dozing while the device was
+ * running on battery.
+ *
+ * {@hide}
+ */
+ public abstract long getDisplayScreenDozeTime(int display, long elapsedRealtimeUs);
+
+ /**
+ * Returns the time in microseconds that a display has been on with the given brightness
+ * level while the device was running on battery.
+ *
+ * {@hide}
+ */
+ public abstract long getDisplayScreenBrightnessTime(int display, int brightnessBin,
+ long elapsedRealtimeUs);
+
+ /**
* Returns the time in microseconds that power save mode has been enabled while the device was
* running on battery.
*
@@ -5038,6 +5070,71 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
+ final int numDisplays = getDisplayCount();
+ if (numDisplays > 1) {
+ pw.println("");
+ pw.print(prefix);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" MULTI-DISPLAY POWER SUMMARY START");
+ pw.println(sb.toString());
+
+ for (int display = 0; display < numDisplays; display++) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Display ");
+ sb.append(display);
+ sb.append(" Statistics:");
+ pw.println(sb.toString());
+
+ final long displayScreenOnTime = getDisplayScreenOnTime(display, rawRealtime);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Screen on: ");
+ formatTimeMs(sb, displayScreenOnTime / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(displayScreenOnTime, whichBatteryRealtime));
+ sb.append(") ");
+ pw.println(sb.toString());
+
+ sb.setLength(0);
+ sb.append(" Screen brightness levels:");
+ didOne = false;
+ for (int bin = 0; bin < NUM_SCREEN_BRIGHTNESS_BINS; bin++) {
+ final long timeUs = getDisplayScreenBrightnessTime(display, bin, rawRealtime);
+ if (timeUs == 0) {
+ continue;
+ }
+ didOne = true;
+ sb.append("\n ");
+ sb.append(prefix);
+ sb.append(SCREEN_BRIGHTNESS_NAMES[bin]);
+ sb.append(" ");
+ formatTimeMs(sb, timeUs / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(timeUs, displayScreenOnTime));
+ sb.append(")");
+ }
+ if (!didOne) sb.append(" (no activity)");
+ pw.println(sb.toString());
+
+ final long displayScreenDozeTimeUs = getDisplayScreenDozeTime(display, rawRealtime);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Screen Doze: ");
+ formatTimeMs(sb, displayScreenDozeTimeUs / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(displayScreenDozeTimeUs, whichBatteryRealtime));
+ sb.append(") ");
+ pw.println(sb.toString());
+ }
+ pw.print(prefix);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" MULTI-DISPLAY POWER SUMMARY END");
+ pw.println(sb.toString());
+ }
+
pw.println("");
pw.print(prefix);
sb.setLength(0);
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 6bf394dc347b..b7dd36e2c4f5 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1024,7 +1024,7 @@ public class Build {
* will also enable {@link StrictMode.ThreadPolicy.Builder#detectUnbufferedIo}.</li>
* <li>{@link android.provider.DocumentsContract}'s various methods will throw failure
* exceptions back to the caller instead of returning null.
- * <li>{@link View#hasFocusable View.hasFocusable} now includes auto-focusable views.</li>
+ * <li>{@link View#hasFocusable() View.hasFocusable} now includes auto-focusable views.</li>
* <li>{@link android.view.SurfaceView} will no longer always change the underlying
* Surface object when something about it changes; apps need to look at the current
* state of the object to determine which things they are interested in have changed.</li>
@@ -1130,6 +1130,15 @@ public class Build {
* S.
*/
public static final int S = 31;
+
+ /**
+ * S V2.
+ *
+ * Once more unto the breach, dear friends, once more.
+ *
+ */
+ public static final int S_V2 = 32;
+
}
/** The type of build, like "user" or "eng". */
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 2ed0bad69460..0257408b3e42 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -189,13 +189,11 @@ public class Environment {
}
@UnsupportedAppUsage
- @Deprecated
public File getExternalStorageDirectory() {
return getExternalDirs()[0];
}
@UnsupportedAppUsage
- @Deprecated
public File getExternalStoragePublicDirectory(String type) {
return buildExternalStoragePublicDirs(type)[0];
}
@@ -695,14 +693,13 @@ public class Environment {
* <p>
* {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
* monitor_storage}
+ * <p>
+ * Note that alternatives such as {@link Context#getExternalFilesDir(String)} or
+ * {@link MediaStore} offer better performance.
*
* @see #getExternalStorageState()
* @see #isExternalStorageRemovable()
- * @deprecated Alternatives such as {@link Context#getExternalFilesDir(String)},
- * {@link MediaStore}, or {@link Intent#ACTION_OPEN_DOCUMENT} offer better
- * performance.
*/
- @Deprecated
public static File getExternalStorageDirectory() {
throwIfUserRequired();
return sCurrentUser.getExternalDirs()[0];
@@ -999,6 +996,9 @@ public class Environment {
* </p>
* {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
* public_picture}
+ * <p>
+ * Note that alternatives such as {@link Context#getExternalFilesDir(String)} or
+ * {@link MediaStore} offer better performance.
*
* @param type The type of storage directory to return. Should be one of
* {@link #DIRECTORY_MUSIC}, {@link #DIRECTORY_PODCASTS},
@@ -1009,11 +1009,7 @@ public class Environment {
* @return Returns the File path for the directory. Note that this directory
* may not yet exist, so you must make sure it exists before using
* it such as with {@link File#mkdirs File.mkdirs()}.
- * @deprecated Alternatives such as {@link Context#getExternalFilesDir(String)},
- * {@link MediaStore}, or {@link Intent#ACTION_OPEN_DOCUMENT} offer better
- * performance.
*/
- @Deprecated
public static File getExternalStoragePublicDirectory(String type) {
throwIfUserRequired();
return sCurrentUser.buildExternalStoragePublicDirs(type)[0];
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 3aa0bcb6abee..4dae7c7c96d5 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -548,6 +548,7 @@ public final class PowerManager {
WAKE_REASON_HDMI,
WAKE_REASON_DISPLAY_GROUP_ADDED,
WAKE_REASON_DISPLAY_GROUP_TURNED_ON,
+ WAKE_REASON_UNFOLD_DEVICE
})
@Retention(RetentionPolicy.SOURCE)
public @interface WakeReason{}
@@ -647,6 +648,12 @@ public final class PowerManager {
public static final int WAKE_REASON_DISPLAY_GROUP_TURNED_ON = 11;
/**
+ * Wake up reason code: Waking the device due to unfolding of a foldable device.
+ * @hide
+ */
+ public static final int WAKE_REASON_UNFOLD_DEVICE = 12;
+
+ /**
* Convert the wake reason to a string for debugging purposes.
* @hide
*/
@@ -664,6 +671,7 @@ public final class PowerManager {
case WAKE_REASON_LID: return "WAKE_REASON_LID";
case WAKE_REASON_DISPLAY_GROUP_ADDED: return "WAKE_REASON_DISPLAY_GROUP_ADDED";
case WAKE_REASON_DISPLAY_GROUP_TURNED_ON: return "WAKE_REASON_DISPLAY_GROUP_TURNED_ON";
+ case WAKE_REASON_UNFOLD_DEVICE: return "WAKE_REASON_UNFOLD_DEVICE";
default: return Integer.toString(wakeReason);
}
}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 217e17ddd7b4..e58419fb688d 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -604,6 +604,14 @@ public final class DeviceConfig {
@TestApi
public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
+ /**
+ * Namespace for App Compat Overrides related features.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 00abc759278f..1b6a03c7a736 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -28,6 +28,7 @@ import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
+import android.app.Activity;
import android.app.ActivityThread;
import android.app.AppOpsManager;
import android.app.Application;
@@ -8979,6 +8980,15 @@ public final class Settings {
public static final String UNSAFE_VOLUME_MUSIC_ACTIVE_MS = "unsafe_volume_music_active_ms";
/**
+ * Indicates whether the spatial audio feature was enabled for this user.
+ *
+ * Type : int (0 disabled, 1 enabled)
+ *
+ * @hide
+ */
+ public static final String SPATIAL_AUDIO_ENABLED = "spatial_audio_enabled";
+
+ /**
* Indicates whether notification display on the lock screen is enabled.
* <p>
* Type: int (0 for false, 1 for true)
@@ -9979,15 +9989,18 @@ public final class Settings {
/**
* Controls the accessibility button mode. System will force-set the value to {@link
- * #ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU} if {@link #NAVIGATION_MODE} is fully
- * gestural.
+ * #ACCESSIBILITY_BUTTON_MODE_GESTURE} if {@link #NAVIGATION_MODE} is button; force-set the
+ * value to {@link ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR} if {@link #NAVIGATION_MODE} is
+ * gestural; otherwise, remain the option.
* <ul>
* <li> 0 = button in navigation bar </li>
* <li> 1 = button floating on the display </li>
+ * <li> 2 = button using gesture to trigger </li>
* </ul>
*
* @see #ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR
* @see #ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU
+ * @see #ACCESSIBILITY_BUTTON_MODE_GESTURE
* @hide
*/
public static final String ACCESSIBILITY_BUTTON_MODE =
@@ -10010,6 +10023,14 @@ public final class Settings {
public static final int ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU = 0x1;
/**
+ * Accessibility button mode value that specifying the accessibility service or feature to
+ * be toggled via the gesture.
+ *
+ * @hide
+ */
+ public static final int ACCESSIBILITY_BUTTON_MODE_GESTURE = 0x2;
+
+ /**
* The size of the accessibility floating menu.
* <ul>
* <li> 0 = small size
@@ -10123,6 +10144,61 @@ public final class Settings {
@Readable
public static final String GAME_DASHBOARD_ALWAYS_ON = "game_dashboard_always_on";
+
+ /**
+ * For this device state, no specific auto-rotation lock setting should be applied.
+ * If the user toggles the auto-rotate lock in this state, the setting will apply to the
+ * previously valid device state.
+ * @hide
+ */
+ public static final int DEVICE_STATE_ROTATION_LOCK_IGNORED = 0;
+ /**
+ * For this device state, the setting for auto-rotation is locked.
+ * @hide
+ */
+ public static final int DEVICE_STATE_ROTATION_LOCK_LOCKED = 1;
+ /**
+ * For this device state, the setting for auto-rotation is unlocked.
+ * @hide
+ */
+ public static final int DEVICE_STATE_ROTATION_LOCK_UNLOCKED = 2;
+
+ /**
+ * The different settings that can be used as values with
+ * {@link #DEVICE_STATE_ROTATION_LOCK}.
+ * @hide
+ */
+ @IntDef(prefix = {"DEVICE_STATE_ROTATION_LOCK_"}, value = {
+ DEVICE_STATE_ROTATION_LOCK_IGNORED,
+ DEVICE_STATE_ROTATION_LOCK_LOCKED,
+ DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface DeviceStateRotationLockSetting {
+ }
+
+ /**
+ * Rotation lock setting keyed on device state.
+ *
+ * This holds a serialized map using int keys that represent Device States and value of
+ * {@link DeviceStateRotationLockSetting} representing the rotation lock setting for that
+ * device state.
+ *
+ * Serialized as key0:value0:key1:value1:...:keyN:valueN.
+ *
+ * Example: "0:1:1:2:2:1"
+ * This example represents a map of:
+ * <ul>
+ * <li>0 -> DEVICE_STATE_ROTATION_LOCK_LOCKED</li>
+ * <li>1 -> DEVICE_STATE_ROTATION_LOCK_UNLOCKED</li>
+ * <li>2 -> DEVICE_STATE_ROTATION_LOCK_IGNORED</li>
+ * </ul>
+ *
+ * @hide
+ */
+ public static final String DEVICE_STATE_ROTATION_LOCK =
+ "device_state_rotation_lock";
+
/**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
@@ -16890,6 +16966,44 @@ public final class Settings {
"android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
/**
+ * Activity Action: For system or preinstalled apps to show their {@link Activity} embedded
+ * in Settings app on large screen devices.
+ * <p>
+ * Input: {@link #EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI} must be included to
+ * specify the intent for the activity which will be embedded in Settings app.
+ * It's an intent URI string from {@code intent.toUri(Intent.URI_INTENT_SCHEME)}.
+ *
+ * Input: {@link #EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY} must be included to
+ * specify a key that indicates the menu item which will be highlighted on settings home menu.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY =
+ "android.settings.SETTINGS_EMBED_DEEP_LINK_ACTIVITY";
+
+ /**
+ * Activity Extra: Specify the intent for the {@link Activity} which will be embedded in
+ * Settings app. It's an intent URI string from
+ * {@code intent.toUri(Intent.URI_INTENT_SCHEME)}.
+ * <p>
+ * This must be passed as an extra field to
+ * {@link #ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY}.
+ */
+ public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI =
+ "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI";
+
+ /**
+ * Activity Extra: Specify a key that indicates the menu item which should be highlighted on
+ * settings home menu.
+ * <p>
+ * This must be passed as an extra field to
+ * {@link #ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY}.
+ */
+ public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY =
+ "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY";
+
+ /**
* Performs a strict and comprehensive check of whether a calling package is allowed to
* write/modify system settings, as the condition differs for pre-M, M+, and
* privileged/preinstalled apps. If the provided uid does not match the
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index f3a8b5d79ea0..9f3a847e12eb 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -5365,5 +5365,14 @@ public final class Telephony {
*/
public static final String COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS =
"d2d_sharing_contacts";
+
+ /**
+ * TelephonyProvider column name for NR Advanced calling
+ * Determines if the user has enabled VoNR settings for this subscription.
+ *
+ * @hide
+ */
+ public static final String COLUMN_NR_ADVANCED_CALLING_ENABLED =
+ "nr_advanced_calling_enabled";
}
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 71f90fd28e3f..c94595468aec 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -83,11 +83,11 @@ import java.util.Objects;
* &lt;/intent-filter>
* &lt;meta-data
* android:name="android.service.notification.default_filter_types"
- * android:value="conversations,alerting">
+ * android:value="conversations|alerting">
* &lt;/meta-data>
* &lt;meta-data
* android:name="android.service.notification.disabled_filter_types"
- * android:value="ongoing,silent">
+ * android:value="ongoing|silent">
* &lt;/meta-data>
* &lt;/service></pre>
*
@@ -112,8 +112,9 @@ public abstract class NotificationListenerService extends Service {
private final String TAG = getClass().getSimpleName();
/**
- * The name of the {@code meta-data} tag containing a comma separated list of default
- * integer notification types that should be provided to this listener. See
+ * The name of the {@code meta-data} tag containing a pipe separated list of default
+ * integer notification types or "ongoing", "conversations", "alerting", or "silent"
+ * that should be provided to this listener. See
* {@link #FLAG_FILTER_TYPE_ONGOING},
* {@link #FLAG_FILTER_TYPE_CONVERSATIONS}, {@link #FLAG_FILTER_TYPE_ALERTING),
* and {@link #FLAG_FILTER_TYPE_SILENT}.
@@ -1698,7 +1699,7 @@ public abstract class NotificationListenerService extends Service {
private ArrayList<Notification.Action> mSmartActions;
private ArrayList<CharSequence> mSmartReplies;
private boolean mCanBubble;
- private boolean mVisuallyInterruptive;
+ private boolean mIsTextChanged;
private boolean mIsConversation;
private ShortcutInfo mShortcutInfo;
private @RankingAdjustment int mRankingAdjustment;
@@ -1735,7 +1736,7 @@ public abstract class NotificationListenerService extends Service {
out.writeTypedList(mSmartActions, flags);
out.writeCharSequenceList(mSmartReplies);
out.writeBoolean(mCanBubble);
- out.writeBoolean(mVisuallyInterruptive);
+ out.writeBoolean(mIsTextChanged);
out.writeBoolean(mIsConversation);
out.writeParcelable(mShortcutInfo, flags);
out.writeInt(mRankingAdjustment);
@@ -1773,7 +1774,7 @@ public abstract class NotificationListenerService extends Service {
mSmartActions = in.createTypedArrayList(Notification.Action.CREATOR);
mSmartReplies = in.readCharSequenceList();
mCanBubble = in.readBoolean();
- mVisuallyInterruptive = in.readBoolean();
+ mIsTextChanged = in.readBoolean();
mIsConversation = in.readBoolean();
mShortcutInfo = in.readParcelable(cl);
mRankingAdjustment = in.readInt();
@@ -1976,8 +1977,8 @@ public abstract class NotificationListenerService extends Service {
}
/** @hide */
- public boolean visuallyInterruptive() {
- return mVisuallyInterruptive;
+ public boolean isTextChanged() {
+ return mIsTextChanged;
}
/** @hide */
@@ -2032,7 +2033,7 @@ public abstract class NotificationListenerService extends Service {
int userSentiment, boolean hidden, long lastAudiblyAlertedMs,
boolean noisy, ArrayList<Notification.Action> smartActions,
ArrayList<CharSequence> smartReplies, boolean canBubble,
- boolean visuallyInterruptive, boolean isConversation, ShortcutInfo shortcutInfo,
+ boolean isTextChanged, boolean isConversation, ShortcutInfo shortcutInfo,
int rankingAdjustment, boolean isBubble) {
mKey = key;
mRank = rank;
@@ -2054,7 +2055,7 @@ public abstract class NotificationListenerService extends Service {
mSmartActions = smartActions;
mSmartReplies = smartReplies;
mCanBubble = canBubble;
- mVisuallyInterruptive = visuallyInterruptive;
+ mIsTextChanged = isTextChanged;
mIsConversation = isConversation;
mShortcutInfo = shortcutInfo;
mRankingAdjustment = rankingAdjustment;
@@ -2095,7 +2096,7 @@ public abstract class NotificationListenerService extends Service {
other.mSmartActions,
other.mSmartReplies,
other.mCanBubble,
- other.mVisuallyInterruptive,
+ other.mIsTextChanged,
other.mIsConversation,
other.mShortcutInfo,
other.mRankingAdjustment,
@@ -2152,7 +2153,7 @@ public abstract class NotificationListenerService extends Service {
== (other.mSmartActions == null ? 0 : other.mSmartActions.size()))
&& Objects.equals(mSmartReplies, other.mSmartReplies)
&& Objects.equals(mCanBubble, other.mCanBubble)
- && Objects.equals(mVisuallyInterruptive, other.mVisuallyInterruptive)
+ && Objects.equals(mIsTextChanged, other.mIsTextChanged)
&& Objects.equals(mIsConversation, other.mIsConversation)
// Shortcutinfo doesn't have equals either; use id
&& Objects.equals((mShortcutInfo == null ? 0 : mShortcutInfo.getId()),
diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl
index c142a53e047e..59f1e8eed89c 100644
--- a/core/java/android/service/voice/IVoiceInteractionSession.aidl
+++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl
@@ -22,6 +22,7 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.IBinder;
+import android.service.voice.VisibleActivityInfo;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
@@ -39,4 +40,5 @@ oneway interface IVoiceInteractionSession {
void closeSystemDialogs();
void onLockscreenShown();
void destroy();
+ void updateVisibleActivityInfo(in VisibleActivityInfo visibleActivityInfo, int type);
}
diff --git a/core/java/android/service/voice/VisibleActivityInfo.aidl b/core/java/android/service/voice/VisibleActivityInfo.aidl
new file mode 100644
index 000000000000..34bd57c15456
--- /dev/null
+++ b/core/java/android/service/voice/VisibleActivityInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.service.voice;
+
+parcelable VisibleActivityInfo;
diff --git a/core/java/android/service/voice/VisibleActivityInfo.java b/core/java/android/service/voice/VisibleActivityInfo.java
new file mode 100644
index 000000000000..139544c76a50
--- /dev/null
+++ b/core/java/android/service/voice/VisibleActivityInfo.java
@@ -0,0 +1,205 @@
+/*
+ * 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 android.service.voice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * The class is used to represent a visible activity information. The system provides this to
+ * services that need to know {@link android.service.voice.VoiceInteractionSession.ActivityId}.
+ */
+@DataClass(
+ genConstructor = false,
+ genEqualsHashCode = true,
+ genHiddenConstDefs = false,
+ genGetters = false,
+ genToString = true
+)
+public final class VisibleActivityInfo implements Parcelable {
+
+ /**
+ * Indicates that it is a new visible activity.
+ *
+ * @hide
+ */
+ public static final int TYPE_ACTIVITY_ADDED = 1;
+
+ /**
+ * Indicates that it has become a invisible activity.
+ *
+ * @hide
+ */
+ public static final int TYPE_ACTIVITY_REMOVED = 2;
+
+ /**
+ * The identifier of the task this activity is in.
+ */
+ private final int mTaskId;
+
+ /**
+ * Token for targeting this activity for assist purposes.
+ */
+ @NonNull
+ private final IBinder mAssistToken;
+
+ /** @hide */
+ @TestApi
+ public VisibleActivityInfo(
+ int taskId,
+ @NonNull IBinder assistToken) {
+ Objects.requireNonNull(assistToken);
+ mTaskId = taskId;
+ mAssistToken = assistToken;
+ }
+
+ /**
+ * Returns the {@link android.service.voice.VoiceInteractionSession.ActivityId} of this
+ * visible activity which can be used to interact with an activity, for example through
+ * {@link VoiceInteractionSession#requestDirectActions(VoiceInteractionSession.ActivityId,
+ * CancellationSignal, Executor, Consumer)}.
+ */
+ public @NonNull VoiceInteractionSession.ActivityId getActivityId() {
+ return new VoiceInteractionSession.ActivityId(mTaskId, mAssistToken);
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/voice/VisibleActivityInfo.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "VisibleActivityInfo { " +
+ "taskId = " + mTaskId + ", " +
+ "assistToken = " + mAssistToken +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(VisibleActivityInfo other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ VisibleActivityInfo that = (VisibleActivityInfo) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mTaskId == that.mTaskId
+ && Objects.equals(mAssistToken, that.mAssistToken);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + mTaskId;
+ _hash = 31 * _hash + Objects.hashCode(mAssistToken);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeInt(mTaskId);
+ dest.writeStrongBinder(mAssistToken);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ VisibleActivityInfo(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int taskId = in.readInt();
+ IBinder assistToken = (IBinder) in.readStrongBinder();
+
+ this.mTaskId = taskId;
+ this.mAssistToken = assistToken;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAssistToken);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<VisibleActivityInfo> CREATOR
+ = new Parcelable.Creator<VisibleActivityInfo>() {
+ @Override
+ public VisibleActivityInfo[] newArray(int size) {
+ return new VisibleActivityInfo[size];
+ }
+
+ @Override
+ public VisibleActivityInfo createFromParcel(@NonNull Parcel in) {
+ return new VisibleActivityInfo(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1632383555284L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/service/voice/VisibleActivityInfo.java",
+ inputSignatures = "public static final int TYPE_ACTIVITY_ADDED\npublic static final int TYPE_ACTIVITY_REMOVED\nprivate final int mTaskId\nprivate final @android.annotation.NonNull android.os.IBinder mAssistToken\npublic @android.annotation.NonNull android.service.voice.VoiceInteractionSession.ActivityId getActivityId()\nclass VisibleActivityInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genEqualsHashCode=true, genHiddenConstDefs=false, genGetters=false, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/service/voice/VoiceInteractionManagerInternal.java b/core/java/android/service/voice/VoiceInteractionManagerInternal.java
index c048286545c3..c80640910bc2 100644
--- a/core/java/android/service/voice/VoiceInteractionManagerInternal.java
+++ b/core/java/android/service/voice/VoiceInteractionManagerInternal.java
@@ -22,7 +22,6 @@ import android.os.IBinder;
import com.android.internal.annotations.Immutable;
-
/**
* @hide
* Private interface to the VoiceInteractionManagerService for use by ActivityManagerService.
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 725e20f2a74d..4d0fc1642874 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -74,9 +74,11 @@ import com.android.internal.util.function.pooled.PooledLambda;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -177,6 +179,10 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
ICancellationSignal mKillCallback;
+ private final Map<VisibleActivityCallback, Executor> mVisibleActivityCallbacks =
+ new ArrayMap<>();
+ private final List<VisibleActivityInfo> mVisibleActivityInfos = new ArrayList<>();
+
final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() {
@Override
public IVoiceInteractorRequest startConfirmation(String callingPackage,
@@ -352,6 +358,13 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
public void destroy() {
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_DESTROY));
}
+
+ @Override
+ public void updateVisibleActivityInfo(VisibleActivityInfo visibleActivityInfo, int type) {
+ mHandlerCaller.sendMessage(
+ mHandlerCaller.obtainMessageIO(MSG_UPDATE_VISIBLE_ACTIVITY_INFO, type,
+ visibleActivityInfo));
+ }
};
/**
@@ -843,6 +856,9 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
static final int MSG_SHOW = 106;
static final int MSG_HIDE = 107;
static final int MSG_ON_LOCKSCREEN_SHOWN = 108;
+ static final int MSG_UPDATE_VISIBLE_ACTIVITY_INFO = 109;
+ static final int MSG_REGISTER_VISIBLE_ACTIVITY_CALLBACK = 110;
+ static final int MSG_UNREGISTER_VISIBLE_ACTIVITY_CALLBACK = 111;
class MyCallbacks implements HandlerCaller.Callback, SoftInputWindow.Callback {
@Override
@@ -928,6 +944,27 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
if (DEBUG) Log.d(TAG, "onLockscreenShown");
onLockscreenShown();
break;
+ case MSG_UPDATE_VISIBLE_ACTIVITY_INFO:
+ if (DEBUG) {
+ Log.d(TAG, "doUpdateVisibleActivityInfo: visibleActivityInfo=" + msg.obj
+ + " type=" + msg.arg1);
+ }
+ doUpdateVisibleActivityInfo((VisibleActivityInfo) msg.obj, msg.arg1);
+ break;
+ case MSG_REGISTER_VISIBLE_ACTIVITY_CALLBACK:
+ if (DEBUG) {
+ Log.d(TAG, "doRegisterVisibleActivityCallback");
+ }
+ args = (SomeArgs) msg.obj;
+ doRegisterVisibleActivityCallback((Executor) args.arg1,
+ (VisibleActivityCallback) args.arg2);
+ break;
+ case MSG_UNREGISTER_VISIBLE_ACTIVITY_CALLBACK:
+ if (DEBUG) {
+ Log.d(TAG, "doUnregisterVisibleActivityCallback");
+ }
+ doUnregisterVisibleActivityCallback((VisibleActivityCallback) msg.obj);
+ break;
}
if (args != null) {
args.recycle();
@@ -1122,6 +1159,86 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
}
}
+ private void doUpdateVisibleActivityInfo(VisibleActivityInfo visibleActivityInfo, int type) {
+
+ if (mVisibleActivityCallbacks.isEmpty()) {
+ return;
+ }
+
+ switch (type) {
+ case VisibleActivityInfo.TYPE_ACTIVITY_ADDED:
+ informVisibleActivityChanged(visibleActivityInfo, type);
+ mVisibleActivityInfos.add(visibleActivityInfo);
+ break;
+ case VisibleActivityInfo.TYPE_ACTIVITY_REMOVED:
+ informVisibleActivityChanged(visibleActivityInfo, type);
+ mVisibleActivityInfos.remove(visibleActivityInfo);
+ break;
+ }
+ }
+
+ private void doRegisterVisibleActivityCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull VisibleActivityCallback callback) {
+ if (mVisibleActivityCallbacks.containsKey(callback)) {
+ if (DEBUG) {
+ Log.d(TAG, "doRegisterVisibleActivityCallback: callback has registered");
+ }
+ return;
+ }
+
+ int preCallbackCount = mVisibleActivityCallbacks.size();
+ mVisibleActivityCallbacks.put(callback, executor);
+
+ if (preCallbackCount == 0) {
+ try {
+ mSystemService.startListeningVisibleActivityChanged(mToken);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ } else {
+ for (int i = 0; i < mVisibleActivityInfos.size(); i++) {
+ final VisibleActivityInfo visibleActivityInfo = mVisibleActivityInfos.get(i);
+ executor.execute(() -> callback.onVisible(visibleActivityInfo));
+ }
+ }
+ }
+
+ private void doUnregisterVisibleActivityCallback(@NonNull VisibleActivityCallback callback) {
+ mVisibleActivityCallbacks.remove(callback);
+
+ if (mVisibleActivityCallbacks.size() == 0) {
+ mVisibleActivityInfos.clear();
+ try {
+ mSystemService.stopListeningVisibleActivityChanged(mToken);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ private void informVisibleActivityChanged(VisibleActivityInfo visibleActivityInfo, int type) {
+ for (Map.Entry<VisibleActivityCallback, Executor> e :
+ mVisibleActivityCallbacks.entrySet()) {
+ final Executor executor = e.getValue();
+ final VisibleActivityCallback visibleActivityCallback = e.getKey();
+
+ switch (type) {
+ case VisibleActivityInfo.TYPE_ACTIVITY_ADDED:
+ Binder.withCleanCallingIdentity(() -> {
+ executor.execute(
+ () -> visibleActivityCallback.onVisible(visibleActivityInfo));
+ });
+ break;
+ case VisibleActivityInfo.TYPE_ACTIVITY_REMOVED:
+ Binder.withCleanCallingIdentity(() -> {
+ executor.execute(() -> visibleActivityCallback.onInvisible(
+ visibleActivityInfo.getActivityId()));
+ });
+ break;
+ }
+ }
+ }
+
void ensureWindowCreated() {
if (mInitialized) {
return;
@@ -1926,6 +2043,49 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
}
/**
+ * Registers a callback that will be notified when visible activities have been changed.
+ *
+ * Note: The {@link VisibleActivityCallback#onVisible(VisibleActivityInfo)} will be called
+ * immediately with current visible activities when the callback is registered for the first
+ * time. If the callback is already registered, this method does nothing.
+ *
+ * @param executor The executor which will be used to invoke the callback.
+ * @param callback The callback to receive the response.
+ *
+ * @throws IllegalStateException if calling this method before onCreate().
+ */
+ public final void registerVisibleActivityCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull VisibleActivityCallback callback) {
+ if (DEBUG) {
+ Log.d(TAG, "registerVisibleActivityCallback");
+ }
+ if (mToken == null) {
+ throw new IllegalStateException("Can't call before onCreate()");
+ }
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ mHandlerCaller.sendMessage(
+ mHandlerCaller.obtainMessageOO(MSG_REGISTER_VISIBLE_ACTIVITY_CALLBACK, executor,
+ callback));
+ }
+
+ /**
+ * Unregisters the callback.
+ *
+ * @param callback The callback to receive the response.
+ */
+ public final void unregisterVisibleActivityCallback(@NonNull VisibleActivityCallback callback) {
+ if (DEBUG) {
+ Log.d(TAG, "unregisterVisibleActivityCallback");
+ }
+ Objects.requireNonNull(callback);
+
+ mHandlerCaller.sendMessage(
+ mHandlerCaller.obtainMessageO(MSG_UNREGISTER_VISIBLE_ACTIVITY_CALLBACK, callback));
+ }
+
+ /**
* Print the Service's state into the given stream. This gets invoked by
* {@link VoiceInteractionSessionService} when its Service
* {@link android.app.Service#dump} method is called.
@@ -1975,6 +2135,17 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
}
/**
+ * Callback interface for receiving visible activity changes used for assistant usage.
+ */
+ public interface VisibleActivityCallback {
+ /** Callback to inform that an activity has become visible. */
+ default void onVisible(@NonNull VisibleActivityInfo activityInfo) {}
+
+ /** Callback to inform that a visible activity has gone. */
+ default void onInvisible(@NonNull ActivityId activityId) {}
+ }
+
+ /**
* Represents assist state captured when this session was started.
* It contains the various assist data objects and a reference to
* the source activity.
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index c9a0121936dc..2d263a54b874 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -16,6 +16,8 @@
package android.service.wallpaper;
+import static android.app.WallpaperManager.COMMAND_FREEZE;
+import static android.app.WallpaperManager.COMMAND_UNFREEZE;
import static android.graphics.Matrix.MSCALE_X;
import static android.graphics.Matrix.MSCALE_Y;
import static android.graphics.Matrix.MSKEW_X;
@@ -42,12 +44,14 @@ import android.content.res.TypedArray;
import android.graphics.BLASTBufferQueue;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.GraphicBuffer;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
+import android.hardware.HardwareBuffer;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Build;
@@ -56,6 +60,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.SystemProperties;
@@ -73,6 +78,7 @@ import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import android.view.MotionEvent;
import android.view.PixelCopy;
import android.view.Surface;
@@ -201,6 +207,12 @@ public abstract class WallpaperService extends Service {
boolean mVisible;
boolean mReportedVisible;
boolean mDestroyed;
+ // Set to true after receiving WallpaperManager#COMMAND_FREEZE. It's reset back to false
+ // after receiving WallpaperManager#COMMAND_UNFREEZE. COMMAND_FREEZE is fully applied once
+ // mScreenshotSurfaceControl isn't null. When this happens, then Engine is notified through
+ // doVisibilityChanged that main wallpaper surface is no longer visible and the wallpaper
+ // host receives onVisibilityChanged(false) callback.
+ private boolean mFrozenRequested = false;
// Current window state.
boolean mCreated;
@@ -226,10 +238,9 @@ public abstract class WallpaperService extends Service {
final ClientWindowFrames mWinFrames = new ClientWindowFrames();
final Rect mDispatchedContentInsets = new Rect();
final Rect mDispatchedStableInsets = new Rect();
- final Rect mFinalSystemInsets = new Rect();
- final Rect mFinalStableInsets = new Rect();
DisplayCutout mDispatchedDisplayCutout = DisplayCutout.NO_CUTOUT;
final InsetsState mInsetsState = new InsetsState();
+ final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0];
final MergedConfiguration mMergedConfiguration = new MergedConfiguration();
private final Point mSurfaceSize = new Point();
@@ -266,6 +277,8 @@ public abstract class WallpaperService extends Service {
SurfaceControl mSurfaceControl = new SurfaceControl();
SurfaceControl mBbqSurfaceControl;
BLASTBufferQueue mBlastBufferQueue;
+ private SurfaceControl mScreenshotSurfaceControl;
+ private Point mScreenshotSize = new Point();
final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() {
{
@@ -966,6 +979,7 @@ public abstract class WallpaperService extends Service {
void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
if (mDestroyed) {
Log.w(TAG, "Ignoring updateSurface due to destroyed");
+ return;
}
boolean fixedSize = false;
@@ -1032,8 +1046,8 @@ public abstract class WallpaperService extends Service {
InputChannel inputChannel = new InputChannel();
if (mSession.addToDisplay(mWindow, mLayout, View.VISIBLE,
- mDisplay.getDisplayId(), mInsetsState, inputChannel, mInsetsState,
- mTempControls) < 0) {
+ mDisplay.getDisplayId(), mRequestedVisibilities, inputChannel,
+ mInsetsState, mTempControls) < 0) {
Log.w(TAG, "Failed to add window while updating wallpaper surface.");
return;
}
@@ -1383,11 +1397,15 @@ public abstract class WallpaperService extends Service {
if (!mDestroyed) {
mVisible = visible;
reportVisibility();
- if (visible) processLocalColors(mPendingXOffset, mPendingXOffsetStep);
+ if (mReportedVisible) processLocalColors(mPendingXOffset, mPendingXOffsetStep);
}
}
void reportVisibility() {
+ if (mScreenshotSurfaceControl != null && mVisible) {
+ if (DEBUG) Log.v(TAG, "Frozen so don't report visibility change");
+ return;
+ }
if (!mDestroyed) {
mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getState();
boolean visible = mVisible && mDisplayState != Display.STATE_OFF;
@@ -1404,6 +1422,10 @@ public abstract class WallpaperService extends Service {
updateSurface(true, false, false);
}
onVisibilityChanged(visible);
+ if (mReportedVisible && mFrozenRequested) {
+ if (DEBUG) Log.v(TAG, "Freezing wallpaper after visibility update");
+ freeze();
+ }
}
}
}
@@ -1864,6 +1886,9 @@ public abstract class WallpaperService extends Service {
void doCommand(WallpaperCommand cmd) {
Bundle result;
if (!mDestroyed) {
+ if (COMMAND_FREEZE.equals(cmd.action) || COMMAND_UNFREEZE.equals(cmd.action)) {
+ updateFrozenState(/* frozenRequested= */ !COMMAND_UNFREEZE.equals(cmd.action));
+ }
result = onCommand(cmd.action, cmd.x, cmd.y, cmd.z,
cmd.extras, cmd.sync);
} else {
@@ -1878,6 +1903,159 @@ public abstract class WallpaperService extends Service {
}
}
+ private void updateFrozenState(boolean frozenRequested) {
+ if (mIWallpaperEngine.mWallpaperManager.getWallpaperInfo() == null
+ // Procees the unfreeze command in case the wallaper became static while
+ // being paused.
+ && frozenRequested) {
+ if (DEBUG) Log.v(TAG, "Ignoring the freeze command for static wallpapers");
+ return;
+ }
+ mFrozenRequested = frozenRequested;
+ boolean isFrozen = mScreenshotSurfaceControl != null;
+ if (mFrozenRequested == isFrozen) {
+ return;
+ }
+ if (mFrozenRequested) {
+ freeze();
+ } else {
+ unfreeze();
+ }
+ }
+
+ private void freeze() {
+ if (!mReportedVisible || mDestroyed) {
+ // Screenshot can't be taken until visibility is reported to the wallpaper host.
+ return;
+ }
+ if (!showScreenshotOfWallpaper()) {
+ return;
+ }
+ // Prevent a wallpaper host from rendering wallpaper behind a screeshot.
+ doVisibilityChanged(false);
+ // Remember that visibility is requested since it's not guaranteed that
+ // mWindow#dispatchAppVisibility will be called when letterboxed application with
+ // wallpaper background transitions to the Home screen.
+ mVisible = true;
+ }
+
+ private void unfreeze() {
+ cleanUpScreenshotSurfaceControl();
+ if (mVisible) {
+ doVisibilityChanged(true);
+ }
+ }
+
+ private void cleanUpScreenshotSurfaceControl() {
+ // TODO(b/194399558): Add crossfade transition.
+ if (mScreenshotSurfaceControl != null) {
+ new SurfaceControl.Transaction()
+ .remove(mScreenshotSurfaceControl)
+ .show(mBbqSurfaceControl)
+ .apply();
+ mScreenshotSurfaceControl = null;
+ }
+ }
+
+ void scaleAndCropScreenshot() {
+ if (mScreenshotSurfaceControl == null) {
+ return;
+ }
+ if (mScreenshotSize.x <= 0 || mScreenshotSize.y <= 0) {
+ Log.w(TAG, "Unexpected screenshot size: " + mScreenshotSize);
+ return;
+ }
+ // Don't scale down and using the same scaling factor for both dimensions to
+ // avoid stretching wallpaper image.
+ float scaleFactor = Math.max(1, Math.max(
+ ((float) mSurfaceSize.x) / mScreenshotSize.x,
+ ((float) mSurfaceSize.y) / mScreenshotSize.y));
+ int diffX = ((int) (mScreenshotSize.x * scaleFactor)) - mSurfaceSize.x;
+ int diffY = ((int) (mScreenshotSize.y * scaleFactor)) - mSurfaceSize.y;
+ if (DEBUG) {
+ Log.v(TAG, "Adjusting screenshot: scaleFactor=" + scaleFactor
+ + " diffX=" + diffX + " diffY=" + diffY + " mSurfaceSize=" + mSurfaceSize
+ + " mScreenshotSize=" + mScreenshotSize);
+ }
+ new SurfaceControl.Transaction()
+ .setMatrix(
+ mScreenshotSurfaceControl,
+ /* dsdx= */ scaleFactor, /* dtdx= */ 0,
+ /* dtdy= */ 0, /* dsdy= */ scaleFactor)
+ .setWindowCrop(
+ mScreenshotSurfaceControl,
+ new Rect(
+ /* left= */ diffX / 2,
+ /* top= */ diffY / 2,
+ /* right= */ diffX / 2 + mScreenshotSize.x,
+ /* bottom= */ diffY / 2 + mScreenshotSize.y))
+ .setPosition(mScreenshotSurfaceControl, -diffX / 2, -diffY / 2)
+ .apply();
+ }
+
+ private boolean showScreenshotOfWallpaper() {
+ if (mDestroyed || mSurfaceControl == null || !mSurfaceControl.isValid()) {
+ if (DEBUG) Log.v(TAG, "Failed to screenshot wallpaper: surface isn't valid");
+ return false;
+ }
+
+ final Rect bounds = new Rect(0, 0, mSurfaceSize.x, mSurfaceSize.y);
+ if (bounds.isEmpty()) {
+ Log.w(TAG, "Failed to screenshot wallpaper: surface bounds are empty");
+ return false;
+ }
+
+ if (mScreenshotSurfaceControl != null) {
+ Log.e(TAG, "Screenshot is unexpectedly not null");
+ // Destroying previous screenshot since it can have different size.
+ cleanUpScreenshotSurfaceControl();
+ }
+
+ SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
+ SurfaceControl.captureLayers(
+ new SurfaceControl.LayerCaptureArgs.Builder(mSurfaceControl)
+ // Needed because SurfaceFlinger#validateScreenshotPermissions
+ // uses this parameter to check whether a caller only attempts
+ // to screenshot itself when call doesn't come from the system.
+ .setUid(Process.myUid())
+ .setChildrenOnly(false)
+ .setSourceCrop(bounds)
+ .build());
+
+ if (screenshotBuffer == null) {
+ Log.w(TAG, "Failed to screenshot wallpaper: screenshotBuffer is null");
+ return false;
+ }
+
+ final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
+
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+
+ // TODO(b/194399558): Add crossfade transition.
+ mScreenshotSurfaceControl = new SurfaceControl.Builder()
+ .setName("Wallpaper snapshot for engine " + this)
+ .setFormat(hardwareBuffer.getFormat())
+ .setParent(mSurfaceControl)
+ .setSecure(screenshotBuffer.containsSecureLayers())
+ .setCallsite("WallpaperService.Engine.showScreenshotOfWallpaper")
+ .setBLASTLayer()
+ .build();
+
+ mScreenshotSize.set(mSurfaceSize.x, mSurfaceSize.y);
+
+ GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(hardwareBuffer);
+
+ t.setBuffer(mScreenshotSurfaceControl, graphicBuffer);
+ t.setColorSpace(mScreenshotSurfaceControl, screenshotBuffer.getColorSpace());
+ // Place on top everything else.
+ t.setLayer(mScreenshotSurfaceControl, Integer.MAX_VALUE);
+ t.show(mScreenshotSurfaceControl);
+ t.hide(mBbqSurfaceControl);
+ t.apply();
+
+ return true;
+ }
+
void reportSurfaceDestroyed() {
if (mSurfaceCreated) {
mSurfaceCreated = false;
@@ -2202,6 +2380,7 @@ public abstract class WallpaperService extends Service {
final boolean reportDraw = message.arg1 != 0;
mEngine.updateSurface(true, false, reportDraw);
mEngine.doOffsetsChanged(true);
+ mEngine.scaleAndCropScreenshot();
} break;
case MSG_WINDOW_MOVED: {
// Do nothing. What does it mean for a Wallpaper to move?
diff --git a/core/java/android/util/DisplayUtils.java b/core/java/android/util/DisplayUtils.java
new file mode 100644
index 000000000000..4fe7f8369f73
--- /dev/null
+++ b/core/java/android/util/DisplayUtils.java
@@ -0,0 +1,54 @@
+/*
+ * 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 android.util;
+
+import android.content.res.Resources;
+
+import com.android.internal.R;
+
+/**
+ * Utils for loading resources for multi-display.
+ *
+ * @hide
+ */
+public class DisplayUtils {
+
+ /**
+ * Gets the index of the given display unique id in {@link R.array#config_displayUniqueIdArray}
+ * which is used to get the related cutout configs for that display.
+ *
+ * For multi-display device, {@link R.array#config_displayUniqueIdArray} should be set for each
+ * display if there are different type of cutouts on each display.
+ * For single display device, {@link R.array#config_displayUniqueIdArray} should not to be set
+ * and the system will load the default configs for main built-in display.
+ */
+ public static int getDisplayUniqueIdConfigIndex(Resources res, String displayUniqueId) {
+ int index = -1;
+ if (displayUniqueId == null || displayUniqueId.isEmpty()) {
+ return index;
+ }
+ final String[] ids = res.getStringArray(R.array.config_displayUniqueIdArray);
+ final int size = ids.length;
+ for (int i = 0; i < size; i++) {
+ if (displayUniqueId.equals(ids[i])) {
+ index = i;
+ break;
+ }
+ }
+ return index;
+ }
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 6c3c38359957..3d39fbeea7d8 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -51,6 +51,9 @@ public class FeatureFlagUtils {
/** @hide */
public static final String SETTINGS_ENABLE_SECURITY_HUB = "settings_enable_security_hub";
+ /** @hide */
+ public static final String SETTINGS_SUPPORT_LARGE_SCREEN = "settings_support_large_screen";
+
private static final Map<String, String> DEFAULT_FLAGS;
static {
@@ -72,12 +75,14 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put(SETTINGS_PROVIDER_MODEL, "true");
DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "true");
+ DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
static {
PERSISTENT_FLAGS = new HashSet<>();
PERSISTENT_FLAGS.add(SETTINGS_PROVIDER_MODEL);
+ PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
}
/**
diff --git a/core/java/android/util/MathUtils.java b/core/java/android/util/MathUtils.java
index 971e16185815..aecde4415117 100644
--- a/core/java/android/util/MathUtils.java
+++ b/core/java/android/util/MathUtils.java
@@ -165,6 +165,10 @@ public final class MathUtils {
return start + (stop - start) * amount;
}
+ public static float lerp(int start, int stop, float amount) {
+ return lerp((float) start, (float) stop, amount);
+ }
+
/**
* Returns the interpolation scalar (s) that satisfies the equation: {@code value = }{@link
* #lerp}{@code (a, b, s)}
diff --git a/core/java/android/util/imetracing/ImeTracingServerImpl.java b/core/java/android/util/imetracing/ImeTracingServerImpl.java
index 06e4c5002776..d605430bcf14 100644
--- a/core/java/android/util/imetracing/ImeTracingServerImpl.java
+++ b/core/java/android/util/imetracing/ImeTracingServerImpl.java
@@ -41,9 +41,9 @@ import java.io.PrintWriter;
*/
class ImeTracingServerImpl extends ImeTracing {
private static final String TRACE_DIRNAME = "/data/misc/wmtrace/";
- private static final String TRACE_FILENAME_CLIENTS = "ime_trace_clients.pb";
- private static final String TRACE_FILENAME_IMS = "ime_trace_service.pb";
- private static final String TRACE_FILENAME_IMMS = "ime_trace_managerservice.pb";
+ private static final String TRACE_FILENAME_CLIENTS = "ime_trace_clients.winscope";
+ private static final String TRACE_FILENAME_IMS = "ime_trace_service.winscope";
+ private static final String TRACE_FILENAME_IMMS = "ime_trace_managerservice.winscope";
private static final int BUFFER_CAPACITY = 4096 * 1024;
// Needed for winscope to auto-detect the dump type. Explained further in
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index bcc5b56459bb..69af2a5ce7fb 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -18,6 +18,7 @@ package android.view;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
+import android.hardware.HardwareBuffer;
/**
* Provides an interface to the root-Surface of a View Hierarchy or Window. This
@@ -53,4 +54,74 @@ public interface AttachedSurfaceControl {
* to the View hierarchy you may need to call {@link android.view.View#invalidate}
*/
boolean applyTransactionOnDraw(@NonNull SurfaceControl.Transaction t);
+
+ /**
+ * The transform hint can be used by a buffer producer to pre-rotate the rendering such that the
+ * final transformation in the system composer is identity. This can be very useful when used in
+ * conjunction with the h/w composer HAL in situations where it cannot handle rotations or
+ * handle them with an additional power cost.
+ *
+ * The transform hint should be used with ASurfaceControl APIs when submitting buffers.
+ * Example usage:
+ *
+ * 1. After a configuration change, before dequeuing a buffer, the buffer producer queries the
+ * function for the transform hint.
+ *
+ * 2. The desired buffer width and height is rotated by the transform hint.
+ *
+ * 3. The producer dequeues a buffer of the new pre-rotated size.
+ *
+ * 4. The producer renders to the buffer such that the image is already transformed, that is
+ * applying the transform hint to the rendering.
+ *
+ * 5. The producer applies the inverse transform hint to the buffer it just rendered.
+ *
+ * 6. The producer queues the pre-transformed buffer with the buffer transform.
+ *
+ * 7. The composer combines the buffer transform with the display transform. If the buffer
+ * transform happens to cancel out the display transform then no rotation is needed and there
+ * will be no performance penalties.
+ *
+ * Note, when using ANativeWindow APIs in conjunction with a NativeActivity Surface or
+ * SurfaceView Surface, the buffer producer will already have access to the transform hint and
+ * no additional work is needed.
+ *
+ * @see HardwareBuffer
+ */
+ default @SurfaceControl.BufferTransform int getBufferTransformHint() {
+ return SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
+ }
+
+ /**
+ * Buffer transform hint change listener.
+ * @see #getBufferTransformHint
+ */
+ @UiThread
+ interface OnBufferTransformHintChangedListener {
+ /**
+ * @param hint new surface transform hint
+ * @see #getBufferTransformHint
+ */
+ void onBufferTransformHintChanged(@SurfaceControl.BufferTransform int hint);
+ }
+
+ /**
+ * Registers a {@link OnBufferTransformHintChangedListener} to receive notifications about when
+ * the transform hint changes.
+ *
+ * @see #getBufferTransformHint
+ * @see #removeOnBufferTransformHintChangedListener
+ */
+ default void addOnBufferTransformHintChangedListener(
+ @NonNull OnBufferTransformHintChangedListener listener) {
+ }
+
+ /**
+ * Unregisters a {@link OnBufferTransformHintChangedListener}.
+ *
+ * @see #addOnBufferTransformHintChangedListener
+ */
+ default void removeOnBufferTransformHintChangedListener(
+ @NonNull OnBufferTransformHintChangedListener listener) {
+ }
}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 9cb0d1ff2c3f..e7ff978266a2 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1463,10 +1463,10 @@ public final class Display {
return false;
}
final Configuration config = mResources.getConfiguration();
- // TODO(b/179308296) Temporarily exclude Launcher from being given max bounds, by checking
- // if the caller is the recents component.
+ // TODO(b/179308296) Temporarily - never report max bounds to only Launcher if the feature
+ // is disabled.
return config != null && !config.windowConfiguration.getMaxBounds().isEmpty()
- && !isRecentsComponent();
+ && (mDisplayInfo.shouldConstrainMetricsForLauncher || !isRecentsComponent());
}
/**
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index e1a4402d8964..c1a5636b7b34 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -31,6 +31,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Path;
@@ -38,6 +39,7 @@ import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.DisplayUtils;
import android.util.Pair;
import android.util.RotationUtils;
import android.util.proto.ProtoOutputStream;
@@ -873,18 +875,122 @@ public final class DisplayCutout {
}
/**
+ * Gets the display cutout by the given display unique id.
+ *
+ * Loads the default config {@link R.string#config_mainBuiltInDisplayCutout) if
+ * {@link R.array#config_displayUniqueIdArray} is not set.
+ */
+ private static String getDisplayCutoutPath(Resources res, String displayUniqueId) {
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final String[] array = res.getStringArray(R.array.config_displayCutoutPathArray);
+ if (index >= 0 && index < array.length) {
+ return array[index];
+ }
+ return res.getString(R.string.config_mainBuiltInDisplayCutout);
+ }
+
+ /**
+ * Gets the display cutout approximation rect by the given display unique id.
+ *
+ * Loads the default config {@link R.string#config_mainBuiltInDisplayCutoutRectApproximation} if
+ * {@link R.array#config_displayUniqueIdArray} is not set.
+ */
+ private static String getDisplayCutoutApproximationRect(Resources res, String displayUniqueId) {
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final String[] array = res.getStringArray(
+ R.array.config_displayCutoutApproximationRectArray);
+ if (index >= 0 && index < array.length) {
+ return array[index];
+ }
+ return res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation);
+ }
+
+ /**
+ * Gets whether to mask a built-in display cutout of a display which is determined by the
+ * given display unique id.
+ *
+ * Loads the default config {@link R.bool#config_maskMainBuiltInDisplayCutout} if
+ * {@link R.array#config_displayUniqueIdArray} is not set.
+ *
+ * @hide
+ */
+ public static boolean getMaskBuiltInDisplayCutout(Resources res, String displayUniqueId) {
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final TypedArray array = res.obtainTypedArray(R.array.config_maskBuiltInDisplayCutoutArray);
+ boolean maskCutout;
+ if (index >= 0 && index < array.length()) {
+ maskCutout = array.getBoolean(index, false);
+ } else {
+ maskCutout = res.getBoolean(R.bool.config_maskMainBuiltInDisplayCutout);
+ }
+ array.recycle();
+ return maskCutout;
+ }
+
+ /**
+ * Gets whether to fill a built-in display cutout of a display which is determined by the
+ * given display unique id.
+ *
+ * Loads the default config{@link R.bool#config_fillMainBuiltInDisplayCutout} if
+ * {@link R.array#config_displayUniqueIdArray} is not set.
+ *
+ * @hide
+ */
+ public static boolean getFillBuiltInDisplayCutout(Resources res, String displayUniqueId) {
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final TypedArray array = res.obtainTypedArray(R.array.config_fillBuiltInDisplayCutoutArray);
+ boolean fillCutout;
+ if (index >= 0 && index < array.length()) {
+ fillCutout = array.getBoolean(index, false);
+ } else {
+ fillCutout = res.getBoolean(R.bool.config_fillMainBuiltInDisplayCutout);
+ }
+ array.recycle();
+ return fillCutout;
+ }
+
+ /**
+ * Gets the waterfall cutout by the given display unique id.
+ *
+ * Loads the default waterfall dimens if {@link R.array#config_displayUniqueIdArray} is not set.
+ * {@link R.dimen#waterfall_display_left_edge_size},
+ * {@link R.dimen#waterfall_display_top_edge_size},
+ * {@link R.dimen#waterfall_display_right_edge_size},
+ * {@link R.dimen#waterfall_display_bottom_edge_size}
+ */
+ private static Insets getWaterfallInsets(Resources res, String displayUniqueId) {
+ Insets insets;
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final TypedArray array = res.obtainTypedArray(R.array.config_waterfallCutoutArray);
+ if (index >= 0 && index < array.length() && array.getResourceId(index, 0) > 0) {
+ final int resourceId = array.getResourceId(index, 0);
+ final TypedArray waterfall = res.obtainTypedArray(resourceId);
+ insets = Insets.of(
+ waterfall.getDimensionPixelSize(0 /* waterfall left edge size */, 0),
+ waterfall.getDimensionPixelSize(1 /* waterfall top edge size */, 0),
+ waterfall.getDimensionPixelSize(2 /* waterfall right edge size */, 0),
+ waterfall.getDimensionPixelSize(3 /* waterfall bottom edge size */, 0));
+ waterfall.recycle();
+ } else {
+ insets = loadWaterfallInset(res);
+ }
+ array.recycle();
+ return insets;
+ }
+
+ /**
* Creates the display cutout according to
* @android:string/config_mainBuiltInDisplayCutoutRectApproximation, which is the closest
* rectangle-base approximation of the cutout.
*
* @hide
*/
- public static DisplayCutout fromResourcesRectApproximation(Resources res, int displayWidth,
- int displayHeight) {
- return pathAndDisplayCutoutFromSpec(res.getString(R.string.config_mainBuiltInDisplayCutout),
- res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation),
+ public static DisplayCutout fromResourcesRectApproximation(Resources res,
+ String displayUniqueId, int displayWidth, int displayHeight) {
+ return pathAndDisplayCutoutFromSpec(getDisplayCutoutPath(res, displayUniqueId),
+ getDisplayCutoutApproximationRect(res, displayUniqueId),
displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT,
- loadWaterfallInset(res)).second;
+ getWaterfallInsets(res, displayUniqueId)).second;
}
/**
@@ -892,11 +998,11 @@ public final class DisplayCutout {
*
* @hide
*/
- public static Path pathFromResources(Resources res, int displayWidth, int displayHeight) {
- return pathAndDisplayCutoutFromSpec(
- res.getString(R.string.config_mainBuiltInDisplayCutout), null,
+ public static Path pathFromResources(Resources res, String displayUniqueId, int displayWidth,
+ int displayHeight) {
+ return pathAndDisplayCutoutFromSpec(getDisplayCutoutPath(res, displayUniqueId), null,
displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT,
- loadWaterfallInset(res)).first;
+ getWaterfallInsets(res, displayUniqueId)).first;
}
/**
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 8e5f905e9c74..657251046551 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -306,6 +306,13 @@ public final class DisplayInfo implements Parcelable {
public float brightnessDefault;
/**
+ * @hide
+ * True if Display#getRealSize and getRealMetrics should be constrained for Launcher, false
+ * otherwise.
+ */
+ public boolean shouldConstrainMetricsForLauncher = false;
+
+ /**
* The {@link RoundedCorners} if present, otherwise {@code null}.
*/
@Nullable
@@ -381,7 +388,8 @@ public final class DisplayInfo implements Parcelable {
&& brightnessMinimum == other.brightnessMinimum
&& brightnessMaximum == other.brightnessMaximum
&& brightnessDefault == other.brightnessDefault
- && Objects.equals(roundedCorners, other.roundedCorners);
+ && Objects.equals(roundedCorners, other.roundedCorners)
+ && shouldConstrainMetricsForLauncher == other.shouldConstrainMetricsForLauncher;
}
@Override
@@ -432,6 +440,7 @@ public final class DisplayInfo implements Parcelable {
brightnessMaximum = other.brightnessMaximum;
brightnessDefault = other.brightnessDefault;
roundedCorners = other.roundedCorners;
+ shouldConstrainMetricsForLauncher = other.shouldConstrainMetricsForLauncher;
}
public void readFromParcel(Parcel source) {
@@ -488,6 +497,7 @@ public final class DisplayInfo implements Parcelable {
for (int i = 0; i < numUserDisabledFormats; i++) {
userDisabledHdrTypes[i] = source.readInt();
}
+ shouldConstrainMetricsForLauncher = source.readBoolean();
}
@Override
@@ -542,6 +552,7 @@ public final class DisplayInfo implements Parcelable {
for (int i = 0; i < userDisabledHdrTypes.length; i++) {
dest.writeInt(userDisabledHdrTypes[i]);
}
+ dest.writeBoolean(shouldConstrainMetricsForLauncher);
}
@Override
@@ -796,6 +807,8 @@ public final class DisplayInfo implements Parcelable {
sb.append(brightnessMaximum);
sb.append(", brightnessDefault ");
sb.append(brightnessDefault);
+ sb.append(", shouldConstrainMetricsForLauncher ");
+ sb.append(shouldConstrainMetricsForLauncher);
sb.append("}");
return sb.toString();
}
diff --git a/core/java/android/view/IDisplayWindowListener.aidl b/core/java/android/view/IDisplayWindowListener.aidl
index 610e0f866b43..f95d6b349221 100644
--- a/core/java/android/view/IDisplayWindowListener.aidl
+++ b/core/java/android/view/IDisplayWindowListener.aidl
@@ -32,7 +32,8 @@ import android.content.res.Configuration;
oneway interface IDisplayWindowListener {
/**
- * Called when a display is added to the WM hierarchy.
+ * Called when a new display is added to the WM hierarchy. The existing display ids are returned
+ * when this listener is registered with WM via {@link #registerDisplayWindowListener}.
*/
void onDisplayAdded(int displayId);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index a7ecf1f2a81d..9e41e4d2906c 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -33,6 +33,7 @@ import android.os.Bundle;
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
import android.view.DisplayCutout;
+import android.view.DisplayInfo;
import android.view.IApplicationToken;
import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.ICrossWindowBlurEnabledListener;
@@ -53,12 +54,14 @@ import android.view.IWindowSessionCallback;
import android.view.KeyEvent;
import android.view.InputEvent;
import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.IInputFilter;
import android.view.AppTransitionAnimationSpec;
+import android.view.TaskTransitionSpec;
import android.view.WindowContentFrameStats;
import android.view.WindowManager;
import android.view.SurfaceControl;
@@ -345,6 +348,14 @@ interface IWindowManager
Bitmap screenshotWallpaper();
/**
+ * Mirrors the wallpaper for the given display.
+ *
+ * @param displayId ID of the display for the wallpaper.
+ * @return A SurfaceControl for the parent of the mirrored wallpaper.
+ */
+ SurfaceControl mirrorWallpaperSurface(int displayId);
+
+ /**
* Registers a wallpaper visibility listener.
* @return Current visibility.
*/
@@ -520,9 +531,10 @@ interface IWindowManager
void unregisterDisplayFoldListener(IDisplayFoldListener listener);
/**
- * Registers an IDisplayContainerListener
+ * Registers an IDisplayContainerListener, and returns the set of existing display ids. The
+ * listener's onDisplayAdded() will not be called for the displays returned.
*/
- void registerDisplayWindowListener(IDisplayWindowListener listener);
+ int[] registerDisplayWindowListener(IDisplayWindowListener listener);
/**
* Unregisters an IDisplayContainerListener.
@@ -720,19 +732,31 @@ interface IWindowManager
int displayId, in IDisplayWindowInsetsController displayWindowInsetsController);
/**
- * Called when a remote process modifies insets on a display window container.
+ * Called when a remote process updates the requested visibilities of insets on a display window
+ * container.
*/
- void modifyDisplayWindowInsets(int displayId, in InsetsState state);
+ void updateDisplayWindowRequestedVisibilities(int displayId, in InsetsVisibilities vis);
/**
* Called to get the expected window insets.
*
- * @return {@code true} if system bars are always comsumed.
+ * @return {@code true} if system bars are always consumed.
*/
boolean getWindowInsets(in WindowManager.LayoutParams attrs, int displayId,
out InsetsState outInsetsState);
/**
+ * Returns a list of {@link android.view.DisplayInfo} for the logical display. This is not
+ * guaranteed to include all possible device states. The list items are unique.
+ *
+ * If invoked through a package other than a launcher app, returns an empty list.
+ *
+ * @param displayId the id of the logical display
+ * @param packageName the name of the calling package
+ */
+ List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName);
+
+ /**
* Called to show global actions.
*/
void showGlobalActions();
@@ -866,4 +890,24 @@ interface IWindowManager
void unregisterCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener);
boolean isTaskSnapshotSupported();
+
+ /**
+ * Returns the preferred display ID to show software keyboard.
+ *
+ * @see android.window.WindowProviderService#getLaunchedDisplayId
+ */
+ int getImeDisplayId();
+
+ /**
+ * Customized the task transition animation with a task transition spec.
+ *
+ * @param spec the spec that will be used to customize the task animations
+ */
+ void setTaskTransitionSpec(in TaskTransitionSpec spec);
+
+ /**
+ * Clears any task transition spec that has been previously set and
+ * reverts to using the default task transition with no spec changes.
+ */
+ void clearTaskTransitionSpec();
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 7bad5cbfbdc3..9da50889e43f 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -32,6 +32,7 @@ import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
@@ -46,12 +47,12 @@ import java.util.List;
*/
interface IWindowSession {
int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs,
- in int viewVisibility, in int layerStackId, in InsetsState requestedVisibility,
+ in int viewVisibility, in int layerStackId, in InsetsVisibilities requestedVisibilities,
out InputChannel outInputChannel, out InsetsState insetsState,
out InsetsSourceControl[] activeControls);
int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, in int userId,
- in InsetsState requestedVisibility, out InputChannel outInputChannel,
+ in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel,
out InsetsState insetsState, out InsetsSourceControl[] activeControls);
int addToDisplayWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, out InsetsState insetsState);
@@ -174,6 +175,11 @@ interface IWindowSession {
float touchX, float touchY, float thumbCenterX, float thumbCenterY, in ClipData data);
/**
+ * Drops the content of the current drag operation for accessibility
+ */
+ boolean dropForAccessibility(IWindow window, int x, int y);
+
+ /**
* Report the result of a drop action targeted to the given window.
* consumed is 'true' when the drop was accepted by a valid recipient,
* 'false' otherwise.
@@ -285,10 +291,9 @@ interface IWindowSession {
oneway void updateTapExcludeRegion(IWindow window, in Region region);
/**
- * Called when the client has changed the local insets state, and now the server should reflect
- * that new state.
+ * Updates the requested visibilities of insets.
*/
- oneway void insetsModified(IWindow window, in InsetsState state);
+ oneway void updateRequestedVisibilities(IWindow window, in InsetsVisibilities visibilities);
/**
* Called when the system gesture exclusion has changed.
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 5a34a92a4b1a..139bff4b0118 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -19,9 +19,10 @@ package android.view;
import static android.view.Display.INVALID_DISPLAY;
import android.annotation.Nullable;
+import android.graphics.Matrix;
import android.graphics.Region;
+import android.gui.TouchOcclusionMode;
import android.os.IBinder;
-import android.os.TouchOcclusionMode;
import java.lang.ref.WeakReference;
@@ -43,6 +44,17 @@ public final class InputWindowHandle {
// channel and the server input channel will both contain this token.
public IBinder token;
+ /**
+ * The {@link IWindow} handle if InputWindowHandle is associated with a window, null otherwise.
+ */
+ @Nullable
+ private IBinder windowToken;
+ /**
+ * Used to cache IWindow from the windowToken so we don't need to convert every time getWindow
+ * is called.
+ */
+ private IWindow window;
+
// The window name.
public String name;
@@ -122,6 +134,12 @@ public final class InputWindowHandle {
*/
public boolean replaceTouchableRegionWithCrop;
+ /**
+ * The transform that should be applied to the Window to get it from screen coordinates to
+ * window coordinates
+ */
+ public Matrix transform;
+
private native void nativeDispose();
public InputWindowHandle(InputApplicationHandle inputApplicationHandle, int displayId) {
@@ -136,6 +154,9 @@ public final class InputWindowHandle {
.append(frameRight).append(",").append(frameBottom).append("]")
.append(", touchableRegion=").append(touchableRegion)
.append(", visible=").append(visible)
+ .append(", scaleFactor=").append(scaleFactor)
+ .append(", transform=").append(transform)
+ .append(", windowToken=").append(windowToken)
.toString();
}
@@ -167,4 +188,17 @@ public final class InputWindowHandle {
public void setTouchableRegionCrop(@Nullable SurfaceControl bounds) {
touchableRegionSurfaceControl = new WeakReference<>(bounds);
}
+
+ public void setWindowToken(IWindow iwindow) {
+ windowToken = iwindow.asBinder();
+ window = iwindow;
+ }
+
+ public IWindow getWindow() {
+ if (window != null) {
+ return window;
+ }
+ window = IWindow.Stub.asInterface(windowToken);
+ return window;
+ }
}
diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java
index 3431c3ecc310..04bb6091672b 100644
--- a/core/java/android/view/InsetsAnimationControlCallbacks.java
+++ b/core/java/android/view/InsetsAnimationControlCallbacks.java
@@ -20,7 +20,8 @@ import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
/**
- * Provide an interface to let InsetsAnimationControlImpl call back into its owner.
+ * Provide an interface to let InsetsAnimationControlImpl and InsetsResizeAnimationRunner call back
+ * into its owner.
* @hide
*/
public interface InsetsAnimationControlCallbacks {
@@ -34,10 +35,9 @@ public interface InsetsAnimationControlCallbacks {
* <li>Dispatch {@link WindowInsetsAnimationControlListener#onReady}</li>
* </ul>
*/
- void startAnimation(InsetsAnimationControlImpl controller,
- WindowInsetsAnimationControlListener listener, int types,
- WindowInsetsAnimation animation,
- Bounds bounds);
+ <T extends InsetsAnimationControlRunner & InternalInsetsAnimationController>
+ void startAnimation(T runner, WindowInsetsAnimationControlListener listener, int types,
+ WindowInsetsAnimation animation, Bounds bounds);
/**
* Schedule the apply by posting the animation callback.
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 17b3020001d4..7d8d653f5ab3 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -69,7 +69,7 @@ import java.util.Objects;
* @hide
*/
@VisibleForTesting
-public class InsetsAnimationControlImpl implements WindowInsetsAnimationController,
+public class InsetsAnimationControlImpl implements InternalInsetsAnimationController,
InsetsAnimationControlRunner {
private static final String TAG = "InsetsAnimationCtrlImpl";
@@ -105,7 +105,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
private float mCurrentAlpha = 1.0f;
private float mPendingAlpha = 1.0f;
@VisibleForTesting(visibility = PACKAGE)
- public boolean mReadyDispatched;
+ private boolean mReadyDispatched;
private Boolean mPerceptible;
@VisibleForTesting
@@ -170,6 +170,11 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
}
@Override
+ public void setReadyDispatched(boolean dispatched) {
+ mReadyDispatched = dispatched;
+ }
+
+ @Override
public Insets getHiddenStateInsets() {
return mHiddenInsets;
}
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index 691e638f3669..fc97541bd34d 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -54,8 +54,8 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro
@Override
@UiThread
- 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) {
// Animation will be started in constructor already.
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 6f915c9182d2..adda721e00a4 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -107,9 +107,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
boolean hasControl);
/**
- * Called when insets have been modified by the client and should be reported back to WM.
+ * Called when the requested visibilities of insets have been modified by the client.
+ * The visibilities should be reported back to WM.
+ *
+ * @param visibilities A collection of the requested visibilities.
*/
- void onInsetsModified(InsetsState insetsState);
+ void updateRequestedVisibilities(InsetsVisibilities visibilities);
/**
* @return Whether the host has any callbacks it wants to synchronize the animations with.
@@ -202,6 +205,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private static final int ANIMATION_DURATION_FADE_IN_MS = 500;
private static final int ANIMATION_DURATION_FADE_OUT_MS = 1500;
+ /** Visible for WindowManagerWrapper */
+ public static final int ANIMATION_DURATION_RESIZE = 300;
+
private static final int ANIMATION_DELAY_DIM_MS = 500;
private static final int ANIMATION_DURATION_SYNC_IME_MS = 285;
@@ -232,6 +238,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private static final Interpolator FAST_OUT_LINEAR_IN_INTERPOLATOR =
new PathInterpolator(0.4f, 0f, 1f, 1f);
+ /** Visible for WindowManagerWrapper */
+ public static final Interpolator RESIZE_INTERPOLATOR = new LinearInterpolator();
+
/** The amount IME will move up/down when animating in floating mode. */
private static final int FLOATING_IME_BOTTOM_INSET_DP = -80;
@@ -285,9 +294,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
@VisibleForTesting
public static final int ANIMATION_TYPE_USER = 2;
+ /** Running animation will resize insets */
+ @VisibleForTesting
+ public static final int ANIMATION_TYPE_RESIZE = 3;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {ANIMATION_TYPE_NONE, ANIMATION_TYPE_SHOW, ANIMATION_TYPE_HIDE,
- ANIMATION_TYPE_USER})
+ ANIMATION_TYPE_USER, ANIMATION_TYPE_RESIZE})
@interface AnimationType {
}
@@ -317,7 +330,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private final boolean mDisable;
private final int mFloatingImeBottomInset;
- private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
+ private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
new ThreadLocal<AnimationHandler>() {
@Override
protected AnimationHandler initialValue() {
@@ -536,10 +549,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
/** The state dispatched from server */
private final InsetsState mLastDispatchedState = new InsetsState();
- // TODO: Use other class to represent the requested visibility of each type, because the
- // display frame and the frame in each source are not used.
/** The requested visibilities sent to server */
- private final InsetsState mRequestedState = new InsetsState();
+ private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
private final Rect mFrame = new Rect();
private final BiFunction<InsetsController, Integer, InsetsSourceConsumer> mConsumerCreator;
@@ -549,7 +560,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>();
private final ArrayList<RunningAnimation> mRunningAnimations = new ArrayList<>();
- private final ArrayList<InsetsAnimationControlImpl> mTmpFinishedControls = new ArrayList<>();
private final ArraySet<InsetsSourceConsumer> mRequestedVisibilityChanged = new ArraySet<>();
private WindowInsets mLastInsets;
@@ -569,7 +579,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private int mCaptionInsetsHeight = 0;
private boolean mAnimationsDisabled;
- private Runnable mPendingControlTimeout = this::abortPendingImeControlRequest;
+ private final Runnable mPendingControlTimeout = this::abortPendingImeControlRequest;
private final ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
= new ArrayList<>();
@@ -579,7 +589,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
/** Set of inset types which cannot be controlled by the user animation */
private @InsetsType int mDisabledUserAnimationInsetsTypes;
- private Runnable mInvokeControllableInsetsChangedListeners =
+ private final Runnable mInvokeControllableInsetsChangedListeners =
this::invokeControllableInsetsChangedListeners;
public InsetsController(Host host) {
@@ -607,23 +617,23 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
final List<WindowInsetsAnimation> runningAnimations = new ArrayList<>();
+ final List<WindowInsetsAnimation> finishedAnimations = new ArrayList<>();
final InsetsState state = new InsetsState(mState, true /* copySources */);
for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
RunningAnimation runningAnimation = mRunningAnimations.get(i);
if (DEBUG) Log.d(TAG, "Running animation type: " + runningAnimation.type);
- InsetsAnimationControlRunner runner = runningAnimation.runner;
- if (runner instanceof InsetsAnimationControlImpl) {
- InsetsAnimationControlImpl control = (InsetsAnimationControlImpl) runner;
+ final InsetsAnimationControlRunner runner = runningAnimation.runner;
+ if (runner instanceof WindowInsetsAnimationController) {
// Keep track of running animation to be dispatched. Aggregate it here such that
// if it gets finished within applyChangeInsets we still dispatch it to
// onProgress.
if (runningAnimation.startDispatched) {
- runningAnimations.add(control.getAnimation());
+ runningAnimations.add(runner.getAnimation());
}
- if (control.applyChangeInsets(state)) {
- mTmpFinishedControls.add(control);
+ if (((InternalInsetsAnimationController) runner).applyChangeInsets(state)) {
+ finishedAnimations.add(runner.getAnimation());
}
}
}
@@ -641,10 +651,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
}
- for (int i = mTmpFinishedControls.size() - 1; i >= 0; i--) {
- dispatchAnimationEnd(mTmpFinishedControls.get(i).getAnimation());
+ for (int i = finishedAnimations.size() - 1; i >= 0; i--) {
+ dispatchAnimationEnd(finishedAnimations.get(i));
}
- mTmpFinishedControls.clear();
};
}
@@ -690,15 +699,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
true /* excludeInvisibleIme */)) {
if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged");
mHost.notifyInsetsChanged();
+ startResizingAnimationIfNeeded(lastState);
}
return true;
}
private void updateState(InsetsState newState) {
- mState.setDisplayFrame(newState.getDisplayFrame());
- mState.setDisplayCutout(newState.getDisplayCutout());
- mState.setRoundedCorners(newState.getRoundedCorners());
- mState.setPrivacyIndicatorBounds(newState.getPrivacyIndicatorBounds());
+ mState.set(newState, 0 /* types */);
@InsetsType int disabledUserAnimationTypes = 0;
@InsetsType int[] cancelledUserAnimationTypes = {0};
for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
@@ -763,6 +770,39 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
return false;
}
+ private void startResizingAnimationIfNeeded(InsetsState fromState) {
+ if (!fromState.getDisplayFrame().equals(mState.getDisplayFrame())) {
+ return;
+ }
+ @InsetsType int types = 0;
+ InsetsState toState = null;
+ final ArraySet<Integer> internalTypes = InsetsState.toInternalType(Type.systemBars());
+ for (int i = internalTypes.size() - 1; i >= 0; i--) {
+ final @InternalInsetsType int type = internalTypes.valueAt(i);
+ final InsetsSource fromSource = fromState.peekSource(type);
+ final InsetsSource toSource = mState.peekSource(type);
+ if (fromSource != null && toSource != null
+ && fromSource.isVisible() && toSource.isVisible()
+ && !fromSource.getFrame().equals(toSource.getFrame())
+ && (Rect.intersects(mFrame, fromSource.getFrame())
+ || Rect.intersects(mFrame, toSource.getFrame()))) {
+ types |= InsetsState.toPublicType(toSource.getType());
+ if (toState == null) {
+ toState = new InsetsState();
+ }
+ toState.addSource(new InsetsSource(toSource));
+ }
+ }
+ if (types == 0) {
+ return;
+ }
+ cancelExistingControllers(types);
+ final InsetsAnimationControlRunner runner = new InsetsResizeAnimationRunner(
+ mFrame, fromState, toState, RESIZE_INTERPOLATOR, ANIMATION_DURATION_RESIZE, types,
+ this);
+ mRunningAnimations.add(new RunningAnimation(runner, runner.getAnimationType()));
+ }
+
/**
* @see InsetsState#calculateInsets
*/
@@ -784,7 +824,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
/**
* @see InsetsState#calculateVisibleInsets(Rect, int)
*/
- public Rect calculateVisibleInsets(@SoftInputModeFlags int softInputMode) {
+ public Insets calculateVisibleInsets(@SoftInputModeFlags int softInputMode) {
return mState.calculateVisibleInsets(mFrame, softInputMode);
}
@@ -801,7 +841,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
}
- boolean requestedStateStale = false;
+ boolean requestedVisibilityStale = false;
final int[] showTypes = new int[1];
final int[] hideTypes = new int[1];
@@ -822,20 +862,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
final InsetsSourceConsumer consumer = getSourceConsumer(type);
consumer.setControl(control, showTypes, hideTypes);
- if (!requestedStateStale) {
+ if (!requestedVisibilityStale) {
final boolean requestedVisible = consumer.isRequestedVisible();
// We might have changed our requested visibilities while we don't have the control,
// so we need to update our requested state once we have control. Otherwise, our
// requested state at the server side might be incorrect.
final boolean requestedVisibilityChanged =
- requestedVisible != mRequestedState.getSourceOrDefaultVisibility(type);
+ requestedVisible != mRequestedVisibilities.getVisibility(type);
// The IME client visibility will be reset by insets source provider while updating
// control, so if IME is requested visible, we need to send the request to server.
final boolean imeRequestedVisible = type == ITYPE_IME && requestedVisible;
- requestedStateStale = requestedVisibilityChanged || imeRequestedVisible;
+ requestedVisibilityStale = requestedVisibilityChanged || imeRequestedVisible;
}
}
@@ -861,7 +901,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
// InsetsSourceConsumer#setControl might change the requested visibility.
- updateRequestedVisibility();
+ updateRequestedVisibilities();
}
@Override
@@ -1015,7 +1055,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
if (types == 0) {
// nothing to animate.
listener.onCancelled(null);
- updateRequestedVisibility();
+ updateRequestedVisibilities();
if (DEBUG) Log.d(TAG, "no types to animate in controlAnimationUnchecked");
return;
}
@@ -1051,7 +1091,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
});
}
- updateRequestedVisibility();
+ updateRequestedVisibilities();
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
return;
}
@@ -1059,7 +1099,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
if (typesReady == 0) {
if (DEBUG) Log.d(TAG, "No types ready. onCancelled()");
listener.onCancelled(null);
- updateRequestedVisibility();
+ updateRequestedVisibilities();
return;
}
@@ -1091,7 +1131,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
} else {
hideDirectly(types, false /* animationFinished */, animationType, fromIme);
}
- updateRequestedVisibility();
+ updateRequestedVisibilities();
}
/**
@@ -1348,7 +1388,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
/**
* Sends the requested visibilities to window manager if any of them is changed.
*/
- private void updateRequestedVisibility() {
+ private void updateRequestedVisibilities() {
boolean changed = false;
for (int i = mRequestedVisibilityChanged.size() - 1; i >= 0; i--) {
final InsetsSourceConsumer consumer = mRequestedVisibilityChanged.valueAt(i);
@@ -1357,8 +1397,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
continue;
}
final boolean requestedVisible = consumer.isRequestedVisible();
- if (requestedVisible != mRequestedState.getSourceOrDefaultVisibility(type)) {
- mRequestedState.getSource(type).setVisible(requestedVisible);
+ if (mRequestedVisibilities.getVisibility(type) != requestedVisible) {
+ mRequestedVisibilities.setVisibility(type, requestedVisible);
changed = true;
}
}
@@ -1366,11 +1406,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
if (!changed) {
return;
}
- mHost.onInsetsModified(mRequestedState);
+ mHost.updateRequestedVisibilities(mRequestedVisibilities);
}
- InsetsState getRequestedVisibility() {
- return mRequestedState;
+ InsetsVisibilities getRequestedVisibilities() {
+ return mRequestedVisibilities;
}
@VisibleForTesting
@@ -1425,7 +1465,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
for (int i = internalTypes.size() - 1; i >= 0; i--) {
getSourceConsumer(internalTypes.valueAt(i)).hide(animationFinished, animationType);
}
- updateRequestedVisibility();
+ updateRequestedVisibilities();
if (fromIme) {
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromIme", 0);
@@ -1441,7 +1481,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
for (int i = internalTypes.size() - 1; i >= 0; i--) {
getSourceConsumer(internalTypes.valueAt(i)).show(false /* fromIme */);
}
- updateRequestedVisibility();
+ updateRequestedVisibilities();
if (fromIme) {
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromIme", 0);
@@ -1473,12 +1513,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
@VisibleForTesting
@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) {
mHost.dispatchWindowInsetsAnimationPrepare(animation);
mHost.addOnPreDrawRunnable(() -> {
- if (controller.isCancelled()) {
+ if (runner.isCancelled()) {
if (WARN) Log.w(TAG, "startAnimation canceled before preDraw");
return;
}
@@ -1486,15 +1526,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
"InsetsAnimation: " + WindowInsets.Type.toString(types), types);
for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
RunningAnimation runningAnimation = mRunningAnimations.get(i);
- if (runningAnimation.runner == controller) {
+ if (runningAnimation.runner == runner) {
runningAnimation.startDispatched = true;
}
}
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.pendingAnim", 0);
mHost.dispatchWindowInsetsAnimationStart(animation, bounds);
mStartingAnimation = true;
- controller.mReadyDispatched = true;
- listener.onReady(controller, types);
+ runner.setReadyDispatched(true);
+ listener.onReady(runner, types);
mStartingAnimation = false;
});
}
diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java
new file mode 100644
index 000000000000..e1352dd8dd4f
--- /dev/null
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -0,0 +1,235 @@
+/*
+ * 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 android.view;
+
+import static android.view.InsetsAnimationControlImplProto.CURRENT_ALPHA;
+import static android.view.InsetsAnimationControlImplProto.IS_CANCELLED;
+import static android.view.InsetsAnimationControlImplProto.IS_FINISHED;
+import static android.view.InsetsAnimationControlImplProto.PENDING_ALPHA;
+import static android.view.InsetsAnimationControlImplProto.PENDING_FRACTION;
+import static android.view.InsetsAnimationControlImplProto.PENDING_INSETS;
+import static android.view.InsetsAnimationControlImplProto.SHOWN_ON_FINISH;
+import static android.view.InsetsAnimationControlImplProto.TMP_MATRIX;
+import static android.view.InsetsController.ANIMATION_TYPE_RESIZE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+import android.view.InsetsState.InternalInsetsType;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowInsetsAnimation.Bounds;
+import android.view.animation.Interpolator;
+
+/**
+ * Runs a fake animation of resizing insets to produce insets animation callbacks.
+ * @hide
+ */
+public class InsetsResizeAnimationRunner implements InsetsAnimationControlRunner,
+ InternalInsetsAnimationController, WindowInsetsAnimationControlListener {
+
+ private final InsetsState mFromState;
+ private final InsetsState mToState;
+ private final @InsetsType int mTypes;
+ private final WindowInsetsAnimation mAnimation;
+ private final InsetsAnimationControlCallbacks mController;
+ private ValueAnimator mAnimator;
+ private boolean mCancelled;
+ private boolean mFinished;
+
+ public InsetsResizeAnimationRunner(Rect frame, InsetsState fromState, InsetsState toState,
+ Interpolator interpolator, long duration, @InsetsType int types,
+ InsetsAnimationControlCallbacks controller) {
+ mFromState = fromState;
+ mToState = toState;
+ mTypes = types;
+ mController = controller;
+ mAnimation = new WindowInsetsAnimation(types, interpolator, duration);
+ mAnimation.setAlpha(1f);
+ final Insets fromInsets = fromState.calculateInsets(
+ frame, types, false /* ignoreVisibility */);
+ final Insets toInsets = toState.calculateInsets(
+ frame, types, false /* ignoreVisibility */);
+ controller.startAnimation(this, this, types, mAnimation,
+ new Bounds(Insets.min(fromInsets, toInsets), Insets.max(fromInsets, toInsets)));
+ }
+
+ @Override
+ public int getTypes() {
+ return mTypes;
+ }
+
+ @Override
+ public int getControllingTypes() {
+ return mTypes;
+ }
+
+ @Override
+ public WindowInsetsAnimation getAnimation() {
+ return mAnimation;
+ }
+
+ @Override
+ public int getAnimationType() {
+ return ANIMATION_TYPE_RESIZE;
+ }
+
+ @Override
+ public void cancel() {
+ if (mCancelled || mFinished) {
+ return;
+ }
+ mCancelled = true;
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return mCancelled;
+ }
+
+ @Override
+ public void onReady(WindowInsetsAnimationController controller, int types) {
+ if (mCancelled) {
+ return;
+ }
+ mAnimator = ValueAnimator.ofFloat(0f, 1f);
+ mAnimator.setDuration(mAnimation.getDurationMillis());
+ mAnimator.addUpdateListener(animation -> {
+ mAnimation.setFraction(animation.getAnimatedFraction());
+ mController.scheduleApplyChangeInsets(InsetsResizeAnimationRunner.this);
+ });
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mFinished = true;
+ mController.scheduleApplyChangeInsets(InsetsResizeAnimationRunner.this);
+ }
+ });
+ mAnimator.start();
+ }
+
+ @Override
+ public boolean applyChangeInsets(InsetsState outState) {
+ final float fraction = mAnimation.getInterpolatedFraction();
+ for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
+ final InsetsSource fromSource = mFromState.peekSource(type);
+ final InsetsSource toSource = mToState.peekSource(type);
+ if (fromSource == null || toSource == null) {
+ continue;
+ }
+ final Rect fromFrame = fromSource.getFrame();
+ final Rect toFrame = toSource.getFrame();
+ final Rect frame = new Rect(
+ (int) (fromFrame.left + fraction * (toFrame.left - fromFrame.left)),
+ (int) (fromFrame.top + fraction * (toFrame.top - fromFrame.top)),
+ (int) (fromFrame.right + fraction * (toFrame.right - fromFrame.right)),
+ (int) (fromFrame.bottom + fraction * (toFrame.bottom - fromFrame.bottom)));
+ final InsetsSource source = new InsetsSource(type);
+ source.setFrame(frame);
+ source.setVisible(toSource.isVisible());
+ outState.addSource(source);
+ }
+ if (mFinished) {
+ mController.notifyFinished(this, true /* shown */);
+ }
+ return mFinished;
+ }
+
+ @Override
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(IS_CANCELLED, mCancelled);
+ proto.write(IS_FINISHED, mFinished);
+ proto.write(TMP_MATRIX, "null");
+ proto.write(PENDING_INSETS, "null");
+ proto.write(PENDING_FRACTION, mAnimation.getInterpolatedFraction());
+ proto.write(SHOWN_ON_FINISH, true);
+ proto.write(CURRENT_ALPHA, 1f);
+ proto.write(PENDING_ALPHA, 1f);
+ proto.end(token);
+ }
+
+ @Override
+ public Insets getHiddenStateInsets() {
+ return Insets.NONE;
+ }
+
+ @Override
+ public Insets getShownStateInsets() {
+ return Insets.NONE;
+ }
+
+ @Override
+ public Insets getCurrentInsets() {
+ return Insets.NONE;
+ }
+
+ @Override
+ public float getCurrentFraction() {
+ return 0;
+ }
+
+ @Override
+ public float getCurrentAlpha() {
+ return 0;
+ }
+
+ @Override
+ public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
+ }
+
+ @Override
+ public void finish(boolean shown) {
+ }
+
+ @Override
+ public boolean isFinished() {
+ return false;
+ }
+
+ @Override
+ public void notifyControlRevoked(int types) {
+ }
+
+ @Override
+ public void updateSurfacePosition(SparseArray<InsetsSourceControl> controls) {
+ }
+
+ @Override
+ public boolean hasZeroInsetsIme() {
+ return false;
+ }
+
+ @Override
+ public void setReadyDispatched(boolean dispatched) {
+ }
+
+ @Override
+ public void onFinished(WindowInsetsAnimationController controller) {
+ }
+
+ @Override
+ public void onCancelled(WindowInsetsAnimationController controller) {
+ }
+}
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 1506ee4c2c7a..9d98a3e4b0e1 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -185,6 +185,15 @@ public class InsetsSourceControl implements Parcelable {
return result;
}
+ @Override
+ public String toString() {
+ return "InsetsSourceControl: {"
+ + "type=" + InsetsState.typeToString(mType)
+ + ", mSurfacePosition=" + mSurfacePosition
+ + ", mInsetsHint=" + mInsetsHint
+ + "}";
+ }
+
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix);
pw.print("InsetsSourceControl type="); pw.print(InsetsState.typeToString(mType));
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 37101b757e66..39172794f602 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -202,7 +202,7 @@ public class InsetsState implements Parcelable {
@Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
Insets[] typeInsetsMap = new Insets[Type.SIZE];
Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
- boolean[] typeVisibilityMap = new boolean[SIZE];
+ boolean[] typeVisibilityMap = new boolean[Type.SIZE];
final Rect relativeFrame = new Rect(frame);
final Rect relativeFrameMax = new Rect(frame);
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
@@ -301,7 +301,7 @@ public class InsetsState implements Parcelable {
return mPrivacyIndicatorBounds.inset(insetLeft, insetTop, insetRight, insetBottom);
}
- public Rect calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) {
+ public Insets calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) {
Insets insets = Insets.NONE;
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
InsetsSource source = mSources[type];
@@ -314,10 +314,10 @@ public class InsetsState implements Parcelable {
}
insets = Insets.max(source.calculateInsets(frame, ignoreVisibility), insets);
}
- return insets.toRect();
+ return insets;
}
- public Rect calculateVisibleInsets(Rect frame, @SoftInputModeFlags int softInputMode) {
+ public Insets calculateVisibleInsets(Rect frame, @SoftInputModeFlags int softInputMode) {
Insets insets = Insets.NONE;
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
InsetsSource source = mSources[type];
@@ -332,7 +332,7 @@ public class InsetsState implements Parcelable {
}
insets = Insets.max(source.calculateVisibleInsets(frame), insets);
}
- return insets.toRect();
+ return insets;
}
/**
@@ -878,16 +878,5 @@ public class InsetsState implements Parcelable {
+ ", mSources= { " + joiner
+ " }";
}
-
- public @NonNull String toSourceVisibilityString() {
- StringJoiner joiner = new StringJoiner(", ");
- for (int i = 0; i < SIZE; i++) {
- InsetsSource source = mSources[i];
- if (source != null) {
- joiner.add(typeToString(i) + ": " + (source.isVisible() ? "visible" : "invisible"));
- }
- }
- return joiner.toString();
- }
}
diff --git a/core/java/android/view/InsetsVisibilities.aidl b/core/java/android/view/InsetsVisibilities.aidl
new file mode 100644
index 000000000000..bd573ea7bc35
--- /dev/null
+++ b/core/java/android/view/InsetsVisibilities.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.view;
+
+parcelable InsetsVisibilities;
diff --git a/core/java/android/view/InsetsVisibilities.java b/core/java/android/view/InsetsVisibilities.java
new file mode 100644
index 000000000000..7d259fb91634
--- /dev/null
+++ b/core/java/android/view/InsetsVisibilities.java
@@ -0,0 +1,134 @@
+/*
+ * 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 android.view;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.StringJoiner;
+
+/**
+ * A collection of visibilities of insets. This is used for carrying the requested visibilities.
+ * @hide
+ */
+public class InsetsVisibilities implements Parcelable {
+
+ private static final int UNSPECIFIED = 0;
+ private static final int VISIBLE = 1;
+ private static final int INVISIBLE = -1;
+
+ private final int[] mVisibilities = new int[InsetsState.SIZE];
+
+ public InsetsVisibilities() {
+ }
+
+ public InsetsVisibilities(InsetsVisibilities other) {
+ set(other);
+ }
+
+ public InsetsVisibilities(Parcel in) {
+ in.readIntArray(mVisibilities);
+ }
+
+ /**
+ * Copies from another {@link InsetsVisibilities}.
+ *
+ * @param other an instance of {@link InsetsVisibilities}.
+ */
+ public void set(InsetsVisibilities other) {
+ System.arraycopy(other.mVisibilities, InsetsState.FIRST_TYPE, mVisibilities,
+ InsetsState.FIRST_TYPE, InsetsState.SIZE);
+ }
+
+ /**
+ * Sets a visibility to a type.
+ *
+ * @param type The {@link @InsetsState.InternalInsetsType}.
+ * @param visible {@code true} represents visible; {@code false} represents invisible.
+ */
+ public void setVisibility(@InsetsState.InternalInsetsType int type, boolean visible) {
+ mVisibilities[type] = visible ? VISIBLE : INVISIBLE;
+ }
+
+ /**
+ * Returns the specified insets visibility of the type. If it has never been specified,
+ * this returns the default visibility.
+ *
+ * @param type The {@link @InsetsState.InternalInsetsType}.
+ * @return The specified visibility or the default one if it is not specified.
+ */
+ public boolean getVisibility(@InsetsState.InternalInsetsType int type) {
+ final int visibility = mVisibilities[type];
+ return visibility == UNSPECIFIED
+ ? InsetsState.getDefaultVisibility(type)
+ : visibility == VISIBLE;
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner joiner = new StringJoiner(", ");
+ for (int type = InsetsState.FIRST_TYPE; type <= InsetsState.LAST_TYPE; type++) {
+ final int visibility = mVisibilities[type];
+ if (visibility != UNSPECIFIED) {
+ joiner.add(InsetsState.typeToString(type) + ": "
+ + (visibility == VISIBLE ? "visible" : "invisible"));
+ }
+ }
+ return joiner.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(mVisibilities);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof InsetsVisibilities)) {
+ return false;
+ }
+ return Arrays.equals(mVisibilities, ((InsetsVisibilities) other).mVisibilities);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeIntArray(mVisibilities);
+ }
+
+ public void readFromParcel(@NonNull Parcel in) {
+ in.readIntArray(mVisibilities);
+ }
+
+ public static final @NonNull Creator<InsetsVisibilities> CREATOR =
+ new Creator<InsetsVisibilities>() {
+
+ public InsetsVisibilities createFromParcel(Parcel in) {
+ return new InsetsVisibilities(in);
+ }
+
+ public InsetsVisibilities[] newArray(int size) {
+ return new InsetsVisibilities[size];
+ }
+ };
+}
diff --git a/core/java/android/view/InternalInsetsAnimationController.java b/core/java/android/view/InternalInsetsAnimationController.java
new file mode 100644
index 000000000000..d7f3e20b80c5
--- /dev/null
+++ b/core/java/android/view/InternalInsetsAnimationController.java
@@ -0,0 +1,41 @@
+/*
+ * 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 android.view;
+
+/**
+ * An internal interface which provides methods that will be only used by the framework.
+ * @hide
+ */
+public interface InternalInsetsAnimationController extends WindowInsetsAnimationController {
+
+ /**
+ * Flags whether {@link WindowInsetsAnimationControlListener#onReady(
+ * WindowInsetsAnimationController, int)} has been invoked.
+ * @hide
+ */
+ void setReadyDispatched(boolean dispatched);
+
+ /**
+ * Returns the {@link InsetsState} based on the current animation progress.
+ *
+ * @param outState the insets state which matches the current animation progress.
+ * @return {@code true} if the animation has been finished; {@code false} otherwise.
+ * @hide
+ */
+ boolean applyChangeInsets(InsetsState outState);
+}
+
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 69ff64f3d6a5..40942ea7f551 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -4008,6 +4008,22 @@ public final class MotionEvent extends InputEvent implements Parcelable {
public float orientation;
/**
+ * The movement of x position of a motion event.
+ *
+ * @see MotionEvent#AXIS_RELATIVE_X
+ * @hide
+ */
+ public float relativeX;
+
+ /**
+ * The movement of y position of a motion event.
+ *
+ * @see MotionEvent#AXIS_RELATIVE_Y
+ * @hide
+ */
+ public float relativeY;
+
+ /**
* Clears the contents of this object.
* Resets all axes to zero.
*/
@@ -4023,6 +4039,8 @@ public final class MotionEvent extends InputEvent implements Parcelable {
toolMajor = 0;
toolMinor = 0;
orientation = 0;
+ relativeX = 0;
+ relativeY = 0;
}
/**
@@ -4053,6 +4071,8 @@ public final class MotionEvent extends InputEvent implements Parcelable {
toolMajor = other.toolMajor;
toolMinor = other.toolMinor;
orientation = other.orientation;
+ relativeX = other.relativeX;
+ relativeY = other.relativeY;
}
/**
@@ -4084,6 +4104,10 @@ public final class MotionEvent extends InputEvent implements Parcelable {
return toolMinor;
case AXIS_ORIENTATION:
return orientation;
+ case AXIS_RELATIVE_X:
+ return relativeX;
+ case AXIS_RELATIVE_Y:
+ return relativeY;
default: {
if (axis < 0 || axis > 63) {
throw new IllegalArgumentException("Axis out of range.");
@@ -4137,6 +4161,12 @@ public final class MotionEvent extends InputEvent implements Parcelable {
case AXIS_ORIENTATION:
orientation = value;
break;
+ case AXIS_RELATIVE_X:
+ relativeX = value;
+ break;
+ case AXIS_RELATIVE_Y:
+ relativeY = value;
+ break;
default: {
if (axis < 0 || axis > 63) {
throw new IllegalArgumentException("Axis out of range.");
diff --git a/core/java/android/view/RemoteAnimationAdapter.java b/core/java/android/view/RemoteAnimationAdapter.java
index a78036fba094..e1cc60491f72 100644
--- a/core/java/android/view/RemoteAnimationAdapter.java
+++ b/core/java/android/view/RemoteAnimationAdapter.java
@@ -17,6 +17,7 @@
package android.view;
import android.app.ActivityOptions;
+import android.app.IApplicationThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
@@ -58,6 +59,9 @@ public class RemoteAnimationAdapter implements Parcelable {
private int mCallingPid;
private int mCallingUid;
+ /** @see #getCallingApplication */
+ private IApplicationThread mCallingApplication;
+
/**
* @param runner The interface that gets notified when we actually need to start the animation.
* @param duration The duration of the animation.
@@ -81,11 +85,19 @@ public class RemoteAnimationAdapter implements Parcelable {
this(runner, duration, statusBarTransitionDelay, false /* changeNeedsSnapshot */);
}
+ @UnsupportedAppUsage
+ public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration,
+ long statusBarTransitionDelay, IApplicationThread callingApplication) {
+ this(runner, duration, statusBarTransitionDelay, false /* changeNeedsSnapshot */);
+ mCallingApplication = callingApplication;
+ }
+
public RemoteAnimationAdapter(Parcel in) {
mRunner = IRemoteAnimationRunner.Stub.asInterface(in.readStrongBinder());
mDuration = in.readLong();
mStatusBarTransitionDelay = in.readLong();
mChangeNeedsSnapshot = in.readBoolean();
+ mCallingApplication = IApplicationThread.Stub.asInterface(in.readStrongBinder());
}
public IRemoteAnimationRunner getRunner() {
@@ -126,6 +138,15 @@ public class RemoteAnimationAdapter implements Parcelable {
return mCallingUid;
}
+ /**
+ * Gets the ApplicationThread that will run the animation. Instead it is intended to pass the
+ * calling information among client processes (eg. shell + launcher) through one-way binder
+ * calls (where binder itself doesn't track calling information).
+ */
+ public IApplicationThread getCallingApplication() {
+ return mCallingApplication;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -137,6 +158,7 @@ public class RemoteAnimationAdapter implements Parcelable {
dest.writeLong(mDuration);
dest.writeLong(mStatusBarTransitionDelay);
dest.writeBoolean(mChangeNeedsSnapshot);
+ dest.writeStrongInterface(mCallingApplication);
}
public static final @android.annotation.NonNull Creator<RemoteAnimationAdapter> CREATOR
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index cdc099b8e2ea..046232a8afaf 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -197,19 +197,35 @@ public class RemoteAnimationTarget implements Parcelable {
public ActivityManager.RunningTaskInfo taskInfo;
/**
+ * {@code true} if picture-in-picture permission is granted in {@link android.app.AppOpsManager}
+ */
+ @UnsupportedAppUsage
+ public boolean allowEnterPip;
+
+ /**
* The {@link android.view.WindowManager.LayoutParams.WindowType} of this window. It's only used
* for non-app window.
*/
public final @WindowManager.LayoutParams.WindowType int windowType;
+ /**
+ * {@code true} if its parent is also a {@link RemoteAnimationTarget} in the same transition.
+ *
+ * For example, when a TaskFragment is resizing while one of its children is open/close, both
+ * windows will be animation targets. This value will be {@code true} for the child, so that
+ * the handler can choose to handle it differently.
+ */
+ public boolean hasAnimatingParent;
+
public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
Rect localBounds, Rect screenSpaceBounds,
WindowConfiguration windowConfig, boolean isNotInRecents,
- SurfaceControl startLeash, Rect startBounds, ActivityManager.RunningTaskInfo taskInfo) {
+ SurfaceControl startLeash, Rect startBounds, ActivityManager.RunningTaskInfo taskInfo,
+ boolean allowEnterPip) {
this(taskId, mode, leash, isTranslucent, clipRect, contentInsets, prefixOrderIndex,
position, localBounds, screenSpaceBounds, windowConfig, isNotInRecents, startLeash,
- startBounds, taskInfo, INVALID_WINDOW_TYPE);
+ startBounds, taskInfo, allowEnterPip, INVALID_WINDOW_TYPE);
}
public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
@@ -217,7 +233,7 @@ public class RemoteAnimationTarget implements Parcelable {
Rect localBounds, Rect screenSpaceBounds,
WindowConfiguration windowConfig, boolean isNotInRecents,
SurfaceControl startLeash, Rect startBounds,
- ActivityManager.RunningTaskInfo taskInfo,
+ ActivityManager.RunningTaskInfo taskInfo, boolean allowEnterPip,
@WindowManager.LayoutParams.WindowType int windowType) {
this.mode = mode;
this.taskId = taskId;
@@ -226,7 +242,7 @@ public class RemoteAnimationTarget implements Parcelable {
this.clipRect = new Rect(clipRect);
this.contentInsets = new Rect(contentInsets);
this.prefixOrderIndex = prefixOrderIndex;
- this.position = new Point(position);
+ this.position = position == null ? new Point() : new Point(position);
this.localBounds = new Rect(localBounds);
this.sourceContainerBounds = new Rect(screenSpaceBounds);
this.screenSpaceBounds = new Rect(screenSpaceBounds);
@@ -235,6 +251,7 @@ public class RemoteAnimationTarget implements Parcelable {
this.startLeash = startLeash;
this.startBounds = startBounds == null ? null : new Rect(startBounds);
this.taskInfo = taskInfo;
+ this.allowEnterPip = allowEnterPip;
this.windowType = windowType;
}
@@ -255,7 +272,9 @@ public class RemoteAnimationTarget implements Parcelable {
startLeash = in.readTypedObject(SurfaceControl.CREATOR);
startBounds = in.readTypedObject(Rect.CREATOR);
taskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
+ allowEnterPip = in.readBoolean();
windowType = in.readInt();
+ hasAnimatingParent = in.readBoolean();
}
@Override
@@ -281,7 +300,9 @@ public class RemoteAnimationTarget implements Parcelable {
dest.writeTypedObject(startLeash, 0 /* flags */);
dest.writeTypedObject(startBounds, 0 /* flags */);
dest.writeTypedObject(taskInfo, 0 /* flags */);
+ dest.writeBoolean(allowEnterPip);
dest.writeInt(windowType);
+ dest.writeBoolean(hasAnimatingParent);
}
public void dump(PrintWriter pw, String prefix) {
@@ -299,7 +320,9 @@ public class RemoteAnimationTarget implements Parcelable {
pw.print(prefix); pw.print("windowConfiguration="); pw.println(windowConfiguration);
pw.print(prefix); pw.print("leash="); pw.println(leash);
pw.print(prefix); pw.print("taskInfo="); pw.println(taskInfo);
+ pw.print(prefix); pw.print("allowEnterPip="); pw.println(allowEnterPip);
pw.print(prefix); pw.print("windowType="); pw.print(windowType);
+ pw.print(prefix); pw.print("hasAnimatingParent="); pw.print(hasAnimatingParent);
}
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/core/java/android/view/RoundedCorners.java b/core/java/android/view/RoundedCorners.java
index 623d9692ac80..6079d8e3f118 100644
--- a/core/java/android/view/RoundedCorners.java
+++ b/core/java/android/view/RoundedCorners.java
@@ -27,9 +27,11 @@ import static android.view.Surface.ROTATION_90;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Point;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.DisplayUtils;
import android.util.Pair;
import android.view.RoundedCorner.Position;
@@ -94,8 +96,8 @@ public class RoundedCorners implements Parcelable {
* @android:dimen/rounded_corner_radius_top and @android:dimen/rounded_corner_radius_bottom
*/
public static RoundedCorners fromResources(
- Resources res, int displayWidth, int displayHeight) {
- return fromRadii(loadRoundedCornerRadii(res), displayWidth, displayHeight);
+ Resources res, String displayUniqueId, int displayWidth, int displayHeight) {
+ return fromRadii(loadRoundedCornerRadii(res, displayUniqueId), displayWidth, displayHeight);
}
/**
@@ -140,14 +142,16 @@ public class RoundedCorners implements Parcelable {
* Loads the rounded corner radii from resources.
*
* @param res
+ * @param displayUniqueId the display unique id.
* @return a Pair of radius. The first is the top rounded corner radius and second is the
* bottom corner radius.
*/
@Nullable
- private static Pair<Integer, Integer> loadRoundedCornerRadii(Resources res) {
- final int radiusDefault = res.getDimensionPixelSize(R.dimen.rounded_corner_radius);
- final int radiusTop = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_top);
- final int radiusBottom = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_bottom);
+ private static Pair<Integer, Integer> loadRoundedCornerRadii(
+ Resources res, String displayUniqueId) {
+ final int radiusDefault = getRoundedCornerRadius(res, displayUniqueId);
+ final int radiusTop = getRoundedCornerTopRadius(res, displayUniqueId);
+ final int radiusBottom = getRoundedCornerBottomRadius(res, displayUniqueId);
if (radiusDefault == 0 && radiusTop == 0 && radiusBottom == 0) {
return null;
}
@@ -158,6 +162,164 @@ public class RoundedCorners implements Parcelable {
}
/**
+ * Gets the default rounded corner radius of a display which is determined by the
+ * given display unique id.
+ *
+ * Loads the default dimen{@link R.dimen#rounded_corner_radius} if
+ * {@link R.array#config_displayUniqueIdArray} is not set.
+ *
+ * @hide
+ */
+ public static int getRoundedCornerRadius(Resources res, String displayUniqueId) {
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final TypedArray array = res.obtainTypedArray(R.array.config_roundedCornerRadiusArray);
+ int radius;
+ if (index >= 0 && index < array.length()) {
+ radius = array.getDimensionPixelSize(index, 0);
+ } else {
+ radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius);
+ }
+ array.recycle();
+ return radius;
+ }
+
+ /**
+ * Gets the top rounded corner radius of a display which is determined by the
+ * given display unique id.
+ *
+ * Loads the default dimen{@link R.dimen#rounded_corner_radius_top} if
+ * {@link R.array#config_displayUniqueIdArray} is not set.
+ *
+ * @hide
+ */
+ public static int getRoundedCornerTopRadius(Resources res, String displayUniqueId) {
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final TypedArray array = res.obtainTypedArray(R.array.config_roundedCornerTopRadiusArray);
+ int radius;
+ if (index >= 0 && index < array.length()) {
+ radius = array.getDimensionPixelSize(index, 0);
+ } else {
+ radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_top);
+ }
+ array.recycle();
+ return radius;
+ }
+
+ /**
+ * Gets the bottom rounded corner radius of a display which is determined by the
+ * given display unique id.
+ *
+ * Loads the default dimen{@link R.dimen#rounded_corner_radius_bottom} if
+ * {@link R.array#config_displayUniqueIdArray} is not set.
+ *
+ * @hide
+ */
+ public static int getRoundedCornerBottomRadius(Resources res, String displayUniqueId) {
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final TypedArray array = res.obtainTypedArray(
+ R.array.config_roundedCornerBottomRadiusArray);
+ int radius;
+ if (index >= 0 && index < array.length()) {
+ radius = array.getDimensionPixelSize(index, 0);
+ } else {
+ radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_bottom);
+ }
+ array.recycle();
+ return radius;
+ }
+
+ /**
+ * Gets the rounded corner radius adjustment of a display which is determined by the
+ * given display unique id.
+ *
+ * Loads the default dimen{@link R.dimen#rounded_corner_radius_adjustment} if
+ * {@link R.array#config_displayUniqueIdArray} is not set.
+ *
+ * @hide
+ */
+ public static int getRoundedCornerRadiusAdjustment(Resources res, String displayUniqueId) {
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final TypedArray array = res.obtainTypedArray(
+ R.array.config_roundedCornerRadiusAdjustmentArray);
+ int radius;
+ if (index >= 0 && index < array.length()) {
+ radius = array.getDimensionPixelSize(index, 0);
+ } else {
+ radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_adjustment);
+ }
+ array.recycle();
+ return radius;
+ }
+
+ /**
+ * Gets the rounded corner top radius adjustment of a display which is determined by the
+ * given display unique id.
+ *
+ * Loads the default dimen{@link R.dimen#rounded_corner_radius_top_adjustment} if
+ * {@link R.array#config_displayUniqueIdArray} is not set.
+ *
+ * @hide
+ */
+ public static int getRoundedCornerRadiusTopAdjustment(Resources res, String displayUniqueId) {
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final TypedArray array = res.obtainTypedArray(
+ R.array.config_roundedCornerTopRadiusAdjustmentArray);
+ int radius;
+ if (index >= 0 && index < array.length()) {
+ radius = array.getDimensionPixelSize(index, 0);
+ } else {
+ radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_top_adjustment);
+ }
+ array.recycle();
+ return radius;
+ }
+
+ /**
+ * Gets the rounded corner bottom radius adjustment of a display which is determined by the
+ * given display unique id.
+ *
+ * Loads the default dimen{@link R.dimen#rounded_corner_radius_bottom_adjustment} if
+ * {@link R.array#config_displayUniqueIdArray} is not set.
+ *
+ * @hide
+ */
+ public static int getRoundedCornerRadiusBottomAdjustment(
+ Resources res, String displayUniqueId) {
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final TypedArray array = res.obtainTypedArray(
+ R.array.config_roundedCornerBottomRadiusAdjustmentArray);
+ int radius;
+ if (index >= 0 && index < array.length()) {
+ radius = array.getDimensionPixelSize(index, 0);
+ } else {
+ radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_bottom_adjustment);
+ }
+ array.recycle();
+ return radius;
+ }
+
+ /**
+ * Gets whether a built-in display is round.
+ *
+ * Loads the default config{@link R.bool#config_mainBuiltInDisplayIsRound} if
+ * {@link R.array#config_displayUniqueIdArray} is not set.
+ *
+ * @hide
+ */
+ public static boolean getBuiltInDisplayIsRound(Resources res, String displayUniqueId) {
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final TypedArray array = res.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray);
+ boolean isRound;
+ if (index >= 0 && index < array.length()) {
+ isRound = array.getBoolean(index, false);
+ } else {
+ isRound = res.getBoolean(R.bool.config_mainBuiltInDisplayIsRound);
+ }
+ array.recycle();
+ return isRound;
+ }
+
+ /**
* Insets the reference frame of the rounded corners.
*
* @return a copy of this instance which has been inset
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index fa7330fb84eb..904aa73f6ac4 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -29,6 +29,7 @@ import android.graphics.Canvas;
import android.graphics.ColorSpace;
import android.graphics.HardwareRenderer;
import android.graphics.Matrix;
+import android.graphics.Point;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RenderNode;
@@ -412,6 +413,20 @@ public class Surface implements Parcelable {
}
/**
+ * Returns the default size of this Surface provided by the consumer of the surface.
+ * Should only be used by the producer of the surface.
+ *
+ * @hide
+ */
+ @NonNull
+ public Point getDefaultSize() {
+ synchronized (mLock) {
+ checkNotReleasedLocked();
+ return new Point(nativeGetWidth(mNativeObject), nativeGetHeight(mNativeObject));
+ }
+ }
+
+ /**
* Gets a {@link Canvas} for drawing into this surface.
*
* After drawing into the provided {@link Canvas}, the caller must
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 8143cf953f19..960d23d7afb0 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -23,6 +23,7 @@ import static android.graphics.Matrix.MSKEW_Y;
import static android.graphics.Matrix.MTRANS_X;
import static android.graphics.Matrix.MTRANS_Y;
import static android.view.SurfaceControlProto.HASH_CODE;
+import static android.view.SurfaceControlProto.LAYER_ID;
import static android.view.SurfaceControlProto.NAME;
import android.annotation.FloatRange;
@@ -41,6 +42,7 @@ import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.gui.DropInputMode;
import android.hardware.HardwareBuffer;
import android.hardware.display.DeviceProductInfo;
import android.hardware.display.DisplayedContentSample;
@@ -150,13 +152,15 @@ public final class SurfaceControl implements Parcelable {
float childRelativeTop, float childRelativeRight, float childRelativeBottom);
private static native void nativeSetTrustedOverlay(long transactionObj, long nativeObject,
boolean isTrustedOverlay);
-
+ private static native void nativeSetDropInputMode(
+ long transactionObj, long nativeObject, int flags);
private static native boolean nativeClearContentFrameStats(long nativeObject);
private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
private static native boolean nativeClearAnimationFrameStats();
private static native boolean nativeGetAnimationFrameStats(WindowAnimationFrameStats outStats);
private static native long[] nativeGetPhysicalDisplayIds();
+ private static native long nativeGetPrimaryPhysicalDisplayId();
private static native IBinder nativeGetPhysicalDisplayToken(long physicalDisplayId);
private static native IBinder nativeCreateDisplay(String name, boolean secure);
private static native void nativeDestroyDisplay(IBinder displayToken);
@@ -164,6 +168,8 @@ public final class SurfaceControl implements Parcelable {
IBinder displayToken, long nativeSurfaceObject);
private static native void nativeSetDisplayLayerStack(long transactionObj,
IBinder displayToken, int layerStack);
+ private static native void nativeSetDisplayFlags(long transactionObj,
+ IBinder displayToken, int flags);
private static native void nativeSetDisplayProjection(long transactionObj,
IBinder displayToken, int orientation,
int l, int t, int r, int b,
@@ -235,8 +241,80 @@ public final class SurfaceControl implements Parcelable {
private static native void nativeRemoveJankDataListener(long nativeListener);
private static native long nativeCreateJankDataListenerWrapper(OnJankDataListener listener);
private static native int nativeGetGPUContextPriority();
- private static native void nativeSetTransformHint(long nativeObject, int transformHint);
+ private static native void nativeSetTransformHint(long nativeObject,
+ @SurfaceControl.BufferTransform int transformHint);
private static native int nativeGetTransformHint(long nativeObject);
+ private static native int nativeGetLayerId(long nativeObject);
+
+ /**
+ * Transforms that can be applied to buffers as they are displayed to a window.
+ *
+ * Supported transforms are any combination of horizontal mirror, vertical mirror, and
+ * clock-wise 90 degree rotation, in that order. Rotations of 180 and 270 degrees are made up
+ * of those basic transforms.
+ * Mirrors {@code ANativeWindowTransform} definitions.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"BUFFER_TRANSFORM_"},
+ value = {BUFFER_TRANSFORM_IDENTITY, BUFFER_TRANSFORM_MIRROR_HORIZONTAL,
+ BUFFER_TRANSFORM_MIRROR_VERTICAL, BUFFER_TRANSFORM_ROTATE_90,
+ BUFFER_TRANSFORM_ROTATE_180, BUFFER_TRANSFORM_ROTATE_270,
+ BUFFER_TRANSFORM_MIRROR_HORIZONTAL | BUFFER_TRANSFORM_ROTATE_90,
+ BUFFER_TRANSFORM_MIRROR_VERTICAL | BUFFER_TRANSFORM_ROTATE_90})
+ public @interface BufferTransform {
+ }
+
+ /**
+ * Identity transform.
+ *
+ * These transforms that can be applied to buffers as they are displayed to a window.
+ * @see HardwareBuffer
+ *
+ * Supported transforms are any combination of horizontal mirror, vertical mirror, and
+ * clock-wise 90 degree rotation, in that order. Rotations of 180 and 270 degrees are
+ * made up of those basic transforms.
+ */
+ public static final int BUFFER_TRANSFORM_IDENTITY = 0x00;
+ /**
+ * Mirror horizontally. Can be combined with {@link #BUFFER_TRANSFORM_MIRROR_VERTICAL}
+ * and {@link #BUFFER_TRANSFORM_ROTATE_90}.
+ */
+ public static final int BUFFER_TRANSFORM_MIRROR_HORIZONTAL = 0x01;
+ /**
+ * Mirror vertically. Can be combined with {@link #BUFFER_TRANSFORM_MIRROR_HORIZONTAL}
+ * and {@link #BUFFER_TRANSFORM_ROTATE_90}.
+ */
+ public static final int BUFFER_TRANSFORM_MIRROR_VERTICAL = 0x02;
+ /**
+ * Rotate 90 degrees clock-wise. Can be combined with {@link
+ * #BUFFER_TRANSFORM_MIRROR_HORIZONTAL} and {@link #BUFFER_TRANSFORM_MIRROR_VERTICAL}.
+ */
+ public static final int BUFFER_TRANSFORM_ROTATE_90 = 0x04;
+ /**
+ * Rotate 180 degrees clock-wise. Cannot be combined with other transforms.
+ */
+ public static final int BUFFER_TRANSFORM_ROTATE_180 =
+ BUFFER_TRANSFORM_MIRROR_HORIZONTAL | BUFFER_TRANSFORM_MIRROR_VERTICAL;
+ /**
+ * Rotate 270 degrees clock-wise. Cannot be combined with other transforms.
+ */
+ public static final int BUFFER_TRANSFORM_ROTATE_270 =
+ BUFFER_TRANSFORM_ROTATE_180 | BUFFER_TRANSFORM_ROTATE_90;
+
+ /**
+ * @hide
+ */
+ public static @BufferTransform int rotationToBufferTransform(@Surface.Rotation int rotation) {
+ switch (rotation) {
+ case Surface.ROTATION_0: return BUFFER_TRANSFORM_IDENTITY;
+ case Surface.ROTATION_90: return BUFFER_TRANSFORM_ROTATE_90;
+ case Surface.ROTATION_180: return BUFFER_TRANSFORM_ROTATE_180;
+ case Surface.ROTATION_270: return BUFFER_TRANSFORM_ROTATE_270;
+ }
+ Log.e(TAG, "Trying to convert unknown rotation=" + rotation);
+ return BUFFER_TRANSFORM_IDENTITY;
+ }
@Nullable
@GuardedBy("mLock")
@@ -352,8 +430,6 @@ public final class SurfaceControl implements Parcelable {
@GuardedBy("mLock")
private int mHeight;
- private int mTransformHint;
-
private WeakReference<View> mLocalOwnerView;
static GlobalTransactionWrapper sGlobalTransaction;
@@ -550,6 +626,15 @@ public final class SurfaceControl implements Parcelable {
*/
private static final int SURFACE_OPAQUE = 0x02;
+ /* flags used with setDisplayFlags() (keep in sync with DisplayDevice.h) */
+
+ /**
+ * DisplayDevice flag: This display's transform is sent to inputflinger and used for input
+ * dispatch. This flag is used to disambiguate displays which share a layerstack.
+ * @hide
+ */
+ public static final int DISPLAY_RECEIVES_INPUT = 0x01;
+
// Display power modes.
/**
* Display power mode off: used while blanking the screen.
@@ -1527,6 +1612,7 @@ public final class SurfaceControl implements Parcelable {
final long token = proto.start(fieldId);
proto.write(HASH_CODE, System.identityHashCode(this));
proto.write(NAME, mName);
+ proto.write(LAYER_ID, getLayerId());
proto.end(token);
}
@@ -2266,6 +2352,15 @@ public final class SurfaceControl implements Parcelable {
}
/**
+ * Exposed to identify the correct display to apply the primary display orientation. Avoid using
+ * for any other purpose.
+ * @hide
+ */
+ public static long getPrimaryPhysicalDisplayId() {
+ return nativeGetPrimaryPhysicalDisplayId();
+ }
+
+ /**
* @hide
*/
public static IBinder getPhysicalDisplayToken(long physicalDisplayId) {
@@ -3169,6 +3264,17 @@ public final class SurfaceControl implements Parcelable {
/**
* @hide
*/
+ public Transaction setDisplayFlags(IBinder displayToken, int flags) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ nativeSetDisplayFlags(mNativeObject, displayToken, flags);
+ return this;
+ }
+
+ /**
+ * @hide
+ */
public Transaction setDisplayProjection(IBinder displayToken,
int orientation, Rect layerStackRect, Rect displayRect) {
if (displayToken == null) {
@@ -3426,7 +3532,18 @@ public final class SurfaceControl implements Parcelable {
return this;
}
- /**
+ /**
+ * Sets the input event drop mode on this SurfaceControl and its children. The caller must
+ * hold the ACCESS_SURFACE_FLINGER permission. See {@code InputEventDropMode}.
+ * @hide
+ */
+ public Transaction setDropInputMode(SurfaceControl sc, @DropInputMode int mode) {
+ checkPreconditions(sc);
+ nativeSetDropInputMode(mNativeObject, sc.mNativeObject, mode);
+ return this;
+ }
+
+ /**
* Merge the other transaction into this transaction, clearing the
* other transaction as if it had been applied.
*
@@ -3613,7 +3730,7 @@ public final class SurfaceControl implements Parcelable {
/**
* @hide
*/
- public int getTransformHint() {
+ public @SurfaceControl.BufferTransform int getTransformHint() {
checkNotReleased();
return nativeGetTransformHint(mNativeObject);
}
@@ -3627,7 +3744,18 @@ public final class SurfaceControl implements Parcelable {
* with the same size.
* @hide
*/
- public void setTransformHint(@Surface.Rotation int transformHint) {
+ public void setTransformHint(@SurfaceControl.BufferTransform int transformHint) {
nativeSetTransformHint(mNativeObject, transformHint);
}
+
+ /**
+ * @hide
+ */
+ public int getLayerId() {
+ if (mNativeObject != 0) {
+ return nativeGetLayerId(mNativeObject);
+ }
+
+ return -1;
+ }
}
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 11b161ad3cb2..a6c5042db275 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -292,11 +292,18 @@ public class SurfaceControlViewHost {
*/
@TestApi
public void relayout(WindowManager.LayoutParams attrs) {
+ relayout(attrs, SurfaceControl.Transaction::apply);
+ }
+
+ /**
+ * Forces relayout and draw and allows to set a custom callback when it is finished
+ * @hide
+ */
+ public void relayout(WindowManager.LayoutParams attrs,
+ WindowlessWindowManager.ResizeCompleteCallback callback) {
mViewRoot.setLayoutParams(attrs, false);
mViewRoot.setReportNextDraw();
- mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), (SurfaceControl.Transaction t) -> {
- t.apply();
- });
+ mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), callback);
}
/**
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 4b8b607de089..856dfe53dfac 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -214,7 +214,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
final Rect mSurfaceFrame = new Rect();
int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
- int mTransformHint = 0;
+ @SurfaceControl.BufferTransform int mTransformHint = 0;
private boolean mGlobalListenersAdded;
private boolean mAttachedToWindow;
@@ -1104,7 +1104,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
|| mWindowSpaceTop != mLocation[1];
final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
|| getHeight() != mScreenRect.height();
- final boolean hintChanged = (viewRoot.getSurfaceTransformHint() != mTransformHint)
+ final boolean hintChanged = (viewRoot.getBufferTransformHint() != mTransformHint)
&& mRequestedVisible;
if (creating || formatChanged || sizeChanged || visibleChanged ||
@@ -1130,7 +1130,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
mSurfaceHeight = myHeight;
mFormat = mRequestedFormat;
mLastWindowVisibility = mWindowVisibility;
- mTransformHint = viewRoot.getSurfaceTransformHint();
+ mTransformHint = viewRoot.getBufferTransformHint();
mScreenRect.left = mWindowSpaceLeft;
mScreenRect.top = mWindowSpaceTop;
@@ -1362,7 +1362,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
if (mBlastBufferQueue != null) {
mBlastBufferQueue.destroy();
}
- mTransformHint = viewRoot.getSurfaceTransformHint();
+ mTransformHint = viewRoot.getBufferTransformHint();
mBlastSurfaceControl.setTransformHint(mTransformHint);
mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
mSurfaceHeight, mFormat);
@@ -1464,19 +1464,18 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
@Override
public void positionChanged(long frameNumber, int left, int top, int right, int bottom) {
- if (mSurfaceControl == null) {
- return;
- }
-
- // TODO: This is teensy bit racey in that a brand new SurfaceView moving on
- // its 2nd frame if RenderThread is running slowly could potentially see
- // this as false, enter the branch, get pre-empted, then this comes along
- // and reports a new position, then the UI thread resumes and reports
- // its position. This could therefore be de-sync'd in that interval, but
- // the synchronization would violate the rule that RT must never block
- // on the UI thread which would open up potential deadlocks. The risk of
- // a single-frame desync is therefore preferable for now.
synchronized(mSurfaceControlLock) {
+ if (mSurfaceControl == null) {
+ return;
+ }
+ // TODO: This is teensy bit racey in that a brand new SurfaceView moving on
+ // its 2nd frame if RenderThread is running slowly could potentially see
+ // this as false, enter the branch, get pre-empted, then this comes along
+ // and reports a new position, then the UI thread resumes and reports
+ // its position. This could therefore be de-sync'd in that interval, but
+ // the synchronization would violate the rule that RT must never block
+ // on the UI thread which would open up potential deadlocks. The risk of
+ // a single-frame desync is therefore preferable for now.
mRtHandlingPositionUpdates = true;
}
if (mRTLastReportedPosition.left == left
@@ -1506,8 +1505,11 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
if (mViewVisibility) {
mPositionChangedTransaction.show(mSurfaceControl);
}
- applyChildSurfaceTransaction_renderWorker(mPositionChangedTransaction,
- getViewRootImpl().mSurface, frameNumber);
+ final ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot != null) {
+ applyChildSurfaceTransaction_renderWorker(mPositionChangedTransaction,
+ viewRoot.mSurface, frameNumber);
+ }
applyOrMergeTransaction(mPositionChangedTransaction, frameNumber);
mPendingTransaction = false;
} catch (Exception ex) {
@@ -1528,7 +1530,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
@Override
public void positionLost(long frameNumber) {
- if (DEBUG) {
+ if (DEBUG_POSITION) {
Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
System.identityHashCode(this), frameNumber));
}
@@ -1540,15 +1542,15 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
mPositionChangedTransaction.clear();
mPendingTransaction = false;
}
- if (mSurfaceControl == null) {
- return;
- }
/**
* positionLost can be called while UI thread is un-paused so we
* need to hold the lock here.
*/
synchronized (mSurfaceControlLock) {
+ if (mSurfaceControl == null) {
+ return;
+ }
mRtTransaction.hide(mSurfaceControl);
if (mRtReleaseSurfaces) {
mRtReleaseSurfaces = false;
@@ -1604,12 +1606,21 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
* @hide
*/
public void setResizeBackgroundColor(int bgColor) {
+ setResizeBackgroundColor(mTmpTransaction, bgColor);
+ mTmpTransaction.apply();
+ }
+
+ /**
+ * Version of {@link #setResizeBackgroundColor(int)} that allows you to provide
+ * {@link SurfaceControl.Transaction}.
+ * @hide
+ */
+ public void setResizeBackgroundColor(@NonNull SurfaceControl.Transaction t, int bgColor) {
if (mBackgroundControl == null) {
return;
}
-
mBackgroundColor = bgColor;
- updateBackgroundColor(mTmpTransaction).apply();
+ updateBackgroundColor(t);
}
@UnsupportedAppUsage
@@ -1878,18 +1889,45 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
* @param p The SurfacePackage to embed.
*/
public void setChildSurfacePackage(@NonNull SurfaceControlViewHost.SurfacePackage p) {
+ setChildSurfacePackage(p, false /* applyTransactionOnDraw */);
+ }
+
+ /**
+ * Similar to setChildSurfacePackage, but using the BLAST queue so the transaction can be
+ * synchronized with the ViewRootImpl frame.
+ * @hide
+ */
+ public void setChildSurfacePackageOnDraw(
+ @NonNull SurfaceControlViewHost.SurfacePackage p) {
+ setChildSurfacePackage(p, true /* applyTransactionOnDraw */);
+ }
+
+ /**
+ * @param applyTransactionOnDraw Whether to apply transaction at onDraw or immediately.
+ */
+ private void setChildSurfacePackage(
+ @NonNull SurfaceControlViewHost.SurfacePackage p, boolean applyTransactionOnDraw) {
final SurfaceControl lastSc = mSurfacePackage != null ?
mSurfacePackage.getSurfaceControl() : null;
if (mSurfaceControl != null && lastSc != null) {
- mTmpTransaction.reparent(lastSc, null).apply();
+ mTmpTransaction.reparent(lastSc, null);
mSurfacePackage.release();
+ applyTransaction(applyTransactionOnDraw);
} else if (mSurfaceControl != null) {
reparentSurfacePackage(mTmpTransaction, p);
- mTmpTransaction.apply();
+ applyTransaction(applyTransactionOnDraw);
}
mSurfacePackage = p;
}
+ private void applyTransaction(boolean applyTransactionOnDraw) {
+ if (applyTransactionOnDraw) {
+ getViewRootImpl().applyTransactionOnDraw(mTmpTransaction);
+ } else {
+ mTmpTransaction.apply();
+ }
+ }
+
private void reparentSurfacePackage(SurfaceControl.Transaction t,
SurfaceControlViewHost.SurfacePackage p) {
final SurfaceControl sc = p.getSurfaceControl();
diff --git a/core/java/android/view/TaskTransitionSpec.aidl b/core/java/android/view/TaskTransitionSpec.aidl
new file mode 100644
index 000000000000..08af15c3f933
--- /dev/null
+++ b/core/java/android/view/TaskTransitionSpec.aidl
@@ -0,0 +1,20 @@
+/*
+** Copyright 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 android.view;
+
+/** @hide */
+parcelable TaskTransitionSpec;
diff --git a/core/java/android/view/TaskTransitionSpec.java b/core/java/android/view/TaskTransitionSpec.java
new file mode 100644
index 000000000000..e90d6e10f846
--- /dev/null
+++ b/core/java/android/view/TaskTransitionSpec.java
@@ -0,0 +1,91 @@
+/*
+ * 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 android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+
+import java.util.Set;
+
+/**
+ * Holds information about how to execute task transition animations.
+ *
+ * This class is intended to be used with IWindowManager.setTaskTransitionSpec methods when
+ * we want more customization over the way default task transitions are executed.
+ *
+ * @hide
+ */
+public class TaskTransitionSpec implements Parcelable {
+ /**
+ * The background color to use during task animations (override the default background color)
+ */
+ public final int backgroundColor;
+
+ /**
+ * TEMPORARY FIELD (b/202383002)
+ * TODO: Remove once we use surfaceflinger rounded corners on tasks rather than taskbar overlays
+ *
+ * A set of {@InsetsState.InternalInsetsType}s we want to use as the source to set the bounds
+ * of the task during the animation. Used to make sure that task animate above the taskbar.
+ * Will also be used to crop to the frame size of the inset source to the inset size to prevent
+ * the taskbar rounded corners overlay from being invisible during task transition animation.
+ */
+ public final Set<Integer> animationBoundInsets;
+
+ public TaskTransitionSpec(
+ int backgroundColor, Set<Integer> animationBoundInsets) {
+ this.backgroundColor = backgroundColor;
+ this.animationBoundInsets = animationBoundInsets;
+ }
+
+ public TaskTransitionSpec(Parcel in) {
+ this.backgroundColor = in.readInt();
+
+ int animationBoundInsetsSize = in.readInt();
+ this.animationBoundInsets = new ArraySet<>(animationBoundInsetsSize);
+ for (int i = 0; i < animationBoundInsetsSize; i++) {
+ this.animationBoundInsets.add(in.readInt());
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(backgroundColor);
+
+ dest.writeInt(animationBoundInsets.size());
+ for (int insetType : animationBoundInsets) {
+ dest.writeInt(insetType);
+ }
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<TaskTransitionSpec>
+ CREATOR = new Parcelable.Creator<TaskTransitionSpec>() {
+ public TaskTransitionSpec createFromParcel(Parcel in) {
+ return new TaskTransitionSpec(in);
+ }
+
+ public TaskTransitionSpec[] newArray(int size) {
+ return new TaskTransitionSpec[size];
+ }
+ };
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cf12955787b1..572a7cdabdc9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3515,6 +3515,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* 1 PFLAG4_ALLOW_CLICK_WHEN_DISABLED
* 1 PFLAG4_DETACHED
* 1 PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE
+ * 1 PFLAG4_DRAG_A11Y_STARTED
* |-------|-------|-------|-------|
*/
@@ -3586,6 +3587,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
private static final int PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE = 0x000004000;
+ /**
+ * Indicates that the view has started a drag with {@link AccessibilityAction#ACTION_DRAG_START}
+ */
+ private static final int PFLAG4_DRAG_A11Y_STARTED = 0x000008000;
+
/* End of masks for mPrivateFlags4 */
/** @hide */
@@ -5036,6 +5042,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public static final int DRAG_FLAG_OPAQUE = 1 << 9;
/**
+ * Flag indicating that the drag was initiated with
+ * {@link AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_START}. When
+ * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called, this
+ * is used by the system to perform a drag without animations.
+ */
+ public static final int DRAG_FLAG_ACCESSIBILITY_ACTION = 1 << 10;
+
+ /**
* Vertical scroll factor cached by {@link #getVerticalScrollFactor}.
*/
private float mVerticalScrollFactor;
@@ -10376,8 +10390,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (mTouchDelegate != null) {
info.setTouchDelegateInfo(mTouchDelegate.getTouchDelegateInfo());
}
+
+ if (startedSystemDragForAccessibility()) {
+ info.addAction(AccessibilityAction.ACTION_DRAG_CANCEL);
+ }
+
+ if (canAcceptAccessibilityDrop()) {
+ info.addAction(AccessibilityAction.ACTION_DRAG_DROP);
+ }
}
+
/**
* Adds extra data to an {@link AccessibilityNodeInfo} based on an explicit request for the
* additional data.
@@ -14214,9 +14237,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return true;
}
}
+
+ if (action == R.id.accessibilityActionDragDrop) {
+ if (!canAcceptAccessibilityDrop()) {
+ return false;
+ }
+ try {
+ if (mAttachInfo != null && mAttachInfo.mSession != null) {
+ final int[] location = new int[2];
+ getLocationInWindow(location);
+ final int centerX = location[0] + getWidth() / 2;
+ final int centerY = location[1] + getHeight() / 2;
+ return mAttachInfo.mSession.dropForAccessibility(mAttachInfo.mWindow,
+ centerX, centerY);
+ }
+ } catch (RemoteException e) {
+ Log.e(VIEW_LOG_TAG, "Unable to drop for accessibility", e);
+ }
+ return false;
+ } else if (action == R.id.accessibilityActionDragCancel) {
+ if (!startedSystemDragForAccessibility()) {
+ return false;
+ }
+ if (mAttachInfo != null && mAttachInfo.mDragToken != null) {
+ cancelDragAndDrop();
+ return true;
+ }
+ return false;
+ }
return false;
}
+ private boolean canAcceptAccessibilityDrop() {
+ if (!canAcceptDrag()) {
+ return false;
+ }
+ ListenerInfo li = mListenerInfo;
+ return (li != null) && (li.mOnDragListener != null || li.mOnReceiveContentListener != null);
+ }
+
private boolean traverseAtGranularity(int granularity, boolean forward,
boolean extendSelection) {
CharSequence text = getIterableTextForAccessibility();
@@ -26633,6 +26692,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <li>{@link #DRAG_FLAG_GLOBAL_URI_READ}</li>
* <li>{@link #DRAG_FLAG_GLOBAL_URI_WRITE}</li>
* <li>{@link #DRAG_FLAG_OPAQUE}</li>
+ * <li>{@link #DRAG_FLAG_ACCESSIBILITY_ACTION}</li>
* </ul>
* @return {@code true} if the method completes successfully, or
* {@code false} if it fails anywhere. Returning {@code false} means the system was unable to
@@ -26656,6 +26716,37 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0);
}
+ Rect bounds = new Rect();
+ getBoundsOnScreen(bounds, true);
+
+ Point lastTouchPoint = new Point();
+ mAttachInfo.mViewRootImpl.getLastTouchPoint(lastTouchPoint);
+ final ViewRootImpl root = mAttachInfo.mViewRootImpl;
+
+ // Skip surface logic since shadows and animation are not required during the a11y drag
+ final boolean a11yEnabled = AccessibilityManager.getInstance(mContext).isEnabled();
+ if (a11yEnabled && (flags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) != 0) {
+ try {
+ IBinder token = mAttachInfo.mSession.performDrag(
+ mAttachInfo.mWindow, flags, null,
+ mAttachInfo.mViewRootImpl.getLastTouchSource(),
+ 0f, 0f, 0f, 0f, data);
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(VIEW_LOG_TAG, "startDragAndDrop via a11y action returned " + token);
+ }
+ if (token != null) {
+ root.setLocalDragState(myLocalState);
+ mAttachInfo.mDragToken = token;
+ mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
+ setAccessibilityDragStarted(true);
+ }
+ return token != null;
+ } catch (Exception e) {
+ Log.e(VIEW_LOG_TAG, "Unable to initiate a11y drag", e);
+ return false;
+ }
+ }
+
Point shadowSize = new Point();
Point shadowTouchPoint = new Point();
shadowBuilder.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
@@ -26680,7 +26771,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
+ " shadowX=" + shadowTouchPoint.x + " shadowY=" + shadowTouchPoint.y);
}
- final ViewRootImpl root = mAttachInfo.mViewRootImpl;
final SurfaceSession session = new SurfaceSession();
final SurfaceControl surfaceControl = new SurfaceControl.Builder(session)
.setName("drag surface")
@@ -26703,12 +26793,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
surface.unlockCanvasAndPost(canvas);
}
- // repurpose 'shadowSize' for the last touch point
- root.getLastTouchPoint(shadowSize);
-
- token = mAttachInfo.mSession.performDrag(
- mAttachInfo.mWindow, flags, surfaceControl, root.getLastTouchSource(),
- shadowSize.x, shadowSize.y, shadowTouchPoint.x, shadowTouchPoint.y, data);
+ token = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, flags, surfaceControl,
+ root.getLastTouchSource(), lastTouchPoint.x, lastTouchPoint.y,
+ shadowTouchPoint.x, shadowTouchPoint.y, data);
if (ViewDebug.DEBUG_DRAG) {
Log.d(VIEW_LOG_TAG, "performDrag returned " + token);
}
@@ -26720,6 +26807,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mAttachInfo.mDragToken = token;
// Cache the local state object for delivery with DragEvents
root.setLocalDragState(myLocalState);
+ if (a11yEnabled) {
+ // Set for AccessibilityEvents
+ mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
+ }
}
return token != null;
} catch (Exception e) {
@@ -26733,6 +26824,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
+ void setAccessibilityDragStarted(boolean started) {
+ int pflags4 = mPrivateFlags4;
+ if (started) {
+ pflags4 |= PFLAG4_DRAG_A11Y_STARTED;
+ } else {
+ pflags4 &= ~PFLAG4_DRAG_A11Y_STARTED;
+ }
+
+ if (pflags4 != mPrivateFlags4) {
+ mPrivateFlags4 = pflags4;
+ sendWindowContentChangedAccessibilityEvent(CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+ }
+
+ private boolean startedSystemDragForAccessibility() {
+ return (mPrivateFlags4 & PFLAG4_DRAG_A11Y_STARTED) != 0;
+ }
+
/**
* Cancels an ongoing drag and drop operation.
* <p>
@@ -26950,6 +27059,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
switch (event.mAction) {
+ case DragEvent.ACTION_DRAG_STARTED: {
+ if (result && li != null && li.mOnDragListener != null) {
+ sendWindowContentChangedAccessibilityEvent(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+ } break;
case DragEvent.ACTION_DRAG_ENTERED: {
mPrivateFlags2 |= View.PFLAG2_DRAG_HOVERED;
refreshDrawableState();
@@ -26958,7 +27073,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED;
refreshDrawableState();
} break;
+ case DragEvent.ACTION_DROP: {
+ if (result && li != null && (li.mOnDragListener != null
+ || li.mOnReceiveContentListener != null)) {
+ sendWindowContentChangedAccessibilityEvent(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_DROPPED);
+ }
+ } break;
case DragEvent.ACTION_DRAG_ENDED: {
+ sendWindowContentChangedAccessibilityEvent(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
mPrivateFlags2 &= ~View.DRAG_MASK;
refreshDrawableState();
} break;
@@ -26971,6 +27095,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return (mPrivateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0;
}
+ void sendWindowContentChangedAccessibilityEvent(int changeType) {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ event.setContentChangeTypes(changeType);
+ sendAccessibilityEventUnchecked(event);
+ }
+ }
+
/**
* This needs to be a better API (NOT ON VIEW) before it is exposed. If
* it is ever exposed at all.
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 0a3d0da6da1e..4b18d3a02282 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -433,9 +433,8 @@ public class ViewConfiguration {
mAmbiguousGestureMultiplier = Math.max(1.0f, multiplierValue.getFloat());
// Size of the screen in bytes, in ARGB_8888 format
- final WindowManager windowManager = context.getSystemService(WindowManager.class);
- final Rect maxWindowBounds = windowManager.getMaximumWindowMetrics().getBounds();
- mMaximumDrawingCacheSize = 4 * maxWindowBounds.width() * maxWindowBounds.height();
+ final Rect maxBounds = config.windowConfiguration.getMaxBounds();
+ mMaximumDrawingCacheSize = 4 * maxBounds.width() * maxBounds.height();
mOverscrollDistance = (int) (sizeAndDensity * OVERSCROLL_DISTANCE + 0.5f);
mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f);
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 775c15e77d5d..49f5229d3c09 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -412,6 +412,9 @@ public interface ViewParent {
* <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_SUBTREE}
* <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT}
* <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED}
+ * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_STARTED}
+ * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_CANCELLED}
+ * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_DROPPED}
* </ul>
*/
public void notifySubtreeAccessibilityStateChanged(
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0ecd76f82a8a..4c0c82fe5c2e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -291,12 +291,31 @@ public final class ViewRootImpl implements ViewParent,
*/
private static final int SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS = 2500;
+ /**
+ * If set to {@code true}, the new logic to layout system bars as normal window and to use
+ * layout result to get insets will be applied. Otherwise, the old hard-coded window logic will
+ * be applied.
+ */
+ private static final String USE_FLEXIBLE_INSETS = "persist.debug.flexible_insets";
+
+ /**
+ * A flag to indicate to use the new generalized insets window logic, or the old hard-coded
+ * insets window layout logic.
+ * {@hide}
+ */
+ public static final boolean INSETS_LAYOUT_GENERALIZATION =
+ SystemProperties.getBoolean(USE_FLEXIBLE_INSETS, false);
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<>();
static boolean sFirstDrawComplete = false;
+ private ArrayList<OnBufferTransformHintChangedListener> mTransformHintListeners =
+ new ArrayList<>();
+ private @SurfaceControl.BufferTransform
+ int mPreviousTransformHint = SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
/**
* Callback for notifying about global configuration changes.
*/
@@ -633,6 +652,7 @@ public final class ViewRootImpl implements ViewParent,
/* Drag/drop */
ClipDescription mDragDescription;
View mCurrentDragView;
+ View mStartedDragViewForA11y;
volatile Object mLocalDragState;
final PointF mDragPoint = new PointF();
final PointF mLastTouchPoint = new PointF();
@@ -800,11 +820,7 @@ public final class ViewRootImpl implements ViewParent,
context);
mCompatibleVisibilityInfo = new SystemUiVisibilityInfo();
mAccessibilityManager = AccessibilityManager.getInstance(context);
- mAccessibilityManager.addAccessibilityStateChangeListener(
- mAccessibilityInteractionConnectionManager, mHandler);
mHighContrastTextManager = new HighContrastTextManager();
- mAccessibilityManager.addHighTextContrastStateChangeListener(
- mHighContrastTextManager, mHandler);
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
@@ -1004,8 +1020,6 @@ public final class ViewRootImpl implements ViewParent,
mView = view;
mAttachInfo.mDisplayState = mDisplay.getState();
- mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
-
mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
mFallbackEventHandler.setView(view);
mWindowAttributes.copyFrom(attrs);
@@ -1118,7 +1132,7 @@ public final class ViewRootImpl implements ViewParent,
controlInsetsForCompatibility(mWindowAttributes);
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
- mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
+ mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
mTempControls);
if (mTranslator != null) {
mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
@@ -1198,6 +1212,7 @@ public final class ViewRootImpl implements ViewParent,
"Unable to add window -- unknown error code " + res);
}
+ registerListeners();
if ((res & WindowManagerGlobal.ADD_FLAG_USE_BLAST) != 0) {
mUseBLASTAdapter = true;
}
@@ -1254,6 +1269,28 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ /**
+ * Register any kind of listeners if setView was success.
+ */
+ private void registerListeners() {
+ mAccessibilityManager.addAccessibilityStateChangeListener(
+ mAccessibilityInteractionConnectionManager, mHandler);
+ mAccessibilityManager.addHighTextContrastStateChangeListener(
+ mHighContrastTextManager, mHandler);
+ mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+ }
+
+ /**
+ * Unregister all listeners while detachedFromWindow.
+ */
+ private void unregisterListeners() {
+ mAccessibilityManager.removeAccessibilityStateChangeListener(
+ mAccessibilityInteractionConnectionManager);
+ mAccessibilityManager.removeHighTextContrastStateChangeListener(
+ mHighContrastTextManager);
+ mDisplayManager.unregisterDisplayListener(mDisplayListener);
+ }
+
private void setTag() {
final String[] split = mWindowAttributes.getTitle().toString().split("\\.");
if (split.length > 0) {
@@ -2452,7 +2489,7 @@ public final class ViewRootImpl implements ViewParent,
mAttachInfo.mContentInsets.set(mLastWindowInsets.getSystemWindowInsets().toRect());
mAttachInfo.mStableInsets.set(mLastWindowInsets.getStableInsets().toRect());
mAttachInfo.mVisibleInsets.set(mInsetsController.calculateVisibleInsets(
- mWindowAttributes.softInputMode));
+ mWindowAttributes.softInputMode).toRect());
}
return mLastWindowInsets;
}
@@ -2502,6 +2539,14 @@ public final class ViewRootImpl implements ViewParent,
|| lp.type == TYPE_VOLUME_OVERLAY;
}
+ private Rect getWindowBoundsInsetSystemBars() {
+ final Rect bounds = new Rect(
+ mContext.getResources().getConfiguration().windowConfiguration.getBounds());
+ bounds.inset(mInsetsController.getState().calculateInsets(
+ bounds, Type.systemBars(), false /* ignoreVisibility */));
+ return bounds;
+ }
+
int dipToPx(int dip) {
final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
return (int) (displayMetrics.density * dip + 0.5f);
@@ -2588,8 +2633,9 @@ public final class ViewRootImpl implements ViewParent,
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
// For wrap content, we have to remeasure later on anyways. Use size consistent with
// below so we get best use of the measure cache.
- desiredWindowWidth = dipToPx(config.screenWidthDp);
- desiredWindowHeight = dipToPx(config.screenHeightDp);
+ final Rect bounds = getWindowBoundsInsetSystemBars();
+ desiredWindowWidth = bounds.width();
+ desiredWindowHeight = bounds.height();
} else {
// After addToDisplay, the frame contains the frameHint from window manager, which
// for most windows is going to be the same size as the result of relayoutWindow.
@@ -2666,9 +2712,9 @@ public final class ViewRootImpl implements ViewParent,
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
- Configuration config = res.getConfiguration();
- desiredWindowWidth = dipToPx(config.screenWidthDp);
- desiredWindowHeight = dipToPx(config.screenHeightDp);
+ final Rect bounds = getWindowBoundsInsetSystemBars();
+ desiredWindowWidth = bounds.width();
+ desiredWindowHeight = bounds.height();
}
}
}
@@ -4252,6 +4298,14 @@ public final class ViewRootImpl implements ViewParent,
try {
if (!isContentCaptureEnabled()) return;
+ // Initial dispatch of window bounds to content capture
+ if (mAttachInfo.mContentCaptureManager != null) {
+ MainContentCaptureSession session =
+ mAttachInfo.mContentCaptureManager.getMainContentCaptureSession();
+ session.notifyWindowBoundsChanged(session.getId(),
+ getConfiguration().windowConfiguration.getBounds());
+ }
+
// Content capture is a go!
rootView.dispatchInitialProvideContentCaptureStructure();
} finally {
@@ -4979,10 +5033,6 @@ public final class ViewRootImpl implements ViewParent,
}
mAccessibilityInteractionConnectionManager.ensureNoConnection();
- mAccessibilityManager.removeAccessibilityStateChangeListener(
- mAccessibilityInteractionConnectionManager);
- mAccessibilityManager.removeHighTextContrastStateChangeListener(
- mHighContrastTextManager);
removeSendWindowContentChangedCallback();
destroyHardwareRenderer();
@@ -5015,8 +5065,7 @@ public final class ViewRootImpl implements ViewParent,
mInputEventReceiver = null;
}
- mDisplayManager.unregisterDisplayListener(mDisplayListener);
-
+ unregisterListeners();
unscheduleTraversals();
}
@@ -7552,6 +7601,11 @@ public final class ViewRootImpl implements ViewParent,
if (what == DragEvent.ACTION_DRAG_STARTED) {
mCurrentDragView = null; // Start the current-recipient tracking
mDragDescription = event.mClipDescription;
+ if (mStartedDragViewForA11y != null) {
+ // Send a drag started a11y event
+ mStartedDragViewForA11y.sendWindowContentChangedAccessibilityEvent(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_STARTED);
+ }
} else {
if (what == DragEvent.ACTION_DRAG_ENDED) {
mDragDescription = null;
@@ -7626,6 +7680,16 @@ public final class ViewRootImpl implements ViewParent,
// When the drag operation ends, reset drag-related state
if (what == DragEvent.ACTION_DRAG_ENDED) {
+ if (mStartedDragViewForA11y != null) {
+ // If the drag failed, send a cancelled event from the source. Otherwise,
+ // the View that accepted the drop sends CONTENT_CHANGE_TYPE_DRAG_DROPPED
+ if (!event.getResult()) {
+ mStartedDragViewForA11y.sendWindowContentChangedAccessibilityEvent(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_CANCELLED);
+ }
+ mStartedDragViewForA11y.setAccessibilityDragStarted(false);
+ }
+ mStartedDragViewForA11y = null;
mCurrentDragView = null;
setLocalDragState(null);
mAttachInfo.mDragToken = null;
@@ -7705,6 +7769,13 @@ public final class ViewRootImpl implements ViewParent,
mCurrentDragView = newDragTarget;
}
+ /** Sets the view that started drag and drop for the purpose of sending AccessibilityEvents */
+ void setDragStartedViewForAccessibility(View view) {
+ if (mStartedDragViewForA11y == null) {
+ mStartedDragViewForA11y = view;
+ }
+ }
+
private AudioManager getAudioManager() {
if (mView == null) {
throw new IllegalStateException("getAudioManager called when there is no mView");
@@ -7783,6 +7854,14 @@ public final class ViewRootImpl implements ViewParent,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
mTempControls, mSurfaceSize);
+
+ if (mAttachInfo.mContentCaptureManager != null) {
+ MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
+ .getMainContentCaptureSession();
+ mainSession.notifyWindowBoundsChanged(mainSession.getId(),
+ getConfiguration().windowConfiguration.getBounds());
+ }
+
mPendingBackDropFrame.set(mTmpFrames.backdropFrame);
if (mSurfaceControl.isValid()) {
if (!useBLAST()) {
@@ -7803,6 +7882,11 @@ public final class ViewRootImpl implements ViewParent,
}
mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
}
+ int transformHint = mSurfaceControl.getTransformHint();
+ if (mPreviousTransformHint != transformHint) {
+ mPreviousTransformHint = transformHint;
+ dispatchTransformHintChanged(transformHint);
+ }
} else {
destroySurface();
}
@@ -9912,7 +9996,10 @@ public final class ViewRootImpl implements ViewParent,
if (!mUseMTRenderer) {
return;
}
- mWindowDrawCountDown = new CountDownLatch(mWindowCallbacks.size());
+ // Only wait if it will report next draw.
+ if (mReportNextDraw) {
+ mWindowDrawCountDown = new CountDownLatch(mWindowCallbacks.size());
+ }
for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
mWindowCallbacks.get(i).onRequestDraw(mReportNextDraw);
}
@@ -10424,7 +10511,39 @@ public final class ViewRootImpl implements ViewParent,
return true;
}
- int getSurfaceTransformHint() {
+ @Override
+ public @SurfaceControl.BufferTransform int getBufferTransformHint() {
return mSurfaceControl.getTransformHint();
}
+
+ @Override
+ public void addOnBufferTransformHintChangedListener(
+ OnBufferTransformHintChangedListener listener) {
+ Objects.requireNonNull(listener);
+ if (mTransformHintListeners.contains(listener)) {
+ throw new IllegalArgumentException(
+ "attempt to call addOnBufferTransformHintChangedListener() "
+ + "with a previously registered listener");
+ }
+ mTransformHintListeners.add(listener);
+ }
+
+ @Override
+ public void removeOnBufferTransformHintChangedListener(
+ OnBufferTransformHintChangedListener listener) {
+ Objects.requireNonNull(listener);
+ mTransformHintListeners.remove(listener);
+ }
+
+ private void dispatchTransformHintChanged(@SurfaceControl.BufferTransform int hint) {
+ if (mTransformHintListeners.isEmpty()) {
+ return;
+ }
+ ArrayList<OnBufferTransformHintChangedListener> listeners =
+ (ArrayList<OnBufferTransformHintChangedListener>) mTransformHintListeners.clone();
+ for (int i = 0; i < listeners.size(); i++) {
+ OnBufferTransformHintChangedListener listener = listeners.get(i);
+ listener.onBufferTransformHintChanged(hint);
+ }
+ }
}
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index ce882da1a6da..efffa2b05a1e 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -149,10 +149,10 @@ public class ViewRootInsetsControllerHost implements InsetsController.Host {
}
@Override
- public void onInsetsModified(InsetsState insetsState) {
+ public void updateRequestedVisibilities(InsetsVisibilities vis) {
try {
if (mViewRoot.mAdded) {
- mViewRoot.mWindowSession.insetsModified(mViewRoot.mWindow, insetsState);
+ mViewRoot.mWindowSession.updateRequestedVisibilities(mViewRoot.mWindow, vis);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to call insetsModified", e);
diff --git a/core/java/android/view/WindowInfo.java b/core/java/android/view/WindowInfo.java
index 57dfc62b4f8f..1edbbba09eef 100644
--- a/core/java/android/view/WindowInfo.java
+++ b/core/java/android/view/WindowInfo.java
@@ -16,6 +16,7 @@
package android.view;
+import android.app.ActivityTaskManager;
import android.graphics.Region;
import android.os.IBinder;
import android.os.Parcel;
@@ -51,6 +52,7 @@ public class WindowInfo implements Parcelable {
public boolean inPictureInPicture;
public boolean hasFlagWatchOutsideTouch;
public int displayId = Display.INVALID_DISPLAY;
+ public int taskId = ActivityTaskManager.INVALID_TASK_ID;
private WindowInfo() {
/* do nothing - hide constructor */
@@ -67,6 +69,7 @@ public class WindowInfo implements Parcelable {
public static WindowInfo obtain(WindowInfo other) {
WindowInfo window = obtain();
window.displayId = other.displayId;
+ window.taskId = other.taskId;
window.type = other.type;
window.layer = other.layer;
window.token = other.token;
@@ -103,6 +106,7 @@ public class WindowInfo implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(displayId);
+ parcel.writeInt(taskId);
parcel.writeInt(type);
parcel.writeInt(layer);
parcel.writeStrongBinder(token);
@@ -129,6 +133,7 @@ public class WindowInfo implements Parcelable {
builder.append("WindowInfo[");
builder.append("title=").append(title);
builder.append(", displayId=").append(displayId);
+ builder.append(", taskId=").append(taskId);
builder.append(", type=").append(type);
builder.append(", layer=").append(layer);
builder.append(", token=").append(token);
@@ -146,6 +151,7 @@ public class WindowInfo implements Parcelable {
private void initFromParcel(Parcel parcel) {
displayId = parcel.readInt();
+ taskId = parcel.readInt();
type = parcel.readInt();
layer = parcel.readInt();
token = parcel.readStrongBinder();
@@ -169,6 +175,7 @@ public class WindowInfo implements Parcelable {
private void clear() {
displayId = Display.INVALID_DISPLAY;
+ taskId = ActivityTaskManager.INVALID_TASK_ID;
type = 0;
layer = 0;
token = null;
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 0f1a9d9c0a98..cde1cc704f92 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -903,6 +903,16 @@ public final class WindowInsets {
result.append(mPrivacyIndicatorBounds != null ? "privacyIndicatorBounds="
+ mPrivacyIndicatorBounds : "");
result.append("\n ");
+ result.append("compatInsetsTypes=" + mCompatInsetsTypes);
+ result.append("\n ");
+ result.append("compatIgnoreVisibility=" + mCompatIgnoreVisibility);
+ result.append("\n ");
+ result.append("systemWindowInsetsConsumed=" + mSystemWindowInsetsConsumed);
+ result.append("\n ");
+ result.append("stableInsetsConsumed=" + mStableInsetsConsumed);
+ result.append("\n ");
+ result.append("displayCutoutConsumed=" + mDisplayCutoutConsumed);
+ result.append("\n ");
result.append(isRound() ? "round" : "");
result.append("}");
return result.toString();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 55beae0f7b3d..51cd95e42742 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -97,6 +97,7 @@ import android.content.ClipData;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
@@ -122,6 +123,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -319,6 +321,25 @@ public interface WindowManager extends ViewManager {
int TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE = 27;
/**
+ * A window in a new task fragment is being opened.
+ * @hide
+ */
+ int TRANSIT_OLD_TASK_FRAGMENT_OPEN = 28;
+
+ /**
+ * A window in the top-most activity of task fragment is being closed to reveal the activity
+ * below.
+ * @hide
+ */
+ int TRANSIT_OLD_TASK_FRAGMENT_CLOSE = 29;
+
+ /**
+ * A window of task fragment is changing bounds.
+ * @hide
+ */
+ int TRANSIT_OLD_TASK_FRAGMENT_CHANGE = 30;
+
+ /**
* @hide
*/
@IntDef(prefix = { "TRANSIT_OLD_" }, value = {
@@ -343,7 +364,10 @@ public interface WindowManager extends ViewManager {
TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN,
TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE,
TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE,
- TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE
+ TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE,
+ TRANSIT_OLD_TASK_FRAGMENT_OPEN,
+ TRANSIT_OLD_TASK_FRAGMENT_CLOSE,
+ TRANSIT_OLD_TASK_FRAGMENT_CHANGE
})
@Retention(RetentionPolicy.SOURCE)
@interface TransitionOldType {}
@@ -380,8 +404,11 @@ public interface WindowManager extends ViewManager {
int TRANSIT_CHANGE = 6;
/**
* The keyguard was visible and has been dismissed.
+ * @deprecated use {@link #TRANSIT_TO_BACK} + {@link #TRANSIT_FLAG_KEYGUARD_GOING_AWAY} for
+ * keyguard going away with Shell transition.
* @hide
*/
+ @Deprecated
int TRANSIT_KEYGUARD_GOING_AWAY = 7;
/**
* A window is appearing above a locked keyguard.
@@ -394,6 +421,16 @@ public interface WindowManager extends ViewManager {
*/
int TRANSIT_KEYGUARD_UNOCCLUDE = 9;
/**
+ * A window is starting to enter PiP.
+ * @hide
+ */
+ int TRANSIT_PIP = 10;
+ /**
+ * The screen is turning on.
+ * @hide
+ */
+ int TRANSIT_WAKE = 11;
+ /**
* The first slot for custom transition types. Callers (like Shell) can make use of custom
* transition types for dealing with special cases. These types are effectively ignored by
* Core and will just be passed along as part of TransitionInfo objects. An example is
@@ -402,7 +439,7 @@ public interface WindowManager extends ViewManager {
* implementation.
* @hide
*/
- int TRANSIT_FIRST_CUSTOM = 10;
+ int TRANSIT_FIRST_CUSTOM = 12;
/**
* @hide
@@ -418,6 +455,8 @@ public interface WindowManager extends ViewManager {
TRANSIT_KEYGUARD_GOING_AWAY,
TRANSIT_KEYGUARD_OCCLUDE,
TRANSIT_KEYGUARD_UNOCCLUDE,
+ TRANSIT_PIP,
+ TRANSIT_WAKE,
TRANSIT_FIRST_CUSTOM
})
@Retention(RetentionPolicy.SOURCE)
@@ -467,6 +506,19 @@ public interface WindowManager extends ViewManager {
int TRANSIT_FLAG_KEYGUARD_LOCKED = 0x40;
/**
+ * Transition flag: Indicates that this transition is for recents animation.
+ * TODO(b/188669821): Remove once special-case logic moves to shell.
+ * @hide
+ */
+ int TRANSIT_FLAG_IS_RECENTS = 0x80;
+
+ /**
+ * Transition flag: Indicates that keyguard should go away with this transition.
+ * @hide
+ */
+ int TRANSIT_FLAG_KEYGUARD_GOING_AWAY = 0x100;
+
+ /**
* @hide
*/
@IntDef(flag = true, prefix = { "TRANSIT_FLAG_" }, value = {
@@ -476,7 +528,9 @@ public interface WindowManager extends ViewManager {
TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION,
TRANSIT_FLAG_APP_CRASHED,
TRANSIT_FLAG_OPEN_BEHIND,
- TRANSIT_FLAG_KEYGUARD_LOCKED
+ TRANSIT_FLAG_KEYGUARD_LOCKED,
+ TRANSIT_FLAG_IS_RECENTS,
+ TRANSIT_FLAG_KEYGUARD_GOING_AWAY
})
@Retention(RetentionPolicy.SOURCE)
@interface TransitionFlags {}
@@ -661,6 +715,20 @@ public interface WindowManager extends ViewManager {
}
/**
+ * Returns a set of {@link WindowMetrics} for the given display. Each WindowMetrics instance
+ * is the maximum WindowMetrics for a device state, including rotations. This is not guaranteed
+ * to include all possible device states.
+ *
+ * This API can only be used by Launcher.
+ *
+ * @param displayId the id of the logical display
+ * @hide
+ */
+ default @NonNull Set<WindowMetrics> getPossibleMaximumWindowMetrics(int displayId) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Used to asynchronously request Keyboard Shortcuts from the focused window.
*
* @hide
@@ -922,6 +990,8 @@ public interface WindowManager extends ViewManager {
case TRANSIT_KEYGUARD_GOING_AWAY: return "KEYGUARD_GOING_AWAY";
case TRANSIT_KEYGUARD_OCCLUDE: return "KEYGUARD_OCCLUDE";
case TRANSIT_KEYGUARD_UNOCCLUDE: return "KEYGUARD_UNOCCLUDE";
+ case TRANSIT_PIP: return "PIP";
+ case TRANSIT_WAKE: return "WAKE";
case TRANSIT_FIRST_CUSTOM: return "FIRST_CUSTOM";
default:
if (type > TRANSIT_FIRST_CUSTOM) {
@@ -3468,6 +3538,30 @@ public interface WindowManager extends ViewManager {
public @InsetsState.InternalInsetsType int[] providesInsetsTypes;
/**
+ * If specified, the insets provided by this window will be our window frame minus the
+ * insets specified by providedInternalInsets.
+ *
+ * @hide
+ */
+ public Insets providedInternalInsets = Insets.NONE;
+
+ /**
+ * If specified, the insets provided by this window for the IME will be our window frame
+ * minus the insets specified by providedInternalImeInsets.
+ *
+ * @hide
+ */
+ public Insets providedInternalImeInsets = Insets.NONE;
+
+ /**
+ * {@link LayoutParams} to be applied to the window when layout with a assigned rotation.
+ * This will make layout during rotation change smoothly.
+ *
+ * @hide
+ */
+ public LayoutParams[] paramsForRotation;
+
+ /**
* Specifies types of insets that this window should avoid overlapping during layout.
*
* @param types which {@link WindowInsets.Type}s of insets that this window should avoid.
@@ -3566,6 +3660,18 @@ public interface WindowManager extends ViewManager {
return mFitInsetsIgnoringVisibility;
}
+ private void checkNonRecursiveParams() {
+ if (paramsForRotation == null) {
+ return;
+ }
+ for (int i = paramsForRotation.length - 1; i >= 0; i--) {
+ if (paramsForRotation[i].paramsForRotation != null) {
+ throw new IllegalArgumentException(
+ "Params cannot contain params recursively.");
+ }
+ }
+ }
+
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
@@ -3820,6 +3926,15 @@ public interface WindowManager extends ViewManager {
} else {
out.writeInt(0);
}
+ providedInternalInsets.writeToParcel(out, 0 /* parcelableFlags */);
+ providedInternalImeInsets.writeToParcel(out, 0 /* parcelableFlags */);
+ if (paramsForRotation != null) {
+ checkNonRecursiveParams();
+ out.writeInt(paramsForRotation.length);
+ out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */);
+ } else {
+ out.writeInt(0);
+ }
}
public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR
@@ -3891,6 +4006,13 @@ public interface WindowManager extends ViewManager {
providesInsetsTypes = new int[insetsTypesLength];
in.readIntArray(providesInsetsTypes);
}
+ providedInternalInsets = Insets.CREATOR.createFromParcel(in);
+ providedInternalImeInsets = Insets.CREATOR.createFromParcel(in);
+ int paramsForRotationLength = in.readInt();
+ if (paramsForRotationLength > 0) {
+ paramsForRotation = new LayoutParams[paramsForRotationLength];
+ in.readTypedArray(paramsForRotation, LayoutParams.CREATOR);
+ }
}
@SuppressWarnings({"PointlessBitwiseExpression"})
@@ -4187,6 +4309,22 @@ public interface WindowManager extends ViewManager {
changes |= LAYOUT_CHANGED;
}
+ if (!providedInternalInsets.equals(o.providedInternalInsets)) {
+ providedInternalInsets = o.providedInternalInsets;
+ changes |= LAYOUT_CHANGED;
+ }
+
+ if (!providedInternalImeInsets.equals(o.providedInternalImeInsets)) {
+ providedInternalImeInsets = o.providedInternalImeInsets;
+ changes |= LAYOUT_CHANGED;
+ }
+
+ if (!Arrays.equals(paramsForRotation, o.paramsForRotation)) {
+ paramsForRotation = o.paramsForRotation;
+ checkNonRecursiveParams();
+ changes |= LAYOUT_CHANGED;
+ }
+
return changes;
}
@@ -4382,6 +4520,22 @@ public interface WindowManager extends ViewManager {
sb.append(InsetsState.typeToString(providesInsetsTypes[i]));
}
}
+ if (!providedInternalInsets.equals(Insets.NONE)) {
+ sb.append(" providedInternalInsets=");
+ sb.append(providedInternalInsets);
+ }
+ if (!providedInternalImeInsets.equals(Insets.NONE)) {
+ sb.append(" providedInternalImeInsets=");
+ sb.append(providedInternalImeInsets);
+ }
+ if (paramsForRotation != null && paramsForRotation.length != 0) {
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" paramsForRotation=");
+ for (int i = 0; i < paramsForRotation.length; ++i) {
+ if (i > 0) sb.append(' ');
+ sb.append(paramsForRotation[i].toString());
+ }
+ }
sb.append('}');
return sb.toString();
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 18013e815d13..c92a3a086a8b 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -18,6 +18,7 @@ package android.view;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentCallbacks2;
@@ -709,6 +710,16 @@ public final class WindowManagerGlobal {
}
}
}
+
+ /** @hide */
+ @Nullable
+ public SurfaceControl mirrorWallpaperSurface(int displayId) {
+ try {
+ return getWindowManagerService().mirrorWallpaperSurface(displayId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
final class WindowLeaked extends AndroidRuntimeException {
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index f800991944ac..1c915cb016d4 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -16,12 +16,12 @@
package android.view;
-import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
-import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.window.WindowProviderService.isWindowProviderService;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
@@ -36,12 +36,16 @@ import android.graphics.Region;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.StrictMode;
import android.window.WindowContext;
+import android.window.WindowProvider;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -145,6 +149,7 @@ public final class WindowManagerImpl implements WindowManager {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
+ assertWindowContextTypeMatches(wparams.type);
// Only use the default token if we don't have a parent window and a token.
if (mDefaultToken != null && mParentWindow == null && wparams.token == null) {
wparams.token = mDefaultToken;
@@ -152,6 +157,34 @@ public final class WindowManagerImpl implements WindowManager {
wparams.mWindowContextToken = mWindowContextToken;
}
+ private void assertWindowContextTypeMatches(@LayoutParams.WindowType int windowType) {
+ if (!(mContext instanceof WindowProvider)) {
+ return;
+ }
+ // Don't need to check sub-window type because sub window should be allowed to be attached
+ // to the parent window.
+ if (windowType >= FIRST_SUB_WINDOW && windowType <= LAST_SUB_WINDOW) {
+ return;
+ }
+ final WindowProvider windowProvider = (WindowProvider) mContext;
+ if (windowProvider.getWindowType() == windowType) {
+ return;
+ }
+ IllegalArgumentException exception = new IllegalArgumentException("Window type mismatch."
+ + " Window Context's window type is " + windowProvider.getWindowType()
+ + ", while LayoutParams' type is set to " + windowType + "."
+ + " Please create another Window Context via"
+ + " createWindowContext(getDisplay(), " + windowType + ", null)"
+ + " to add window with type:" + windowType);
+ if (!isWindowProviderService(windowProvider.getWindowContextOptions())) {
+ throw exception;
+ }
+ // Throw IncorrectCorrectViolation if the Window Context is allowed to provide multiple
+ // window types. Usually it's because the Window Context is a WindowProviderService.
+ StrictMode.onIncorrectContextUsed("WindowContext's window type must"
+ + " match type in WindowManager.LayoutParams", exception);
+ }
+
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
@@ -270,34 +303,86 @@ public final class WindowManagerImpl implements WindowManager {
private WindowInsets computeWindowInsets(Rect bounds) {
// Initialize params which used for obtaining all system insets.
final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- params.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
final Context context = (mParentWindow != null) ? mParentWindow.getContext() : mContext;
params.token = Context.getToken(context);
- params.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
- params.setFitInsetsTypes(0);
- params.setFitInsetsSides(0);
+ return getWindowInsetsFromServerForCurrentDisplay(params, bounds);
+ }
- return getWindowInsetsFromServer(params, bounds);
+ private WindowInsets getWindowInsetsFromServerForCurrentDisplay(
+ WindowManager.LayoutParams attrs, Rect bounds) {
+ final Configuration config = mContext.getResources().getConfiguration();
+ return getWindowInsetsFromServerForDisplay(mContext.getDisplayId(), attrs, bounds,
+ config.isScreenRound(), config.windowConfiguration.getWindowingMode());
}
- private WindowInsets getWindowInsetsFromServer(WindowManager.LayoutParams attrs, Rect bounds) {
+ /**
+ * Retrieves WindowInsets for the given context and display, given the window bounds.
+ *
+ * @param displayId the ID of the logical display to calculate insets for
+ * @param attrs the LayoutParams for the calling app
+ * @param bounds the window bounds to calculate insets for
+ * @param isScreenRound if the display identified by displayId is round
+ * @param windowingMode the windowing mode of the window to calculate insets for
+ * @return WindowInsets calculated for the given window bounds, on the given display
+ */
+ private static WindowInsets getWindowInsetsFromServerForDisplay(int displayId,
+ WindowManager.LayoutParams attrs, Rect bounds, boolean isScreenRound,
+ int windowingMode) {
try {
final InsetsState insetsState = new InsetsState();
final boolean alwaysConsumeSystemBars = WindowManagerGlobal.getWindowManagerService()
- .getWindowInsets(attrs, mContext.getDisplayId(), insetsState);
- final Configuration config = mContext.getResources().getConfiguration();
- final boolean isScreenRound = config.isScreenRound();
- final int windowingMode = config.windowConfiguration.getWindowingMode();
+ .getWindowInsets(attrs, displayId, insetsState);
return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState*/,
isScreenRound, alwaysConsumeSystemBars, SOFT_INPUT_ADJUST_NOTHING, attrs.flags,
- SYSTEM_UI_FLAG_VISIBLE, attrs.type, windowingMode, null /* typeSideMap */);
+ SYSTEM_UI_FLAG_VISIBLE, attrs.type, windowingMode,
+ null /* typeSideMap */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Override
+ @NonNull
+ public Set<WindowMetrics> getPossibleMaximumWindowMetrics(int displayId) {
+ List<DisplayInfo> possibleDisplayInfos;
+ try {
+ possibleDisplayInfos = WindowManagerGlobal.getWindowManagerService()
+ .getPossibleDisplayInfo(displayId, mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ Set<WindowMetrics> maxMetrics = new HashSet<>();
+ WindowInsets windowInsets;
+ DisplayInfo currentDisplayInfo;
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ for (int i = 0; i < possibleDisplayInfos.size(); i++) {
+ currentDisplayInfo = possibleDisplayInfos.get(i);
+
+ // Calculate max bounds for this rotation and state.
+ Rect maxBounds = new Rect(0, 0, currentDisplayInfo.logicalWidth,
+ currentDisplayInfo.logicalHeight);
+
+ // Calculate insets for the rotated max bounds.
+ final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0;
+ // Initialize insets based upon display rotation. Note any window-provided insets
+ // will not be set.
+ windowInsets = getWindowInsetsFromServerForDisplay(
+ currentDisplayInfo.displayId, params,
+ new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
+ currentDisplayInfo.getNaturalHeight()), isScreenRound,
+ WINDOWING_MODE_FULLSCREEN);
+ // Set the hardware-provided insets.
+ windowInsets = new WindowInsets.Builder(windowInsets).setRoundedCorners(
+ currentDisplayInfo.roundedCorners)
+ .setDisplayCutout(currentDisplayInfo.displayCutout).build();
+
+ maxMetrics.add(new WindowMetrics(maxBounds, windowInsets));
+ }
+ return maxMetrics;
+ }
+
+ @Override
public void holdLock(IBinder token, int durationMs) {
try {
WindowManagerGlobal.getWindowManagerService().holdLock(token, durationMs);
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index bbef3e6aeefa..e634d601c1f1 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -209,4 +209,41 @@ public interface WindowManagerPolicyConstants {
return Integer.toString(why);
}
}
+
+ /**
+ * 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.
+ */
+ int TYPE_LAYER_MULTIPLIER = 10000;
+
+ /**
+ * Offset from TYPE_LAYER_MULTIPLIER for moving a group of windows above
+ * or below others in the same layer.
+ */
+ int TYPE_LAYER_OFFSET = 1000;
+
+ /**
+ * How much to increment the layer for each window, to reserve room
+ * for effect surfaces between them.
+ */
+ 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).
+ */
+ int LAYER_OFFSET_THUMBNAIL = WINDOW_LAYER_MULTIPLIER - 1;
+
+ int SPLIT_DIVIDER_LAYER = TYPE_LAYER_MULTIPLIER * 3;
+ int WATERMARK_LAYER = TYPE_LAYER_MULTIPLIER * 100;
+ int STRICT_MODE_LAYER = TYPE_LAYER_MULTIPLIER * 101;
+ int WINDOW_FREEZE_LAYER = TYPE_LAYER_MULTIPLIER * 200;
+
+ /**
+ * Layers for screen rotation animation. We put these layers above
+ * WINDOW_FREEZE_LAYER so that screen freeze will cover all windows.
+ */
+ int SCREEN_FREEZE_LAYER_BASE = WINDOW_FREEZE_LAYER + TYPE_LAYER_MULTIPLIER;
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index ae54f51f35d1..ffb7efa67243 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -135,7 +135,7 @@ public class WindowlessWindowManager implements IWindowSession {
*/
@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) {
final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession)
@@ -181,10 +181,10 @@ public class WindowlessWindowManager implements IWindowSession {
*/
@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 addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibility,
+ return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibilities,
outInputChannel, outInsetsState, outActiveControls);
}
@@ -454,7 +454,7 @@ public class WindowlessWindowManager implements IWindowSession {
}
@Override
- public void insetsModified(android.view.IWindow window, android.view.InsetsState state) {
+ public void updateRequestedVisibilities(IWindow window, InsetsVisibilities visibilities) {
}
@Override
@@ -498,4 +498,9 @@ public class WindowlessWindowManager implements IWindowSession {
public void generateDisplayHash(IWindow window, Rect boundsInWindow, String hashAlgorithm,
RemoteCallback callback) {
}
+
+ @Override
+ public boolean dropForAccessibility(IWindow window, int x, int y) {
+ return false;
+ }
}
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index f6d6fde6435f..3b65ffd35730 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -613,6 +613,36 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
public static final int CONTENT_CHANGE_TYPE_STATE_DESCRIPTION = 0x00000040;
/**
+ * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+ * A drag has started while accessibility is enabled. This is either via an
+ * AccessibilityAction, or via touch events. This is sent from the source that initiated the
+ * drag.
+ *
+ * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_START
+ */
+ public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 0x00000080;
+
+ /**
+ * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+ * A drag in with accessibility enabled has ended. This means the content has been
+ * successfully dropped. This is sent from the target that accepted the dragged content.
+ *
+ * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_DROP
+ */
+ public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 0x00000100;
+
+ /**
+ * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+ * A drag in with accessibility enabled has ended. This means the content has been
+ * unsuccessfully dropped, the user has canceled the action via an AccessibilityAction, or
+ * no drop has been detected within a timeout and the drag was automatically cancelled. This is
+ * sent from the source that initiated the drag.
+ *
+ * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_CANCEL
+ */
+ public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 0x0000200;
+
+ /**
* Change type for {@link #TYPE_WINDOWS_CHANGED} event:
* The window was added.
*/
@@ -710,7 +740,10 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
CONTENT_CHANGE_TYPE_STATE_DESCRIPTION,
CONTENT_CHANGE_TYPE_PANE_TITLE,
CONTENT_CHANGE_TYPE_PANE_APPEARED,
- CONTENT_CHANGE_TYPE_PANE_DISAPPEARED
+ CONTENT_CHANGE_TYPE_PANE_DISAPPEARED,
+ CONTENT_CHANGE_TYPE_DRAG_STARTED,
+ CONTENT_CHANGE_TYPE_DRAG_DROPPED,
+ CONTENT_CHANGE_TYPE_DRAG_CANCELLED
})
public @interface ContentChangeTypes {}
@@ -959,6 +992,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
case CONTENT_CHANGE_TYPE_PANE_APPEARED: return "CONTENT_CHANGE_TYPE_PANE_APPEARED";
case CONTENT_CHANGE_TYPE_PANE_DISAPPEARED:
return "CONTENT_CHANGE_TYPE_PANE_DISAPPEARED";
+ case CONTENT_CHANGE_TYPE_DRAG_STARTED: return "CONTENT_CHANGE_TYPE_DRAG_STARTED";
+ case CONTENT_CHANGE_TYPE_DRAG_DROPPED: return "CONTENT_CHANGE_TYPE_DRAG_DROPPED";
+ case CONTENT_CHANGE_TYPE_DRAG_CANCELLED: return "CONTENT_CHANGE_TYPE_DRAG_CANCELLED";
default: return Integer.toHexString(type);
}
}
@@ -1017,6 +1053,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
/**
* Sets the event type.
*
+ * <b>Note: An event must represent a single event type.</b>
* @param eventType The event type.
*
* @throws IllegalStateException If called from an AccessibilityService.
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index dd81dd93380b..aac09b8a3b57 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -16,6 +16,9 @@
package android.view.accessibility;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CLIENT;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK;
+
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -86,6 +89,7 @@ public final class AccessibilityInteractionClient
public static final int NO_ID = -1;
public static final String CALL_STACK = "call_stack";
+ public static final String IGNORE_CALL_STACK = "ignore_call_stack";
private static final String LOG_TAG = "AccessibilityInteractionClient";
@@ -121,6 +125,12 @@ public final class AccessibilityInteractionClient
private volatile int mInteractionId = -1;
private volatile int mCallingUid = Process.INVALID_UID;
+ // call stack for IAccessibilityInteractionConnectionCallback APIs. These callback APIs are
+ // shared by multiple requests APIs in IAccessibilityServiceConnection. To correctly log the
+ // request API which triggers the callback, we log trace entries for callback after the
+ // request API thread waiting for the callback returns. To log the correct callback stack in
+ // the request API thread, we save the callback stack in this member variables.
+ private List<StackTraceElement> mCallStackOfCallback;
private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult;
@@ -307,18 +317,30 @@ public final class AccessibilityInteractionClient
if (DEBUG) {
Log.i(LOG_TAG, "Window cache hit");
}
+ if (shouldTraceClient()) {
+ logTraceClient(connection, "getWindow cache",
+ "connectionId=" + connectionId + ";accessibilityWindowId="
+ + accessibilityWindowId + ";bypassCache=false");
+ }
return window;
}
if (DEBUG) {
Log.i(LOG_TAG, "Window cache miss");
}
}
+
final long identityToken = Binder.clearCallingIdentity();
try {
window = connection.getWindow(accessibilityWindowId);
} finally {
Binder.restoreCallingIdentity(identityToken);
}
+ if (shouldTraceClient()) {
+ logTraceClient(connection, "getWindow", "connectionId=" + connectionId
+ + ";accessibilityWindowId=" + accessibilityWindowId + ";bypassCache="
+ + bypassCache);
+ }
+
if (window != null) {
if (!bypassCache) {
sAccessibilityCache.addWindow(window);
@@ -368,6 +390,10 @@ public final class AccessibilityInteractionClient
if (DEBUG) {
Log.i(LOG_TAG, "Windows cache hit");
}
+ if (shouldTraceClient()) {
+ logTraceClient(
+ connection, "getWindows cache", "connectionId=" + connectionId);
+ }
return windows;
}
if (DEBUG) {
@@ -379,6 +405,9 @@ public final class AccessibilityInteractionClient
} finally {
Binder.restoreCallingIdentity(identityToken);
}
+ if (shouldTraceClient()) {
+ logTraceClient(connection, "getWindows", "connectionId=" + connectionId);
+ }
if (windows != null) {
sAccessibilityCache.setWindowsOnAllDisplays(windows);
return windows;
@@ -472,6 +501,15 @@ public final class AccessibilityInteractionClient
Log.i(LOG_TAG, "Node cache hit for "
+ idToString(accessibilityWindowId, accessibilityNodeId));
}
+ if (shouldTraceClient()) {
+ logTraceClient(connection,
+ "findAccessibilityNodeInfoByAccessibilityId cache",
+ "connectionId=" + connectionId + ";accessibilityWindowId="
+ + accessibilityWindowId + ";accessibilityNodeId="
+ + accessibilityNodeId + ";bypassCache=" + bypassCache
+ + ";prefetchFlags=" + prefetchFlags + ";arguments="
+ + arguments);
+ }
return cachedInfo;
}
if (DEBUG) {
@@ -488,6 +526,14 @@ public final class AccessibilityInteractionClient
prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
}
final int interactionId = mInteractionIdCounter.getAndIncrement();
+ if (shouldTraceClient()) {
+ logTraceClient(connection, "findAccessibilityNodeInfoByAccessibilityId",
+ "InteractionId:" + interactionId + "connectionId=" + connectionId
+ + ";accessibilityWindowId=" + accessibilityWindowId
+ + ";accessibilityNodeId=" + accessibilityNodeId + ";bypassCache="
+ + bypassCache + ";prefetchFlags=" + prefetchFlags + ";arguments="
+ + arguments);
+ }
final String[] packageNames;
final long identityToken = Binder.clearCallingIdentity();
try {
@@ -500,16 +546,10 @@ public final class AccessibilityInteractionClient
if (packageNames != null) {
AccessibilityNodeInfo info =
getFindAccessibilityNodeInfoResultAndClear(interactionId);
- if (mAccessibilityManager != null
- && mAccessibilityManager.isAccessibilityTracingEnabled()) {
- logTrace(connection, "findAccessibilityNodeInfoByAccessibilityId",
- "InteractionId:" + interactionId + ";Result: " + info
- + ";connectionId=" + connectionId
- + ";accessibilityWindowId="
- + accessibilityWindowId + ";accessibilityNodeId="
- + accessibilityNodeId + ";bypassCache=" + bypassCache
- + ";prefetchFlags=" + prefetchFlags
- + ";arguments=" + arguments);
+ if (shouldTraceCallback()) {
+ logTraceCallback(connection, "findAccessibilityNodeInfoByAccessibilityId",
+ "InteractionId:" + interactionId + ";connectionId="
+ + connectionId + ";Result: " + info);
}
if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0
&& info != null) {
@@ -571,6 +611,14 @@ public final class AccessibilityInteractionClient
final String[] packageNames;
final long identityToken = Binder.clearCallingIdentity();
try {
+ if (shouldTraceClient()) {
+ logTraceClient(connection, "findAccessibilityNodeInfosByViewId",
+ "InteractionId=" + interactionId + ";connectionId=" + connectionId
+ + ";accessibilityWindowId=" + accessibilityWindowId
+ + ";accessibilityNodeId=" + accessibilityNodeId + ";viewId="
+ + viewId);
+ }
+
packageNames = connection.findAccessibilityNodeInfosByViewId(
accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
Thread.currentThread().getId());
@@ -581,13 +629,10 @@ public final class AccessibilityInteractionClient
if (packageNames != null) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- if (mAccessibilityManager != null
- && mAccessibilityManager.isAccessibilityTracingEnabled()) {
- logTrace(connection, "findAccessibilityNodeInfosByViewId", "InteractionId="
- + interactionId + ":Result: " + infos + ";connectionId="
- + connectionId + ";accessibilityWindowId=" + accessibilityWindowId
- + ";accessibilityNodeId=" + accessibilityNodeId + ";viewId="
- + viewId);
+ if (shouldTraceCallback()) {
+ logTraceCallback(connection, "findAccessibilityNodeInfosByViewId",
+ "InteractionId=" + interactionId + ";connectionId=" + connectionId
+ + ":Result: " + infos);
}
if (infos != null) {
finalizeAndCacheAccessibilityNodeInfos(infos, connectionId,
@@ -630,6 +675,12 @@ public final class AccessibilityInteractionClient
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
+ if (shouldTraceClient()) {
+ logTraceClient(connection, "findAccessibilityNodeInfosByText",
+ "InteractionId:" + interactionId + "connectionId=" + connectionId
+ + ";accessibilityWindowId=" + accessibilityWindowId
+ + ";accessibilityNodeId=" + accessibilityNodeId + ";text=" + text);
+ }
final String[] packageNames;
final long identityToken = Binder.clearCallingIdentity();
try {
@@ -643,12 +694,10 @@ public final class AccessibilityInteractionClient
if (packageNames != null) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- if (mAccessibilityManager != null
- && mAccessibilityManager.isAccessibilityTracingEnabled()) {
- logTrace(connection, "findAccessibilityNodeInfosByText", "InteractionId="
- + interactionId + ":Result: " + infos + ";connectionId="
- + connectionId + ";accessibilityWindowId=" + accessibilityWindowId
- + ";accessibilityNodeId=" + accessibilityNodeId + ";text=" + text);
+ if (shouldTraceCallback()) {
+ logTraceCallback(connection, "findAccessibilityNodeInfosByText",
+ "InteractionId=" + interactionId + ";connectionId=" + connectionId
+ + ";Result: " + infos);
}
if (infos != null) {
finalizeAndCacheAccessibilityNodeInfos(infos, connectionId,
@@ -690,6 +739,13 @@ public final class AccessibilityInteractionClient
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
+ if (shouldTraceClient()) {
+ logTraceClient(connection, "findFocus",
+ "InteractionId:" + interactionId + "connectionId=" + connectionId
+ + ";accessibilityWindowId=" + accessibilityWindowId
+ + ";accessibilityNodeId=" + accessibilityNodeId + ";focusType="
+ + focusType);
+ }
final String[] packageNames;
final long identityToken = Binder.clearCallingIdentity();
try {
@@ -703,13 +759,9 @@ public final class AccessibilityInteractionClient
if (packageNames != null) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
- if (mAccessibilityManager != null
- && mAccessibilityManager.isAccessibilityTracingEnabled()) {
- logTrace(connection, "findFocus", "InteractionId=" + interactionId
- + ":Result: " + info + ";connectionId=" + connectionId
- + ";accessibilityWindowId=" + accessibilityWindowId
- + ";accessibilityNodeId=" + accessibilityNodeId + ";focusType="
- + focusType);
+ if (shouldTraceCallback()) {
+ logTraceCallback(connection, "findFocus", "InteractionId=" + interactionId
+ + ";connectionId=" + connectionId + ";Result:" + info);
}
finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false, packageNames);
return info;
@@ -747,6 +799,13 @@ public final class AccessibilityInteractionClient
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
+ if (shouldTraceClient()) {
+ logTraceClient(connection, "focusSearch",
+ "InteractionId:" + interactionId + "connectionId=" + connectionId
+ + ";accessibilityWindowId=" + accessibilityWindowId
+ + ";accessibilityNodeId=" + accessibilityNodeId + ";direction="
+ + direction);
+ }
final String[] packageNames;
final long identityToken = Binder.clearCallingIdentity();
try {
@@ -761,13 +820,9 @@ public final class AccessibilityInteractionClient
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false, packageNames);
- if (mAccessibilityManager != null
- && mAccessibilityManager.isAccessibilityTracingEnabled()) {
- logTrace(connection, "focusSearch", "InteractionId=" + interactionId
- + ":Result: " + info + ";connectionId=" + connectionId
- + ";accessibilityWindowId=" + accessibilityWindowId
- + ";accessibilityNodeId=" + accessibilityNodeId + ";direction="
- + direction);
+ if (shouldTraceCallback()) {
+ logTraceCallback(connection, "focusSearch", "InteractionId=" + interactionId
+ + ";connectionId=" + connectionId + ";Result:" + info);
}
return info;
}
@@ -803,6 +858,13 @@ public final class AccessibilityInteractionClient
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
+ if (shouldTraceClient()) {
+ logTraceClient(connection, "performAccessibilityAction",
+ "InteractionId:" + interactionId + "connectionId=" + connectionId
+ + ";accessibilityWindowId=" + accessibilityWindowId
+ + ";accessibilityNodeId=" + accessibilityNodeId + ";action=" + action
+ + ";arguments=" + arguments);
+ }
final boolean success;
final long identityToken = Binder.clearCallingIdentity();
try {
@@ -816,13 +878,10 @@ public final class AccessibilityInteractionClient
if (success) {
final boolean result =
getPerformAccessibilityActionResultAndClear(interactionId);
- if (mAccessibilityManager != null
- && mAccessibilityManager.isAccessibilityTracingEnabled()) {
- logTrace(connection, "performAccessibilityAction", "InteractionId="
- + interactionId + ":Result: " + result + ";connectionId="
- + connectionId + ";accessibilityWindowId=" + accessibilityWindowId
- + ";accessibilityNodeId=" + accessibilityNodeId + ";action="
- + action + ";arguments=" + arguments);
+ if (shouldTraceCallback()) {
+ logTraceCallback(connection, "performAccessibilityAction",
+ "InteractionId=" + interactionId + ";connectionId=" + connectionId
+ + ";Result: " + result);
}
return result;
}
@@ -886,6 +945,8 @@ public final class AccessibilityInteractionClient
mFindAccessibilityNodeInfoResult = info;
mInteractionId = interactionId;
mCallingUid = Binder.getCallingUid();
+ mCallStackOfCallback = new ArrayList<StackTraceElement>(
+ Arrays.asList(Thread.currentThread().getStackTrace()));
}
mInstanceLock.notifyAll();
}
@@ -936,6 +997,8 @@ public final class AccessibilityInteractionClient
}
mInteractionId = interactionId;
mCallingUid = Binder.getCallingUid();
+ mCallStackOfCallback = new ArrayList<StackTraceElement>(
+ Arrays.asList(Thread.currentThread().getStackTrace()));
}
mInstanceLock.notifyAll();
}
@@ -975,13 +1038,15 @@ public final class AccessibilityInteractionClient
finalizeAndCacheAccessibilityNodeInfos(
infos, connectionIdWaitingForPrefetchResultCopy, false,
packageNamesForNextPrefetchResultCopy);
- if (mAccessibilityManager != null
- && mAccessibilityManager.isAccessibilityTracingEnabled()) {
+ if (shouldTraceCallback()) {
logTrace(getConnection(connectionIdWaitingForPrefetchResultCopy),
"setPrefetchAccessibilityNodeInfoResult",
- "InteractionId:" + interactionId + ";Result: " + infos
- + ";connectionId=" + connectionIdWaitingForPrefetchResultCopy,
- Binder.getCallingUid());
+ "InteractionId:" + interactionId + ";connectionId="
+ + connectionIdWaitingForPrefetchResultCopy + ";Result: " + infos,
+ Binder.getCallingUid(),
+ Arrays.asList(Thread.currentThread().getStackTrace()),
+ new HashSet<String>(Arrays.asList("getStackTrace")),
+ FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK);
}
} else if (DEBUG) {
Log.w(LOG_TAG, "Prefetching for interaction with id " + interactionId + " dropped "
@@ -1013,6 +1078,8 @@ public final class AccessibilityInteractionClient
mPerformAccessibilityActionResult = succeeded;
mInteractionId = interactionId;
mCallingUid = Binder.getCallingUid();
+ mCallStackOfCallback = new ArrayList<StackTraceElement>(
+ Arrays.asList(Thread.currentThread().getStackTrace()));
}
mInstanceLock.notifyAll();
}
@@ -1222,24 +1289,45 @@ public final class AccessibilityInteractionClient
return true;
}
+ private boolean shouldTraceClient() {
+ return (mAccessibilityManager != null)
+ && mAccessibilityManager.isA11yInteractionClientTraceEnabled();
+ }
+
+ private boolean shouldTraceCallback() {
+ return (mAccessibilityManager != null)
+ && mAccessibilityManager.isA11yInteractionConnectionCBTraceEnabled();
+ }
+
private void logTrace(
IAccessibilityServiceConnection connection, String method, String params,
- int callingUid) {
+ int callingUid, List<StackTraceElement> callStack, HashSet<String> ignoreSet,
+ long logTypes) {
try {
Bundle b = new Bundle();
- ArrayList<StackTraceElement> callStack = new ArrayList<StackTraceElement>(
- Arrays.asList(Thread.currentThread().getStackTrace()));
- b.putSerializable(CALL_STACK, callStack);
+ b.putSerializable(CALL_STACK, new ArrayList<StackTraceElement>(callStack));
+ if (ignoreSet != null) {
+ b.putSerializable(IGNORE_CALL_STACK, ignoreSet);
+ }
connection.logTrace(SystemClock.elapsedRealtimeNanos(),
- LOG_TAG + ".callback for " + method, params, Process.myPid(),
- Thread.currentThread().getId(), callingUid, b);
+ LOG_TAG + "." + method,
+ logTypes, params, Process.myPid(), Thread.currentThread().getId(),
+ callingUid, b);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Failed to log trace. " + e);
}
}
- private void logTrace(
+ private void logTraceCallback(
+ IAccessibilityServiceConnection connection, String method, String params) {
+ logTrace(connection, method + " callback", params, mCallingUid, mCallStackOfCallback,
+ new HashSet<String>(Arrays.asList("getStackTrace")),
+ FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK);
+ }
+
+ private void logTraceClient(
IAccessibilityServiceConnection connection, String method, String params) {
- logTrace(connection, method, params, mCallingUid);
+ logTrace(connection, method, params, Binder.getCallingUid(),
+ Collections.emptyList(), null, FLAGS_ACCESSIBILITY_INTERACTION_CLIENT);
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index f9cdbd322c26..17fad7e57de7 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -111,7 +111,13 @@ public final class AccessibilityManager {
public static final int STATE_FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x00000010;
/** @hide */
- public static final int STATE_FLAG_ACCESSIBILITY_TRACING_ENABLED = 0x00000020;
+ public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_ENABLED = 0x00000100;
+ /** @hide */
+ public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_CB_ENABLED = 0x00000200;
+ /** @hide */
+ public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED = 0x00000400;
+ /** @hide */
+ public static final int STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED = 0x00000800;
/** @hide */
public static final int DALTONIZER_DISABLED = -1;
@@ -235,8 +241,8 @@ public final class AccessibilityManager {
@UnsupportedAppUsage(trackingBug = 123768939L)
boolean mIsHighTextContrastEnabled;
- // Whether accessibility tracing is enabled or not
- boolean mIsAccessibilityTracingEnabled = false;
+ // accessibility tracing state
+ int mAccessibilityTracingState = 0;
AccessibilityPolicy mAccessibilityPolicy;
@@ -1029,13 +1035,50 @@ public final class AccessibilityManager {
}
/**
- * Gets accessibility tracing enabled state.
+ * Gets accessibility interaction connection tracing enabled state.
+ *
+ * @hide
+ */
+ public boolean isA11yInteractionConnectionTraceEnabled() {
+ synchronized (mLock) {
+ return ((mAccessibilityTracingState
+ & STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_ENABLED) != 0);
+ }
+ }
+
+ /**
+ * Gets accessibility interaction connection callback tracing enabled state.
+ *
+ * @hide
+ */
+ public boolean isA11yInteractionConnectionCBTraceEnabled() {
+ synchronized (mLock) {
+ return ((mAccessibilityTracingState
+ & STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_CB_ENABLED) != 0);
+ }
+ }
+
+ /**
+ * Gets accessibility interaction client tracing enabled state.
+ *
+ * @hide
+ */
+ public boolean isA11yInteractionClientTraceEnabled() {
+ synchronized (mLock) {
+ return ((mAccessibilityTracingState
+ & STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED) != 0);
+ }
+ }
+
+ /**
+ * Gets accessibility service tracing enabled state.
*
* @hide
*/
- public boolean isAccessibilityTracingEnabled() {
+ public boolean isA11yServiceTraceEnabled() {
synchronized (mLock) {
- return mIsAccessibilityTracingEnabled;
+ return ((mAccessibilityTracingState
+ & STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED) != 0);
}
}
@@ -1233,8 +1276,6 @@ public final class AccessibilityManager {
(stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
final boolean highTextContrastEnabled =
(stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
- final boolean accessibilityTracingEnabled =
- (stateFlags & STATE_FLAG_ACCESSIBILITY_TRACING_ENABLED) != 0;
final boolean wasEnabled = isEnabled();
final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
@@ -1257,7 +1298,7 @@ public final class AccessibilityManager {
notifyHighTextContrastStateChanged();
}
- updateAccessibilityTracingState(accessibilityTracingEnabled);
+ updateAccessibilityTracingState(stateFlags);
}
/**
@@ -1715,11 +1756,11 @@ public final class AccessibilityManager {
}
/**
- * Update mIsAccessibilityTracingEnabled.
+ * Update mAccessibilityTracingState.
*/
- private void updateAccessibilityTracingState(boolean enabled) {
+ private void updateAccessibilityTracingState(int stateFlag) {
synchronized (mLock) {
- mIsAccessibilityTracingEnabled = enabled;
+ mAccessibilityTracingState = stateFlag;
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 085eb81182f1..587a27074544 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -27,6 +27,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ClipData;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Build;
@@ -4353,6 +4354,14 @@ public class AccessibilityNodeInfo implements Parcelable {
case R.id.accessibilityActionImeEnter:
return "ACTION_IME_ENTER";
default:
+ // TODO(197520937): Use finalized constants in switch
+ if (action == R.id.accessibilityActionDragStart) {
+ return "ACTION_DRAG";
+ } else if (action == R.id.accessibilityActionDragCancel) {
+ return "ACTION_CANCEL_DRAG";
+ } else if (action == R.id.accessibilityActionDragDrop) {
+ return "ACTION_DROP";
+ }
return "ACTION_UNKNOWN";
}
}
@@ -4995,6 +5004,46 @@ public class AccessibilityNodeInfo implements Parcelable {
@NonNull public static final AccessibilityAction ACTION_IME_ENTER =
new AccessibilityAction(R.id.accessibilityActionImeEnter);
+ /**
+ * Action to start a drag.
+ * <p>
+ * This action initiates a drag & drop within the system. The source's dragged content is
+ * prepared before the drag begins. In View, this action should prepare the arguments to
+ * {@link View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)} and then
+ * call {@link View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)}. The
+ * equivalent should be performed for other UI toolkits.
+ * </p>
+ *
+ * @see AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_STARTED
+ */
+ @NonNull public static final AccessibilityAction ACTION_DRAG_START =
+ new AccessibilityAction(R.id.accessibilityActionDragStart);
+
+ /**
+ * Action to trigger a drop of the content being dragged.
+ * <p>
+ * This action is added to potential drop targets if the source started a drag with
+ * {@link #ACTION_DRAG_START}. In View, these targets are Views that accepted
+ * {@link android.view.DragEvent#ACTION_DRAG_STARTED} and have an
+ * {@link View.OnDragListener}.
+ * </p>
+ *
+ * @see AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_DROPPED
+ */
+ @NonNull public static final AccessibilityAction ACTION_DRAG_DROP =
+ new AccessibilityAction(R.id.accessibilityActionDragDrop);
+
+ /**
+ * Action to cancel a drag.
+ * <p>
+ * This action is added to the source that started a drag with {@link #ACTION_DRAG_START}.
+ * </p>
+ *
+ * @see AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_CANCELLED
+ */
+ @NonNull public static final AccessibilityAction ACTION_DRAG_CANCEL =
+ new AccessibilityAction(R.id.accessibilityActionDragCancel);
+
private final int mActionId;
private final CharSequence mLabel;
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index edcb59a79c70..76e226163ca1 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -19,6 +19,7 @@ package android.view.accessibility;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.app.ActivityTaskManager;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Parcel;
@@ -114,6 +115,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
private int mBooleanProperties;
private int mId = UNDEFINED_WINDOW_ID;
private int mParentId = UNDEFINED_WINDOW_ID;
+ private int mTaskId = ActivityTaskManager.INVALID_TASK_ID;
private Region mRegionInScreen = new Region();
private LongArray mChildIds;
private CharSequence mTitle;
@@ -307,6 +309,28 @@ public final class AccessibilityWindowInfo implements Parcelable {
}
/**
+ * Gets the task ID.
+ *
+ * @return The task ID.
+ *
+ * @hide
+ */
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ /**
+ * Sets the task ID.
+ *
+ * @param taskId The task ID.
+ *
+ * @hide
+ */
+ public void setTaskId(int taskId) {
+ mTaskId = taskId;
+ }
+
+ /**
* Sets the unique id of the IAccessibilityServiceConnection over which
* this instance can send requests to the system.
*
@@ -578,6 +602,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
parcel.writeInt(mBooleanProperties);
parcel.writeInt(mId);
parcel.writeInt(mParentId);
+ parcel.writeInt(mTaskId);
mRegionInScreen.writeToParcel(parcel, flags);
parcel.writeCharSequence(mTitle);
parcel.writeLong(mAnchorId);
@@ -608,6 +633,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
mBooleanProperties = other.mBooleanProperties;
mId = other.mId;
mParentId = other.mParentId;
+ mTaskId = other.mTaskId;
mRegionInScreen.set(other.mRegionInScreen);
mTitle = other.mTitle;
mAnchorId = other.mAnchorId;
@@ -631,6 +657,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
mBooleanProperties = parcel.readInt();
mId = parcel.readInt();
mParentId = parcel.readInt();
+ mTaskId = parcel.readInt();
mRegionInScreen = Region.CREATOR.createFromParcel(parcel);
mTitle = parcel.readCharSequence();
mAnchorId = parcel.readLong();
@@ -676,6 +703,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
builder.append("title=").append(mTitle);
builder.append(", displayId=").append(mDisplayId);
builder.append(", id=").append(mId);
+ builder.append(", taskId=").append(mTaskId);
builder.append(", type=").append(typeToString(mType));
builder.append(", layer=").append(mLayer);
builder.append(", region=").append(mRegionInScreen);
@@ -719,6 +747,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
mBooleanProperties = 0;
mId = UNDEFINED_WINDOW_ID;
mParentId = UNDEFINED_WINDOW_ID;
+ mTaskId = ActivityTaskManager.INVALID_TASK_ID;
mRegionInScreen.setEmpty();
mChildIds = null;
mConnectionId = UNDEFINED_WINDOW_ID;
diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java
index 9998fbc02d12..0da54e57ed79 100644
--- a/core/java/android/view/contentcapture/ContentCaptureContext.java
+++ b/core/java/android/view/contentcapture/ContentCaptureContext.java
@@ -27,6 +27,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.LocusId;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.Display;
@@ -105,6 +106,7 @@ public final class ContentCaptureContext implements Parcelable {
private final int mFlags;
private final int mDisplayId;
private final ActivityId mActivityId;
+ private final IBinder mWindowToken;
// Fields below are set by the service upon "delivery" and are not marshalled in the parcel
private int mParentSessionId = NO_SESSION_ID;
@@ -112,7 +114,7 @@ public final class ContentCaptureContext implements Parcelable {
/** @hide */
public ContentCaptureContext(@Nullable ContentCaptureContext clientContext,
@NonNull ActivityId activityId, @NonNull ComponentName componentName, int displayId,
- int flags) {
+ IBinder windowToken, int flags) {
if (clientContext != null) {
mHasClientContext = true;
mExtras = clientContext.mExtras;
@@ -126,6 +128,7 @@ public final class ContentCaptureContext implements Parcelable {
mFlags = flags;
mDisplayId = displayId;
mActivityId = activityId;
+ mWindowToken = windowToken;
}
private ContentCaptureContext(@NonNull Builder builder) {
@@ -137,6 +140,7 @@ public final class ContentCaptureContext implements Parcelable {
mFlags = 0;
mDisplayId = Display.INVALID_DISPLAY;
mActivityId = null;
+ mWindowToken = null;
}
/** @hide */
@@ -148,6 +152,7 @@ public final class ContentCaptureContext implements Parcelable {
mFlags = original.mFlags | extraFlags;
mDisplayId = original.mDisplayId;
mActivityId = original.mActivityId;
+ mWindowToken = original.mWindowToken;
}
/**
@@ -230,6 +235,20 @@ public final class ContentCaptureContext implements Parcelable {
}
/**
+ * Gets the window token of the activity associated with this context.
+ *
+ * <p>The token can be used to attach relevant overlay views to the activity's window. This can
+ * be done through {@link android.view.WindowManager.LayoutParams#token}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public IBinder getWindowToken() {
+ return mWindowToken;
+ }
+
+ /**
* Gets the flags associated with this context.
*
* @return any combination of {@link #FLAG_DISABLED_BY_FLAG_SECURE},
@@ -328,6 +347,7 @@ public final class ContentCaptureContext implements Parcelable {
}
pw.print(", activityId="); pw.print(mActivityId);
pw.print(", displayId="); pw.print(mDisplayId);
+ pw.print(", windowToken="); pw.print(mWindowToken);
if (mParentSessionId != NO_SESSION_ID) {
pw.print(", parentId="); pw.print(mParentSessionId);
}
@@ -352,6 +372,7 @@ public final class ContentCaptureContext implements Parcelable {
builder.append("act=").append(ComponentName.flattenToShortString(mComponentName))
.append(", activityId=").append(mActivityId)
.append(", displayId=").append(mDisplayId)
+ .append(", windowToken=").append(mWindowToken)
.append(", flags=").append(mFlags);
} else {
builder.append("id=").append(mId);
@@ -381,6 +402,7 @@ public final class ContentCaptureContext implements Parcelable {
parcel.writeParcelable(mComponentName, flags);
if (fromServer()) {
parcel.writeInt(mDisplayId);
+ parcel.writeStrongBinder(mWindowToken);
parcel.writeInt(mFlags);
mActivityId.writeToParcel(parcel, flags);
}
@@ -411,11 +433,12 @@ public final class ContentCaptureContext implements Parcelable {
return clientContext;
} else {
final int displayId = parcel.readInt();
+ final IBinder windowToken = parcel.readStrongBinder();
final int flags = parcel.readInt();
final ActivityId activityId = new ActivityId(parcel);
return new ContentCaptureContext(clientContext, activityId, componentName,
- displayId, flags);
+ displayId, windowToken, flags);
}
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index ce6d034c585e..4b2d3a97bed2 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -24,6 +24,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.graphics.Insets;
+import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.Selection;
@@ -122,6 +123,12 @@ public final class ContentCaptureEvent implements Parcelable {
*/
public static final int TYPE_VIEW_INSETS_CHANGED = 9;
+ /**
+ * Called before {@link #TYPE_VIEW_TREE_APPEARING}, or after the size of the window containing
+ * the views changed.
+ */
+ public static final int TYPE_WINDOW_BOUNDS_CHANGED = 10;
+
/** @hide */
@IntDef(prefix = { "TYPE_" }, value = {
TYPE_VIEW_APPEARED,
@@ -132,7 +139,8 @@ public final class ContentCaptureEvent implements Parcelable {
TYPE_CONTEXT_UPDATED,
TYPE_SESSION_PAUSED,
TYPE_SESSION_RESUMED,
- TYPE_VIEW_INSETS_CHANGED
+ TYPE_VIEW_INSETS_CHANGED,
+ TYPE_WINDOW_BOUNDS_CHANGED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventType{}
@@ -150,6 +158,7 @@ public final class ContentCaptureEvent implements Parcelable {
private int mParentSessionId = NO_SESSION_ID;
private @Nullable ContentCaptureContext mClientContext;
private @Nullable Insets mInsets;
+ private @Nullable Rect mBounds;
private int mComposingStart = MAX_INVALID_VALUE;
private int mComposingEnd = MAX_INVALID_VALUE;
@@ -346,6 +355,13 @@ public final class ContentCaptureEvent implements Parcelable {
return this;
}
+ /** @hide */
+ @NonNull
+ public ContentCaptureEvent setBounds(@NonNull Rect bounds) {
+ mBounds = bounds;
+ return this;
+ }
+
/**
* Gets the type of the event.
*
@@ -419,6 +435,16 @@ public final class ContentCaptureEvent implements Parcelable {
}
/**
+ * Gets the {@link Rect} bounds of the window associated with the event. Valid bounds will only
+ * be returned if the type of the event is {@link #TYPE_WINDOW_BOUNDS_CHANGED}, otherwise they
+ * will be null.
+ */
+ @Nullable
+ public Rect getBounds() {
+ return mBounds;
+ }
+
+ /**
* Merges event of the same type, either {@link #TYPE_VIEW_TEXT_CHANGED}
* or {@link #TYPE_VIEW_DISAPPEARED}.
*
@@ -489,6 +515,9 @@ public final class ContentCaptureEvent implements Parcelable {
if (mInsets != null) {
pw.print(", insets="); pw.println(mInsets);
}
+ if (mBounds != null) {
+ pw.print(", bounds="); pw.println(mBounds);
+ }
if (mComposingStart > MAX_INVALID_VALUE) {
pw.print(", composing("); pw.print(mComposingStart);
pw.print(", "); pw.print(mComposingEnd); pw.print(")");
@@ -533,6 +562,9 @@ public final class ContentCaptureEvent implements Parcelable {
if (mInsets != null) {
string.append(", insets=").append(mInsets);
}
+ if (mBounds != null) {
+ string.append(", bounds=").append(mBounds);
+ }
if (mComposingStart > MAX_INVALID_VALUE) {
string.append(", composing=[")
.append(mComposingStart).append(",").append(mComposingEnd).append("]");
@@ -568,6 +600,9 @@ public final class ContentCaptureEvent implements Parcelable {
if (mType == TYPE_VIEW_INSETS_CHANGED) {
parcel.writeParcelable(mInsets, flags);
}
+ if (mType == TYPE_WINDOW_BOUNDS_CHANGED) {
+ parcel.writeParcelable(mBounds, flags);
+ }
if (mType == TYPE_VIEW_TEXT_CHANGED) {
parcel.writeInt(mComposingStart);
parcel.writeInt(mComposingEnd);
@@ -608,6 +643,9 @@ public final class ContentCaptureEvent implements Parcelable {
if (type == TYPE_VIEW_INSETS_CHANGED) {
event.setInsets(parcel.readParcelable(null));
}
+ if (type == TYPE_WINDOW_BOUNDS_CHANGED) {
+ event.setBounds(parcel.readParcelable(null));
+ }
if (type == TYPE_VIEW_TEXT_CHANGED) {
event.setComposingIndex(parcel.readInt(), parcel.readInt());
event.restoreComposingSpan();
@@ -649,6 +687,8 @@ public final class ContentCaptureEvent implements Parcelable {
return "CONTEXT_UPDATED";
case TYPE_VIEW_INSETS_CHANGED:
return "VIEW_INSETS_CHANGED";
+ case TYPE_WINDOW_BOUNDS_CHANGED:
+ return "TYPE_WINDOW_BOUNDS_CHANGED";
default:
return "UKNOWN_TYPE: " + type;
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 4cf553207b6e..98ef4e7ae6fa 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -26,6 +26,7 @@ import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_INSETS_C
import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARED;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARING;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_WINDOW_BOUNDS_CHANGED;
import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
import static android.view.contentcapture.ContentCaptureHelper.sDebug;
import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
@@ -38,6 +39,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.graphics.Insets;
+import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -776,6 +778,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
.setClientContext(context), FORCE_FLUSH));
}
+ /** public because is also used by ViewRootImpl */
+ public void notifyWindowBoundsChanged(int sessionId, @NonNull Rect bounds) {
+ mHandler.post(() -> sendEvent(
+ new ContentCaptureEvent(sessionId, TYPE_WINDOW_BOUNDS_CHANGED)
+ .setBounds(bounds)
+ ));
+ }
+
@Override
void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
super.dump(prefix, pw);
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index d2db0df6c597..5b2068ff16cd 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -96,8 +96,6 @@ public interface InputMethod {
*
* @param token special token for the system to identify
* {@link InputMethodService}
- * @param displayId The id of the display that current IME shown.
- * Used for {{@link #updateInputMethodDisplay(int)}}
* @param privilegedOperations IPC endpoint to do some privileged
* operations that are allowed only to the
* current IME.
@@ -105,9 +103,8 @@ public interface InputMethod {
* @hide
*/
@MainThread
- default void initializeInternal(IBinder token, int displayId,
+ default void initializeInternal(IBinder token,
IInputMethodPrivilegedOperations privilegedOperations, int configChanges) {
- updateInputMethodDisplay(displayId);
attachToken(token);
}
@@ -143,16 +140,6 @@ public interface InputMethod {
public void attachToken(IBinder token);
/**
- * Update context display according to given displayId.
- *
- * @param displayId The id of the display that need to update for context.
- * @hide
- */
- @MainThread
- default void updateInputMethodDisplay(int displayId) {
- }
-
- /**
* Bind a new application environment in to the input method, so that it
* can later start and stop input processing.
* Typically this method is called when this input method is enabled in an
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 42d77cd09689..e6f103e6d53b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2154,6 +2154,7 @@ public final class InputMethodManager {
* @hide
*/
public boolean requestImeShow(IBinder windowToken) {
+ checkFocus();
synchronized (mH) {
final View servedView = getServedViewLocked();
if (servedView == null || servedView.getWindowToken() != windowToken) {
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index d078c2cfbfd1..9d1bf171128e 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -110,11 +110,10 @@ public class UiTranslationController {
public void updateUiTranslationState(@UiTranslationState int state, TranslationSpec sourceSpec,
TranslationSpec targetSpec, List<AutofillId> views,
UiTranslationSpec uiTranslationSpec) {
- if (!mActivity.isResumed() && (state == STATE_UI_TRANSLATION_STARTED
- || state == STATE_UI_TRANSLATION_RESUMED)) {
+ if (mActivity.isDestroyed()) {
+ Log.i(TAG, "Cannot update " + stateToString(state) + " for destroyed " + mActivity);
return;
}
-
Log.i(TAG, "updateUiTranslationState state: " + stateToString(state)
+ (DEBUG ? (", views: " + views + ", spec: " + uiTranslationSpec) : ""));
synchronized (mLock) {
@@ -342,10 +341,8 @@ public class UiTranslationController {
*/
private void onVirtualViewTranslationCompleted(
SparseArray<LongSparseArray<ViewTranslationResponse>> translatedResult) {
- if (!mActivity.isResumed()) {
- if (DEBUG) {
- Log.v(TAG, "onTranslationCompleted: Activity is not resumed.");
- }
+ if (mActivity.isDestroyed()) {
+ Log.v(TAG, "onTranslationCompleted:" + mActivity + "is destroyed.");
return;
}
synchronized (mLock) {
@@ -372,6 +369,10 @@ public class UiTranslationController {
Log.v(TAG, "onVirtualViewTranslationCompleted: received response for "
+ "AutofillId " + autofillId);
}
+ view.onVirtualViewTranslationResponses(virtualChildResponse);
+ if (mCurrentState == STATE_UI_TRANSLATION_PAUSED) {
+ return;
+ }
mActivity.runOnUiThread(() -> {
if (view.getViewTranslationCallback() == null) {
if (DEBUG) {
@@ -380,7 +381,6 @@ public class UiTranslationController {
}
return;
}
- view.onVirtualViewTranslationResponses(virtualChildResponse);
if (view.getViewTranslationCallback() != null) {
view.getViewTranslationCallback().onShowTranslation(view);
}
@@ -393,10 +393,8 @@ public class UiTranslationController {
* The method is used to handle the translation result for non-vertual views.
*/
private void onTranslationCompleted(SparseArray<ViewTranslationResponse> translatedResult) {
- if (!mActivity.isResumed()) {
- if (DEBUG) {
- Log.v(TAG, "onTranslationCompleted: Activity is not resumed.");
- }
+ if (mActivity.isDestroyed()) {
+ Log.v(TAG, "onTranslationCompleted:" + mActivity + "is destroyed.");
return;
}
final int resultCount = translatedResult.size();
@@ -430,6 +428,8 @@ public class UiTranslationController {
+ " may be gone.");
continue;
}
+ int currentState;
+ currentState = mCurrentState;
mActivity.runOnUiThread(() -> {
ViewTranslationCallback callback = view.getViewTranslationCallback();
if (view.getViewTranslationResponse() != null
@@ -463,6 +463,9 @@ public class UiTranslationController {
callback.enableContentPadding();
}
view.onViewTranslationResponse(response);
+ if (currentState == STATE_UI_TRANSLATION_PAUSED) {
+ return;
+ }
callback.onShowTranslation(view);
});
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index fe5eb085dc5c..2357d13c8d41 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -34,6 +34,7 @@ import android.app.Activity;
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.Application;
+import android.app.LoadedApk;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.appwidget.AppWidgetHostView;
@@ -5475,7 +5476,8 @@ public class RemoteViews implements Parcelable, Filter {
// user. So build a context that loads resources from that user but
// still returns the current users userId so settings like data / time formats
// are loaded without requiring cross user persmissions.
- final Context contextForResources = getContextForResources(context);
+ final Context contextForResources =
+ getContextForResourcesEnsuringCorrectCachedApkPaths(context);
if (colorResources != null) {
colorResources.apply(contextForResources);
}
@@ -5793,7 +5795,7 @@ public class RemoteViews implements Parcelable, Filter {
// across orientation change, and has the RemoteViews re-applied in the new orientation,
// we throw an exception, since the layouts may be completely unrelated.
if (hasMultipleLayouts()) {
- if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
+ if (!rvToApply.canRecycleView(v)) {
throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
" that does not share the same root layout id.");
}
@@ -5853,13 +5855,14 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- private Context getContextForResources(Context context) {
+ private Context getContextForResourcesEnsuringCorrectCachedApkPaths(Context context) {
if (mApplication != null) {
if (context.getUserId() == UserHandle.getUserId(mApplication.uid)
&& context.getPackageName().equals(mApplication.packageName)) {
return context;
}
try {
+ LoadedApk.checkAndUpdateApkPaths(mApplication);
return context.createApplicationContext(mApplication,
Context.CONTEXT_RESTRICTED);
} catch (NameNotFoundException e) {
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 6b33428d7fe4..8e293f4b356d 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -408,7 +408,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
@Override
- protected Context getRemoteContext() {
+ protected Context getRemoteContextEnsuringCorrectCachedApkPath() {
return null;
}
diff --git a/core/java/android/window/ConfigurationHelper.java b/core/java/android/window/ConfigurationHelper.java
new file mode 100644
index 000000000000..9a079751553f
--- /dev/null
+++ b/core/java/android/window/ConfigurationHelper.java
@@ -0,0 +1,132 @@
+/*
+ * 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 android.window;
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ResourcesManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.Display;
+import android.view.WindowManager;
+
+/**
+ * A helper class to maintain {@link android.content.res.Configuration} related methods used both
+ * in {@link android.app.Activity} and {@link WindowContext}.
+ *
+ * @hide
+ */
+public class ConfigurationHelper {
+ private ConfigurationHelper() {}
+
+ /** Ask text layout engine to free its caches if there is a locale change. */
+ public static void freeTextLayoutCachesIfNeeded(int configDiff) {
+ if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) {
+ Canvas.freeTextLayoutCaches();
+ }
+ }
+
+ /**
+ * A helper method to filter out {@link ActivityInfo#CONFIG_SCREEN_SIZE} if the
+ * {@link Configuration#diffPublicOnly(Configuration) diff} of two {@link Configuration}
+ * doesn't cross the boundary.
+ *
+ * @see SizeConfigurationBuckets#filterDiff(int, Configuration, Configuration,
+ * SizeConfigurationBuckets)
+ */
+ public static int diffPublicWithSizeBuckets(@Nullable Configuration currentConfig,
+ @NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets buckets) {
+ // If current configuration is null, it is definitely different from updated Configuration.
+ if (currentConfig == null) {
+ return 0xffffffff;
+ }
+ int publicDiff = currentConfig.diffPublicOnly(newConfig);
+ return SizeConfigurationBuckets.filterDiff(publicDiff, currentConfig, newConfig, buckets);
+ }
+
+ /**
+ * Returns {@code true} if the {@link android.content.res.Resources} associated with
+ * a {@code token} needs to be updated.
+ *
+ * @param token A {@link Context#getActivityToken() activity token} or
+ * {@link Context#getWindowContextToken() window context token}
+ * @param config The original {@link Configuration}
+ * @param newConfig The updated Configuration
+ * @param displayChanged a flag to indicate there's a display change
+ * @param configChanged a flag to indicate there's a Configuration change.
+ *
+ * @see ResourcesManager#updateResourcesForActivity(IBinder, Configuration, int)
+ */
+ public static boolean shouldUpdateResources(IBinder token, @Nullable Configuration config,
+ @NonNull Configuration newConfig, @NonNull Configuration overrideConfig,
+ boolean displayChanged, @Nullable Boolean configChanged) {
+ // The configuration has not yet been initialized. We should update it.
+ if (config == null) {
+ return true;
+ }
+ // If the token associated context is moved to another display, we should update the
+ // ResourcesKey.
+ if (displayChanged) {
+ return true;
+ }
+ // If the new config is the same as the config this Activity is already running with and
+ // the override config also didn't change, then don't update the Resources
+ if (!ResourcesManager.getInstance().isSameResourcesOverrideConfig(token, overrideConfig)) {
+ return true;
+ }
+ // If there's a update on WindowConfiguration#mBounds or maxBounds, we should update the
+ // Resources to make WindowMetrics API report the updated result.
+ if (shouldUpdateWindowMetricsBounds(config, newConfig)) {
+ return true;
+ }
+ return configChanged == null ? config.diff(newConfig) != 0 : configChanged;
+ }
+
+ /**
+ * Returns {@code true} if {@code displayId} is different from {@code newDisplayId}.
+ * Note that {@link Display#INVALID_DISPLAY} means no difference.
+ */
+ public static boolean isDifferentDisplay(int displayId, int newDisplayId) {
+ return newDisplayId != INVALID_DISPLAY && displayId != newDisplayId;
+ }
+
+ // TODO(b/173090263): Remove this method after the improvement of AssetManager and ResourcesImpl
+ // constructions.
+ /**
+ * Returns {@code true} if the metrics reported by {@link android.view.WindowMetrics} APIs
+ * should be updated.
+ *
+ * @see WindowManager#getCurrentWindowMetrics()
+ * @see WindowManager#getMaximumWindowMetrics()
+ */
+ private static boolean shouldUpdateWindowMetricsBounds(@NonNull Configuration currentConfig,
+ @NonNull Configuration newConfig) {
+ final Rect currentBounds = currentConfig.windowConfiguration.getBounds();
+ final Rect newBounds = newConfig.windowConfiguration.getBounds();
+
+ final Rect currentMaxBounds = currentConfig.windowConfiguration.getMaxBounds();
+ final Rect newMaxBounds = newConfig.windowConfiguration.getMaxBounds();
+
+ return !currentBounds.equals(newBounds) || !currentMaxBounds.equals(newMaxBounds);
+ }
+}
diff --git a/core/java/android/window/DisplayAreaInfo.java b/core/java/android/window/DisplayAreaInfo.java
index 358467ff599f..1a7aab6852b6 100644
--- a/core/java/android/window/DisplayAreaInfo.java
+++ b/core/java/android/window/DisplayAreaInfo.java
@@ -16,6 +16,8 @@
package android.window;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
+
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.content.res.Configuration;
@@ -43,8 +45,17 @@ public final class DisplayAreaInfo implements Parcelable {
*/
public final int displayId;
+ /**
+ * The feature id of this display area.
+ */
public final int featureId;
+ /**
+ * The feature id of the root display area this display area is associated with.
+ * @hide
+ */
+ public int rootDisplayAreaId = FEATURE_UNDEFINED;
+
public DisplayAreaInfo(@NonNull WindowContainerToken token, int displayId, int featureId) {
this.token = token;
this.displayId = displayId;
@@ -56,6 +67,7 @@ public final class DisplayAreaInfo implements Parcelable {
configuration.readFromParcel(in);
displayId = in.readInt();
featureId = in.readInt();
+ rootDisplayAreaId = in.readInt();
}
@Override
@@ -64,6 +76,7 @@ public final class DisplayAreaInfo implements Parcelable {
configuration.writeToParcel(dest, flags);
dest.writeInt(displayId);
dest.writeInt(featureId);
+ dest.writeInt(rootDisplayAreaId);
}
@NonNull
diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
index 878439906de2..6758a3b411a2 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -34,6 +34,15 @@ import java.util.concurrent.Executor;
public class DisplayAreaOrganizer extends WindowOrganizer {
/**
+ * Key to specify the {@link com.android.server.wm.RootDisplayArea} to attach a window to.
+ * It will be used by the function passed in from
+ * {@link com.android.server.wm.DisplayAreaPolicyBuilder#setSelectRootForWindowFunc(BiFunction)}
+ * to find the Root DA to attach the window.
+ * @hide
+ */
+ public static final String KEY_ROOT_DISPLAY_AREA_ID = "root_display_area_id";
+
+ /**
* The value in display area indicating that no value has been set.
*/
public static final int FEATURE_UNDEFINED = -1;
@@ -256,6 +265,7 @@ public class DisplayAreaOrganizer extends WindowOrganizer {
}
};
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
private IDisplayAreaOrganizerController getController() {
try {
return getWindowOrganizerController().getDisplayAreaOrganizerController();
@@ -263,5 +273,4 @@ public class DisplayAreaOrganizer extends WindowOrganizer {
return null;
}
}
-
}
diff --git a/core/java/android/window/IRemoteTransitionFinishedCallback.aidl b/core/java/android/window/IRemoteTransitionFinishedCallback.aidl
index 02aa1a93a35f..7864c245310e 100644
--- a/core/java/android/window/IRemoteTransitionFinishedCallback.aidl
+++ b/core/java/android/window/IRemoteTransitionFinishedCallback.aidl
@@ -16,14 +16,18 @@
package android.window;
+import android.view.SurfaceControl;
import android.window.WindowContainerTransaction;
/**
* Interface to be invoked by the controlling process when a remote transition has finished.
*
* @see IRemoteTransition
+ * @param wct An optional WindowContainerTransaction to apply before the transition finished.
+ * @param sct An optional Surface Transaction that is added to the end of the finish/cleanup
+ * transaction. This is applied by shell.Transitions (before submitting the wct).
* {@hide}
*/
interface IRemoteTransitionFinishedCallback {
- void onTransitionFinished(in WindowContainerTransaction wct);
+ void onTransitionFinished(in WindowContainerTransaction wct, in SurfaceControl.Transaction sct);
}
diff --git a/core/java/android/window/ITaskFragmentOrganizer.aidl b/core/java/android/window/ITaskFragmentOrganizer.aidl
new file mode 100644
index 000000000000..5eb432e785ee
--- /dev/null
+++ b/core/java/android/window/ITaskFragmentOrganizer.aidl
@@ -0,0 +1,52 @@
+/**
+ * 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 android.window;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.window.TaskFragmentAppearedInfo;
+import android.window.TaskFragmentInfo;
+
+/** @hide */
+oneway interface ITaskFragmentOrganizer {
+ void onTaskFragmentAppeared(in TaskFragmentAppearedInfo taskFragmentAppearedInfo);
+ void onTaskFragmentInfoChanged(in TaskFragmentInfo taskFragmentInfo);
+ void onTaskFragmentVanished(in TaskFragmentInfo taskFragmentInfo);
+
+ /**
+ * Called when the parent leaf Task of organized TaskFragments is changed.
+ * When the leaf Task is changed, the organizer may want to update the TaskFragments in one
+ * transaction.
+ *
+ * For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new
+ * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override
+ * bounds.
+ */
+ void onTaskFragmentParentInfoChanged(in IBinder fragmentToken, in Configuration parentConfig);
+
+ /**
+ * Called when the {@link WindowContainerTransaction} created with
+ * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
+ *
+ * @param errorCallbackToken Token set through {@link
+ * WindowContainerTransaction#setErrorCallbackToken(IBinder)}
+ * @param exceptionBundle Bundle containing the exception. Should be created with
+ * {@link TaskFragmentOrganizer#putExceptionInBundle}.
+ */
+ void onTaskFragmentError(in IBinder errorCallbackToken, in Bundle exceptionBundle);
+}
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
new file mode 100644
index 000000000000..1ad0452dd837
--- /dev/null
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -0,0 +1,47 @@
+/**
+ * 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 android.window;
+
+import android.view.RemoteAnimationDefinition;
+import android.window.ITaskFragmentOrganizer;
+
+/** @hide */
+interface ITaskFragmentOrganizerController {
+
+ /**
+ * Registers a TaskFragmentOrganizer to manage TaskFragments.
+ */
+ void registerOrganizer(in ITaskFragmentOrganizer organizer);
+
+ /**
+ * Unregisters a previously registered TaskFragmentOrganizer.
+ */
+ void unregisterOrganizer(in ITaskFragmentOrganizer organizer);
+
+ /**
+ * Registers remote animations per transition type for the organizer. It will override the
+ * animations if the transition only contains windows that belong to the organized
+ * TaskFragments.
+ */
+ void registerRemoteAnimations(in ITaskFragmentOrganizer organizer,
+ in RemoteAnimationDefinition definition);
+
+ /**
+ * Unregisters remote animations per transition type for the organizer.
+ */
+ void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer);
+}
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index 69bc1b5f7763..fd86769293a6 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -20,6 +20,7 @@ import android.view.SurfaceControl;
import android.app.ActivityManager;
import android.graphics.Rect;
import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
import android.window.WindowContainerToken;
/**
@@ -39,12 +40,9 @@ oneway interface ITaskOrganizer {
/**
* Called when the Task want to remove the starting window.
- * @param leash A persistent leash for the top window in this task.
- * @param frame Window frame of the top window.
- * @param playRevealAnimation Play vanish animation.
+ * @param removalInfo The information used to remove the starting window.
*/
- void removeStartingWindow(int taskId, in SurfaceControl leash, in Rect frame,
- in boolean playRevealAnimation);
+ void removeStartingWindow(in StartingWindowRemovalInfo removalInfo);
/**
* Called when the Task want to copy the splash screen.
diff --git a/core/java/android/window/ITransitionMetricsReporter.aidl b/core/java/android/window/ITransitionMetricsReporter.aidl
new file mode 100644
index 000000000000..00f71dc7bb90
--- /dev/null
+++ b/core/java/android/window/ITransitionMetricsReporter.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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 android.window;
+
+import android.os.IBinder;
+
+/**
+ * Implemented by WM Core to know the metrics of transition that runs on a different process.
+ * @hide
+ */
+oneway interface ITransitionMetricsReporter {
+
+ /**
+ * Called when the transition animation starts.
+ *
+ * @param startTime The time when the animation started.
+ */
+ void reportAnimationStart(IBinder transitionToken, long startTime);
+}
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 1223d72f643e..3c7cd0254e78 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -19,8 +19,11 @@ package android.window;
import android.view.SurfaceControl;
import android.os.IBinder;
+import android.view.RemoteAnimationAdapter;
import android.window.IDisplayAreaOrganizerController;
+import android.window.ITaskFragmentOrganizerController;
import android.window.ITaskOrganizerController;
+import android.window.ITransitionMetricsReporter;
import android.window.ITransitionPlayer;
import android.window.IWindowContainerTransactionCallback;
import android.window.WindowContainerToken;
@@ -60,6 +63,17 @@ interface IWindowOrganizerController {
in @nullable WindowContainerTransaction t);
/**
+ * Starts a legacy transition.
+ * @param type The transition type.
+ * @param adapter The animation to use.
+ * @param syncCallback A sync callback for the contents of `t`
+ * @param t Operations that are part of the transition.
+ * @return sync-id or -1 if this no-op'd because a transition is already running.
+ */
+ int startLegacyTransition(int type, in RemoteAnimationAdapter adapter,
+ in IWindowContainerTransactionCallback syncCallback, in WindowContainerTransaction t);
+
+ /**
* Finishes a transition. This must be called for all created transitions.
* @param transitionToken Which transition to finish
* @param t Changes to make before finishing but in the same SF Transaction. Can be null.
@@ -77,9 +91,15 @@ interface IWindowOrganizerController {
/** @return An interface enabling the management of display area organizers. */
IDisplayAreaOrganizerController getDisplayAreaOrganizerController();
+ /** @return An interface enabling the management of task fragment organizers. */
+ ITaskFragmentOrganizerController getTaskFragmentOrganizerController();
+
/**
* Registers a transition player with Core. There is only one of these at a time and calling
* this will replace the existing one if set.
*/
void registerTransitionPlayer(in ITransitionPlayer player);
+
+ /** @return An interface enabling the transition players to report its metrics. */
+ ITransitionMetricsReporter getTransitionMetricsReporter();
}
diff --git a/core/java/android/window/RemoteTransition.aidl b/core/java/android/window/RemoteTransition.aidl
new file mode 100644
index 000000000000..f3c3f54410ed
--- /dev/null
+++ b/core/java/android/window/RemoteTransition.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.window;
+
+parcelable RemoteTransition;
diff --git a/core/java/android/window/RemoteTransition.java b/core/java/android/window/RemoteTransition.java
new file mode 100644
index 000000000000..b243b656b8cd
--- /dev/null
+++ b/core/java/android/window/RemoteTransition.java
@@ -0,0 +1,158 @@
+/*
+ * 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 android.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Represents a remote transition animation and information required to run it (eg. the app thread
+ * that needs to be boosted).
+ * @hide
+ */
+@DataClass(genToString = true, genSetters = true, genAidl = true)
+public class RemoteTransition implements Parcelable {
+
+ /** The actual remote-transition interface used to run the transition animation. */
+ private @NonNull IRemoteTransition mRemoteTransition;
+
+ /** Get the IBinder associated with the underlying IRemoteTransition. */
+ public @Nullable IBinder asBinder() {
+ return mRemoteTransition.asBinder();
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/window/RemoteTransition.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new RemoteTransition.
+ *
+ * @param remoteTransition
+ * The actual remote-transition interface used to run the transition animation.
+ */
+ @DataClass.Generated.Member
+ public RemoteTransition(
+ @NonNull IRemoteTransition remoteTransition) {
+ this.mRemoteTransition = remoteTransition;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mRemoteTransition);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The actual remote-transition interface used to run the transition animation.
+ */
+ @DataClass.Generated.Member
+ public @NonNull IRemoteTransition getRemoteTransition() {
+ return mRemoteTransition;
+ }
+
+ /**
+ * The actual remote-transition interface used to run the transition animation.
+ */
+ @DataClass.Generated.Member
+ public @NonNull RemoteTransition setRemoteTransition(@NonNull IRemoteTransition value) {
+ mRemoteTransition = value;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mRemoteTransition);
+ return this;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "RemoteTransition { " +
+ "remoteTransition = " + mRemoteTransition +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeStrongInterface(mRemoteTransition);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected RemoteTransition(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ IRemoteTransition remoteTransition = IRemoteTransition.Stub.asInterface(in.readStrongBinder());
+
+ this.mRemoteTransition = remoteTransition;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mRemoteTransition);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<RemoteTransition> CREATOR
+ = new Parcelable.Creator<RemoteTransition>() {
+ @Override
+ public RemoteTransition[] newArray(int size) {
+ return new RemoteTransition[size];
+ }
+
+ @Override
+ public RemoteTransition createFromParcel(@NonNull android.os.Parcel in) {
+ return new RemoteTransition(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1630613039043L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/window/RemoteTransition.java",
+ inputSignatures = "private @android.annotation.NonNull android.window.IRemoteTransition mRemoteTransition\npublic @android.annotation.Nullable android.os.IBinder asBinder()\nclass RemoteTransition extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/window/SizeConfigurationBuckets.java b/core/java/android/window/SizeConfigurationBuckets.java
index 7422f2449a8d..f474f0a76cc6 100644
--- a/core/java/android/window/SizeConfigurationBuckets.java
+++ b/core/java/android/window/SizeConfigurationBuckets.java
@@ -16,6 +16,7 @@
package android.window;
+import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
@@ -25,6 +26,7 @@ import android.content.res.Configuration;
import android.os.Parcelable;
import android.util.SparseIntArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DataClass;
import java.util.Arrays;
@@ -54,10 +56,24 @@ public final class SizeConfigurationBuckets implements Parcelable {
@Nullable
private final int[] mSmallest;
+ /** Screen Layout Size (screenLayout & SCREENLAYOUT_SIZE_MASK) buckets */
+ @Nullable
+ private final int[] mScreenLayoutSize;
+
+ /**
+ * Screen Layout Long (screenLayout & SCREENLAYOUT_LONG_MASK) boolean. Only need to know if a
+ * value is set because only two possible buckets, SCREENLAYOUT_LONG_NO and
+ * SCREENLAYOUT_LONG_YES, so if either is set, then any change is a bucket change.
+ */
+ private final boolean mScreenLayoutLongSet;
+
public SizeConfigurationBuckets(Configuration[] sizeConfigurations) {
SparseIntArray horizontal = new SparseIntArray();
SparseIntArray vertical = new SparseIntArray();
SparseIntArray smallest = new SparseIntArray();
+ SparseIntArray screenLayoutSize = new SparseIntArray();
+ int curScreenLayoutSize;
+ boolean screenLayoutLongSet = false;
for (int i = sizeConfigurations.length - 1; i >= 0; i--) {
Configuration config = sizeConfigurations[i];
if (config.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
@@ -69,23 +85,42 @@ public final class SizeConfigurationBuckets implements Parcelable {
if (config.smallestScreenWidthDp != Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
smallest.put(config.smallestScreenWidthDp, 0);
}
+ if ((curScreenLayoutSize = config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)
+ != Configuration.SCREENLAYOUT_SIZE_UNDEFINED) {
+ screenLayoutSize.put(curScreenLayoutSize, 0);
+ }
+ if (!screenLayoutLongSet && (config.screenLayout & Configuration.SCREENLAYOUT_LONG_MASK)
+ != Configuration.SCREENLAYOUT_LONG_UNDEFINED) {
+ screenLayoutLongSet = true;
+ }
}
mHorizontal = horizontal.copyKeys();
mVertical = vertical.copyKeys();
mSmallest = smallest.copyKeys();
+ mScreenLayoutSize = screenLayoutSize.copyKeys();
+ mScreenLayoutLongSet = screenLayoutLongSet;
}
/**
* Get the changes between two configurations but don't count changes in sizes if they don't
- * cross boundaries that are important to the app.
+ * cross boundaries that are important to the app.
*
* This is a static helper to deal with null `buckets`. When no buckets have been specified,
* this actually filters out all 3 size-configs. This is legacy behavior.
*/
- public static int filterDiff(int diff, Configuration oldConfig, Configuration newConfig,
- @Nullable SizeConfigurationBuckets buckets) {
+ public static int filterDiff(int diff, @NonNull Configuration oldConfig,
+ @NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets buckets) {
+ final boolean nonSizeLayoutFieldsUnchanged =
+ areNonSizeLayoutFieldsUnchanged(oldConfig.screenLayout, newConfig.screenLayout);
if (buckets == null) {
- return diff & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE);
+ // Only unflip CONFIG_SCREEN_LAYOUT if non-size-related attributes of screen layout do
+ // not change.
+ if (nonSizeLayoutFieldsUnchanged) {
+ return diff & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE
+ | CONFIG_SCREEN_LAYOUT);
+ } else {
+ return diff & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE);
+ }
}
if ((diff & CONFIG_SCREEN_SIZE) != 0) {
final boolean crosses = buckets.crossesHorizontalSizeThreshold(oldConfig.screenWidthDp,
@@ -103,6 +138,13 @@ public final class SizeConfigurationBuckets implements Parcelable {
diff &= ~CONFIG_SMALLEST_SCREEN_SIZE;
}
}
+ if ((diff & CONFIG_SCREEN_LAYOUT) != 0 && nonSizeLayoutFieldsUnchanged) {
+ if (!buckets.crossesScreenLayoutSizeThreshold(oldConfig, newConfig)
+ && !buckets.crossesScreenLayoutLongThreshold(oldConfig.screenLayout,
+ newConfig.screenLayout)) {
+ diff &= ~CONFIG_SCREEN_LAYOUT;
+ }
+ }
return diff;
}
@@ -119,6 +161,61 @@ public final class SizeConfigurationBuckets implements Parcelable {
}
/**
+ * Returns whether a screen layout size threshold has been crossed.
+ */
+ @VisibleForTesting
+ public boolean crossesScreenLayoutSizeThreshold(@NonNull Configuration firstConfig,
+ @NonNull Configuration secondConfig) {
+ // If both the old and new screen layout are equal (both can be undefined), then no
+ // threshold is crossed.
+ if ((firstConfig.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)
+ == (secondConfig.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)) {
+ return false;
+ }
+ // Any time the new layout size is smaller than the old layout size, the activity has
+ // crossed a size threshold because layout size represents the smallest possible size the
+ // activity can occupy.
+ if (!secondConfig.isLayoutSizeAtLeast(firstConfig.screenLayout
+ & Configuration.SCREENLAYOUT_SIZE_MASK)) {
+ return true;
+ }
+ // If the new layout size is at least as large as the old layout size, then check if the new
+ // layout size has crossed a threshold.
+ if (mScreenLayoutSize != null) {
+ for (int screenLayoutSize : mScreenLayoutSize) {
+ if (firstConfig.isLayoutSizeAtLeast(screenLayoutSize)
+ != secondConfig.isLayoutSizeAtLeast(screenLayoutSize)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean crossesScreenLayoutLongThreshold(int firstScreenLayout,
+ int secondScreenLayout) {
+ final int firstScreenLayoutLongValue = firstScreenLayout
+ & Configuration.SCREENLAYOUT_LONG_MASK;
+ final int secondScreenLayoutLongValue = secondScreenLayout
+ & Configuration.SCREENLAYOUT_LONG_MASK;
+ return mScreenLayoutLongSet && firstScreenLayoutLongValue != secondScreenLayoutLongValue;
+ }
+
+ /**
+ * Returns whether non-size related screen layout attributes have changed. If true, then
+ * {@link ActivityInfo#CONFIG_SCREEN_LAYOUT} should not be filtered out in
+ * {@link SizeConfigurationBuckets#filterDiff()} because the non-size related attributes
+ * do not have a bucket range like the size-related attributes of screen layout.
+ */
+ @VisibleForTesting
+ public static boolean areNonSizeLayoutFieldsUnchanged(int oldScreenLayout,
+ int newScreenLayout) {
+ final int nonSizeRelatedFields = Configuration.SCREENLAYOUT_LAYOUTDIR_MASK
+ | Configuration.SCREENLAYOUT_ROUND_MASK | Configuration.SCREENLAYOUT_COMPAT_NEEDED;
+ return (oldScreenLayout & nonSizeRelatedFields) == (newScreenLayout & nonSizeRelatedFields);
+ }
+
+ /**
* The purpose of this method is to decide whether the activity needs to be relaunched upon
* changing its size. In most cases the activities don't need to be relaunched, if the resize
* is small, all the activity content has to do is relayout itself within new bounds. There are
@@ -132,7 +229,8 @@ public final class SizeConfigurationBuckets implements Parcelable {
* it resizes width from 620dp to 700dp, it won't be relaunched as it stays on the same side
* of the threshold.
*/
- private static boolean crossesSizeThreshold(int[] thresholds, int firstDp,
+ @VisibleForTesting
+ public static boolean crossesSizeThreshold(int[] thresholds, int firstDp,
int secondDp) {
if (thresholds == null) {
return false;
@@ -150,12 +248,13 @@ public final class SizeConfigurationBuckets implements Parcelable {
@Override
public String toString() {
return Arrays.toString(mHorizontal) + " " + Arrays.toString(mVertical) + " "
- + Arrays.toString(mSmallest);
+ + Arrays.toString(mSmallest) + " " + Arrays.toString(mScreenLayoutSize) + " "
+ + mScreenLayoutLongSet;
}
- // Code below generated by codegen v1.0.22.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -177,15 +276,25 @@ public final class SizeConfigurationBuckets implements Parcelable {
* Vertical (screenHeightDp) buckets
* @param smallest
* Smallest (smallestScreenWidthDp) buckets
+ * @param screenLayoutSize
+ * Screen Layout Size (screenLayout & SCREENLAYOUT_SIZE_MASK) buckets
+ * @param screenLayoutLongSet
+ * Screen Layout Long (screenLayout & SCREENLAYOUT_LONG_MASK) boolean. Only need to know if a
+ * value is set because only two possible buckets, SCREENLAYOUT_LONG_NO and
+ * SCREENLAYOUT_LONG_YES, so if either is set, then any change is a bucket change.
*/
@DataClass.Generated.Member
public SizeConfigurationBuckets(
@Nullable int[] horizontal,
@Nullable int[] vertical,
- @Nullable int[] smallest) {
+ @Nullable int[] smallest,
+ @Nullable int[] screenLayoutSize,
+ boolean screenLayoutLongSet) {
this.mHorizontal = horizontal;
this.mVertical = vertical;
this.mSmallest = smallest;
+ this.mScreenLayoutSize = screenLayoutSize;
+ this.mScreenLayoutLongSet = screenLayoutLongSet;
// onConstructed(); // You can define this method to get a callback
}
@@ -214,6 +323,24 @@ public final class SizeConfigurationBuckets implements Parcelable {
return mSmallest;
}
+ /**
+ * Screen Layout Size (screenLayout & SCREENLAYOUT_SIZE_MASK) buckets
+ */
+ @DataClass.Generated.Member
+ public @Nullable int[] getScreenLayoutSize() {
+ return mScreenLayoutSize;
+ }
+
+ /**
+ * Screen Layout Long (screenLayout & SCREENLAYOUT_LONG_MASK) boolean. Only need to know if a
+ * value is set because only two possible buckets, SCREENLAYOUT_LONG_NO and
+ * SCREENLAYOUT_LONG_YES, so if either is set, then any change is a bucket change.
+ */
+ @DataClass.Generated.Member
+ public boolean isScreenLayoutLongSet() {
+ return mScreenLayoutLongSet;
+ }
+
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
@@ -221,13 +348,16 @@ public final class SizeConfigurationBuckets implements Parcelable {
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
+ if (mScreenLayoutLongSet) flg |= 0x10;
if (mHorizontal != null) flg |= 0x1;
if (mVertical != null) flg |= 0x2;
if (mSmallest != null) flg |= 0x4;
+ if (mScreenLayoutSize != null) flg |= 0x8;
dest.writeByte(flg);
if (mHorizontal != null) dest.writeIntArray(mHorizontal);
if (mVertical != null) dest.writeIntArray(mVertical);
if (mSmallest != null) dest.writeIntArray(mSmallest);
+ if (mScreenLayoutSize != null) dest.writeIntArray(mScreenLayoutSize);
}
@Override
@@ -242,13 +372,17 @@ public final class SizeConfigurationBuckets implements Parcelable {
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
+ boolean screenLayoutLongSet = (flg & 0x10) != 0;
int[] horizontal = (flg & 0x1) == 0 ? null : in.createIntArray();
int[] vertical = (flg & 0x2) == 0 ? null : in.createIntArray();
int[] smallest = (flg & 0x4) == 0 ? null : in.createIntArray();
+ int[] screenLayoutSize = (flg & 0x8) == 0 ? null : in.createIntArray();
this.mHorizontal = horizontal;
this.mVertical = vertical;
this.mSmallest = smallest;
+ this.mScreenLayoutSize = screenLayoutSize;
+ this.mScreenLayoutLongSet = screenLayoutLongSet;
// onConstructed(); // You can define this method to get a callback
}
@@ -268,10 +402,10 @@ public final class SizeConfigurationBuckets implements Parcelable {
};
@DataClass.Generated(
- time = 1615845864280L,
- codegenVersion = "1.0.22",
+ time = 1628273704583L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/window/SizeConfigurationBuckets.java",
- inputSignatures = "private final @android.annotation.Nullable int[] mHorizontal\nprivate final @android.annotation.Nullable int[] mVertical\nprivate final @android.annotation.Nullable int[] mSmallest\npublic static int filterDiff(int,android.content.res.Configuration,android.content.res.Configuration,android.window.SizeConfigurationBuckets)\nprivate boolean crossesHorizontalSizeThreshold(int,int)\nprivate boolean crossesVerticalSizeThreshold(int,int)\nprivate boolean crossesSmallestSizeThreshold(int,int)\nprivate static boolean crossesSizeThreshold(int[],int,int)\npublic @java.lang.Override java.lang.String toString()\nclass SizeConfigurationBuckets extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true)")
+ inputSignatures = "private final @android.annotation.Nullable int[] mHorizontal\nprivate final @android.annotation.Nullable int[] mVertical\nprivate final @android.annotation.Nullable int[] mSmallest\nprivate final @android.annotation.Nullable int[] mScreenLayoutSize\nprivate final boolean mScreenLayoutLongSet\npublic static int filterDiff(int,android.content.res.Configuration,android.content.res.Configuration,android.window.SizeConfigurationBuckets)\nprivate boolean crossesHorizontalSizeThreshold(int,int)\nprivate boolean crossesVerticalSizeThreshold(int,int)\nprivate boolean crossesSmallestSizeThreshold(int,int)\npublic @com.android.internal.annotations.VisibleForTesting boolean crossesScreenLayoutSizeThreshold(android.content.res.Configuration,android.content.res.Configuration)\nprivate boolean crossesScreenLayoutLongThreshold(int,int)\npublic static @com.android.internal.annotations.VisibleForTesting boolean areNonSizeLayoutFieldsUnchanged(int,int)\npublic static @com.android.internal.annotations.VisibleForTesting boolean crossesSizeThreshold(int[],int,int)\npublic @java.lang.Override java.lang.String toString()\nclass SizeConfigurationBuckets extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java
index 3e0075857402..3354a6ca7738 100644
--- a/core/java/android/window/SplashScreen.java
+++ b/core/java/android/window/SplashScreen.java
@@ -241,7 +241,6 @@ public interface SplashScreen {
public void handOverSplashScreenView(@NonNull IBinder token,
@NonNull SplashScreenView splashScreenView) {
- transferSurface(splashScreenView);
dispatchOnExitAnimation(token, splashScreenView);
}
@@ -265,9 +264,5 @@ public interface SplashScreen {
return impl != null && impl.mExitAnimationListener != null;
}
}
-
- private void transferSurface(@NonNull SplashScreenView splashScreenView) {
- splashScreenView.transferSurface();
- }
}
}
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index acf20d701eeb..f748d4bc121d 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -59,6 +59,7 @@ import com.android.internal.util.ContrastColorUtil;
import java.time.Duration;
import java.time.Instant;
+import java.util.function.Consumer;
/**
* <p>The view which allows an activity to customize its splash screen exit animation.</p>
@@ -144,6 +145,7 @@ public final class SplashScreenView extends FrameLayout {
private Bitmap mParceledBrandingBitmap;
private Instant mIconAnimationStart;
private Duration mIconAnimationDuration;
+ private Consumer<Runnable> mUiThreadInitTask;
public Builder(@NonNull Context context) {
mContext = context;
@@ -232,6 +234,15 @@ public final class SplashScreenView extends FrameLayout {
}
/**
+ * Set the Runnable that can receive the task which should be executed on UI thread.
+ * @param uiThreadInitTask
+ */
+ public Builder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) {
+ mUiThreadInitTask = uiThreadInitTask;
+ return this;
+ }
+
+ /**
* Set the Drawable object and size for the branding view.
*/
public Builder setBrandingDrawable(@Nullable Drawable branding, int width, int height) {
@@ -262,7 +273,11 @@ public final class SplashScreenView extends FrameLayout {
// center icon
if (mIconDrawable instanceof SplashScreenView.IconAnimateListener
|| mSurfacePackage != null) {
- view.mIconView = createSurfaceView(view);
+ if (mUiThreadInitTask != null) {
+ mUiThreadInitTask.accept(() -> view.mIconView = createSurfaceView(view));
+ } else {
+ view.mIconView = createSurfaceView(view);
+ }
view.initIconAnimation(mIconDrawable,
mIconAnimationDuration != null ? mIconAnimationDuration.toMillis() : 0);
view.mIconAnimationStart = mIconAnimationStart;
@@ -316,7 +331,9 @@ public final class SplashScreenView extends FrameLayout {
}
private SurfaceView createSurfaceView(@NonNull SplashScreenView view) {
- final SurfaceView surfaceView = new SurfaceView(view.getContext());
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SplashScreenView#createSurfaceView");
+ final Context viewContext = view.getContext();
+ final SurfaceView surfaceView = new SurfaceView(viewContext);
surfaceView.setPadding(0, 0, 0, 0);
surfaceView.setBackground(mIconBackground);
if (mSurfacePackage == null) {
@@ -326,10 +343,10 @@ public final class SplashScreenView extends FrameLayout {
+ Thread.currentThread().getId());
}
- SurfaceControlViewHost viewHost = new SurfaceControlViewHost(mContext,
- mContext.getDisplay(),
+ SurfaceControlViewHost viewHost = new SurfaceControlViewHost(viewContext,
+ viewContext.getDisplay(),
surfaceView.getHostToken());
- ImageView imageView = new ImageView(mContext);
+ ImageView imageView = new ImageView(viewContext);
imageView.setBackground(mIconDrawable);
viewHost.setView(imageView, mIconSize, mIconSize);
SurfaceControlViewHost.SurfacePackage surfacePackage = viewHost.getSurfacePackage();
@@ -360,6 +377,7 @@ public final class SplashScreenView extends FrameLayout {
view.addView(surfaceView);
view.mSurfaceView = surfaceView;
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return surfaceView;
}
}
@@ -446,7 +464,10 @@ public final class SplashScreenView extends FrameLayout {
}
- void transferSurface() {
+ /**
+ * @hide
+ */
+ public void syncTransferSurfaceOnDraw() {
if (mSurfacePackage == null) {
return;
}
@@ -456,8 +477,8 @@ public final class SplashScreenView extends FrameLayout {
String.format("SurfacePackage'surface reparented to %s", parent)));
Log.d(TAG, "Transferring surface " + mSurfaceView.toString());
}
- mSurfaceView.setChildSurfacePackage(mSurfacePackage);
+ mSurfaceView.setChildSurfacePackageOnDraw(mSurfacePackage);
}
void initIconAnimation(Drawable iconDrawable, long duration) {
@@ -515,10 +536,6 @@ public final class SplashScreenView extends FrameLayout {
restoreSystemUIColors();
mWindow = null;
}
- if (mHostActivity != null) {
- mHostActivity.setSplashScreenView(null);
- mHostActivity = null;
- }
mHasRemoved = true;
}
@@ -531,17 +548,14 @@ public final class SplashScreenView extends FrameLayout {
private void releaseAnimationSurfaceHost() {
if (mSurfaceHost != null && !mIsCopied) {
- final SurfaceControlViewHost finalSurfaceHost = mSurfaceHost;
+ if (DEBUG) {
+ Log.d(TAG,
+ "Shell removed splash screen."
+ + " Releasing SurfaceControlViewHost on thread #"
+ + Thread.currentThread().getId());
+ }
+ releaseIconHost(mSurfaceHost);
mSurfaceHost = null;
- finalSurfaceHost.getView().post(() -> {
- if (DEBUG) {
- Log.d(TAG,
- "Shell removed splash screen."
- + " Releasing SurfaceControlViewHost on thread #"
- + Thread.currentThread().getId());
- }
- finalSurfaceHost.release();
- });
} else if (mSurfacePackage != null && mSurfaceHost == null) {
mSurfacePackage = null;
mClientCallback.sendResult(null);
@@ -549,13 +563,24 @@ public final class SplashScreenView extends FrameLayout {
}
/**
+ * Release the host which hold the SurfaceView of the icon.
+ * @hide
+ */
+ public static void releaseIconHost(SurfaceControlViewHost host) {
+ final Drawable background = host.getView().getBackground();
+ if (background instanceof SplashScreenView.IconAnimateListener) {
+ ((SplashScreenView.IconAnimateListener) background).stopAnimation();
+ }
+ host.release();
+ }
+
+ /**
* Called when this view is attached to an activity. This also makes SystemUI colors
* transparent so the content of splash screen view can draw fully.
*
* @hide
*/
public void attachHostActivityAndSetSystemUIColors(Activity activity, Window window) {
- activity.setSplashScreenView(this);
mHostActivity = activity;
mWindow = window;
final WindowManager.LayoutParams attr = window.getAttributes();
@@ -639,6 +664,11 @@ public final class SplashScreenView extends FrameLayout {
* @return true if this drawable object can also be animated and it can be played now.
*/
boolean prepareAnimate(long duration, Runnable startListener);
+
+ /**
+ * Stop animation.
+ */
+ void stopAnimation();
}
/**
diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java
index 8c64474dc887..5950e9fc6456 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -19,13 +19,13 @@ package android.window;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.pm.ActivityInfo;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import android.view.WindowManager;
/**
@@ -33,7 +33,6 @@ import android.view.WindowManager;
* start in the system.
* @hide
*/
-@TestApi
public final class StartingWindowInfo implements Parcelable {
/**
* Prefer nothing or not care the type of starting window.
@@ -165,7 +164,13 @@ public final class StartingWindowInfo implements Parcelable {
* TaskSnapshot.
* @hide
*/
- public TaskSnapshot mTaskSnapshot;
+ public TaskSnapshot taskSnapshot;
+
+ /**
+ * The requested insets visibility of the top main window.
+ * @hide
+ */
+ public final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
public StartingWindowInfo() {
@@ -190,7 +195,8 @@ public final class StartingWindowInfo implements Parcelable {
dest.writeTypedObject(mainWindowLayoutParams, flags);
dest.writeInt(splashScreenThemeResId);
dest.writeBoolean(isKeyguardOccluded);
- dest.writeTypedObject(mTaskSnapshot, flags);
+ dest.writeTypedObject(taskSnapshot, flags);
+ requestedVisibilities.writeToParcel(dest, flags);
}
void readFromParcel(@NonNull Parcel source) {
@@ -203,7 +209,8 @@ public final class StartingWindowInfo implements Parcelable {
mainWindowLayoutParams = source.readTypedObject(WindowManager.LayoutParams.CREATOR);
splashScreenThemeResId = source.readInt();
isKeyguardOccluded = source.readBoolean();
- mTaskSnapshot = source.readTypedObject(TaskSnapshot.CREATOR);
+ taskSnapshot = source.readTypedObject(TaskSnapshot.CREATOR);
+ requestedVisibilities.readFromParcel(source);
}
@Override
diff --git a/core/java/android/window/StartingWindowRemovalInfo.aidl b/core/java/android/window/StartingWindowRemovalInfo.aidl
new file mode 100644
index 000000000000..8e4ac0453f83
--- /dev/null
+++ b/core/java/android/window/StartingWindowRemovalInfo.aidl
@@ -0,0 +1,20 @@
+/**
+ * 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 android.window;
+
+/** @hide */
+parcelable StartingWindowRemovalInfo; \ No newline at end of file
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
new file mode 100644
index 000000000000..573db0d58625
--- /dev/null
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -0,0 +1,111 @@
+/*
+ * 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 android.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.SurfaceControl;
+
+/**
+ * Information when removing a starting window of a particular task.
+ * @hide
+ */
+public final class StartingWindowRemovalInfo implements Parcelable {
+
+ /**
+ * The identifier of a task.
+ * @hide
+ */
+ public int taskId;
+
+ /**
+ * The animation container layer of the top activity.
+ * @hide
+ */
+ @Nullable
+ public SurfaceControl windowAnimationLeash;
+
+ /**
+ * The main window frame for the window of the top activity.
+ * @hide
+ */
+ @Nullable
+ public Rect mainFrame;
+
+ /**
+ * Whether need to play reveal animation.
+ * @hide
+ */
+ public boolean playRevealAnimation;
+
+ /**
+ * Whether need to defer removing the starting window for IME.
+ * @hide
+ */
+ public boolean deferRemoveForIme;
+
+ public StartingWindowRemovalInfo() {
+
+ }
+
+ private StartingWindowRemovalInfo(@NonNull Parcel source) {
+ readFromParcel(source);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ void readFromParcel(@NonNull Parcel source) {
+ taskId = source.readInt();
+ windowAnimationLeash = source.readTypedObject(SurfaceControl.CREATOR);
+ mainFrame = source.readTypedObject(Rect.CREATOR);
+ playRevealAnimation = source.readBoolean();
+ deferRemoveForIme = source.readBoolean();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(taskId);
+ dest.writeTypedObject(windowAnimationLeash, flags);
+ dest.writeTypedObject(mainFrame, flags);
+ dest.writeBoolean(playRevealAnimation);
+ dest.writeBoolean(deferRemoveForIme);
+ }
+
+ @Override
+ public String toString() {
+ return "StartingWindowRemovalInfo{taskId=" + taskId
+ + " frame=" + mainFrame
+ + " playRevealAnimation=" + playRevealAnimation
+ + " deferRemoveForIme=" + deferRemoveForIme + "}";
+ }
+
+ public static final @android.annotation.NonNull Creator<StartingWindowRemovalInfo> CREATOR =
+ new Creator<StartingWindowRemovalInfo>() {
+ public StartingWindowRemovalInfo createFromParcel(@NonNull Parcel source) {
+ return new StartingWindowRemovalInfo(source);
+ }
+ public StartingWindowRemovalInfo[] newArray(int size) {
+ return new StartingWindowRemovalInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/window/TaskFragmentAppearedInfo.aidl b/core/java/android/window/TaskFragmentAppearedInfo.aidl
new file mode 100644
index 000000000000..3729c09168a6
--- /dev/null
+++ b/core/java/android/window/TaskFragmentAppearedInfo.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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 android.window;
+
+/**
+ * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
+ * @hide
+ */
+parcelable TaskFragmentAppearedInfo;
diff --git a/core/java/android/window/TaskFragmentAppearedInfo.java b/core/java/android/window/TaskFragmentAppearedInfo.java
new file mode 100644
index 000000000000..89d9a9508a71
--- /dev/null
+++ b/core/java/android/window/TaskFragmentAppearedInfo.java
@@ -0,0 +1,93 @@
+/*
+ * 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 android.window;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.SurfaceControl;
+
+/**
+ * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
+ * @hide
+ */
+@TestApi
+public final class TaskFragmentAppearedInfo implements Parcelable {
+
+ @NonNull
+ private final TaskFragmentInfo mTaskFragmentInfo;
+
+ @NonNull
+ private final SurfaceControl mLeash;
+
+ /** @hide */
+ public TaskFragmentAppearedInfo(
+ @NonNull TaskFragmentInfo taskFragmentInfo, @NonNull SurfaceControl leash) {
+ mTaskFragmentInfo = taskFragmentInfo;
+ mLeash = leash;
+ }
+
+ @NonNull
+ public TaskFragmentInfo getTaskFragmentInfo() {
+ return mTaskFragmentInfo;
+ }
+
+ @NonNull
+ public SurfaceControl getLeash() {
+ return mLeash;
+ }
+
+ private TaskFragmentAppearedInfo(Parcel in) {
+ mTaskFragmentInfo = in.readTypedObject(TaskFragmentInfo.CREATOR);
+ mLeash = in.readTypedObject(SurfaceControl.CREATOR);
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedObject(mTaskFragmentInfo, flags);
+ dest.writeTypedObject(mLeash, flags);
+ }
+
+ @NonNull
+ public static final Creator<TaskFragmentAppearedInfo> CREATOR =
+ new Creator<TaskFragmentAppearedInfo>() {
+ @Override
+ public TaskFragmentAppearedInfo createFromParcel(Parcel in) {
+ return new TaskFragmentAppearedInfo(in);
+ }
+
+ @Override
+ public TaskFragmentAppearedInfo[] newArray(int size) {
+ return new TaskFragmentAppearedInfo[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "TaskFragmentAppearedInfo{"
+ + " taskFragmentInfo=" + mTaskFragmentInfo
+ + "}";
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/window/TaskFragmentCreationParams.aidl b/core/java/android/window/TaskFragmentCreationParams.aidl
new file mode 100644
index 000000000000..fde50892640b
--- /dev/null
+++ b/core/java/android/window/TaskFragmentCreationParams.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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 android.window;
+
+/**
+ * Data object for options to create TaskFragment with.
+ * @hide
+ */
+parcelable TaskFragmentCreationParams;
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
new file mode 100644
index 000000000000..81ab7836435d
--- /dev/null
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -0,0 +1,193 @@
+/*
+ * 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 android.window;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.WindowingMode;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Data object for options to create TaskFragment with.
+ * @hide
+ */
+@TestApi
+public final class TaskFragmentCreationParams implements Parcelable {
+
+ /** The organizer that will organize this TaskFragment. */
+ @NonNull
+ private final TaskFragmentOrganizerToken mOrganizer;
+
+ /**
+ * Unique token assigned from the client organizer to identify the {@link TaskFragmentInfo} when
+ * a new TaskFragment is created with this option.
+ */
+ @NonNull
+ private final IBinder mFragmentToken;
+
+ /**
+ * Activity token used to identify the leaf Task to create the TaskFragment in. It has to belong
+ * to the same app as the root Activity of the target Task.
+ */
+ @NonNull
+ private final IBinder mOwnerToken;
+
+ /** The initial bounds of the TaskFragment. Fills parent if empty. */
+ @NonNull
+ private final Rect mInitialBounds = new Rect();
+
+ /** The initial windowing mode of the TaskFragment. Inherits from parent if not set. */
+ @WindowingMode
+ private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
+
+ private TaskFragmentCreationParams(
+ @NonNull TaskFragmentOrganizerToken organizer,
+ @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
+ mOrganizer = organizer;
+ mFragmentToken = fragmentToken;
+ mOwnerToken = ownerToken;
+ }
+
+ @NonNull
+ public TaskFragmentOrganizerToken getOrganizer() {
+ return mOrganizer;
+ }
+
+ @NonNull
+ public IBinder getFragmentToken() {
+ return mFragmentToken;
+ }
+
+ @NonNull
+ public IBinder getOwnerToken() {
+ return mOwnerToken;
+ }
+
+ @NonNull
+ public Rect getInitialBounds() {
+ return mInitialBounds;
+ }
+
+ @WindowingMode
+ public int getWindowingMode() {
+ return mWindowingMode;
+ }
+
+ private TaskFragmentCreationParams(Parcel in) {
+ mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
+ mFragmentToken = in.readStrongBinder();
+ mOwnerToken = in.readStrongBinder();
+ mInitialBounds.readFromParcel(in);
+ mWindowingMode = in.readInt();
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mOrganizer.writeToParcel(dest, flags);
+ dest.writeStrongBinder(mFragmentToken);
+ dest.writeStrongBinder(mOwnerToken);
+ mInitialBounds.writeToParcel(dest, flags);
+ dest.writeInt(mWindowingMode);
+ }
+
+ @NonNull
+ public static final Creator<TaskFragmentCreationParams> CREATOR =
+ new Creator<TaskFragmentCreationParams>() {
+ @Override
+ public TaskFragmentCreationParams createFromParcel(Parcel in) {
+ return new TaskFragmentCreationParams(in);
+ }
+
+ @Override
+ public TaskFragmentCreationParams[] newArray(int size) {
+ return new TaskFragmentCreationParams[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "TaskFragmentCreationParams{"
+ + " organizer=" + mOrganizer
+ + " fragmentToken=" + mFragmentToken
+ + " ownerToken=" + mOwnerToken
+ + " initialBounds=" + mInitialBounds
+ + " windowingMode=" + mWindowingMode
+ + "}";
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Builder to construct the options to create TaskFragment with. */
+ public static final class Builder {
+
+ @NonNull
+ private final TaskFragmentOrganizerToken mOrganizer;
+
+ @NonNull
+ private final IBinder mFragmentToken;
+
+ @NonNull
+ private final IBinder mOwnerToken;
+
+ @NonNull
+ private final Rect mInitialBounds = new Rect();
+
+ @WindowingMode
+ private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
+
+ public Builder(@NonNull TaskFragmentOrganizerToken organizer,
+ @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
+ mOrganizer = organizer;
+ mFragmentToken = fragmentToken;
+ mOwnerToken = ownerToken;
+ }
+
+ /** Sets the initial bounds for the TaskFragment. */
+ @NonNull
+ public Builder setInitialBounds(@NonNull Rect bounds) {
+ mInitialBounds.set(bounds);
+ return this;
+ }
+
+ /** Sets the initial windowing mode for the TaskFragment. */
+ @NonNull
+ public Builder setWindowingMode(@WindowingMode int windowingMode) {
+ mWindowingMode = windowingMode;
+ return this;
+ }
+
+ /** Constructs the options to create TaskFragment with. */
+ @NonNull
+ public TaskFragmentCreationParams build() {
+ final TaskFragmentCreationParams result = new TaskFragmentCreationParams(
+ mOrganizer, mFragmentToken, mOwnerToken);
+ result.mInitialBounds.set(mInitialBounds);
+ result.mWindowingMode = mWindowingMode;
+ return result;
+ }
+ }
+}
diff --git a/core/java/android/window/TaskFragmentInfo.aidl b/core/java/android/window/TaskFragmentInfo.aidl
new file mode 100644
index 000000000000..461a7364803f
--- /dev/null
+++ b/core/java/android/window/TaskFragmentInfo.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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 android.window;
+
+/**
+ * Stores information about a particular TaskFragment.
+ * @hide
+ */
+parcelable TaskFragmentInfo;
diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java
new file mode 100644
index 000000000000..165dcdf3a836
--- /dev/null
+++ b/core/java/android/window/TaskFragmentInfo.java
@@ -0,0 +1,226 @@
+/*
+ * 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 android.window;
+
+import static android.app.WindowConfiguration.WindowingMode;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Stores information about a particular TaskFragment.
+ * @hide
+ */
+@TestApi
+public final class TaskFragmentInfo implements Parcelable {
+
+ /**
+ * Client assigned unique token in {@link TaskFragmentCreationParams#getFragmentToken()} to
+ * create this TaskFragment with.
+ */
+ @NonNull
+ private final IBinder mFragmentToken;
+
+ @NonNull
+ private final WindowContainerToken mToken;
+
+ @NonNull
+ private final Configuration mConfiguration = new Configuration();
+
+ /** Whether the TaskFragment contains any child Window Container. */
+ private final boolean mIsEmpty;
+
+ /** The number of the running activities in the TaskFragment. */
+ private final int mRunningActivityCount;
+
+ /** Whether this TaskFragment is visible on the window hierarchy. */
+ private final boolean mIsVisible;
+
+ /**
+ * List of Activity tokens that are children of this TaskFragment. It only contains Activities
+ * that belong to the organizer process for security.
+ */
+ @NonNull
+ private final List<IBinder> mActivities = new ArrayList<>();
+
+ /** Relative position of the fragment's top left corner in the parent container. */
+ private final Point mPositionInParent;
+
+ /**
+ * Whether the last running activity in the TaskFragment was finished due to clearing task while
+ * launching an activity in the host Task.
+ */
+ private final boolean mIsTaskClearedForReuse;
+
+ /** @hide */
+ public TaskFragmentInfo(
+ @NonNull IBinder fragmentToken, @NonNull WindowContainerToken token,
+ @NonNull Configuration configuration, boolean isEmpty, int runningActivityCount,
+ boolean isVisible, @NonNull List<IBinder> activities, @NonNull Point positionInParent,
+ boolean isTaskClearedForReuse) {
+ mFragmentToken = requireNonNull(fragmentToken);
+ mToken = requireNonNull(token);
+ mConfiguration.setTo(configuration);
+ mIsEmpty = isEmpty;
+ mRunningActivityCount = runningActivityCount;
+ mIsVisible = isVisible;
+ mActivities.addAll(activities);
+ mPositionInParent = requireNonNull(positionInParent);
+ mIsTaskClearedForReuse = isTaskClearedForReuse;
+ }
+
+ @NonNull
+ public IBinder getFragmentToken() {
+ return mFragmentToken;
+ }
+
+ @NonNull
+ public WindowContainerToken getToken() {
+ return mToken;
+ }
+
+ @NonNull
+ public Configuration getConfiguration() {
+ return mConfiguration;
+ }
+
+ public boolean isEmpty() {
+ return mIsEmpty;
+ }
+
+ public boolean hasRunningActivity() {
+ return mRunningActivityCount > 0;
+ }
+
+ public int getRunningActivityCount() {
+ return mRunningActivityCount;
+ }
+
+ public boolean isVisible() {
+ return mIsVisible;
+ }
+
+ @NonNull
+ public List<IBinder> getActivities() {
+ return mActivities;
+ }
+
+ /** Returns the relative position of the fragment's top left corner in the parent container. */
+ @NonNull
+ public Point getPositionInParent() {
+ return mPositionInParent;
+ }
+
+ public boolean isTaskClearedForReuse() {
+ return mIsTaskClearedForReuse;
+ }
+
+ @WindowingMode
+ public int getWindowingMode() {
+ return mConfiguration.windowConfiguration.getWindowingMode();
+ }
+
+ /**
+ * Returns {@code true} if the parameters that are important for task fragment organizers are
+ * equal between this {@link TaskFragmentInfo} and {@param that}.
+ */
+ public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentInfo that) {
+ if (that == null) {
+ return false;
+ }
+
+ return mFragmentToken.equals(that.mFragmentToken)
+ && mToken.equals(that.mToken)
+ && mIsEmpty == that.mIsEmpty
+ && mRunningActivityCount == that.mRunningActivityCount
+ && mIsVisible == that.mIsVisible
+ && getWindowingMode() == that.getWindowingMode()
+ && mActivities.equals(that.mActivities)
+ && mPositionInParent.equals(that.mPositionInParent)
+ && mIsTaskClearedForReuse == that.mIsTaskClearedForReuse;
+ }
+
+ private TaskFragmentInfo(Parcel in) {
+ mFragmentToken = in.readStrongBinder();
+ mToken = in.readTypedObject(WindowContainerToken.CREATOR);
+ mConfiguration.readFromParcel(in);
+ mIsEmpty = in.readBoolean();
+ mRunningActivityCount = in.readInt();
+ mIsVisible = in.readBoolean();
+ in.readBinderList(mActivities);
+ mPositionInParent = requireNonNull(in.readTypedObject(Point.CREATOR));
+ mIsTaskClearedForReuse = in.readBoolean();
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeStrongBinder(mFragmentToken);
+ dest.writeTypedObject(mToken, flags);
+ mConfiguration.writeToParcel(dest, flags);
+ dest.writeBoolean(mIsEmpty);
+ dest.writeInt(mRunningActivityCount);
+ dest.writeBoolean(mIsVisible);
+ dest.writeBinderList(mActivities);
+ dest.writeTypedObject(mPositionInParent, flags);
+ dest.writeBoolean(mIsTaskClearedForReuse);
+ }
+
+ @NonNull
+ public static final Creator<TaskFragmentInfo> CREATOR =
+ new Creator<TaskFragmentInfo>() {
+ @Override
+ public TaskFragmentInfo createFromParcel(Parcel in) {
+ return new TaskFragmentInfo(in);
+ }
+
+ @Override
+ public TaskFragmentInfo[] newArray(int size) {
+ return new TaskFragmentInfo[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "TaskFragmentInfo{"
+ + " fragmentToken=" + mFragmentToken
+ + " token=" + mToken
+ + " isEmpty=" + mIsEmpty
+ + " runningActivityCount=" + mRunningActivityCount
+ + " isVisible=" + mIsVisible
+ + " positionInParent=" + mPositionInParent
+ + " isTaskClearedForReuse=" + mIsTaskClearedForReuse
+ + "}";
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
new file mode 100644
index 000000000000..337c5a14e9d3
--- /dev/null
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -0,0 +1,220 @@
+/*
+ * 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 android.window;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.RemoteAnimationDefinition;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface for WindowManager to delegate control of {@code TaskFragment}.
+ * @hide
+ */
+@TestApi
+public class TaskFragmentOrganizer extends WindowOrganizer {
+
+ /**
+ * Key to the exception in {@link Bundle} in {@link ITaskFragmentOrganizer#onTaskFragmentError}.
+ */
+ private static final String KEY_ERROR_CALLBACK_EXCEPTION = "fragment_exception";
+
+ /**
+ * Creates a {@link Bundle} with an exception that can be passed to
+ * {@link ITaskFragmentOrganizer#onTaskFragmentError}.
+ * @hide
+ */
+ public static Bundle putExceptionInBundle(@NonNull Throwable exception) {
+ final Bundle exceptionBundle = new Bundle();
+ exceptionBundle.putSerializable(KEY_ERROR_CALLBACK_EXCEPTION, exception);
+ return exceptionBundle;
+ }
+
+ /**
+ * Callbacks from WM Core are posted on this executor.
+ */
+ private final Executor mExecutor;
+
+ public TaskFragmentOrganizer(@NonNull Executor executor) {
+ mExecutor = executor;
+ }
+
+ /**
+ * Gets the executor to run callbacks on.
+ */
+ @NonNull
+ public Executor getExecutor() {
+ return mExecutor;
+ }
+
+ /**
+ * Registers a TaskFragmentOrganizer to manage TaskFragments.
+ */
+ @CallSuper
+ public void registerOrganizer() {
+ try {
+ getController().registerOrganizer(mInterface);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregisters a previously registered TaskFragmentOrganizer.
+ */
+ @CallSuper
+ public void unregisterOrganizer() {
+ try {
+ getController().unregisterOrganizer(mInterface);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers remote animations per transition type for the organizer. It will override the
+ * animations if the transition only contains windows that belong to the organized
+ * TaskFragments.
+ * @hide
+ */
+ @CallSuper
+ public void registerRemoteAnimations(@NonNull RemoteAnimationDefinition definition) {
+ try {
+ getController().registerRemoteAnimations(mInterface, definition);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregisters remote animations per transition type for the organizer.
+ * @hide
+ */
+ @CallSuper
+ public void unregisterRemoteAnimations() {
+ try {
+ getController().unregisterRemoteAnimations(mInterface);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Called when a TaskFragment is created and organized by this organizer. */
+ public void onTaskFragmentAppeared(
+ @NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) {}
+
+ /** Called when the status of an organized TaskFragment is changed. */
+ public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {}
+
+ /** Called when an organized TaskFragment is removed. */
+ public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {}
+
+ /**
+ * Called when the parent leaf Task of organized TaskFragments is changed.
+ * When the leaf Task is changed, the organizer may want to update the TaskFragments in one
+ * transaction.
+ *
+ * For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new
+ * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override
+ * bounds.
+ */
+ public void onTaskFragmentParentInfoChanged(
+ @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {}
+
+ /**
+ * Called when the {@link WindowContainerTransaction} created with
+ * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
+ *
+ * @param errorCallbackToken token set in
+ * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
+ * @param exception exception from the server side.
+ */
+ public void onTaskFragmentError(
+ @NonNull IBinder errorCallbackToken, @NonNull Throwable exception) {}
+
+ @Override
+ public void applyTransaction(@NonNull WindowContainerTransaction t) {
+ t.setTaskFragmentOrganizer(mInterface);
+ super.applyTransaction(t);
+ }
+
+ // Suppress the lint because it is not a registration method.
+ @SuppressWarnings("ExecutorRegistration")
+ @Override
+ public int applySyncTransaction(@NonNull WindowContainerTransaction t,
+ @NonNull WindowContainerTransactionCallback callback) {
+ t.setTaskFragmentOrganizer(mInterface);
+ return super.applySyncTransaction(t, callback);
+ }
+
+ private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() {
+ @Override
+ public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentInfo) {
+ mExecutor.execute(
+ () -> TaskFragmentOrganizer.this.onTaskFragmentAppeared(taskFragmentInfo));
+ }
+
+ @Override
+ public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ mExecutor.execute(
+ () -> TaskFragmentOrganizer.this.onTaskFragmentInfoChanged(taskFragmentInfo));
+ }
+
+ @Override
+ public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ mExecutor.execute(
+ () -> TaskFragmentOrganizer.this.onTaskFragmentVanished(taskFragmentInfo));
+ }
+
+ @Override
+ public void onTaskFragmentParentInfoChanged(
+ @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {
+ mExecutor.execute(
+ () -> TaskFragmentOrganizer.this.onTaskFragmentParentInfoChanged(
+ fragmentToken, parentConfig));
+ }
+
+ @Override
+ public void onTaskFragmentError(
+ @NonNull IBinder errorCallbackToken, @NonNull Bundle exceptionBundle) {
+ mExecutor.execute(() -> TaskFragmentOrganizer.this.onTaskFragmentError(
+ errorCallbackToken,
+ (Throwable) exceptionBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION)));
+ }
+ };
+
+ private final TaskFragmentOrganizerToken mToken = new TaskFragmentOrganizerToken(mInterface);
+
+ @NonNull
+ public TaskFragmentOrganizerToken getOrganizerToken() {
+ return mToken;
+ }
+
+ private ITaskFragmentOrganizerController getController() {
+ try {
+ return getWindowOrganizerController().getTaskFragmentOrganizerController();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+}
diff --git a/core/java/android/window/TaskFragmentOrganizerToken.java b/core/java/android/window/TaskFragmentOrganizerToken.java
new file mode 100644
index 000000000000..d5216a6444d9
--- /dev/null
+++ b/core/java/android/window/TaskFragmentOrganizerToken.java
@@ -0,0 +1,97 @@
+/*
+ * 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 android.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Interface to communicate between window manager and {@link TaskFragmentOrganizer}.
+ * <p>
+ * Window manager will dispatch TaskFragment information updates via this interface.
+ * It is necessary because {@link ITaskFragmentOrganizer} aidl interface can not be used as a
+ * {@link TestApi}.
+ * @hide
+ */
+@TestApi
+public final class TaskFragmentOrganizerToken implements Parcelable {
+ private final ITaskFragmentOrganizer mRealToken;
+
+ TaskFragmentOrganizerToken(ITaskFragmentOrganizer realToken) {
+ mRealToken = realToken;
+ }
+
+ /** @hide */
+ public IBinder asBinder() {
+ return mRealToken.asBinder();
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongInterface(mRealToken);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<TaskFragmentOrganizerToken> CREATOR =
+ new Creator<TaskFragmentOrganizerToken>() {
+ @Override
+ public TaskFragmentOrganizerToken createFromParcel(Parcel in) {
+ final ITaskFragmentOrganizer realToken =
+ ITaskFragmentOrganizer.Stub.asInterface(in.readStrongBinder());
+ // The TaskFragmentOrganizerToken may be null for TaskOrganizer or
+ // DisplayAreaOrganizer.
+ if (realToken == null) {
+ return null;
+ }
+ return new TaskFragmentOrganizerToken(realToken);
+ }
+
+ @Override
+ public TaskFragmentOrganizerToken[] newArray(int size) {
+ return new TaskFragmentOrganizerToken[size];
+ }
+ };
+
+ @Override
+ public int hashCode() {
+ return mRealToken.asBinder().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "TaskFragmentOrganizerToken{" + mRealToken + "}";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof TaskFragmentOrganizerToken)) {
+ return false;
+ }
+ return mRealToken.asBinder() == ((TaskFragmentOrganizerToken) obj).asBinder();
+ }
+}
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index d8723a821a22..27c7d3158f95 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -24,7 +24,6 @@ import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.ActivityManager;
-import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.SurfaceControl;
@@ -94,6 +93,7 @@ public class TaskOrganizer extends WindowOrganizer {
* @param info The information about the Task that's available
* @param appToken Token of the application being started.
* context to for resources
+ * @hide
*/
@BinderThread
public void addStartingWindow(@NonNull StartingWindowInfo info,
@@ -101,14 +101,11 @@ public class TaskOrganizer extends WindowOrganizer {
/**
* Called when the Task want to remove the starting window.
- * @param leash A persistent leash for the top window in this task. Release it once exit
- * animation has finished.
- * @param frame Window frame of the top window.
- * @param playRevealAnimation Play vanish animation.
+ * @param removalInfo The information used to remove the starting window.
+ * @hide
*/
@BinderThread
- public void removeStartingWindow(int taskId, @Nullable SurfaceControl leash,
- @Nullable Rect frame, boolean playRevealAnimation) {}
+ public void removeStartingWindow(@NonNull StartingWindowRemovalInfo removalInfo) {}
/**
* Called when the Task want to copy the splash screen.
@@ -226,6 +223,7 @@ public class TaskOrganizer extends WindowOrganizer {
}
}
+
/**
* Restarts the top activity in the given task by killing its process if it is visible.
* @hide
@@ -256,10 +254,8 @@ public class TaskOrganizer extends WindowOrganizer {
}
@Override
- public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
- boolean playRevealAnimation) {
- mExecutor.execute(() -> TaskOrganizer.this.removeStartingWindow(taskId, leash, frame,
- playRevealAnimation));
+ public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
+ mExecutor.execute(() -> TaskOrganizer.this.removeStartingWindow(removalInfo));
}
@Override
@@ -298,6 +294,7 @@ public class TaskOrganizer extends WindowOrganizer {
}
};
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
private ITaskOrganizerController getController() {
try {
return getWindowOrganizerController().getTaskOrganizerController();
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index 141f47b130d1..db15145b0a1e 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -17,12 +17,17 @@
package android.window;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.view.WindowManager.TransitionType;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.WindowConfiguration;
+import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.WindowManager;
/**
* A parcelable filter that can be used for rerouting transitions to a remote. This is a local
@@ -33,11 +38,29 @@ import android.os.Parcelable;
*/
public final class TransitionFilter implements Parcelable {
+ /** The associated requirement doesn't care about the z-order. */
+ public static final int CONTAINER_ORDER_ANY = 0;
+ /** The associated requirement only matches the top-most (z-order) container. */
+ public static final int CONTAINER_ORDER_TOP = 1;
+
+ /** @hide */
+ @IntDef(prefix = { "CONTAINER_ORDER_" }, value = {
+ CONTAINER_ORDER_ANY,
+ CONTAINER_ORDER_TOP,
+ })
+ public @interface ContainerOrder {}
+
/**
* When non-null: this is a list of transition types that this filter applies to. This filter
* will fail for transitions that aren't one of these types.
*/
- @Nullable public int[] mTypeSet = null;
+ @Nullable public @TransitionType int[] mTypeSet = null;
+
+ /** All flags must be set on a transition. */
+ public @WindowManager.TransitionFlags int mFlags = 0;
+
+ /** All flags must NOT be set on a transition. */
+ public @WindowManager.TransitionFlags int mNotFlags = 0;
/**
* A list of required changes. To pass, a transition must meet all requirements.
@@ -49,6 +72,8 @@ public final class TransitionFilter implements Parcelable {
private TransitionFilter(Parcel in) {
mTypeSet = in.createIntArray();
+ mFlags = in.readInt();
+ mNotFlags = in.readInt();
mRequirements = in.createTypedArray(Requirement.CREATOR);
}
@@ -65,10 +90,19 @@ public final class TransitionFilter implements Parcelable {
}
if (!typePass) return false;
}
+ if ((info.getFlags() & mFlags) != mFlags) {
+ return false;
+ }
+ if ((info.getFlags() & mNotFlags) != 0) {
+ return false;
+ }
// Make sure info meets all of the requirements.
if (mRequirements != null) {
for (int i = 0; i < mRequirements.length; ++i) {
- if (!mRequirements[i].matches(info)) return false;
+ final boolean matches = mRequirements[i].matches(info);
+ if (matches == mRequirements[i].mNot) {
+ return false;
+ }
}
}
return true;
@@ -78,6 +112,8 @@ public final class TransitionFilter implements Parcelable {
/** @hide */
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeIntArray(mTypeSet);
+ dest.writeInt(mFlags);
+ dest.writeInt(mNotFlags);
dest.writeTypedArray(mRequirements, flags);
}
@@ -107,10 +143,12 @@ public final class TransitionFilter implements Parcelable {
sb.append("{types=[");
if (mTypeSet != null) {
for (int i = 0; i < mTypeSet.length; ++i) {
- sb.append((i == 0 ? "" : ",") + mTypeSet[i]);
+ sb.append((i == 0 ? "" : ",") + WindowManager.transitTypeToString(mTypeSet[i]));
}
}
- sb.append("] checks=[");
+ sb.append("] flags=0x" + Integer.toHexString(mFlags));
+ sb.append("] notFlags=0x" + Integer.toHexString(mNotFlags));
+ sb.append(" checks=[");
if (mRequirements != null) {
for (int i = 0; i < mRequirements.length; ++i) {
sb.append((i == 0 ? "" : ",") + mRequirements[i]);
@@ -125,30 +163,56 @@ public final class TransitionFilter implements Parcelable {
*/
public static final class Requirement implements Parcelable {
public int mActivityType = ACTIVITY_TYPE_UNDEFINED;
+
+ /** This only matches if the change is independent of its parents. */
+ public boolean mMustBeIndependent = true;
+
+ /** If this matches, the parent filter will fail */
+ public boolean mNot = false;
+
public int[] mModes = null;
+ /** Matches only if all the flags here are set on the change. */
+ public @TransitionInfo.ChangeFlags int mFlags = 0;
+
+ /** If this needs to be a task. */
+ public boolean mMustBeTask = false;
+
+ public @ContainerOrder int mOrder = CONTAINER_ORDER_ANY;
+ public ComponentName mTopActivity;
+
public Requirement() {
}
private Requirement(Parcel in) {
mActivityType = in.readInt();
+ mMustBeIndependent = in.readBoolean();
+ mNot = in.readBoolean();
mModes = in.createIntArray();
+ mFlags = in.readInt();
+ mMustBeTask = in.readBoolean();
+ mOrder = in.readInt();
+ mTopActivity = in.readTypedObject(ComponentName.CREATOR);
}
/** Go through changes and find if at-least one change matches this filter */
boolean matches(@NonNull TransitionInfo info) {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
- if (!TransitionInfo.isIndependent(change, info)) {
+ if (mMustBeIndependent && !TransitionInfo.isIndependent(change, info)) {
// Only look at independent animating windows.
continue;
}
+ if (mOrder == CONTAINER_ORDER_TOP && i > 0) {
+ continue;
+ }
if (mActivityType != ACTIVITY_TYPE_UNDEFINED) {
if (change.getTaskInfo() == null
|| change.getTaskInfo().getActivityType() != mActivityType) {
continue;
}
}
+ if (!matchesTopActivity(change.getTaskInfo())) continue;
if (mModes != null) {
boolean pass = false;
for (int m = 0; m < mModes.length; ++m) {
@@ -159,24 +223,44 @@ public final class TransitionFilter implements Parcelable {
}
if (!pass) continue;
}
+ if ((change.getFlags() & mFlags) != mFlags) {
+ continue;
+ }
+ if (mMustBeTask && change.getTaskInfo() == null) {
+ continue;
+ }
return true;
}
return false;
}
+ private boolean matchesTopActivity(ActivityManager.RunningTaskInfo info) {
+ if (mTopActivity == null) return true;
+ if (info == null) return false;
+ final ComponentName component = info.topActivity;
+ return mTopActivity.equals(component);
+ }
+
/** Check if the request matches this filter. It may generate false positives */
boolean matches(@NonNull TransitionRequestInfo request) {
- // Can't check modes since the transition hasn't been built at this point.
+ // Can't check modes/order since the transition hasn't been built at this point.
if (mActivityType == ACTIVITY_TYPE_UNDEFINED) return true;
return request.getTriggerTask() != null
- && request.getTriggerTask().getActivityType() == mActivityType;
+ && request.getTriggerTask().getActivityType() == mActivityType
+ && matchesTopActivity(request.getTriggerTask());
}
@Override
/** @hide */
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mActivityType);
+ dest.writeBoolean(mMustBeIndependent);
+ dest.writeBoolean(mNot);
dest.writeIntArray(mModes);
+ dest.writeInt(mFlags);
+ dest.writeBoolean(mMustBeTask);
+ dest.writeInt(mOrder);
+ dest.writeTypedObject(mTopActivity, flags);
}
@NonNull
@@ -202,14 +286,31 @@ public final class TransitionFilter implements Parcelable {
@Override
public String toString() {
StringBuilder out = new StringBuilder();
- out.append("{atype=" + WindowConfiguration.activityTypeToString(mActivityType));
+ out.append('{');
+ if (mNot) out.append("NOT ");
+ out.append("atype=" + WindowConfiguration.activityTypeToString(mActivityType));
+ out.append(" independent=" + mMustBeIndependent);
out.append(" modes=[");
if (mModes != null) {
for (int i = 0; i < mModes.length; ++i) {
out.append((i == 0 ? "" : ",") + TransitionInfo.modeToString(mModes[i]));
}
}
- return out.append("]}").toString();
+ out.append("]").toString();
+ out.append(" flags=" + TransitionInfo.flagsToString(mFlags));
+ out.append(" mustBeTask=" + mMustBeTask);
+ out.append(" order=" + containerOrderToString(mOrder));
+ out.append(" topActivity=").append(mTopActivity);
+ out.append("}");
+ return out.toString();
+ }
+ }
+
+ private static String containerOrderToString(int order) {
+ switch (order) {
+ case CONTAINER_ORDER_ANY: return "ANY";
+ case CONTAINER_ORDER_TOP: return "TOP";
}
+ return "UNKNOWN(" + order + ")";
}
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 23b8ee4a019f..7208930c0b20 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -16,13 +16,23 @@
package android.window;
+import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
+import static android.app.ActivityOptions.ANIM_CUSTOM;
+import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
+import static android.app.ActivityOptions.ANIM_SCALE_UP;
+import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
+import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+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_KEYGUARD_GOING_AWAY;
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 android.annotation.IntDef;
@@ -31,11 +41,11 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.graphics.Point;
import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.Surface;
import android.view.SurfaceControl;
-import android.view.WindowManager;
import java.util.ArrayList;
import java.util.List;
@@ -80,8 +90,22 @@ public final class TransitionInfo implements Parcelable {
/** The container has voice session. */
public static final int FLAG_IS_VOICE_INTERACTION = 1 << 4;
+ /** The container is the display. */
+ public static final int FLAG_IS_DISPLAY = 1 << 5;
+
+ /** The container can show on top of lock screen. */
+ public static final int FLAG_OCCLUDES_KEYGUARD = 1 << 6;
+
+ /**
+ * Only for IS_DISPLAY containers. Is set if the display has system alert windows. This is
+ * used to prevent seamless rotation.
+ * TODO(b/194540864): Once we can include all windows in transition, then replace this with
+ * something like FLAG_IS_SYSTEM_ALERT instead. Then we can do mixed rotations.
+ */
+ public static final int FLAG_DISPLAY_HAS_ALERT_WINDOWS = 1 << 7;
+
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
- public static final int FLAG_FIRST_CUSTOM = 1 << 5;
+ public static final int FLAG_FIRST_CUSTOM = 1 << 8;
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
@@ -91,20 +115,24 @@ public final class TransitionInfo implements Parcelable {
FLAG_TRANSLUCENT,
FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT,
FLAG_IS_VOICE_INTERACTION,
+ FLAG_IS_DISPLAY,
+ FLAG_OCCLUDES_KEYGUARD,
+ FLAG_DISPLAY_HAS_ALERT_WINDOWS,
FLAG_FIRST_CUSTOM
})
public @interface ChangeFlags {}
- private final @WindowManager.TransitionOldType int mType;
- private final @WindowManager.TransitionFlags int mFlags;
+ private final @TransitionType int mType;
+ private final @TransitionFlags int mFlags;
private final ArrayList<Change> mChanges = new ArrayList<>();
private SurfaceControl mRootLeash;
private final Point mRootOffset = new Point();
+ private AnimationOptions mOptions;
+
/** @hide */
- public TransitionInfo(@WindowManager.TransitionOldType int type,
- @WindowManager.TransitionFlags int flags) {
+ public TransitionInfo(@TransitionType int type, @TransitionFlags int flags) {
mType = type;
mFlags = flags;
}
@@ -112,10 +140,11 @@ public final class TransitionInfo implements Parcelable {
private TransitionInfo(Parcel in) {
mType = in.readInt();
mFlags = in.readInt();
- in.readList(mChanges, null /* classLoader */);
+ in.readTypedList(mChanges, Change.CREATOR);
mRootLeash = new SurfaceControl();
mRootLeash.readFromParcel(in);
mRootOffset.readFromParcel(in);
+ mOptions = in.readTypedObject(AnimationOptions.CREATOR);
}
@Override
@@ -123,9 +152,10 @@ public final class TransitionInfo implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeInt(mFlags);
- dest.writeList(mChanges);
+ dest.writeTypedList(mChanges);
mRootLeash.writeToParcel(dest, flags);
mRootOffset.writeToParcel(dest, flags);
+ dest.writeTypedObject(mOptions, flags);
}
@NonNull
@@ -154,7 +184,11 @@ public final class TransitionInfo implements Parcelable {
mRootOffset.set(offsetLeft, offsetTop);
}
- public int getType() {
+ public void setAnimationOptions(AnimationOptions options) {
+ mOptions = options;
+ }
+
+ public @TransitionType int getType() {
return mType;
}
@@ -182,6 +216,14 @@ public final class TransitionInfo implements Parcelable {
return mRootOffset;
}
+ public AnimationOptions getAnimationOptions() {
+ return mOptions;
+ }
+
+ /**
+ * @return the list of {@link Change}s in this transition. The list is sorted top-to-bottom
+ * in Z (meaning index 0 is the top-most container).
+ */
@NonNull
public List<Change> getChanges() {
return mChanges;
@@ -208,10 +250,17 @@ public final class TransitionInfo implements Parcelable {
mChanges.add(change);
}
+ /**
+ * Whether this transition includes keyguard going away.
+ */
+ public boolean isKeyguardGoingAway() {
+ return (mFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0;
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
- sb.append("{t=" + transitTypeToString(mType) + " f=" + Integer.toHexString(mFlags)
+ sb.append("{t=" + transitTypeToString(mType) + " f=0x" + Integer.toHexString(mFlags)
+ " ro=" + mRootOffset + " c=[");
for (int i = 0; i < mChanges.size(); ++i) {
if (i > 0) {
@@ -257,6 +306,15 @@ public final class TransitionInfo implements Parcelable {
if ((flags & FLAG_IS_VOICE_INTERACTION) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "IS_VOICE_INTERACTION");
}
+ if ((flags & FLAG_IS_DISPLAY) != 0) {
+ sb.append((sb.length() == 0 ? "" : "|") + "IS_DISPLAY");
+ }
+ if ((flags & FLAG_OCCLUDES_KEYGUARD) != 0) {
+ sb.append((sb.length() == 0 ? "" : "|") + "OCCLUDES_KEYGUARD");
+ }
+ if ((flags & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
+ sb.append((sb.length() == 0 ? "" : "|") + "DISPLAY_HAS_ALERT_WINDOWS");
+ }
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "FIRST_CUSTOM");
}
@@ -302,8 +360,10 @@ public final class TransitionInfo implements Parcelable {
private final Rect mEndAbsBounds = new Rect();
private final Point mEndRelOffset = new Point();
private ActivityManager.RunningTaskInfo mTaskInfo = null;
+ private boolean mAllowEnterPip;
private int mStartRotation = ROTATION_UNDEFINED;
private int mEndRotation = ROTATION_UNDEFINED;
+ private int mRotationAnimation = ROTATION_ANIMATION_UNSPECIFIED;
public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) {
mContainer = container;
@@ -321,8 +381,10 @@ public final class TransitionInfo implements Parcelable {
mEndAbsBounds.readFromParcel(in);
mEndRelOffset.readFromParcel(in);
mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
+ mAllowEnterPip = in.readBoolean();
mStartRotation = in.readInt();
mEndRotation = in.readInt();
+ mRotationAnimation = in.readInt();
}
/** Sets the parent of this change's container. The parent must be a participant or null. */
@@ -363,12 +425,25 @@ public final class TransitionInfo implements Parcelable {
mTaskInfo = taskInfo;
}
+ /** Sets the allowEnterPip flag which represents AppOpsManager check on PiP permission */
+ public void setAllowEnterPip(boolean allowEnterPip) {
+ mAllowEnterPip = allowEnterPip;
+ }
+
/** Sets the start and end rotation of this container. */
public void setRotation(@Surface.Rotation int start, @Surface.Rotation int end) {
mStartRotation = start;
mEndRotation = end;
}
+ /**
+ * Sets the app-requested animation type for rotation. Will be one of the
+ * ROTATION_ANIMATION_ values in {@link android.view.WindowManager.LayoutParams};
+ */
+ public void setRotationAnimation(int anim) {
+ mRotationAnimation = anim;
+ }
+
/** @return the container that is changing. May be null if non-remotable (eg. activity) */
@Nullable
public WindowContainerToken getContainer() {
@@ -432,6 +507,10 @@ public final class TransitionInfo implements Parcelable {
return mTaskInfo;
}
+ public boolean getAllowEnterPip() {
+ return mAllowEnterPip;
+ }
+
public int getStartRotation() {
return mStartRotation;
}
@@ -440,6 +519,11 @@ public final class TransitionInfo implements Parcelable {
return mEndRotation;
}
+ /** @return the rotation animation. */
+ public int getRotationAnimation() {
+ return mRotationAnimation;
+ }
+
/** @hide */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -452,8 +536,10 @@ public final class TransitionInfo implements Parcelable {
mEndAbsBounds.writeToParcel(dest, flags);
mEndRelOffset.writeToParcel(dest, flags);
dest.writeTypedObject(mTaskInfo, flags);
+ dest.writeBoolean(mAllowEnterPip);
dest.writeInt(mStartRotation);
dest.writeInt(mEndRotation);
+ dest.writeInt(mRotationAnimation);
}
@NonNull
@@ -481,7 +567,149 @@ public final class TransitionInfo implements Parcelable {
return "{" + mContainer + "(" + mParent + ") leash=" + mLeash
+ " m=" + modeToString(mMode) + " f=" + flagsToString(mFlags) + " sb="
+ mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + " r="
- + mStartRotation + "->" + mEndRotation + "}";
+ + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation + "}";
+ }
+ }
+
+ /** Represents animation options during a transition */
+ public static final class AnimationOptions implements Parcelable {
+
+ private int mType;
+ private int mEnterResId;
+ private int mExitResId;
+ private boolean mOverrideTaskTransition;
+ private String mPackageName;
+ private final Rect mTransitionBounds = new Rect();
+ private HardwareBuffer mThumbnail;
+
+ private AnimationOptions(int type) {
+ mType = type;
+ }
+
+ public AnimationOptions(Parcel in) {
+ mType = in.readInt();
+ mEnterResId = in.readInt();
+ mExitResId = in.readInt();
+ mOverrideTaskTransition = in.readBoolean();
+ mPackageName = in.readString();
+ mTransitionBounds.readFromParcel(in);
+ mThumbnail = in.readTypedObject(HardwareBuffer.CREATOR);
+ }
+
+ public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId,
+ int exitResId, boolean overrideTaskTransition) {
+ AnimationOptions options = new AnimationOptions(ANIM_CUSTOM);
+ options.mPackageName = packageName;
+ options.mEnterResId = enterResId;
+ options.mExitResId = exitResId;
+ options.mOverrideTaskTransition = overrideTaskTransition;
+ return options;
+ }
+
+ public static AnimationOptions makeClipRevealAnimOptions(int startX, int startY, int width,
+ int height) {
+ AnimationOptions options = new AnimationOptions(ANIM_CLIP_REVEAL);
+ options.mTransitionBounds.set(startX, startY, startX + width, startY + height);
+ return options;
+ }
+
+ public static AnimationOptions makeScaleUpAnimOptions(int startX, int startY, int width,
+ int height) {
+ AnimationOptions options = new AnimationOptions(ANIM_SCALE_UP);
+ options.mTransitionBounds.set(startX, startY, startX + width, startY + height);
+ return options;
+ }
+
+ public static AnimationOptions makeThumnbnailAnimOptions(HardwareBuffer srcThumb,
+ int startX, int startY, boolean scaleUp) {
+ AnimationOptions options = new AnimationOptions(
+ scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN);
+ options.mTransitionBounds.set(startX, startY, startX, startY);
+ options.mThumbnail = srcThumb;
+ return options;
+ }
+
+ public static AnimationOptions makeCrossProfileAnimOptions() {
+ AnimationOptions options = new AnimationOptions(ANIM_OPEN_CROSS_PROFILE_APPS);
+ return options;
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ public int getEnterResId() {
+ return mEnterResId;
+ }
+
+ public int getExitResId() {
+ return mExitResId;
+ }
+
+ public boolean getOverrideTaskTransition() {
+ return mOverrideTaskTransition;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public Rect getTransitionBounds() {
+ return mTransitionBounds;
+ }
+
+ public HardwareBuffer getThumbnail() {
+ return mThumbnail;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeInt(mEnterResId);
+ dest.writeInt(mExitResId);
+ dest.writeBoolean(mOverrideTaskTransition);
+ dest.writeString(mPackageName);
+ mTransitionBounds.writeToParcel(dest, flags);
+ dest.writeTypedObject(mThumbnail, flags);
+ }
+
+ @NonNull
+ public static final Creator<AnimationOptions> CREATOR =
+ new Creator<AnimationOptions>() {
+ @Override
+ public AnimationOptions createFromParcel(Parcel in) {
+ return new AnimationOptions(in);
+ }
+
+ @Override
+ public AnimationOptions[] newArray(int size) {
+ return new AnimationOptions[size];
+ }
+ };
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ private static String typeToString(int mode) {
+ switch(mode) {
+ case ANIM_CUSTOM: return "ANIM_CUSTOM";
+ case ANIM_CLIP_REVEAL: return "ANIM_CLIP_REVEAL";
+ case ANIM_SCALE_UP: return "ANIM_SCALE_UP";
+ case ANIM_THUMBNAIL_SCALE_UP: return "ANIM_THUMBNAIL_SCALE_UP";
+ case ANIM_THUMBNAIL_SCALE_DOWN: return "ANIM_THUMBNAIL_SCALE_DOWN";
+ case ANIM_OPEN_CROSS_PROFILE_APPS: return "ANIM_OPEN_CROSS_PROFILE_APPS";
+ default: return "<unknown:" + mode + ">";
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "{ AnimationOtions type= " + typeToString(mType) + " package=" + mPackageName
+ + " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}";
}
}
}
diff --git a/core/java/android/window/TransitionMetrics.java b/core/java/android/window/TransitionMetrics.java
new file mode 100644
index 000000000000..9a93c1a1ffd6
--- /dev/null
+++ b/core/java/android/window/TransitionMetrics.java
@@ -0,0 +1,57 @@
+/*
+ * 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 android.window;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Singleton;
+
+/**
+ * A helper class for who plays transition animation can report its metrics easily.
+ * @hide
+ */
+public class TransitionMetrics {
+
+ private final ITransitionMetricsReporter mTransitionMetricsReporter;
+
+ private TransitionMetrics(ITransitionMetricsReporter reporter) {
+ mTransitionMetricsReporter = reporter;
+ }
+
+ /** Reports the current timestamp as when the transition animation starts. */
+ public void reportAnimationStart(IBinder transitionToken) {
+ try {
+ mTransitionMetricsReporter.reportAnimationStart(transitionToken,
+ SystemClock.elapsedRealtime());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Gets the singleton instance of TransitionMetrics. */
+ public static TransitionMetrics getInstance() {
+ return sTransitionMetrics.get();
+ }
+
+ private static final Singleton<TransitionMetrics> sTransitionMetrics = new Singleton<>() {
+ @Override
+ protected TransitionMetrics create() {
+ return new TransitionMetrics(WindowOrganizer.getTransitionMetricsReporter());
+ }
+ };
+}
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index cc493ab63e22..f7707317efd7 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -40,11 +40,11 @@ public final class TransitionRequestInfo implements Parcelable {
private @Nullable ActivityManager.RunningTaskInfo mTriggerTask;
/** If non-null, a remote-transition associated with the source of this transition. */
- private @Nullable IRemoteTransition mRemoteTransition;
+ private @Nullable RemoteTransition mRemoteTransition;
- // Code below generated by codegen v1.0.22.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -72,7 +72,7 @@ public final class TransitionRequestInfo implements Parcelable {
public TransitionRequestInfo(
@WindowManager.TransitionType int type,
@Nullable ActivityManager.RunningTaskInfo triggerTask,
- @Nullable IRemoteTransition remoteTransition) {
+ @Nullable RemoteTransition remoteTransition) {
this.mType = type;
com.android.internal.util.AnnotationValidations.validate(
WindowManager.TransitionType.class, null, mType);
@@ -103,7 +103,7 @@ public final class TransitionRequestInfo implements Parcelable {
* If non-null, a remote-transition associated with the source of this transition.
*/
@DataClass.Generated.Member
- public @Nullable IRemoteTransition getRemoteTransition() {
+ public @Nullable RemoteTransition getRemoteTransition() {
return mRemoteTransition;
}
@@ -121,7 +121,7 @@ public final class TransitionRequestInfo implements Parcelable {
* If non-null, a remote-transition associated with the source of this transition.
*/
@DataClass.Generated.Member
- public @android.annotation.NonNull TransitionRequestInfo setRemoteTransition(@android.annotation.NonNull IRemoteTransition value) {
+ public @android.annotation.NonNull TransitionRequestInfo setRemoteTransition(@android.annotation.NonNull RemoteTransition value) {
mRemoteTransition = value;
return this;
}
@@ -151,7 +151,7 @@ public final class TransitionRequestInfo implements Parcelable {
dest.writeByte(flg);
dest.writeInt(mType);
if (mTriggerTask != null) dest.writeTypedObject(mTriggerTask, flags);
- if (mRemoteTransition != null) dest.writeStrongInterface(mRemoteTransition);
+ if (mRemoteTransition != null) dest.writeTypedObject(mRemoteTransition, flags);
}
@Override
@@ -168,7 +168,7 @@ public final class TransitionRequestInfo implements Parcelable {
byte flg = in.readByte();
int type = in.readInt();
ActivityManager.RunningTaskInfo triggerTask = (flg & 0x2) == 0 ? null : (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
- IRemoteTransition remoteTransition = (flg & 0x4) == 0 ? null : IRemoteTransition.Stub.asInterface(in.readStrongBinder());
+ RemoteTransition remoteTransition = (flg & 0x4) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
this.mType = type;
com.android.internal.util.AnnotationValidations.validate(
@@ -194,10 +194,10 @@ public final class TransitionRequestInfo implements Parcelable {
};
@DataClass.Generated(
- time = 1610060387917L,
- codegenVersion = "1.0.22",
+ time = 1629321632222L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
- inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.window.IRemoteTransition mRemoteTransition\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
+ inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index c0af57214e5e..bbf813891387 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -19,7 +19,9 @@ package android.window;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.app.PendingIntent;
import android.app.WindowConfiguration;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -34,6 +36,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* Represents a collection of operations on some WindowContainers that should be applied all at
@@ -48,11 +51,19 @@ public final class WindowContainerTransaction implements Parcelable {
// Flat list because re-order operations are order-dependent
private final ArrayList<HierarchyOp> mHierarchyOps = new ArrayList<>();
+ @Nullable
+ private IBinder mErrorCallbackToken;
+
+ @Nullable
+ private ITaskFragmentOrganizer mTaskFragmentOrganizer;
+
public WindowContainerTransaction() {}
private WindowContainerTransaction(Parcel in) {
in.readMap(mChanges, null /* loader */);
in.readList(mHierarchyOps, null /* loader */);
+ mErrorCallbackToken = in.readStrongBinder();
+ mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(in.readStrongBinder());
}
private Change getOrCreateChange(IBinder token) {
@@ -284,7 +295,7 @@ public final class WindowContainerTransaction implements Parcelable {
}
/**
- * Reparent's all children tasks of {@param currentParent} in the specified
+ * Reparent's all children tasks or the top task of {@param currentParent} in the specified
* {@param windowingMode} and {@param activityType} to {@param newParent} in their current
* z-order.
*
@@ -295,21 +306,46 @@ public final class WindowContainerTransaction implements Parcelable {
* @param activityTypes of the tasks to reparent.
* @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
* the bottom.
+ * @param reparentTopOnly When {@code true}, only reparent the top task which fit windowingModes
+ * and activityTypes.
+ * @hide
*/
@NonNull
public WindowContainerTransaction reparentTasks(@Nullable WindowContainerToken currentParent,
@Nullable WindowContainerToken newParent, @Nullable int[] windowingModes,
- @Nullable int[] activityTypes, boolean onTop) {
+ @Nullable int[] activityTypes, boolean onTop, boolean reparentTopOnly) {
mHierarchyOps.add(HierarchyOp.createForChildrenTasksReparent(
currentParent != null ? currentParent.asBinder() : null,
newParent != null ? newParent.asBinder() : null,
windowingModes,
activityTypes,
- onTop));
+ onTop,
+ reparentTopOnly));
return this;
}
/**
+ * Reparent's all children tasks of {@param currentParent} in the specified
+ * {@param windowingMode} and {@param activityType} to {@param newParent} in their current
+ * z-order.
+ *
+ * @param currentParent of the tasks to perform the operation no.
+ * {@code null} will perform the operation on the display.
+ * @param newParent for the tasks. {@code null} will perform the operation on the display.
+ * @param windowingModes of the tasks to reparent.
+ * @param activityTypes of the tasks to reparent.
+ * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
+ * the bottom.
+ */
+ @NonNull
+ public WindowContainerTransaction reparentTasks(@Nullable WindowContainerToken currentParent,
+ @Nullable WindowContainerToken newParent, @Nullable int[] windowingModes,
+ @Nullable int[] activityTypes, boolean onTop) {
+ return reparentTasks(currentParent, newParent, windowingModes, activityTypes, onTop,
+ false /* reparentTopOnly */);
+ }
+
+ /**
* Sets whether a container should be the launch root for the specified windowing mode and
* activity type. This currently only applies to Task containers created by organizer.
*/
@@ -325,7 +361,8 @@ public final class WindowContainerTransaction implements Parcelable {
/**
* Sets to containers adjacent to each other. Containers below two visible adjacent roots will
- * be made invisible. This currently only applies to Task containers created by organizer.
+ * be made invisible. This currently only applies to TaskFragment containers created by
+ * organizer.
* @param root1 the first root.
* @param root2 the second root.
*/
@@ -378,6 +415,177 @@ public final class WindowContainerTransaction implements Parcelable {
}
/**
+ * Sends a pending intent in sync.
+ * @param sender The PendingIntent sender.
+ * @param intent The fillIn intent to patch over the sender's base intent.
+ * @param options bundle containing ActivityOptions for the task's top activity.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction sendPendingIntent(PendingIntent sender, Intent intent,
+ @Nullable Bundle options) {
+ mHierarchyOps.add(new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT)
+ .setLaunchOptions(options)
+ .setPendingIntent(sender)
+ .setActivityIntent(intent)
+ .build());
+ return this;
+ }
+
+ /**
+ * Creates a new TaskFragment with the given options.
+ * @param taskFragmentOptions the options used to create the TaskFragment.
+ */
+ @NonNull
+ public WindowContainerTransaction createTaskFragment(
+ @NonNull TaskFragmentCreationParams taskFragmentOptions) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT)
+ .setTaskFragmentCreationOptions(taskFragmentOptions)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
+ * Deletes an existing TaskFragment. Any remaining activities below it will be destroyed.
+ * @param taskFragment the TaskFragment to be removed.
+ */
+ @NonNull
+ public WindowContainerTransaction deleteTaskFragment(
+ @NonNull WindowContainerToken taskFragment) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT)
+ .setContainer(taskFragment.asBinder())
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
+ * Starts an activity in the TaskFragment.
+ * @param fragmentToken client assigned unique token to create TaskFragment with specified in
+ * {@link TaskFragmentCreationParams#getFragmentToken()}.
+ * @param callerToken the activity token that initialized the activity launch.
+ * @param activityIntent intent to start the activity.
+ * @param activityOptions ActivityOptions to start the activity with.
+ * @see android.content.Context#startActivity(Intent, Bundle).
+ */
+ @NonNull
+ public WindowContainerTransaction startActivityInTaskFragment(
+ @NonNull IBinder fragmentToken, @NonNull IBinder callerToken,
+ @NonNull Intent activityIntent, @Nullable Bundle activityOptions) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(
+ HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT)
+ .setContainer(fragmentToken)
+ .setReparentContainer(callerToken)
+ .setActivityIntent(activityIntent)
+ .setLaunchOptions(activityOptions)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
+ * Moves an activity into the TaskFragment.
+ * @param fragmentToken client assigned unique token to create TaskFragment with specified in
+ * {@link TaskFragmentCreationParams#getFragmentToken()}.
+ * @param activityToken activity to be reparented.
+ */
+ @NonNull
+ public WindowContainerTransaction reparentActivityToTaskFragment(
+ @NonNull IBinder fragmentToken, @NonNull IBinder activityToken) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(
+ HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT)
+ .setReparentContainer(fragmentToken)
+ .setContainer(activityToken)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
+ * Reparents all children of one TaskFragment to another.
+ * @param oldParent children of this TaskFragment will be reparented.
+ * @param newParent the new parent TaskFragment to move the children to. If {@code null}, the
+ * children will be moved to the leaf Task.
+ */
+ @NonNull
+ public WindowContainerTransaction reparentChildren(
+ @NonNull WindowContainerToken oldParent,
+ @Nullable WindowContainerToken newParent) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN)
+ .setContainer(oldParent.asBinder())
+ .setReparentContainer(newParent != null ? newParent.asBinder() : null)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
+ * Sets to TaskFragments adjacent to each other. Containers below two visible adjacent
+ * TaskFragments will be made invisible. This is similar to
+ * {@link #setAdjacentRoots(WindowContainerToken, WindowContainerToken)}, but can be used with
+ * fragmentTokens when that TaskFragments haven't been created (but will be created in the same
+ * {@link WindowContainerTransaction}).
+ * To reset it, pass {@code null} for {@code fragmentToken2}.
+ * @param fragmentToken1 client assigned unique token to create TaskFragment with specified
+ * in {@link TaskFragmentCreationParams#getFragmentToken()}.
+ * @param fragmentToken2 client assigned unique token to create TaskFragment with specified
+ * in {@link TaskFragmentCreationParams#getFragmentToken()}. If it is
+ * {@code null}, the transaction will reset the adjacent TaskFragment.
+ */
+ @NonNull
+ public WindowContainerTransaction setAdjacentTaskFragments(
+ @NonNull IBinder fragmentToken1, @Nullable IBinder fragmentToken2,
+ @Nullable TaskFragmentAdjacentParams params) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS)
+ .setContainer(fragmentToken1)
+ .setReparentContainer(fragmentToken2)
+ .setLaunchOptions(params != null ? params.toBundle() : null)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
+ * When this {@link WindowContainerTransaction} failed to finish on the server side, it will
+ * trigger callback with this {@param errorCallbackToken}.
+ * @param errorCallbackToken client provided token that will be passed back as parameter in
+ * the callback if there is an error on the server side.
+ * @see ITaskFragmentOrganizer#onTaskFragmentError
+ */
+ @NonNull
+ public WindowContainerTransaction setErrorCallbackToken(@NonNull IBinder errorCallbackToken) {
+ if (mErrorCallbackToken != null) {
+ throw new IllegalStateException("Can't set multiple error token for one transaction.");
+ }
+ mErrorCallbackToken = errorCallbackToken;
+ return this;
+ }
+
+ /**
+ * Sets the {@link TaskFragmentOrganizer} that applies this {@link WindowContainerTransaction}.
+ * When this is set, the server side will not check for the permission of
+ * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, but will ensure this WCT only
+ * contains operations that are allowed for this organizer, such as modifying TaskFragments that
+ * are organized by this organizer.
+ * @hide
+ */
+ @NonNull
+ WindowContainerTransaction setTaskFragmentOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
+ if (mTaskFragmentOrganizer != null) {
+ throw new IllegalStateException("Can't set multiple organizers for one transaction.");
+ }
+ mTaskFragmentOrganizer = organizer;
+ return this;
+ }
+
+ /**
* Merges another WCT into this one.
* @param transfer When true, this will transfer everything from other potentially leaving
* other in an unusable state. When false, other is left alone, but
@@ -398,6 +606,23 @@ public final class WindowContainerTransaction implements Parcelable {
mHierarchyOps.add(transfer ? other.mHierarchyOps.get(i)
: new HierarchyOp(other.mHierarchyOps.get(i)));
}
+ if (mErrorCallbackToken != null && other.mErrorCallbackToken != null && mErrorCallbackToken
+ != other.mErrorCallbackToken) {
+ throw new IllegalArgumentException("Can't merge two WCTs with different error token");
+ }
+ final IBinder taskFragmentOrganizerAsBinder = mTaskFragmentOrganizer != null
+ ? mTaskFragmentOrganizer.asBinder()
+ : null;
+ final IBinder otherTaskFragmentOrganizerAsBinder = other.mTaskFragmentOrganizer != null
+ ? other.mTaskFragmentOrganizer.asBinder()
+ : null;
+ if (!Objects.equals(taskFragmentOrganizerAsBinder, otherTaskFragmentOrganizerAsBinder)) {
+ throw new IllegalArgumentException(
+ "Can't merge two WCTs from different TaskFragmentOrganizers");
+ }
+ mErrorCallbackToken = mErrorCallbackToken != null
+ ? mErrorCallbackToken
+ : other.mErrorCallbackToken;
}
/** @hide */
@@ -415,10 +640,26 @@ public final class WindowContainerTransaction implements Parcelable {
return mHierarchyOps;
}
+ /** @hide */
+ @Nullable
+ public IBinder getErrorCallbackToken() {
+ return mErrorCallbackToken;
+ }
+
+ /** @hide */
+ @Nullable
+ public ITaskFragmentOrganizer getTaskFragmentOrganizer() {
+ return mTaskFragmentOrganizer;
+ }
+
@Override
@NonNull
public String toString() {
- return "WindowContainerTransaction { changes = " + mChanges + " hops = " + mHierarchyOps
+ return "WindowContainerTransaction {"
+ + " changes = " + mChanges
+ + " hops = " + mHierarchyOps
+ + " errorCallbackToken=" + mErrorCallbackToken
+ + " taskFragmentOrganizer=" + mTaskFragmentOrganizer
+ " }";
}
@@ -427,6 +668,8 @@ public final class WindowContainerTransaction implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeMap(mChanges);
dest.writeList(mHierarchyOps);
+ dest.writeStrongBinder(mErrorCallbackToken);
+ dest.writeStrongInterface(mTaskFragmentOrganizer);
}
@Override
@@ -705,6 +948,13 @@ public final class WindowContainerTransaction implements Parcelable {
public static final int HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS = 4;
public static final int HIERARCHY_OP_TYPE_LAUNCH_TASK = 5;
public static final int HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT = 6;
+ public static final int HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT = 7;
+ public static final int HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT = 8;
+ public static final int HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT = 9;
+ public static final int HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT = 10;
+ public static final int HIERARCHY_OP_TYPE_REPARENT_CHILDREN = 11;
+ public static final int HIERARCHY_OP_TYPE_PENDING_INTENT = 12;
+ public static final int HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS = 13;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -713,75 +963,105 @@ public final class WindowContainerTransaction implements Parcelable {
private final int mType;
// Container we are performing the operation on.
- private final IBinder mContainer;
+ @Nullable
+ private IBinder mContainer;
// If this is same as mContainer, then only change position, don't reparent.
- private final IBinder mReparent;
+ @Nullable
+ private IBinder mReparent;
// Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
- private final boolean mToTop;
+ private boolean mToTop;
+
+ private boolean mReparentTopOnly;
+
+ @Nullable
+ private int[] mWindowingModes;
- final private int[] mWindowingModes;
- final private int[] mActivityTypes;
+ @Nullable
+ private int[] mActivityTypes;
+
+ @Nullable
+ private Bundle mLaunchOptions;
- private final Bundle mLaunchOptions;
+ @Nullable
+ private Intent mActivityIntent;
+
+ // Used as options for WindowContainerTransaction#createTaskFragment().
+ @Nullable
+ private TaskFragmentCreationParams mTaskFragmentCreationOptions;
+
+ @Nullable
+ private PendingIntent mPendingIntent;
public static HierarchyOp createForReparent(
@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
- return new HierarchyOp(HIERARCHY_OP_TYPE_REPARENT,
- container, reparent, null, null, toTop, null);
+ return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
+ .setContainer(container)
+ .setReparentContainer(reparent)
+ .setToTop(toTop)
+ .build();
}
public static HierarchyOp createForReorder(@NonNull IBinder container, boolean toTop) {
- return new HierarchyOp(HIERARCHY_OP_TYPE_REORDER,
- container, container, null, null, toTop, null);
+ return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REORDER)
+ .setContainer(container)
+ .setReparentContainer(container)
+ .setToTop(toTop)
+ .build();
}
public static HierarchyOp createForChildrenTasksReparent(IBinder currentParent,
- IBinder newParent, int[] windowingModes, int[] activityTypes, boolean onTop) {
- return new HierarchyOp(HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT,
- currentParent, newParent, windowingModes, activityTypes, onTop, null);
+ IBinder newParent, int[] windowingModes, int[] activityTypes, boolean onTop,
+ boolean reparentTopOnly) {
+ return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT)
+ .setContainer(currentParent)
+ .setReparentContainer(newParent)
+ .setWindowingModes(windowingModes)
+ .setActivityTypes(activityTypes)
+ .setToTop(onTop)
+ .setReparentTopOnly(reparentTopOnly)
+ .build();
}
public static HierarchyOp createForSetLaunchRoot(IBinder container,
int[] windowingModes, int[] activityTypes) {
- return new HierarchyOp(HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT,
- container, null, windowingModes, activityTypes, false, null);
+ return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT)
+ .setContainer(container)
+ .setWindowingModes(windowingModes)
+ .setActivityTypes(activityTypes)
+ .build();
}
public static HierarchyOp createForAdjacentRoots(IBinder root1, IBinder root2) {
- return new HierarchyOp(HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS,
- root1, root2, null, null, false, null);
+ return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS)
+ .setContainer(root1)
+ .setReparentContainer(root2)
+ .build();
}
/** Create a hierarchy op for launching a task. */
public static HierarchyOp createForTaskLaunch(int taskId, @Nullable Bundle options) {
final Bundle fullOptions = options == null ? new Bundle() : options;
fullOptions.putInt(LAUNCH_KEY_TASK_ID, taskId);
- return new HierarchyOp(HIERARCHY_OP_TYPE_LAUNCH_TASK, null, null, null, null, true,
- fullOptions);
+ return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_LAUNCH_TASK)
+ .setToTop(true)
+ .setLaunchOptions(fullOptions)
+ .build();
}
/** Create a hierarchy op for setting launch adjacent flag root. */
public static HierarchyOp createForSetLaunchAdjacentFlagRoot(IBinder container,
boolean clearRoot) {
- return new HierarchyOp(HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT, container, null,
- null, null, clearRoot, null);
+ return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT)
+ .setContainer(container)
+ .setToTop(clearRoot)
+ .build();
}
-
- private HierarchyOp(int type, @Nullable IBinder container, @Nullable IBinder reparent,
- int[] windowingModes, int[] activityTypes, boolean toTop,
- @Nullable Bundle launchOptions) {
+ /** Only creates through {@link Builder}. */
+ private HierarchyOp(int type) {
mType = type;
- mContainer = container;
- mReparent = reparent;
- mWindowingModes = windowingModes != null ?
- Arrays.copyOf(windowingModes, windowingModes.length) : null;
- mActivityTypes = activityTypes != null ?
- Arrays.copyOf(activityTypes, activityTypes.length) : null;
- mToTop = toTop;
- mLaunchOptions = launchOptions;
}
public HierarchyOp(@NonNull HierarchyOp copy) {
@@ -789,9 +1069,13 @@ public final class WindowContainerTransaction implements Parcelable {
mContainer = copy.mContainer;
mReparent = copy.mReparent;
mToTop = copy.mToTop;
+ mReparentTopOnly = copy.mReparentTopOnly;
mWindowingModes = copy.mWindowingModes;
mActivityTypes = copy.mActivityTypes;
mLaunchOptions = copy.mLaunchOptions;
+ mActivityIntent = copy.mActivityIntent;
+ mTaskFragmentCreationOptions = copy.mTaskFragmentCreationOptions;
+ mPendingIntent = copy.mPendingIntent;
}
protected HierarchyOp(Parcel in) {
@@ -799,9 +1083,13 @@ public final class WindowContainerTransaction implements Parcelable {
mContainer = in.readStrongBinder();
mReparent = in.readStrongBinder();
mToTop = in.readBoolean();
+ mReparentTopOnly = in.readBoolean();
mWindowingModes = in.createIntArray();
mActivityTypes = in.createIntArray();
mLaunchOptions = in.readBundle();
+ mActivityIntent = in.readTypedObject(Intent.CREATOR);
+ mTaskFragmentCreationOptions = in.readTypedObject(TaskFragmentCreationParams.CREATOR);
+ mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
}
public int getType() {
@@ -827,10 +1115,19 @@ public final class WindowContainerTransaction implements Parcelable {
return mReparent;
}
+ @NonNull
+ public IBinder getCallingActivity() {
+ return mReparent;
+ }
+
public boolean getToTop() {
return mToTop;
}
+ public boolean getReparentTopOnly() {
+ return mReparentTopOnly;
+ }
+
public int[] getWindowingModes() {
return mWindowingModes;
}
@@ -844,17 +1141,33 @@ public final class WindowContainerTransaction implements Parcelable {
return mLaunchOptions;
}
+ @Nullable
+ public Intent getActivityIntent() {
+ return mActivityIntent;
+ }
+
+ @Nullable
+ public TaskFragmentCreationParams getTaskFragmentCreationOptions() {
+ return mTaskFragmentCreationOptions;
+ }
+
+ @Nullable
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
@Override
public String toString() {
switch (mType) {
case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
return "{ChildrenTasksReparent: from=" + mContainer + " to=" + mReparent
- + " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes
- + " mActivityType=" + mActivityTypes + "}";
+ + " mToTop=" + mToTop + " mReparentTopOnly=" + mReparentTopOnly
+ + " mWindowingMode=" + Arrays.toString(mWindowingModes)
+ + " mActivityType=" + Arrays.toString(mActivityTypes) + "}";
case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT:
return "{SetLaunchRoot: container=" + mContainer
- + " mWindowingMode=" + mWindowingModes
- + " mActivityType=" + mActivityTypes + "}";
+ + " mWindowingMode=" + Arrays.toString(mWindowingModes)
+ + " mActivityType=" + Arrays.toString(mActivityTypes) + "}";
case HIERARCHY_OP_TYPE_REPARENT:
return "{reparent: " + mContainer + " to " + (mToTop ? "top of " : "bottom of ")
+ mReparent + "}";
@@ -868,10 +1181,27 @@ public final class WindowContainerTransaction implements Parcelable {
case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT:
return "{SetAdjacentFlagRoot: container=" + mContainer + " clearRoot=" + mToTop
+ "}";
+ case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT:
+ return "{CreateTaskFragment: options=" + mTaskFragmentCreationOptions + "}";
+ case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT:
+ return "{DeleteTaskFragment: taskFragment=" + mContainer + "}";
+ case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
+ return "{StartActivityInTaskFragment: fragmentToken=" + mContainer + " intent="
+ + mActivityIntent + " options=" + mLaunchOptions + "}";
+ case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT:
+ return "{ReparentActivityToTaskFragment: fragmentToken=" + mReparent
+ + " activity=" + mContainer + "}";
+ case HIERARCHY_OP_TYPE_REPARENT_CHILDREN:
+ return "{ReparentChildren: oldParent=" + mContainer + " newParent=" + mReparent
+ + "}";
+ case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS:
+ return "{SetAdjacentTaskFragments: container=" + mContainer
+ + " adjacentContainer=" + mReparent + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
- + " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes
- + " mActivityType=" + mActivityTypes + "}";
+ + " mToTop=" + mToTop
+ + " mWindowingMode=" + Arrays.toString(mWindowingModes)
+ + " mActivityType=" + Arrays.toString(mActivityTypes) + "}";
}
}
@@ -881,9 +1211,13 @@ public final class WindowContainerTransaction implements Parcelable {
dest.writeStrongBinder(mContainer);
dest.writeStrongBinder(mReparent);
dest.writeBoolean(mToTop);
+ dest.writeBoolean(mReparentTopOnly);
dest.writeIntArray(mWindowingModes);
dest.writeIntArray(mActivityTypes);
dest.writeBundle(mLaunchOptions);
+ dest.writeTypedObject(mActivityIntent, flags);
+ dest.writeTypedObject(mTaskFragmentCreationOptions, flags);
+ dest.writeTypedObject(mPendingIntent, flags);
}
@Override
@@ -902,5 +1236,174 @@ public final class WindowContainerTransaction implements Parcelable {
return new HierarchyOp[size];
}
};
+
+ private static class Builder {
+
+ private final int mType;
+
+ @Nullable
+ private IBinder mContainer;
+
+ @Nullable
+ private IBinder mReparent;
+
+ private boolean mToTop;
+
+ private boolean mReparentTopOnly;
+
+ @Nullable
+ private int[] mWindowingModes;
+
+ @Nullable
+ private int[] mActivityTypes;
+
+ @Nullable
+ private Bundle mLaunchOptions;
+
+ @Nullable
+ private Intent mActivityIntent;
+
+ @Nullable
+ private TaskFragmentCreationParams mTaskFragmentCreationOptions;
+
+ @Nullable
+ private PendingIntent mPendingIntent;
+
+ Builder(int type) {
+ mType = type;
+ }
+
+ Builder setContainer(@Nullable IBinder container) {
+ mContainer = container;
+ return this;
+ }
+
+ Builder setReparentContainer(@Nullable IBinder reparentContainer) {
+ mReparent = reparentContainer;
+ return this;
+ }
+
+ Builder setToTop(boolean toTop) {
+ mToTop = toTop;
+ return this;
+ }
+
+ Builder setReparentTopOnly(boolean reparentTopOnly) {
+ mReparentTopOnly = reparentTopOnly;
+ return this;
+ }
+
+ Builder setWindowingModes(@Nullable int[] windowingModes) {
+ mWindowingModes = windowingModes;
+ return this;
+ }
+
+ Builder setActivityTypes(@Nullable int[] activityTypes) {
+ mActivityTypes = activityTypes;
+ return this;
+ }
+
+ Builder setLaunchOptions(@Nullable Bundle launchOptions) {
+ mLaunchOptions = launchOptions;
+ return this;
+ }
+
+ Builder setActivityIntent(@Nullable Intent activityIntent) {
+ mActivityIntent = activityIntent;
+ return this;
+ }
+
+ Builder setPendingIntent(@Nullable PendingIntent sender) {
+ mPendingIntent = sender;
+ return this;
+ }
+
+ Builder setTaskFragmentCreationOptions(
+ @Nullable TaskFragmentCreationParams taskFragmentCreationOptions) {
+ mTaskFragmentCreationOptions = taskFragmentCreationOptions;
+ return this;
+ }
+
+ HierarchyOp build() {
+ final HierarchyOp hierarchyOp = new HierarchyOp(mType);
+ hierarchyOp.mContainer = mContainer;
+ hierarchyOp.mReparent = mReparent;
+ hierarchyOp.mWindowingModes = mWindowingModes != null
+ ? Arrays.copyOf(mWindowingModes, mWindowingModes.length)
+ : null;
+ hierarchyOp.mActivityTypes = mActivityTypes != null
+ ? Arrays.copyOf(mActivityTypes, mActivityTypes.length)
+ : null;
+ hierarchyOp.mToTop = mToTop;
+ hierarchyOp.mReparentTopOnly = mReparentTopOnly;
+ hierarchyOp.mLaunchOptions = mLaunchOptions;
+ hierarchyOp.mActivityIntent = mActivityIntent;
+ hierarchyOp.mPendingIntent = mPendingIntent;
+ hierarchyOp.mTaskFragmentCreationOptions = mTaskFragmentCreationOptions;
+
+ return hierarchyOp;
+ }
+ }
+ }
+
+ /**
+ * Helper class for building an options Bundle that can be used to set adjacent rules of
+ * TaskFragments.
+ */
+ public static class TaskFragmentAdjacentParams {
+ private static final String DELAY_PRIMARY_LAST_ACTIVITY_REMOVAL =
+ "android:transaction.adjacent.option.delay_primary_removal";
+ private static final String DELAY_SECONDARY_LAST_ACTIVITY_REMOVAL =
+ "android:transaction.adjacent.option.delay_secondary_removal";
+
+ private boolean mDelayPrimaryLastActivityRemoval;
+ private boolean mDelaySecondaryLastActivityRemoval;
+
+ public TaskFragmentAdjacentParams() {
+ }
+
+ public TaskFragmentAdjacentParams(@NonNull Bundle bundle) {
+ mDelayPrimaryLastActivityRemoval = bundle.getBoolean(
+ DELAY_PRIMARY_LAST_ACTIVITY_REMOVAL);
+ mDelaySecondaryLastActivityRemoval = bundle.getBoolean(
+ DELAY_SECONDARY_LAST_ACTIVITY_REMOVAL);
+ }
+
+ /** @see #shouldDelayPrimaryLastActivityRemoval() */
+ public void setShouldDelayPrimaryLastActivityRemoval(boolean delay) {
+ mDelayPrimaryLastActivityRemoval = delay;
+ }
+
+ /** @see #shouldDelaySecondaryLastActivityRemoval() */
+ public void setShouldDelaySecondaryLastActivityRemoval(boolean delay) {
+ mDelaySecondaryLastActivityRemoval = delay;
+ }
+
+ /**
+ * Whether to delay the last activity of the primary adjacent TaskFragment being immediately
+ * removed while finishing.
+ * <p>
+ * It is usually set to {@code true} to give organizer an opportunity to perform other
+ * actions or animations. An example is to finish together with the adjacent TaskFragment.
+ * </p>
+ */
+ public boolean shouldDelayPrimaryLastActivityRemoval() {
+ return mDelayPrimaryLastActivityRemoval;
+ }
+
+ /**
+ * Similar to {@link #shouldDelayPrimaryLastActivityRemoval()}, but for the secondary
+ * TaskFragment.
+ */
+ public boolean shouldDelaySecondaryLastActivityRemoval() {
+ return mDelaySecondaryLastActivityRemoval;
+ }
+
+ Bundle toBundle() {
+ final Bundle b = new Bundle();
+ b.putBoolean(DELAY_PRIMARY_LAST_ACTIVITY_REMOVAL, mDelayPrimaryLastActivityRemoval);
+ b.putBoolean(DELAY_SECONDARY_LAST_ACTIVITY_REMOVAL, mDelaySecondaryLastActivityRemoval);
+ return b;
+ }
}
}
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
index 6d0a6bd559ae..cfccb712127e 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -26,6 +26,7 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.view.Display;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -42,23 +43,33 @@ import java.lang.ref.Reference;
* @hide
*/
@UiContext
-public class WindowContext extends ContextWrapper {
+public class WindowContext extends ContextWrapper implements WindowProvider {
private final WindowManager mWindowManager;
- private final @WindowManager.LayoutParams.WindowType int mType;
- private final @Nullable Bundle mOptions;
+ @WindowManager.LayoutParams.WindowType
+ private final int mType;
+ @Nullable
+ private final Bundle mOptions;
private final ComponentCallbacksController mCallbacksController =
new ComponentCallbacksController();
private final WindowContextController mController;
/**
- * Default constructor. Will generate a {@link WindowTokenClient} and attach this context to
- * the token.
+ * Default implementation of {@link WindowContext}
+ * <p>
+ * Note that the users should call {@link Context#createWindowContext(Display, int, Bundle)}
+ * to create a {@link WindowContext} instead of using this constructor
+ * </p><p>
+ * Example usage:
+ * <pre class="prettyprint">
+ * Bundle options = new Bundle();
+ * options.put(KEY_ROOT_DISPLAY_AREA_ID, displayAreaInfo.rootDisplayAreaId);
+ * Context windowContext = context.createWindowContext(display, windowType, options);
+ * </pre></p>
*
- * @param base Base {@link Context} for this new instance.
- * @param type Window type to be used with this context.
+ * @param base Base {@link Context} for this new instance.
+ * @param type Window type to be used with this context.
* @param options A bundle used to pass window-related options.
- *
- * @hide
+ * @see DisplayAreaInfo#rootDisplayAreaId
*/
public WindowContext(@NonNull Context base, int type, @Nullable Bundle options) {
super(base);
@@ -104,10 +115,13 @@ public class WindowContext extends ContextWrapper {
@Override
public void destroy() {
- mCallbacksController.clearCallbacks();
- // Called to the base ContextImpl to do final clean-up.
- getBaseContext().destroy();
- Reference.reachabilityFence(this);
+ try {
+ mCallbacksController.clearCallbacks();
+ // Called to the base ContextImpl to do final clean-up.
+ getBaseContext().destroy();
+ } finally {
+ Reference.reachabilityFence(this);
+ }
}
@Override
@@ -124,4 +138,15 @@ public class WindowContext extends ContextWrapper {
void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
mCallbacksController.dispatchConfigurationChanged(newConfig);
}
+
+ @Override
+ public int getWindowType() {
+ return mType;
+ }
+
+ @Nullable
+ @Override
+ public Bundle getWindowContextOptions() {
+ return mOptions;
+ }
}
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index 505b45008663..5aa623388574 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -87,7 +87,8 @@ public class WindowContextController {
mAttachedToDisplayArea = true;
// Send the DisplayArea's configuration to WindowContext directly instead of
// waiting for dispatching from WMS.
- mToken.onConfigurationChanged(configuration, displayId);
+ mToken.onConfigurationChanged(configuration, displayId,
+ false /* shouldReportConfigChange */);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/window/WindowInfosListener.java b/core/java/android/window/WindowInfosListener.java
new file mode 100644
index 000000000000..4376e3eb572e
--- /dev/null
+++ b/core/java/android/window/WindowInfosListener.java
@@ -0,0 +1,63 @@
+/*
+ * 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 android.window;
+
+import android.view.InputWindowHandle;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * Listener for getting {@link InputWindowHandle} updates from SurfaceFlinger.
+ * @hide
+ */
+public abstract class WindowInfosListener {
+ private final long mNativeListener;
+
+ public WindowInfosListener() {
+ NativeAllocationRegistry registry = NativeAllocationRegistry.createMalloced(
+ WindowInfosListener.class.getClassLoader(), nativeGetFinalizer());
+
+ mNativeListener = nativeCreate(this);
+ registry.registerNativeAllocation(this, mNativeListener);
+ }
+
+ /**
+ * Called when WindowInfos in SurfaceFlinger have changed.
+ * @param windowHandles Reverse Z ordered array of window information that was on screen,
+ * where the first value is the topmost window.
+ */
+ public abstract void onWindowInfosChanged(InputWindowHandle[] windowHandles);
+
+ /**
+ * Register the WindowInfosListener.
+ */
+ public void register() {
+ nativeRegister(mNativeListener);
+ }
+
+ /**
+ * Unregisters the WindowInfosListener.
+ */
+ public void unregister() {
+ nativeUnregister(mNativeListener);
+ }
+
+ private static native long nativeCreate(WindowInfosListener thiz);
+ private static native void nativeRegister(long ptr);
+ private static native void nativeUnregister(long ptr);
+ private static native long nativeGetFinalizer();
+}
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 544d42240079..4ea5ea5694fa 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -25,6 +25,7 @@ import android.app.ActivityTaskManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Singleton;
+import android.view.RemoteAnimationAdapter;
/**
* Base class for organizing specific types of windows like Tasks and DisplayAreas
@@ -36,9 +37,16 @@ public class WindowOrganizer {
/**
* Apply multiple WindowContainer operations at once.
+ *
+ * Note that using this API requires the caller to hold
+ * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, unless the caller is using
+ * {@link TaskFragmentOrganizer}, in which case it is allowed to change TaskFragment that is
+ * created by itself.
+ *
* @param t The transaction to apply.
*/
- @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
+ conditional = true)
public void applyTransaction(@NonNull WindowContainerTransaction t) {
try {
if (!t.isEmpty()) {
@@ -51,6 +59,12 @@ public class WindowOrganizer {
/**
* Apply multiple WindowContainer operations at once.
+ *
+ * Note that using this API requires the caller to hold
+ * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, unless the caller is using
+ * {@link TaskFragmentOrganizer}, in which case it is allowed to change TaskFragment that is
+ * created by itself.
+ *
* @param t The transaction to apply.
* @param callback This transaction will use the synchronization scheme described in
* BLASTSyncEngine.java. The SurfaceControl transaction containing the effects of this
@@ -58,7 +72,8 @@ public class WindowOrganizer {
* @return An ID for the sync operation which will later be passed to transactionReady callback.
* This lets the caller differentiate overlapping sync operations.
*/
- @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
+ conditional = true)
public int applySyncTransaction(@NonNull WindowContainerTransaction t,
@NonNull WindowContainerTransactionCallback callback) {
try {
@@ -111,6 +126,27 @@ public class WindowOrganizer {
}
/**
+ * Start a legacy transition.
+ * @param type The type of the transition. This is ignored if a transitionToken is provided.
+ * @param adapter An existing transition to start. If null, a new transition is created.
+ * @param t The set of window operations that are part of this transition.
+ * @return true on success, false if a transition was already running.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ @NonNull
+ public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter,
+ @NonNull WindowContainerTransactionCallback syncCallback,
+ @NonNull WindowContainerTransaction t) {
+ try {
+ return getWindowOrganizerController().startLegacyTransition(
+ type, adapter, syncCallback.mInterface, t);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Register an ITransitionPlayer to handle transition animations.
* @hide
*/
@@ -123,8 +159,19 @@ public class WindowOrganizer {
}
}
- @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
- IWindowOrganizerController getWindowOrganizerController() {
+ /**
+ * @see TransitionMetrics
+ * @hide
+ */
+ public static ITransitionMetricsReporter getTransitionMetricsReporter() {
+ try {
+ return getWindowOrganizerController().getTransitionMetricsReporter();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ static IWindowOrganizerController getWindowOrganizerController() {
return IWindowOrganizerControllerSingleton.get();
}
diff --git a/core/java/android/window/WindowProvider.java b/core/java/android/window/WindowProvider.java
new file mode 100644
index 000000000000..b078b9362b90
--- /dev/null
+++ b/core/java/android/window/WindowProvider.java
@@ -0,0 +1,39 @@
+/*
+ * 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 android.window;
+
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.view.WindowManager.LayoutParams.WindowType;
+
+/**
+ * An interface to provide a non-activity window.
+ * Examples are {@link WindowContext} and {@link WindowProviderService}.
+ *
+ * @hide
+ */
+public interface WindowProvider {
+ /** @hide */
+ String KEY_IS_WINDOW_PROVIDER_SERVICE = "android.windowContext.isWindowProviderService";
+
+ /** Gets the window type of this provider */
+ @WindowType
+ int getWindowType();
+
+ /** Gets the launch options of this provider */
+ @Nullable
+ Bundle getWindowContextOptions();
+}
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index b8619fbcf334..f8484d15344e 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -36,27 +36,45 @@ import android.view.WindowManager;
import android.view.WindowManager.LayoutParams.WindowType;
import android.view.WindowManagerImpl;
-// TODO(b/159767464): handle #onConfigurationChanged(Configuration)
/**
* A {@link Service} responsible for showing a non-activity window, such as software keyboards or
* accessibility overlay windows. This {@link Service} has similar behavior to
* {@link WindowContext}, but is represented as {@link Service}.
*
* @see android.inputmethodservice.InputMethodService
- * @see android.accessibilityservice.AccessibilityService
*
* @hide
*/
@TestApi
@UiContext
-public abstract class WindowProviderService extends Service {
+public abstract class WindowProviderService extends Service implements WindowProvider {
+ private final Bundle mOptions;
private final WindowTokenClient mWindowToken = new WindowTokenClient();
private final WindowContextController mController = new WindowContextController(mWindowToken);
private WindowManager mWindowManager;
+ private boolean mInitialized;
/**
- * Returns the type of this {@link WindowProviderService}.
+ * Returns {@code true} if the {@code windowContextOptions} declares that it is a
+ * {@link WindowProviderService}.
+ *
+ * @hide
+ */
+ public static boolean isWindowProviderService(@Nullable Bundle windowContextOptions) {
+ if (windowContextOptions == null) {
+ return false;
+ }
+ return (windowContextOptions.getBoolean(KEY_IS_WINDOW_PROVIDER_SERVICE, false));
+ }
+
+ public WindowProviderService() {
+ mOptions = new Bundle();
+ mOptions.putBoolean(KEY_IS_WINDOW_PROVIDER_SERVICE, true);
+ }
+
+ /**
+ * Returns the window type of this {@link WindowProviderService}.
* Each inheriting class must implement this method to provide the type of the window. It is
* used similar to {@code type} of {@link Context#createWindowContext(int, Bundle)}
*
@@ -68,15 +86,24 @@ public abstract class WindowProviderService extends Service {
@SuppressLint("OnNameExpected")
// Suppress the lint because it is not a callback and users should provide window type
// so we cannot make it final.
- public abstract @WindowType int getWindowType();
+ @WindowType
+ @Override
+ public abstract int getWindowType();
/**
* Returns the option of this {@link WindowProviderService}.
- * Default is {@code null}. The inheriting class can implement this method to provide the
- * customization {@code option} of the window. It is used similar to {@code options} of
- * {@link Context#createWindowContext(int, Bundle)}
- *
- * @see Context#createWindowContext(int, Bundle)
+ * <p>
+ * The inheriting class can implement this method to provide the customization {@code option} of
+ * the window, but must be based on this method's returned value.
+ * It is used similar to {@code options} of {@link Context#createWindowContext(int, Bundle)}
+ * </p>
+ * <pre class="prettyprint">
+ * public Bundle getWindowContextOptions() {
+ * final Bundle options = super.getWindowContextOptions();
+ * options.put(KEY_ROOT_DISPLAY_AREA_ID, displayAreaInfo.rootDisplayAreaId);
+ * return options;
+ * }
+ * </pre>
*
* @hide
*/
@@ -85,8 +112,24 @@ public abstract class WindowProviderService extends Service {
// Suppress the lint because it is not a callback and users may override this API to provide
// launch option. Also, the return value of this API is null by default.
@Nullable
+ @CallSuper
+ @Override
public Bundle getWindowContextOptions() {
- return null;
+ return mOptions;
+ }
+
+ /**
+ * Returns the display ID to launch this {@link WindowProviderService}.
+ *
+ * @hide
+ */
+ @TestApi
+ @SuppressLint({"OnNameExpected"})
+ // Suppress the lint because it is not a callback and users may override this API to provide
+ // display.
+ @NonNull
+ public int getInitialDisplayId() {
+ return DEFAULT_DISPLAY;
}
/**
@@ -104,19 +147,22 @@ public abstract class WindowProviderService extends Service {
public final Context createServiceBaseContext(ActivityThread mainThread,
LoadedApk packageInfo) {
final Context context = super.createServiceBaseContext(mainThread, packageInfo);
- // Always associate with the default display at initialization.
- final Display defaultDisplay = context.getSystemService(DisplayManager.class)
- .getDisplay(DEFAULT_DISPLAY);
- return context.createTokenContext(mWindowToken, defaultDisplay);
+ final Display display = context.getSystemService(DisplayManager.class)
+ .getDisplay(getInitialDisplayId());
+ return context.createTokenContext(mWindowToken, display);
}
- @CallSuper
+ /** @hide */
@Override
- public void onCreate() {
- super.onCreate();
- mWindowToken.attachContext(this);
- mController.attachToDisplayArea(getWindowType(), getDisplayId(), getWindowContextOptions());
- mWindowManager = WindowManagerImpl.createWindowContextWindowManager(this);
+ protected void attachBaseContext(Context newBase) {
+ super.attachBaseContext(newBase);
+ if (!mInitialized) {
+ mWindowToken.attachContext(this);
+ mController.attachToDisplayArea(getWindowType(), getDisplayId(),
+ getWindowContextOptions());
+ mWindowManager = WindowManagerImpl.createWindowContextWindowManager(this);
+ mInitialized = true;
+ }
}
@SuppressLint("OnNameExpected")
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 4dcd2e74a53f..f3e3859b4256 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -15,14 +15,23 @@
*/
package android.window;
+import static android.window.ConfigurationHelper.diffPublicWithSizeBuckets;
+import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
+import static android.window.ConfigurationHelper.isDifferentDisplay;
+import static android.window.ConfigurationHelper.shouldUpdateResources;
+
import android.annotation.NonNull;
import android.app.ActivityThread;
import android.app.IWindowToken;
import android.app.ResourcesManager;
import android.content.Context;
import android.content.res.Configuration;
+import android.inputmethodservice.AbstractInputMethodService;
+import android.os.Build;
import android.os.Bundle;
+import android.os.Debug;
import android.os.IBinder;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -40,6 +49,8 @@ import java.lang.ref.WeakReference;
* @hide
*/
public class WindowTokenClient extends IWindowToken.Stub {
+ private static final String TAG = WindowTokenClient.class.getSimpleName();
+
/**
* Attached {@link Context} for this window token to update configuration and resources.
* Initialized by {@link #attachContext(Context)}.
@@ -48,6 +59,10 @@ public class WindowTokenClient extends IWindowToken.Stub {
private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
+ private final Configuration mConfiguration = new Configuration();
+
+ private boolean mShouldDumpConfigForIme;
+
/**
* Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
* can only attach one {@link Context}.
@@ -63,6 +78,9 @@ public class WindowTokenClient extends IWindowToken.Stub {
throw new IllegalStateException("Context is already attached.");
}
mContextRef = new WeakReference<>(context);
+ mConfiguration.setTo(context.getResources().getConfiguration());
+ mShouldDumpConfigForIme = Build.IS_DEBUGGABLE
+ && context instanceof AbstractInputMethodService;
}
/**
@@ -71,24 +89,76 @@ public class WindowTokenClient extends IWindowToken.Stub {
* @param newConfig the updated {@link Configuration}
* @param newDisplayId the updated {@link android.view.Display} ID
*/
- @VisibleForTesting
@Override
public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
+ onConfigurationChanged(newConfig, newDisplayId, true /* shouldReportConfigChange */);
+ }
+
+ // TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService
+ // are inherited from WindowProvider.
+ /**
+ * Called when {@link Configuration} updates from the server side receive.
+ *
+ * Similar to {@link #onConfigurationChanged(Configuration, int)}, but adds a flag to control
+ * whether to dispatch configuration update or not.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void onConfigurationChanged(Configuration newConfig, int newDisplayId,
+ boolean shouldReportConfigChange) {
final Context context = mContextRef.get();
if (context == null) {
return;
}
- final int currentDisplayId = context.getDisplayId();
- final boolean displayChanged = newDisplayId != currentDisplayId;
- final Configuration config = context.getResources().getConfiguration();
- final boolean configChanged = config.diff(newConfig) != 0;
- if (displayChanged || configChanged) {
+ final boolean displayChanged = isDifferentDisplay(context.getDisplayId(), newDisplayId);
+ final boolean shouldUpdateResources = shouldUpdateResources(this, mConfiguration,
+ newConfig, newConfig /* overrideConfig */, displayChanged,
+ null /* configChanged */);
+
+ if (!shouldUpdateResources && mShouldDumpConfigForIme) {
+ Log.d(TAG, "Configuration not dispatch to IME because configuration is up"
+ + " to date. Current config=" + context.getResources().getConfiguration()
+ + ", reported config=" + mConfiguration
+ + ", updated config=" + newConfig);
+ }
+
+ if (shouldUpdateResources) {
// TODO(ag/9789103): update resource manager logic to track non-activity tokens
mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
- if (context instanceof WindowContext) {
+
+ if (shouldReportConfigChange && context instanceof WindowContext) {
+ final WindowContext windowContext = (WindowContext) context;
ActivityThread.currentActivityThread().getHandler().post(
- () -> ((WindowContext) context).dispatchConfigurationChanged(newConfig));
+ () -> windowContext.dispatchConfigurationChanged(newConfig));
+ }
+
+ // Dispatch onConfigurationChanged only if there's a significant public change to
+ // make it compatible with the original behavior.
+ final Configuration[] sizeConfigurations = context.getResources()
+ .getSizeConfigurations();
+ final SizeConfigurationBuckets buckets = sizeConfigurations != null
+ ? new SizeConfigurationBuckets(sizeConfigurations) : null;
+ final int diff = diffPublicWithSizeBuckets(mConfiguration, newConfig, buckets);
+
+ if (shouldReportConfigChange && diff != 0
+ && context instanceof WindowProviderService) {
+ final WindowProviderService windowProviderService = (WindowProviderService) context;
+ ActivityThread.currentActivityThread().getHandler().post(
+ () -> windowProviderService.onConfigurationChanged(newConfig));
+ }
+ freeTextLayoutCachesIfNeeded(diff);
+ if (mShouldDumpConfigForIme) {
+ if (!shouldReportConfigChange) {
+ Log.d(TAG, "Only apply configuration update to Resources because "
+ + "shouldReportConfigChange is false.\n" + Debug.getCallers(5));
+ } else if (diff == 0) {
+ Log.d(TAG, "Configuration not dispatch to IME because configuration has no "
+ + " public difference with updated config. "
+ + " Current config=" + context.getResources().getConfiguration()
+ + ", reported config=" + mConfiguration
+ + ", updated config=" + newConfig);
+ }
}
+ mConfiguration.setTo(newConfig);
}
if (displayChanged) {
context.updateDisplay(newDisplayId);
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 0b92b93f2c88..874e3f4ae26a 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -47,6 +47,8 @@ import java.util.List;
public class AccessibilityShortcutChooserActivity extends Activity {
@ShortcutType
private final int mShortcutType = ACCESSIBILITY_SHORTCUT_KEY;
+ private static final String KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE =
+ "accessibility_shortcut_menu_mode";
private final List<AccessibilityTarget> mTargets = new ArrayList<>();
private AlertDialog mMenuDialog;
private AlertDialog mPermissionDialog;
@@ -66,14 +68,30 @@ public class AccessibilityShortcutChooserActivity extends Activity {
mMenuDialog = createMenuDialog();
mMenuDialog.setOnShowListener(dialog -> updateDialogListeners());
mMenuDialog.show();
+
+ if (savedInstanceState != null) {
+ final int restoreShortcutMenuMode =
+ savedInstanceState.getInt(KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE,
+ ShortcutMenuMode.LAUNCH);
+ if (restoreShortcutMenuMode == ShortcutMenuMode.EDIT) {
+ onEditButtonClicked();
+ }
+ }
}
@Override
protected void onDestroy() {
+ mMenuDialog.setOnDismissListener(null);
mMenuDialog.dismiss();
super.onDestroy();
}
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE, mTargetAdapter.getShortcutMenuMode());
+ }
+
private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) {
final AccessibilityTarget target = mTargets.get(position);
target.onSelected();
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index c57afbc67494..179ac8b1c528 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -17,6 +17,7 @@
package com.android.internal.accessibility.util;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
@@ -30,6 +31,7 @@ import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT
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_BUTTON_LONG_PRESS;
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__TRIPLE_TAP;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
@@ -152,19 +154,29 @@ public final class AccessibilityStatsLogUtils {
convertToLoggingMagnificationMode(mode));
}
- private static boolean isFloatingMenuEnabled(Context context) {
+ private static boolean isAccessibilityFloatingMenuEnabled(Context context) {
return Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_MODE, /* def= */ -1)
== ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
}
+ private static boolean isAccessibilityGestureEnabled(Context context) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, /* def= */ -1)
+ == ACCESSIBILITY_BUTTON_MODE_GESTURE;
+ }
+
private static int convertToLoggingShortcutType(Context context,
@ShortcutType int shortcutType) {
switch (shortcutType) {
case ACCESSIBILITY_BUTTON:
- return isFloatingMenuEnabled(context)
- ? ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU
- : ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
+ if (isAccessibilityFloatingMenuEnabled(context)) {
+ return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
+ } else if (isAccessibilityGestureEnabled(context)) {
+ return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
+ } else {
+ return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
+ }
case ACCESSIBILITY_SHORTCUT_KEY:
return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index c8a4425409e8..998526209c72 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -272,4 +272,14 @@ interface IVoiceInteractionManagerService {
void triggerHardwareRecognitionEventForTest(
in SoundTrigger.KeyphraseRecognitionEvent event,
in IHotwordRecognitionStatusCallback callback);
+
+ /**
+ * Starts to listen the status of visible activity.
+ */
+ void startListeningVisibleActivityChanged(in IBinder token);
+
+ /**
+ * Stops to listen the status of visible activity.
+ */
+ void stopListeningVisibleActivityChanged(in IBinder token);
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index a00b993749a5..bf094dbd8f1e 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -236,6 +236,8 @@ public final class InputMethodDebug {
return "HIDE_TOGGLE_SOFT_INPUT";
case SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API:
return "SHOW_SOFT_INPUT_BY_INSETS_API";
+ case SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE:
+ return "HIDE_DISPLAY_IME_POLICY_HIDE";
default:
return "Unknown=" + reason;
}
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index e3713a3b8971..9e5776292031 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -19,6 +19,7 @@ package com.android.internal.inputmethod;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
+import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import java.lang.annotation.Retention;
@@ -53,7 +54,8 @@ import java.lang.annotation.Retention;
SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY,
SoftInputShowHideReason.SHOW_TOGGLE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT,
- SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API})
+ SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API,
+ SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE})
public @interface SoftInputShowHideReason {
/** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */
int SHOW_SOFT_INPUT = 0;
@@ -195,4 +197,10 @@ public @interface SoftInputShowHideReason {
* {@link android.view.InsetsController#show(int)};
*/
int SHOW_SOFT_INPUT_BY_INSETS_API = 25;
+
+ /**
+ * Hide soft input if Ime policy has been set to {@link WindowManager#DISPLAY_IME_POLICY_HIDE}.
+ * See also {@code InputMethodManagerService#mImeHiddenByDisplayPolicy}.
+ */
+ int HIDE_DISPLAY_IME_POLICY_HIDE = 26;
}
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index d12c870d2591..d14054d4f9f7 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -45,6 +45,7 @@ import android.view.ThreadedRenderer;
import android.view.ViewRootImpl;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.internal.jank.InteractionJankMonitor.Session;
import com.android.internal.util.FrameworkStatsLog;
@@ -70,6 +71,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
static final int REASON_CANCEL_NORMAL = 16;
static final int REASON_CANCEL_NOT_BEGUN = 17;
static final int REASON_CANCEL_SAME_VSYNC = 18;
+ static final int REASON_CANCEL_TIMEOUT = 19;
/** @hide */
@IntDef({
@@ -97,6 +99,10 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback;
private final Handler mHandler;
private final ChoreographerWrapper mChoreographer;
+ private final Object mLock = InteractionJankMonitor.getInstance().getLock();
+
+ @VisibleForTesting
+ public final boolean mSurfaceOnly;
private long mBeginVsyncId = INVALID_ID;
private long mEndVsyncId = INVALID_ID;
@@ -138,90 +144,104 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
}
public FrameTracker(@NonNull Session session, @NonNull Handler handler,
- @NonNull ThreadedRendererWrapper renderer, @NonNull ViewRootWrapper viewRootWrapper,
+ @Nullable ThreadedRendererWrapper renderer, @Nullable ViewRootWrapper viewRootWrapper,
@NonNull SurfaceControlWrapper surfaceControlWrapper,
@NonNull ChoreographerWrapper choreographer,
- @NonNull FrameMetricsWrapper metrics, int traceThresholdMissedFrames,
- int traceThresholdFrameTimeMillis, @Nullable FrameTrackerListener listener) {
+ @Nullable FrameMetricsWrapper metrics,
+ int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis,
+ @Nullable FrameTrackerListener listener, @NonNull Configuration config) {
+ mSurfaceOnly = config.isSurfaceOnly();
mSession = session;
- mRendererWrapper = renderer;
- mMetricsWrapper = metrics;
- mViewRoot = viewRootWrapper;
+ mHandler = handler;
mChoreographer = choreographer;
mSurfaceControlWrapper = surfaceControlWrapper;
- mHandler = handler;
- mObserver = new HardwareRendererObserver(
- this, mMetricsWrapper.getTiming(), handler, false /*waitForPresentTime*/);
+
+ // HWUI instrumentation init.
+ mRendererWrapper = mSurfaceOnly ? null : renderer;
+ mMetricsWrapper = mSurfaceOnly ? null : metrics;
+ mViewRoot = mSurfaceOnly ? null : viewRootWrapper;
+ mObserver = mSurfaceOnly
+ ? null
+ : new HardwareRendererObserver(this, mMetricsWrapper.getTiming(),
+ handler, /* waitForPresentTime= */ false);
+
mTraceThresholdMissedFrames = traceThresholdMissedFrames;
mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis;
mListener = listener;
- // If the surface isn't valid yet, wait until it's created.
- if (viewRootWrapper.getSurfaceControl().isValid()) {
- mSurfaceControl = viewRootWrapper.getSurfaceControl();
- }
- mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() {
- @Override
- public void surfaceCreated(SurfaceControl.Transaction t) {
- synchronized (FrameTracker.this) {
- if (mSurfaceControl == null) {
- mSurfaceControl = viewRootWrapper.getSurfaceControl();
- if (mBeginVsyncId != INVALID_ID) {
- mSurfaceControlWrapper.addJankStatsListener(
- FrameTracker.this, mSurfaceControl);
- postTraceStartMarker();
+ if (mSurfaceOnly) {
+ mSurfaceControl = config.getSurfaceControl();
+ mSurfaceChangedCallback = null;
+ } else {
+ // HWUI instrumentation init.
+ // If the surface isn't valid yet, wait until it's created.
+ if (mViewRoot.getSurfaceControl().isValid()) {
+ mSurfaceControl = mViewRoot.getSurfaceControl();
+ }
+
+ mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() {
+ @Override
+ public void surfaceCreated(SurfaceControl.Transaction t) {
+ synchronized (mLock) {
+ if (mSurfaceControl == null) {
+ mSurfaceControl = mViewRoot.getSurfaceControl();
+ if (mBeginVsyncId != INVALID_ID) {
+ mSurfaceControlWrapper.addJankStatsListener(
+ FrameTracker.this, mSurfaceControl);
+ postTraceStartMarker();
+ }
}
}
}
- }
- @Override
- public void surfaceReplaced(SurfaceControl.Transaction t) {
- }
+ @Override
+ public void surfaceReplaced(SurfaceControl.Transaction t) {
+ }
- @Override
- public void surfaceDestroyed() {
-
- // Wait a while to give the system a chance for the remaining frames to arrive, then
- // force finish the session.
- mHandler.postDelayed(() -> {
- synchronized (FrameTracker.this) {
- if (DEBUG) {
- Log.d(TAG, "surfaceDestroyed: " + mSession.getName()
- + ", finalized=" + mMetricsFinalized
- + ", info=" + mJankInfos.size()
- + ", vsync=" + mBeginVsyncId + "-" + mEndVsyncId);
- }
- if (!mMetricsFinalized) {
- end(REASON_END_SURFACE_DESTROYED);
- finish(mJankInfos.size() - 1);
+ @Override
+ public void surfaceDestroyed() {
+
+ // Wait a while to give the system a chance for the remaining
+ // frames to arrive, then force finish the session.
+ mHandler.postDelayed(() -> {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Log.d(TAG, "surfaceDestroyed: " + mSession.getName()
+ + ", finalized=" + mMetricsFinalized
+ + ", info=" + mJankInfos.size()
+ + ", vsync=" + mBeginVsyncId);
+ }
+ if (!mMetricsFinalized) {
+ end(REASON_END_SURFACE_DESTROYED);
+ finish(mJankInfos.size() - 1);
+ }
}
- }
- }, 50);
- }
- };
-
- // This callback has a reference to FrameTracker, remember to remove it to avoid leakage.
- viewRootWrapper.addSurfaceChangedCallback(mSurfaceChangedCallback);
+ }, 50);
+ }
+ };
+ // This callback has a reference to FrameTracker,
+ // remember to remove it to avoid leakage.
+ mViewRoot.addSurfaceChangedCallback(mSurfaceChangedCallback);
+ }
}
/**
* Begin a trace session of the CUJ.
*/
- public synchronized void begin() {
- mBeginVsyncId = mChoreographer.getVsyncId() + 1;
- if (mSurfaceControl != null) {
- postTraceStartMarker();
- }
- mRendererWrapper.addObserver(mObserver);
- if (DEBUG) {
- Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId);
- }
- if (mSurfaceControl != null) {
- mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
- }
- if (mListener != null) {
- mListener.onCujEvents(mSession, ACTION_SESSION_BEGIN);
+ public void begin() {
+ synchronized (mLock) {
+ mBeginVsyncId = mChoreographer.getVsyncId() + 1;
+ if (DEBUG) {
+ Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId);
+ }
+ if (mSurfaceControl != null) {
+ postTraceStartMarker();
+ mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
+ }
+ if (!mSurfaceOnly) {
+ mRendererWrapper.addObserver(mObserver);
+ }
+ notifyCujEvent(ACTION_SESSION_BEGIN);
}
}
@@ -231,7 +251,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
@VisibleForTesting
public void postTraceStartMarker() {
mChoreographer.mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, () -> {
- synchronized (FrameTracker.this) {
+ synchronized (mLock) {
if (mCancelled || mEndVsyncId != INVALID_ID) {
return;
}
@@ -244,86 +264,98 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
/**
* End the trace session of the CUJ.
*/
- public synchronized void end(@Reasons int reason) {
- if (mEndVsyncId != INVALID_ID) return;
- mEndVsyncId = mChoreographer.getVsyncId();
-
- // Cancel the session if:
- // 1. The session begins and ends at the same vsync id.
- // 2. The session never begun.
- if (mBeginVsyncId == INVALID_ID) {
- cancel(REASON_CANCEL_NOT_BEGUN);
- } else if (mEndVsyncId <= mBeginVsyncId) {
- cancel(REASON_CANCEL_SAME_VSYNC);
- } else {
- if (DEBUG) {
- Log.d(TAG, "end: " + mSession.getName()
- + ", end=" + mEndVsyncId + ", reason=" + reason);
- }
- Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId);
- mSession.setReason(reason);
- if (mListener != null) {
- mListener.onCujEvents(mSession, ACTION_SESSION_END);
+ public boolean end(@Reasons int reason) {
+ synchronized (mLock) {
+ if (mCancelled || mEndVsyncId != INVALID_ID) return false;
+ mEndVsyncId = mChoreographer.getVsyncId();
+ // Cancel the session if:
+ // 1. The session begins and ends at the same vsync id.
+ // 2. The session never begun.
+ if (mBeginVsyncId == INVALID_ID) {
+ return cancel(REASON_CANCEL_NOT_BEGUN);
+ } else if (mEndVsyncId <= mBeginVsyncId) {
+ return cancel(REASON_CANCEL_SAME_VSYNC);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "end: " + mSession.getName()
+ + ", end=" + mEndVsyncId + ", reason=" + reason);
+ }
+ Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId);
+ mSession.setReason(reason);
+
+ // We don't remove observer here,
+ // will remove it when all the frame metrics in this duration are called back.
+ // See onFrameMetricsAvailable for the logic of removing the observer.
+ // Waiting at most 10 seconds for all callbacks to finish.
+ mWaitForFinishTimedOut = () -> {
+ Log.e(TAG, "force finish cuj because of time out:" + mSession.getName());
+ finish(mJankInfos.size() - 1);
+ };
+ mHandler.postDelayed(mWaitForFinishTimedOut, TimeUnit.SECONDS.toMillis(10));
+ notifyCujEvent(ACTION_SESSION_END);
+ return true;
}
- // We don't remove observer here,
- // will remove it when all the frame metrics in this duration are called back.
- // See onFrameMetricsAvailable for the logic of removing the observer.
- // Waiting at most 10 seconds for all callbacks to finish.
- mWaitForFinishTimedOut = () -> {
- Log.e(TAG, "force finish cuj because of time out:" + mSession.getName());
- finish(mJankInfos.size() - 1);
- };
- mHandler.postDelayed(mWaitForFinishTimedOut, TimeUnit.SECONDS.toMillis(10));
}
}
/**
* Cancel the trace session of the CUJ.
*/
- public synchronized void cancel(@Reasons int reason) {
- // We don't need to end the trace section if it never begun.
- if (mTracingStarted) {
- Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId);
- }
- mCancelled = true;
+ public boolean cancel(@Reasons int reason) {
+ synchronized (mLock) {
+ final boolean cancelFromEnd =
+ reason == REASON_CANCEL_NOT_BEGUN || reason == REASON_CANCEL_SAME_VSYNC;
+ if (mCancelled || (mEndVsyncId != INVALID_ID && !cancelFromEnd)) return false;
+ mCancelled = true;
+ // We don't need to end the trace section if it never begun.
+ if (mTracingStarted) {
+ Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId);
+ }
- // Always remove the observers in cancel call to avoid leakage.
- removeObservers();
+ // Always remove the observers in cancel call to avoid leakage.
+ removeObservers();
- if (DEBUG) {
- Log.d(TAG, "cancel: " + mSession.getName()
- + ", begin=" + mBeginVsyncId + ", end=" + mEndVsyncId + ", reason=" + reason);
- }
+ if (DEBUG) {
+ Log.d(TAG, "cancel: " + mSession.getName() + ", begin=" + mBeginVsyncId
+ + ", end=" + mEndVsyncId + ", reason=" + reason);
+ }
- mSession.setReason(reason);
- // Notify the listener the session has been cancelled.
- // We don't notify the listeners if the session never begun.
- if (mListener != null) {
- mListener.onCujEvents(mSession, ACTION_SESSION_CANCEL);
+ mSession.setReason(reason);
+ // Notify the listener the session has been cancelled.
+ // We don't notify the listeners if the session never begun.
+ notifyCujEvent(ACTION_SESSION_CANCEL);
+ return true;
}
}
- @Override
- public synchronized void onJankDataAvailable(SurfaceControl.JankData[] jankData) {
- if (mCancelled) {
- return;
- }
+ private void notifyCujEvent(String action) {
+ if (mListener == null) return;
+ mListener.onCujEvents(mSession, action);
+ }
- for (SurfaceControl.JankData jankStat : jankData) {
- if (!isInRange(jankStat.frameVsyncId)) {
- continue;
+ @Override
+ public void onJankDataAvailable(SurfaceControl.JankData[] jankData) {
+ synchronized (mLock) {
+ if (mCancelled) {
+ return;
}
- JankInfo info = findJankInfo(jankStat.frameVsyncId);
- if (info != null) {
- info.surfaceControlCallbackFired = true;
- info.jankType = jankStat.jankType;
- } else {
- mJankInfos.put((int) jankStat.frameVsyncId,
- JankInfo.createFromSurfaceControlCallback(
- jankStat.frameVsyncId, jankStat.jankType));
+
+ for (SurfaceControl.JankData jankStat : jankData) {
+ if (!isInRange(jankStat.frameVsyncId)) {
+ continue;
+ }
+ JankInfo info = findJankInfo(jankStat.frameVsyncId);
+ if (info != null) {
+ info.surfaceControlCallbackFired = true;
+ info.jankType = jankStat.jankType;
+ } else {
+ mJankInfos.put((int) jankStat.frameVsyncId,
+ JankInfo.createFromSurfaceControlCallback(
+ jankStat.frameVsyncId, jankStat.jankType));
+ }
}
+ processJankInfos();
}
- processJankInfos();
}
private @Nullable JankInfo findJankInfo(long frameVsyncId) {
@@ -338,31 +370,34 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
}
@Override
- public synchronized void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
- if (mCancelled) {
- return;
- }
+ public void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
+ synchronized (mLock) {
+ if (mCancelled) {
+ return;
+ }
- // Since this callback might come a little bit late after the end() call.
- // We should keep tracking the begin / end timestamp.
- // Then compare with vsync timestamp to check if the frame is in the duration of the CUJ.
- long totalDurationNanos = mMetricsWrapper.getMetric(FrameMetrics.TOTAL_DURATION);
- boolean isFirstFrame = mMetricsWrapper.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1;
- long frameVsyncId = mMetricsWrapper.getTiming()[FrameMetrics.Index.FRAME_TIMELINE_VSYNC_ID];
+ // Since this callback might come a little bit late after the end() call.
+ // We should keep tracking the begin / end timestamp that we can compare with
+ // vsync timestamp to check if the frame is in the duration of the CUJ.
+ long totalDurationNanos = mMetricsWrapper.getMetric(FrameMetrics.TOTAL_DURATION);
+ boolean isFirstFrame = mMetricsWrapper.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1;
+ long frameVsyncId =
+ mMetricsWrapper.getTiming()[FrameMetrics.Index.FRAME_TIMELINE_VSYNC_ID];
- if (!isInRange(frameVsyncId)) {
- return;
- }
- JankInfo info = findJankInfo(frameVsyncId);
- if (info != null) {
- info.hwuiCallbackFired = true;
- info.totalDurationNanos = totalDurationNanos;
- info.isFirstFrame = isFirstFrame;
- } else {
- mJankInfos.put((int) frameVsyncId, JankInfo.createFromHwuiCallback(
- frameVsyncId, totalDurationNanos, isFirstFrame));
+ if (!isInRange(frameVsyncId)) {
+ return;
+ }
+ JankInfo info = findJankInfo(frameVsyncId);
+ if (info != null) {
+ info.hwuiCallbackFired = true;
+ info.totalDurationNanos = totalDurationNanos;
+ info.isFirstFrame = isFirstFrame;
+ } else {
+ mJankInfos.put((int) frameVsyncId, JankInfo.createFromHwuiCallback(
+ frameVsyncId, totalDurationNanos, isFirstFrame));
+ }
+ processJankInfos();
}
- processJankInfos();
}
/**
@@ -385,7 +420,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
for (int i = mJankInfos.size() - 1; i >= 0; i--) {
JankInfo info = mJankInfos.valueAt(i);
if (info.frameVsyncId >= mEndVsyncId) {
- if (info.hwuiCallbackFired && info.surfaceControlCallbackFired) {
+ if (isLastIndexCandidate(info)) {
lastIndex = i;
}
} else {
@@ -403,6 +438,12 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
finish(indexOnOrAfterEnd);
}
+ private boolean isLastIndexCandidate(JankInfo info) {
+ return mSurfaceOnly
+ ? info.surfaceControlCallbackFired
+ : info.hwuiCallbackFired && info.surfaceControlCallbackFired;
+ }
+
private void finish(int indexOnOrAfterEnd) {
mHandler.removeCallbacks(mWaitForFinishTimedOut);
mWaitForFinishTimedOut = null;
@@ -419,7 +460,8 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
for (int i = 0; i <= indexOnOrAfterEnd; i++) {
JankInfo info = mJankInfos.valueAt(i);
- if (info.isFirstFrame) {
+ final boolean isFirstDrawn = !mSurfaceOnly && info.isFirstFrame;
+ if (isFirstDrawn) {
continue;
}
if (info.surfaceControlCallbackFired) {
@@ -444,11 +486,11 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
}
// TODO (b/174755489): Early latch currently gets fired way too often, so we have
// to ignore it for now.
- if (!info.hwuiCallbackFired) {
+ if (!mSurfaceOnly && !info.hwuiCallbackFired) {
Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId);
}
}
- if (info.hwuiCallbackFired) {
+ if (!mSurfaceOnly && info.hwuiCallbackFired) {
maxFrameTimeNanos = Math.max(info.totalDurationNanos, maxFrameTimeNanos);
if (!info.surfaceControlCallbackFired) {
Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId);
@@ -469,11 +511,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
(int) (maxFrameTimeNanos / NANOS_IN_MILLISECOND));
// Trigger perfetto if necessary.
- boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1
- && missedFramesCount >= mTraceThresholdMissedFrames;
- boolean overFrameTimeThreshold = mTraceThresholdFrameTimeMillis != -1
- && maxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND;
- if (overMissedFramesThreshold || overFrameTimeThreshold) {
+ if (shouldTriggerPerfetto(missedFramesCount, (int) maxFrameTimeNanos)) {
triggerPerfetto();
}
if (mSession.logToStatsd()) {
@@ -482,12 +520,10 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
mSession.getStatsdInteractionType(),
totalFramesCount,
missedFramesCount,
- maxFrameTimeNanos,
+ maxFrameTimeNanos, /* will be 0 if mSurfaceOnly == true */
missedSfFramesCount,
missedAppFramesCount);
- if (mListener != null) {
- mListener.onCujEvents(mSession, ACTION_METRICS_LOGGED);
- }
+ notifyCujEvent(ACTION_METRICS_LOGGED);
}
if (DEBUG) {
Log.i(TAG, "finish: CUJ=" + mSession.getName()
@@ -500,15 +536,26 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
}
}
+ private boolean shouldTriggerPerfetto(int missedFramesCount, int maxFrameTimeNanos) {
+ boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1
+ && missedFramesCount >= mTraceThresholdMissedFrames;
+ boolean overFrameTimeThreshold = !mSurfaceOnly && mTraceThresholdFrameTimeMillis != -1
+ && maxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND;
+ return overMissedFramesThreshold || overFrameTimeThreshold;
+ }
+
/**
* Remove all the registered listeners, observers and callbacks.
*/
@VisibleForTesting
public void removeObservers() {
- mRendererWrapper.removeObserver(mObserver);
mSurfaceControlWrapper.removeJankStatsListener(this);
- if (mSurfaceChangedCallback != null) {
- mViewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
+ if (!mSurfaceOnly) {
+ // HWUI part.
+ mRendererWrapper.removeObserver(mObserver);
+ if (mSurfaceChangedCallback != null) {
+ mViewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
+ }
}
}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 610cd7339001..c608864c6caa 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -18,11 +18,11 @@ package com.android.internal.jank;
import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY;
-import static com.android.internal.jank.FrameTracker.ChoreographerWrapper;
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NOT_BEGUN;
+import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
-import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
+import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
@@ -41,6 +41,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
@@ -58,6 +59,8 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -72,11 +75,15 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
+import android.view.SurfaceControl;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.FrameTracker.ChoreographerWrapper;
import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
import com.android.internal.jank.FrameTracker.FrameTrackerListener;
+import com.android.internal.jank.FrameTracker.Reasons;
+import com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
import com.android.internal.jank.FrameTracker.ViewRootWrapper;
import com.android.internal.util.PerfettoTrigger;
@@ -163,6 +170,9 @@ public class InteractionJankMonitor {
public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32;
public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33;
public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34;
+ public static final int CUJ_PIP_TRANSITION = 35;
+ public static final int CUJ_WALLPAPER_TRANSITION = 36;
+ public static final int CUJ_USER_SWITCH = 37;
private static final int NO_STATSD_LOGGING = -1;
@@ -206,6 +216,9 @@ public class InteractionJankMonitor {
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH,
};
private static volatile InteractionJankMonitor sInstance;
@@ -213,10 +226,11 @@ public class InteractionJankMonitor {
private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
this::updateProperties;
- private FrameMetricsWrapper mMetrics;
- private SparseArray<FrameTracker> mRunningTrackers;
- private SparseArray<Runnable> mTimeoutActions;
- private HandlerThread mWorker;
+ private final FrameMetricsWrapper mMetrics;
+ private final SparseArray<FrameTracker> mRunningTrackers;
+ private final SparseArray<Runnable> mTimeoutActions;
+ private final HandlerThread mWorker;
+ private final Object mLock = new Object();
private boolean mEnabled = DEFAULT_ENABLED;
private int mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
@@ -260,6 +274,9 @@ public class InteractionJankMonitor {
CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+ CUJ_PIP_TRANSITION,
+ CUJ_WALLPAPER_TRANSITION,
+ CUJ_USER_SWITCH,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -309,25 +326,36 @@ public class InteractionJankMonitor {
mPropertiesChangedListener);
}
+ Object getLock() {
+ return mLock;
+ }
+
/**
- * Create a {@link FrameTracker} instance.
+ * Creates a {@link FrameTracker} instance.
*
+ * @param config the config used in instrumenting
* @param session the session associates with this tracker
* @return instance of the FrameTracker
*/
@VisibleForTesting
- public FrameTracker createFrameTracker(Configuration conf, Session session) {
- final View v = conf.mView;
- final Context c = v.getContext().getApplicationContext();
- final ThreadedRendererWrapper r = new ThreadedRendererWrapper(v.getThreadedRenderer());
- final ViewRootWrapper vr = new ViewRootWrapper(v.getViewRootImpl());
- final SurfaceControlWrapper sc = new SurfaceControlWrapper();
- final ChoreographerWrapper cg = new ChoreographerWrapper(Choreographer.getInstance());
-
- synchronized (this) {
- FrameTrackerListener eventsListener = (s, act) -> handleCujEvents(c, act, s);
- return new FrameTracker(session, mWorker.getThreadHandler(), r, vr, sc, cg, mMetrics,
- mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis, eventsListener);
+ public FrameTracker createFrameTracker(Configuration config, Session session) {
+ final View view = config.mView;
+ final ThreadedRendererWrapper threadedRenderer =
+ view == null ? null : new ThreadedRendererWrapper(view.getThreadedRenderer());
+ final ViewRootWrapper viewRoot =
+ view == null ? null : new ViewRootWrapper(view.getViewRootImpl());
+
+ final SurfaceControlWrapper surfaceControl = new SurfaceControlWrapper();
+ final ChoreographerWrapper choreographer =
+ new ChoreographerWrapper(Choreographer.getInstance());
+
+ synchronized (mLock) {
+ FrameTrackerListener eventsListener =
+ (s, act) -> handleCujEvents(config.getContext(), act, s);
+ return new FrameTracker(session, mWorker.getThreadHandler(),
+ threadedRenderer, viewRoot, surfaceControl, choreographer, mMetrics,
+ mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis,
+ eventsListener, config);
}
}
@@ -349,11 +377,16 @@ public class InteractionJankMonitor {
final boolean badEnd = action.equals(ACTION_SESSION_END)
&& session.getReason() != REASON_END_NORMAL;
final boolean badCancel = action.equals(ACTION_SESSION_CANCEL)
- && session.getReason() != REASON_CANCEL_NORMAL;
+ && !(session.getReason() == REASON_CANCEL_NORMAL
+ || session.getReason() == REASON_CANCEL_TIMEOUT);
return badEnd || badCancel;
}
- private void notifyEvents(Context context, String action, Session session) {
+ /**
+ * Notifies who may interest in some CUJ events.
+ */
+ @VisibleForTesting
+ public void notifyEvents(Context context, String action, Session session) {
if (action.equals(ACTION_SESSION_CANCEL)
&& session.getReason() == REASON_CANCEL_NOT_BEGUN) {
return;
@@ -366,7 +399,7 @@ public class InteractionJankMonitor {
}
private void removeTimeout(@CujType int cujType) {
- synchronized (this) {
+ synchronized (mLock) {
Runnable timeout = mTimeoutActions.get(cujType);
if (timeout != null) {
mWorker.getThreadHandler().removeCallbacks(timeout);
@@ -376,7 +409,7 @@ public class InteractionJankMonitor {
}
/**
- * Begin a trace session.
+ * Begins a trace session.
*
* @param v an attached view.
* @param cujType the specific {@link InteractionJankMonitor.CujType}.
@@ -385,8 +418,7 @@ public class InteractionJankMonitor {
public boolean begin(View v, @CujType int cujType) {
try {
return beginInternal(
- new Configuration.Builder(cujType)
- .setView(v)
+ Configuration.Builder.withView(cujType, v)
.build());
} catch (IllegalArgumentException ex) {
Log.d(TAG, "Build configuration failed!", ex);
@@ -395,7 +427,7 @@ public class InteractionJankMonitor {
}
/**
- * Begin a trace session.
+ * Begins a trace session.
*
* @param builder the builder of the configurations for instrumenting the CUJ.
* @return boolean true if the tracker is started successfully, false otherwise.
@@ -410,17 +442,9 @@ public class InteractionJankMonitor {
}
private boolean beginInternal(@NonNull Configuration conf) {
- synchronized (this) {
+ synchronized (mLock) {
int cujType = conf.mCujType;
- boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
- if (!mEnabled || !shouldSample) {
- if (DEBUG) {
- Log.d(TAG, "Skip monitoring cuj: " + getNameOfCuj(cujType)
- + ", enable=" + mEnabled + ", debuggable=" + DEFAULT_ENABLED
- + ", sample=" + shouldSample + ", interval=" + mSamplingInterval);
- }
- return false;
- }
+ if (!shouldMonitor(cujType)) return false;
FrameTracker tracker = getTracker(cujType);
// Skip subsequent calls if we already have an ongoing tracing.
if (tracker != null) return false;
@@ -431,67 +455,103 @@ public class InteractionJankMonitor {
tracker.begin();
// Cancel the trace if we don't get an end() call in specified duration.
- Runnable timeoutAction = () -> cancel(cujType);
- mTimeoutActions.put(cujType, timeoutAction);
- mWorker.getThreadHandler().postDelayed(timeoutAction, conf.mTimeout);
+ scheduleTimeoutAction(
+ cujType, conf.mTimeout, () -> cancel(cujType, REASON_CANCEL_TIMEOUT));
return true;
}
}
/**
- * End a trace session.
+ * Check if the monitoring is enabled and if it should be sampled.
+ */
+ @SuppressWarnings("RandomModInteger")
+ @VisibleForTesting
+ public boolean shouldMonitor(@CujType int cujType) {
+ boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
+ if (!mEnabled || !shouldSample) {
+ if (DEBUG) {
+ Log.d(TAG, "Skip monitoring cuj: " + getNameOfCuj(cujType)
+ + ", enable=" + mEnabled + ", debuggable=" + DEFAULT_ENABLED
+ + ", sample=" + shouldSample + ", interval=" + mSamplingInterval);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Schedules a timeout action.
+ * @param cuj cuj type
+ * @param timeout duration to timeout
+ * @param action action once timeout
+ */
+ @VisibleForTesting
+ public void scheduleTimeoutAction(@CujType int cuj, long timeout, Runnable action) {
+ mTimeoutActions.put(cuj, action);
+ mWorker.getThreadHandler().postDelayed(action, timeout);
+ }
+
+ /**
+ * Ends a trace session.
*
* @param cujType the specific {@link InteractionJankMonitor.CujType}.
* @return boolean true if the tracker is ended successfully, false otherwise.
*/
public boolean end(@CujType int cujType) {
- //TODO (163505250): This should be no-op if not in droid food rom.
- synchronized (this) {
-
+ synchronized (mLock) {
// remove the timeout action first.
removeTimeout(cujType);
FrameTracker tracker = getTracker(cujType);
// Skip this call since we haven't started a trace yet.
if (tracker == null) return false;
- tracker.end(FrameTracker.REASON_END_NORMAL);
- removeTracker(cujType);
+ // if the end call doesn't return true, another thread is handling end of the cuj.
+ if (tracker.end(REASON_END_NORMAL)) {
+ removeTracker(cujType);
+ }
return true;
}
}
/**
- * Cancel the trace session.
+ * Cancels the trace session.
*
* @return boolean true if the tracker is cancelled successfully, false otherwise.
*/
public boolean cancel(@CujType int cujType) {
- //TODO (163505250): This should be no-op if not in droid food rom.
- synchronized (this) {
+ return cancel(cujType, REASON_CANCEL_NORMAL);
+ }
+
+ /**
+ * Cancels the trace session.
+ *
+ * @return boolean true if the tracker is cancelled successfully, false otherwise.
+ */
+ @VisibleForTesting
+ public boolean cancel(@CujType int cujType, @Reasons int reason) {
+ synchronized (mLock) {
// remove the timeout action first.
removeTimeout(cujType);
FrameTracker tracker = getTracker(cujType);
// Skip this call since we haven't started a trace yet.
if (tracker == null) return false;
- tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
- removeTracker(cujType);
+ // if the cancel call doesn't return true, another thread is handling cancel of the cuj.
+ if (tracker.cancel(reason)) {
+ removeTracker(cujType);
+ }
return true;
}
}
private FrameTracker getTracker(@CujType int cuj) {
- synchronized (this) {
- return mRunningTrackers.get(cuj);
- }
+ return mRunningTrackers.get(cuj);
}
private void removeTracker(@CujType int cuj) {
- synchronized (this) {
- mRunningTrackers.remove(cuj);
- }
+ mRunningTrackers.remove(cuj);
}
private void updateProperties(DeviceConfig.Properties properties) {
- synchronized (this) {
+ synchronized (mLock) {
mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
DEFAULT_SAMPLING_INTERVAL);
mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
@@ -509,14 +569,12 @@ public class InteractionJankMonitor {
}
/**
- * Trigger the perfetto daemon to collect and upload data.
+ * Triggers the perfetto daemon to collect and upload data.
*/
@VisibleForTesting
public void trigger(Session session) {
- synchronized (this) {
- mWorker.getThreadHandler().post(
- () -> PerfettoTrigger.trigger(session.getPerfettoTrigger()));
- }
+ mWorker.getThreadHandler().post(
+ () -> PerfettoTrigger.trigger(session.getPerfettoTrigger()));
}
/**
@@ -608,6 +666,12 @@ public class InteractionJankMonitor {
return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON";
case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP:
return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP";
+ case CUJ_PIP_TRANSITION:
+ return "PIP_TRANSITION";
+ case CUJ_WALLPAPER_TRANSITION:
+ return "WALLPAPER_TRANSITION";
+ case CUJ_USER_SWITCH:
+ return "USER_SWITCH";
}
return "UNKNOWN";
}
@@ -618,32 +682,64 @@ public class InteractionJankMonitor {
*/
public static class Configuration {
private final View mView;
+ private final Context mContext;
private final long mTimeout;
private final String mTag;
+ private final boolean mSurfaceOnly;
+ private final SurfaceControl mSurfaceControl;
private final @CujType int mCujType;
/**
- * A builder for building Configuration. <br/>
+ * A builder for building Configuration. {@link #setView(View)} is essential
+ * if {@link #setSurfaceOnly(boolean)} is not set, otherwise both
+ * {@link #setSurfaceControl(SurfaceControl)} and {@link #setContext(Context)}
+ * are necessary<br/>
* <b>It may refer to an attached view, don't use static reference for any purpose.</b>
*/
public static class Builder {
private View mAttrView = null;
+ private Context mAttrContext = null;
private long mAttrTimeout = DEFAULT_TIMEOUT_MS;
private String mAttrTag = "";
+ private boolean mAttrSurfaceOnly;
+ private SurfaceControl mAttrSurfaceControl;
private @CujType int mAttrCujType;
/**
+ * Creates a builder which instruments only surface.
+ * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}.
+ * @param context context
+ * @param surfaceControl surface control
+ * @return builder
+ */
+ public static Builder withSurface(@CujType int cuj, @NonNull Context context,
+ @NonNull SurfaceControl surfaceControl) {
+ return new Builder(cuj)
+ .setContext(context)
+ .setSurfaceControl(surfaceControl)
+ .setSurfaceOnly(true);
+ }
+
+ /**
+ * Creates a builder which instruments both surface and view.
* @param cuj The enum defined in {@link InteractionJankMonitor.CujType}.
+ * @param view view
+ * @return builder
*/
- public Builder(@CujType int cuj) {
+ public static Builder withView(@CujType int cuj, @NonNull View view) {
+ return new Builder(cuj).setView(view);
+ }
+
+ private Builder(@CujType int cuj) {
mAttrCujType = cuj;
}
/**
+ * Specifies a view, must be set if {@link #setSurfaceOnly(boolean)} is set to false.
* @param view an attached view
* @return builder
*/
- public Builder setView(@NonNull View view) {
+ private Builder setView(@NonNull View view) {
mAttrView = view;
return this;
}
@@ -669,20 +765,56 @@ public class InteractionJankMonitor {
}
/**
- * Build the {@link Configuration} instance
+ * Indicates if only instrument with surface,
+ * if true, must also setup with {@link #setContext(Context)}
+ * and {@link #setSurfaceControl(SurfaceControl)}.
+ * @param surfaceOnly true if only instrument with surface, false otherwise
+ * @return builder Surface only builder.
+ */
+ private Builder setSurfaceOnly(boolean surfaceOnly) {
+ mAttrSurfaceOnly = surfaceOnly;
+ return this;
+ }
+
+ /**
+ * Specifies a context, must set if {@link #setSurfaceOnly(boolean)} is set.
+ */
+ private Builder setContext(Context context) {
+ mAttrContext = context;
+ return this;
+ }
+
+ /**
+ * Specifies a surface control, must be set if {@link #setSurfaceOnly(boolean)} is set.
+ */
+ private Builder setSurfaceControl(SurfaceControl surfaceControl) {
+ mAttrSurfaceControl = surfaceControl;
+ return this;
+ }
+
+ /**
+ * Builds the {@link Configuration} instance
* @return the instance of {@link Configuration}
* @throws IllegalArgumentException if any invalid attribute is set
*/
public Configuration build() throws IllegalArgumentException {
- return new Configuration(mAttrCujType, mAttrView, mAttrTag, mAttrTimeout);
+ return new Configuration(
+ mAttrCujType, mAttrView, mAttrTag, mAttrTimeout,
+ mAttrSurfaceOnly, mAttrContext, mAttrSurfaceControl);
}
}
- private Configuration(@CujType int cuj, View view, String tag, long timeout) {
+ private Configuration(@CujType int cuj, View view, String tag, long timeout,
+ boolean surfaceOnly, Context context, SurfaceControl surfaceControl) {
mCujType = cuj;
mTag = tag;
mTimeout = timeout;
mView = view;
+ mSurfaceOnly = surfaceOnly;
+ mContext = context != null
+ ? context
+ : (view != null ? view.getContext().getApplicationContext() : null);
+ mSurfaceControl = surfaceControl;
validate();
}
@@ -698,14 +830,47 @@ public class InteractionJankMonitor {
shouldThrow = true;
msg.append("Invalid timeout value; ");
}
- if (mView == null || !mView.isAttachedToWindow()) {
- shouldThrow = true;
- msg.append("Null view or view is not attached yet; ");
+ if (mSurfaceOnly) {
+ if (mContext == null) {
+ shouldThrow = true;
+ msg.append("Must pass in a context if only instrument surface; ");
+ }
+ if (mSurfaceControl == null || !mSurfaceControl.isValid()) {
+ shouldThrow = true;
+ msg.append("Must pass in a valid surface control if only instrument surface; ");
+ }
+ } else {
+ if (mView == null || !mView.isAttachedToWindow()) {
+ shouldThrow = true;
+ msg.append("Null view or unattached view while instrumenting view; ");
+ }
}
if (shouldThrow) {
throw new IllegalArgumentException(msg.toString());
}
}
+
+ /**
+ * @return true if only instrumenting surface, false otherwise
+ */
+ public boolean isSurfaceOnly() {
+ return mSurfaceOnly;
+ }
+
+ /**
+ * @return the surafce control which is instrumenting
+ */
+ public SurfaceControl getSurfaceControl() {
+ return mSurfaceControl;
+ }
+
+ View getView() {
+ return mView;
+ }
+
+ Context getContext() {
+ return mContext;
+ }
}
/**
@@ -715,8 +880,8 @@ public class InteractionJankMonitor {
@CujType
private final int mCujType;
private final long mTimeStamp;
- @FrameTracker.Reasons
- private int mReason = FrameTracker.REASON_END_UNKNOWN;
+ @Reasons
+ private int mReason = REASON_END_UNKNOWN;
private final boolean mShouldNotify;
private final String mName;
@@ -756,15 +921,15 @@ public class InteractionJankMonitor {
return mTimeStamp;
}
- public void setReason(@FrameTracker.Reasons int reason) {
+ public void setReason(@Reasons int reason) {
mReason = reason;
}
- public int getReason() {
+ public @Reasons int getReason() {
return mReason;
}
- /** Determine if should notify the receivers of cuj events */
+ /** Determines if should notify the receivers of cuj events */
public boolean shouldNotify() {
return mShouldNotify;
}
diff --git a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
index 4ce6f609ef73..3eb980465214 100644
--- a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
+++ b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java
@@ -17,19 +17,27 @@
package com.android.internal.notification;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import com.android.internal.R;
+
+/**
+ * This class provides methods to create intents for NotificationAccessConfirmationActivity.
+ */
public final class NotificationAccessConfirmationActivityContract {
- private static final ComponentName COMPONENT_NAME = new ComponentName(
- "com.android.settings",
- "com.android.settings.notification.NotificationAccessConfirmationActivity");
public static final String EXTRA_USER_ID = "user_id";
public static final String EXTRA_COMPONENT_NAME = "component_name";
public static final String EXTRA_PACKAGE_TITLE = "package_title";
- public static Intent launcherIntent(int userId, ComponentName component, String packageTitle) {
+ /**
+ * Creates a launcher intent for NotificationAccessConfirmationActivity.
+ */
+ public static Intent launcherIntent(Context context, int userId, ComponentName component,
+ String packageTitle) {
return new Intent()
- .setComponent(COMPONENT_NAME)
+ .setComponent(ComponentName.unflattenFromString(context.getString(
+ R.string.config_notificationAccessConfirmationActivity)))
.putExtra(EXTRA_USER_ID, userId)
.putExtra(EXTRA_COMPONENT_NAME, component)
.putExtra(EXTRA_PACKAGE_TITLE, packageTitle);
diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index 0307268a28b5..94430704468f 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -16,6 +16,8 @@
package com.android.internal.os;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT;
+
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
@@ -29,11 +31,15 @@ import java.util.List;
* Estimates power consumed by the ambient display
*/
public class AmbientDisplayPowerCalculator extends PowerCalculator {
- private final UsageBasedPowerEstimator mPowerEstimator;
+ private final UsageBasedPowerEstimator[] mPowerEstimators;
public AmbientDisplayPowerCalculator(PowerProfile powerProfile) {
- mPowerEstimator = new UsageBasedPowerEstimator(
- powerProfile.getAveragePower(PowerProfile.POWER_AMBIENT_DISPLAY));
+ final int numDisplays = powerProfile.getNumDisplays();
+ mPowerEstimators = new UsageBasedPowerEstimator[numDisplays];
+ for (int display = 0; display < numDisplays; display++) {
+ mPowerEstimators[display] = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, display));
+ }
}
/**
@@ -47,8 +53,8 @@ public class AmbientDisplayPowerCalculator extends PowerCalculator {
final int powerModel = getPowerModel(measuredEnergyUC, query);
final long durationMs = calculateDuration(batteryStats, rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED);
- final double powerMah = getMeasuredOrEstimatedPower(powerModel,
- measuredEnergyUC, mPowerEstimator, durationMs);
+ final double powerMah = calculateTotalPower(powerModel, batteryStats, rawRealtimeUs,
+ measuredEnergyUC);
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY, durationMs)
@@ -68,9 +74,8 @@ public class AmbientDisplayPowerCalculator extends PowerCalculator {
final long measuredEnergyUC = batteryStats.getScreenDozeMeasuredBatteryConsumptionUC();
final long durationMs = calculateDuration(batteryStats, rawRealtimeUs, statsType);
final int powerModel = getPowerModel(measuredEnergyUC);
- final double powerMah = getMeasuredOrEstimatedPower(powerModel,
- batteryStats.getScreenDozeMeasuredBatteryConsumptionUC(),
- mPowerEstimator, durationMs);
+ final double powerMah = calculateTotalPower(powerModel, batteryStats, rawRealtimeUs,
+ measuredEnergyUC);
if (powerMah > 0) {
BatterySipper bs = new BatterySipper(BatterySipper.DrainType.AMBIENT_DISPLAY, null, 0);
bs.usagePowerMah = powerMah;
@@ -83,4 +88,26 @@ public class AmbientDisplayPowerCalculator extends PowerCalculator {
private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
return batteryStats.getScreenDozeTime(rawRealtimeUs, statsType) / 1000;
}
+
+ private double calculateTotalPower(@BatteryConsumer.PowerModel int powerModel,
+ BatteryStats batteryStats, long rawRealtimeUs, long consumptionUC) {
+ switch (powerModel) {
+ case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
+ return uCtoMah(consumptionUC);
+ case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
+ default:
+ return calculateEstimatedPower(batteryStats, rawRealtimeUs);
+ }
+ }
+
+ private double calculateEstimatedPower(BatteryStats batteryStats, long rawRealtimeUs) {
+ final int numDisplays = mPowerEstimators.length;
+ double power = 0;
+ for (int display = 0; display < numDisplays; display++) {
+ final long dozeTime = batteryStats.getDisplayScreenDozeTime(display, rawRealtimeUs)
+ / 1000;
+ power += mPowerEstimators[display].calculatePower(dozeTime);
+ }
+ return power;
+ }
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index a817119a735f..169eff009bff 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -690,7 +690,7 @@ public class BatteryStatsImpl extends BatteryStats {
* Schedule a sync because of a screen state change.
*/
Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery,
- boolean onBatteryScreenOff, int screenState);
+ boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates);
Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis);
void cancelCpuSyncDueToWakelockChange();
Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis);
@@ -851,17 +851,91 @@ public class BatteryStatsImpl extends BatteryStats {
public boolean mRecordAllHistory;
boolean mNoAutoReset;
+ /**
+ * Overall screen state. For multidisplay devices, this represents the current highest screen
+ * state of the displays.
+ */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
protected int mScreenState = Display.STATE_UNKNOWN;
+ /**
+ * Overall screen on timer. For multidisplay devices, this represents the time spent with at
+ * least one display in the screen on state.
+ */
StopwatchTimer mScreenOnTimer;
+ /**
+ * Overall screen doze timer. For multidisplay devices, this represents the time spent with
+ * screen doze being the highest screen state.
+ */
StopwatchTimer mScreenDozeTimer;
-
+ /**
+ * Overall screen brightness bin. For multidisplay devices, this represents the current
+ * brightest screen.
+ */
int mScreenBrightnessBin = -1;
+ /**
+ * Overall screen brightness timers. For multidisplay devices, the {@link mScreenBrightnessBin}
+ * timer will be active at any given time
+ */
final StopwatchTimer[] mScreenBrightnessTimer =
new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
boolean mPretendScreenOff;
+ private static class DisplayBatteryStats {
+ /**
+ * Per display screen state.
+ */
+ public int screenState = Display.STATE_UNKNOWN;
+ /**
+ * Per display screen on timers.
+ */
+ public StopwatchTimer screenOnTimer;
+ /**
+ * Per display screen doze timers.
+ */
+ public StopwatchTimer screenDozeTimer;
+ /**
+ * Per display screen brightness bins.
+ */
+ public int screenBrightnessBin = -1;
+ /**
+ * Per display screen brightness timers.
+ */
+ public StopwatchTimer[] screenBrightnessTimers =
+ new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
+ /**
+ * Per display screen state the last time {@link #updateDisplayMeasuredEnergyStatsLocked}
+ * was called.
+ */
+ public int screenStateAtLastEnergyMeasurement = Display.STATE_UNKNOWN;
+
+ DisplayBatteryStats(Clocks clocks, TimeBase timeBase) {
+ screenOnTimer = new StopwatchTimer(clocks, null, -1, null,
+ timeBase);
+ screenDozeTimer = new StopwatchTimer(clocks, null, -1, null,
+ timeBase);
+ for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+ screenBrightnessTimers[i] = new StopwatchTimer(clocks, null, -100 - i, null,
+ timeBase);
+ }
+ }
+
+ /**
+ * Reset display timers.
+ */
+ public void reset(long elapsedRealtimeUs) {
+ screenOnTimer.reset(false, elapsedRealtimeUs);
+ screenDozeTimer.reset(false, elapsedRealtimeUs);
+ for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+ screenBrightnessTimers[i].reset(false, elapsedRealtimeUs);
+ }
+ }
+ }
+
+ DisplayBatteryStats[] mPerDisplayBatteryStats;
+
+ private int mDisplayMismatchWtfCount = 0;
+
boolean mInteractive;
StopwatchTimer mInteractiveTimer;
@@ -1006,8 +1080,6 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
@VisibleForTesting
protected @Nullable MeasuredEnergyStats mGlobalMeasuredEnergyStats;
- /** Last known screen state. Needed for apportioning display energy. */
- int mScreenStateAtLastEnergyMeasurement = Display.STATE_UNKNOWN;
/** Bluetooth Power calculator for attributing measured bluetooth charge consumption to uids */
@Nullable BluetoothPowerCalculator mBluetoothPowerCalculator = null;
/** Cpu Power calculator for attributing measured cpu charge consumption to uids */
@@ -4308,8 +4380,10 @@ public class BatteryStatsImpl extends BatteryStats {
public void setPretendScreenOff(boolean pretendScreenOff) {
if (mPretendScreenOff != pretendScreenOff) {
mPretendScreenOff = pretendScreenOff;
- noteScreenStateLocked(pretendScreenOff ? Display.STATE_OFF : Display.STATE_ON,
- mClocks.elapsedRealtime(), mClocks.uptimeMillis(), mClocks.currentTimeMillis());
+ final int primaryScreenState = mPerDisplayBatteryStats[0].screenState;
+ noteScreenStateLocked(0, primaryScreenState,
+ mClocks.elapsedRealtime(), mClocks.uptimeMillis(),
+ mClocks.currentTimeMillis());
}
}
@@ -4907,29 +4981,158 @@ public class BatteryStatsImpl extends BatteryStats {
}
@GuardedBy("this")
- public void noteScreenStateLocked(int state) {
- noteScreenStateLocked(state, mClocks.elapsedRealtime(), mClocks.uptimeMillis(),
+ public void noteScreenStateLocked(int display, int state) {
+ noteScreenStateLocked(display, state, mClocks.elapsedRealtime(), mClocks.uptimeMillis(),
mClocks.currentTimeMillis());
}
@GuardedBy("this")
- public void noteScreenStateLocked(int state,
+ public void noteScreenStateLocked(int display, int displayState,
long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) {
- state = mPretendScreenOff ? Display.STATE_OFF : state;
-
// Battery stats relies on there being 4 states. To accommodate this, new states beyond the
// original 4 are mapped to one of the originals.
- if (state > MAX_TRACKED_SCREEN_STATE) {
- switch (state) {
- case Display.STATE_VR:
- state = Display.STATE_ON;
+ if (displayState > MAX_TRACKED_SCREEN_STATE) {
+ if (Display.isOnState(displayState)) {
+ displayState = Display.STATE_ON;
+ } else if (Display.isDozeState(displayState)) {
+ if (Display.isSuspendedState(displayState)) {
+ displayState = Display.STATE_DOZE_SUSPEND;
+ } else {
+ displayState = Display.STATE_DOZE;
+ }
+ } else if (Display.isOffState(displayState)) {
+ displayState = Display.STATE_OFF;
+ } else {
+ Slog.wtf(TAG, "Unknown screen state (not mapped): " + displayState);
+ displayState = Display.STATE_UNKNOWN;
+ }
+ }
+ // As of this point, displayState should be mapped to one of:
+ // - Display.STATE_ON,
+ // - Display.STATE_DOZE
+ // - Display.STATE_DOZE_SUSPEND
+ // - Display.STATE_OFF
+ // - Display.STATE_UNKNOWN
+
+ int state;
+ int overallBin = mScreenBrightnessBin;
+ int externalUpdateFlag = 0;
+ boolean shouldScheduleSync = false;
+ final int numDisplay = mPerDisplayBatteryStats.length;
+ if (display < 0 || display >= numDisplay) {
+ Slog.wtf(TAG, "Unexpected note screen state for display " + display + " (only "
+ + mPerDisplayBatteryStats.length + " displays exist...)");
+ return;
+ }
+ final DisplayBatteryStats displayStats = mPerDisplayBatteryStats[display];
+ final int oldDisplayState = displayStats.screenState;
+
+ if (oldDisplayState == displayState) {
+ // Nothing changed
+ state = mScreenState;
+ } else {
+ displayStats.screenState = displayState;
+
+ // Stop timer for previous display state.
+ switch (oldDisplayState) {
+ case Display.STATE_ON:
+ displayStats.screenOnTimer.stopRunningLocked(elapsedRealtimeMs);
+ final int bin = displayStats.screenBrightnessBin;
+ if (bin >= 0) {
+ displayStats.screenBrightnessTimers[bin].stopRunningLocked(
+ elapsedRealtimeMs);
+ }
+ overallBin = evaluateOverallScreenBrightnessBinLocked();
+ shouldScheduleSync = true;
+ break;
+ case Display.STATE_DOZE:
+ // Transition from doze to doze suspend can be ignored.
+ if (displayState == Display.STATE_DOZE_SUSPEND) break;
+ displayStats.screenDozeTimer.stopRunningLocked(elapsedRealtimeMs);
+ shouldScheduleSync = true;
+ break;
+ case Display.STATE_DOZE_SUSPEND:
+ // Transition from doze suspend to doze can be ignored.
+ if (displayState == Display.STATE_DOZE) break;
+ displayStats.screenDozeTimer.stopRunningLocked(elapsedRealtimeMs);
+ shouldScheduleSync = true;
+ break;
+ case Display.STATE_OFF: // fallthrough
+ case Display.STATE_UNKNOWN:
+ // Not tracked by timers.
break;
default:
- Slog.wtf(TAG, "Unknown screen state (not mapped): " + state);
+ Slog.wtf(TAG,
+ "Attempted to stop timer for unexpected display state " + display);
+ }
+
+ // Start timer for new display state.
+ switch (displayState) {
+ case Display.STATE_ON:
+ displayStats.screenOnTimer.startRunningLocked(elapsedRealtimeMs);
+ final int bin = displayStats.screenBrightnessBin;
+ if (bin >= 0) {
+ displayStats.screenBrightnessTimers[bin].startRunningLocked(
+ elapsedRealtimeMs);
+ }
+ overallBin = evaluateOverallScreenBrightnessBinLocked();
+ shouldScheduleSync = true;
+ break;
+ case Display.STATE_DOZE:
+ // Transition from doze suspend to doze can be ignored.
+ if (oldDisplayState == Display.STATE_DOZE_SUSPEND) break;
+ displayStats.screenDozeTimer.startRunningLocked(elapsedRealtimeMs);
+ shouldScheduleSync = true;
break;
+ case Display.STATE_DOZE_SUSPEND:
+ // Transition from doze to doze suspend can be ignored.
+ if (oldDisplayState == Display.STATE_DOZE) break;
+ displayStats.screenDozeTimer.startRunningLocked(elapsedRealtimeMs);
+ shouldScheduleSync = true;
+ break;
+ case Display.STATE_OFF: // fallthrough
+ case Display.STATE_UNKNOWN:
+ // Not tracked by timers.
+ break;
+ default:
+ Slog.wtf(TAG,
+ "Attempted to start timer for unexpected display state " + displayState
+ + " for display " + display);
+ }
+
+ if (shouldScheduleSync
+ && mGlobalMeasuredEnergyStats != null
+ && mGlobalMeasuredEnergyStats.isStandardBucketSupported(
+ MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON)) {
+ // Display measured energy stats is available. Prepare to schedule an
+ // external sync.
+ externalUpdateFlag |= ExternalStatsSync.UPDATE_DISPLAY;
+ }
+
+ // Reevaluate most important display screen state.
+ state = Display.STATE_UNKNOWN;
+ for (int i = 0; i < numDisplay; i++) {
+ final int tempState = mPerDisplayBatteryStats[i].screenState;
+ if (tempState == Display.STATE_ON
+ || state == Display.STATE_ON) {
+ state = Display.STATE_ON;
+ } else if (tempState == Display.STATE_DOZE
+ || state == Display.STATE_DOZE) {
+ state = Display.STATE_DOZE;
+ } else if (tempState == Display.STATE_DOZE_SUSPEND
+ || state == Display.STATE_DOZE_SUSPEND) {
+ state = Display.STATE_DOZE_SUSPEND;
+ } else if (tempState == Display.STATE_OFF
+ || state == Display.STATE_OFF) {
+ state = Display.STATE_OFF;
+ }
}
}
+ final boolean batteryRunning = mOnBatteryTimeBase.isRunning();
+ final boolean batteryScreenOffRunning = mOnBatteryScreenOffTimeBase.isRunning();
+
+ state = mPretendScreenOff ? Display.STATE_OFF : state;
if (mScreenState != state) {
recordDailyStatsIfNeededLocked(true, currentTimeMs);
final int oldState = mScreenState;
@@ -4983,11 +5186,11 @@ public class BatteryStatsImpl extends BatteryStats {
+ Display.stateToString(state));
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
}
- // TODO: (Probably overkill) Have mGlobalMeasuredEnergyStats store supported flags and
- // only update DISPLAY if it is. Currently overkill since CPU is scheduled anyway.
- final int updateFlag = ExternalStatsSync.UPDATE_CPU | ExternalStatsSync.UPDATE_DISPLAY;
- mExternalSync.scheduleSyncDueToScreenStateChange(updateFlag,
- mOnBatteryTimeBase.isRunning(), mOnBatteryScreenOffTimeBase.isRunning(), state);
+
+ // Per screen state Cpu stats needed. Prepare to schedule an external sync.
+ externalUpdateFlag |= ExternalStatsSync.UPDATE_CPU;
+ shouldScheduleSync = true;
+
if (Display.isOnState(state)) {
updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state,
uptimeMs * 1000, elapsedRealtimeMs * 1000);
@@ -5005,33 +5208,116 @@ public class BatteryStatsImpl extends BatteryStats {
updateDischargeScreenLevelsLocked(oldState, state);
}
}
+
+ // Changing display states might have changed the screen used to determine the overall
+ // brightness.
+ maybeUpdateOverallScreenBrightness(overallBin, elapsedRealtimeMs, uptimeMs);
+
+ if (shouldScheduleSync) {
+ final int numDisplays = mPerDisplayBatteryStats.length;
+ final int[] displayStates = new int[numDisplays];
+ for (int i = 0; i < numDisplays; i++) {
+ displayStates[i] = mPerDisplayBatteryStats[i].screenState;
+ }
+ mExternalSync.scheduleSyncDueToScreenStateChange(externalUpdateFlag,
+ batteryRunning, batteryScreenOffRunning, state, displayStates);
+ }
}
@UnsupportedAppUsage
public void noteScreenBrightnessLocked(int brightness) {
- noteScreenBrightnessLocked(brightness, mClocks.elapsedRealtime(), mClocks.uptimeMillis());
+ noteScreenBrightnessLocked(0, brightness);
+ }
+
+ /**
+ * Note screen brightness change for a display.
+ */
+ public void noteScreenBrightnessLocked(int display, int brightness) {
+ noteScreenBrightnessLocked(display, brightness, mClocks.elapsedRealtime(),
+ mClocks.uptimeMillis());
}
- public void noteScreenBrightnessLocked(int brightness, long elapsedRealtimeMs, long uptimeMs) {
+
+ /**
+ * Note screen brightness change for a display.
+ */
+ public void noteScreenBrightnessLocked(int display, int brightness, long elapsedRealtimeMs,
+ long uptimeMs) {
// Bin the brightness.
int bin = brightness / (256/NUM_SCREEN_BRIGHTNESS_BINS);
if (bin < 0) bin = 0;
else if (bin >= NUM_SCREEN_BRIGHTNESS_BINS) bin = NUM_SCREEN_BRIGHTNESS_BINS-1;
- if (mScreenBrightnessBin != bin) {
- mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_BRIGHTNESS_MASK)
- | (bin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Screen brightness " + bin + " to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+
+ final int overallBin;
+
+ final int numDisplays = mPerDisplayBatteryStats.length;
+ if (display < 0 || display >= numDisplays) {
+ Slog.wtf(TAG, "Unexpected note screen brightness for display " + display + " (only "
+ + mPerDisplayBatteryStats.length + " displays exist...)");
+ return;
+ }
+
+ final DisplayBatteryStats displayStats = mPerDisplayBatteryStats[display];
+ final int oldBin = displayStats.screenBrightnessBin;
+ if (oldBin == bin) {
+ // Nothing changed
+ overallBin = mScreenBrightnessBin;
+ } else {
+ displayStats.screenBrightnessBin = bin;
+ if (displayStats.screenState == Display.STATE_ON) {
+ if (oldBin >= 0) {
+ displayStats.screenBrightnessTimers[oldBin].stopRunningLocked(
+ elapsedRealtimeMs);
+ }
+ displayStats.screenBrightnessTimers[bin].startRunningLocked(
+ elapsedRealtimeMs);
+ }
+ overallBin = evaluateOverallScreenBrightnessBinLocked();
+ }
+
+ maybeUpdateOverallScreenBrightness(overallBin, elapsedRealtimeMs, uptimeMs);
+ }
+
+ private int evaluateOverallScreenBrightnessBinLocked() {
+ int overallBin = -1;
+ final int numDisplays = getDisplayCount();
+ for (int display = 0; display < numDisplays; display++) {
+ final int displayBrightnessBin;
+ if (mPerDisplayBatteryStats[display].screenState == Display.STATE_ON) {
+ displayBrightnessBin = mPerDisplayBatteryStats[display].screenBrightnessBin;
+ } else {
+ displayBrightnessBin = -1;
+ }
+ if (displayBrightnessBin > overallBin) {
+ overallBin = displayBrightnessBin;
+ }
+ }
+ return overallBin;
+ }
+
+ private void maybeUpdateOverallScreenBrightness(int overallBin, long elapsedRealtimeMs,
+ long uptimeMs) {
+ if (mScreenBrightnessBin != overallBin) {
+ if (overallBin >= 0) {
+ mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_BRIGHTNESS_MASK)
+ | (overallBin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
+ if (DEBUG_HISTORY) {
+ Slog.v(TAG, "Screen brightness " + overallBin + " to: "
+ + Integer.toHexString(mHistoryCur.states));
+ }
+ addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ }
if (mScreenState == Display.STATE_ON) {
if (mScreenBrightnessBin >= 0) {
mScreenBrightnessTimer[mScreenBrightnessBin]
.stopRunningLocked(elapsedRealtimeMs);
}
- mScreenBrightnessTimer[bin]
- .startRunningLocked(elapsedRealtimeMs);
+ if (overallBin >= 0) {
+ mScreenBrightnessTimer[overallBin]
+ .startRunningLocked(elapsedRealtimeMs);
+ }
}
- mScreenBrightnessBin = bin;
+ mScreenBrightnessBin = overallBin;
}
}
@@ -6693,6 +6979,31 @@ public class BatteryStatsImpl extends BatteryStats {
return mScreenBrightnessTimer[brightnessBin];
}
+ @Override
+ public int getDisplayCount() {
+ return mPerDisplayBatteryStats.length;
+ }
+
+ @Override
+ public long getDisplayScreenOnTime(int display, long elapsedRealtimeUs) {
+ return mPerDisplayBatteryStats[display].screenOnTimer.getTotalTimeLocked(elapsedRealtimeUs,
+ STATS_SINCE_CHARGED);
+ }
+
+ @Override
+ public long getDisplayScreenDozeTime(int display, long elapsedRealtimeUs) {
+ return mPerDisplayBatteryStats[display].screenDozeTimer.getTotalTimeLocked(
+ elapsedRealtimeUs, STATS_SINCE_CHARGED);
+ }
+
+ @Override
+ public long getDisplayScreenBrightnessTime(int display, int brightnessBin,
+ long elapsedRealtimeUs) {
+ final DisplayBatteryStats displayStats = mPerDisplayBatteryStats[display];
+ return displayStats.screenBrightnessTimers[brightnessBin].getTotalTimeLocked(
+ elapsedRealtimeUs, STATS_SINCE_CHARGED);
+ }
+
@Override public long getInteractiveTime(long elapsedRealtimeUs, int which) {
return mInteractiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@@ -10694,6 +11005,10 @@ public class BatteryStatsImpl extends BatteryStats {
mScreenBrightnessTimer[i] = new StopwatchTimer(mClocks, null, -100-i, null,
mOnBatteryTimeBase);
}
+
+ mPerDisplayBatteryStats = new DisplayBatteryStats[1];
+ mPerDisplayBatteryStats[0] = new DisplayBatteryStats(mClocks, mOnBatteryTimeBase);
+
mInteractiveTimer = new StopwatchTimer(mClocks, null, -10, null, mOnBatteryTimeBase);
mPowerSaveModeEnabledTimer = new StopwatchTimer(mClocks, null, -2, null,
mOnBatteryTimeBase);
@@ -10806,6 +11121,8 @@ public class BatteryStatsImpl extends BatteryStats {
// Initialize the estimated battery capacity to a known preset one.
mEstimatedBatteryCapacityMah = (int) mPowerProfile.getBatteryCapacity();
}
+
+ setDisplayCountLocked(mPowerProfile.getNumDisplays());
}
PowerProfile getPowerProfile() {
@@ -10838,6 +11155,16 @@ public class BatteryStatsImpl extends BatteryStats {
mExternalSync = sync;
}
+ /**
+ * Initialize and set multi display timers and states.
+ */
+ public void setDisplayCountLocked(int numDisplays) {
+ mPerDisplayBatteryStats = new DisplayBatteryStats[numDisplays];
+ for (int i = 0; i < numDisplays; i++) {
+ mPerDisplayBatteryStats[i] = new DisplayBatteryStats(mClocks, mOnBatteryTimeBase);
+ }
+ }
+
public void updateDailyDeadlineLocked() {
// Get the current time.
long currentTimeMs = mDailyStartTimeMs = mClocks.currentTimeMillis();
@@ -11314,6 +11641,11 @@ public class BatteryStatsImpl extends BatteryStats {
mScreenBrightnessTimer[i].reset(false, elapsedRealtimeUs);
}
+ final int numDisplays = mPerDisplayBatteryStats.length;
+ for (int i = 0; i < numDisplays; i++) {
+ mPerDisplayBatteryStats[i].reset(elapsedRealtimeUs);
+ }
+
if (mPowerProfile != null) {
mEstimatedBatteryCapacityMah = (int) mPowerProfile.getBatteryCapacity();
} else {
@@ -12597,22 +12929,43 @@ public class BatteryStatsImpl extends BatteryStats {
* is always 0 when the screen is not "ON" and whenever the rail energy is 0 (if supported).
* To the extent that those assumptions are violated, the algorithm will err.
*
- * @param chargeUC amount of charge (microcoulombs) used by Display since this was last called.
- * @param screenState screen state at the time this data collection was scheduled
+ * @param chargesUC amount of charge (microcoulombs) used by each Display since this was last
+ * called.
+ * @param screenStates each screen state at the time this data collection was scheduled
*/
@GuardedBy("this")
- public void updateDisplayMeasuredEnergyStatsLocked(long chargeUC, int screenState,
+ public void updateDisplayMeasuredEnergyStatsLocked(long[] chargesUC, int[] screenStates,
long elapsedRealtimeMs) {
- if (DEBUG_ENERGY) Slog.d(TAG, "Updating display stats: " + chargeUC);
+ if (DEBUG_ENERGY) Slog.d(TAG, "Updating display stats: " + Arrays.toString(chargesUC));
if (mGlobalMeasuredEnergyStats == null) {
return;
}
- final @StandardPowerBucket int powerBucket =
- MeasuredEnergyStats.getDisplayPowerBucket(mScreenStateAtLastEnergyMeasurement);
- mScreenStateAtLastEnergyMeasurement = screenState;
+ final int numDisplays;
+ if (mPerDisplayBatteryStats.length == screenStates.length) {
+ numDisplays = screenStates.length;
+ } else {
+ // if this point is reached, it will be reached every display state change.
+ // Rate limit the wtf logging to once every 100 display updates.
+ if (mDisplayMismatchWtfCount++ % 100 == 0) {
+ Slog.wtf(TAG, "Mismatch between PowerProfile reported display count ("
+ + mPerDisplayBatteryStats.length
+ + ") and PowerStatsHal reported display count (" + screenStates.length
+ + ")");
+ }
+ // Keep the show going, use the shorter of the two.
+ numDisplays = mPerDisplayBatteryStats.length < screenStates.length
+ ? mPerDisplayBatteryStats.length : screenStates.length;
+ }
- if (!mOnBatteryInternal || chargeUC <= 0) {
+ final int[] oldScreenStates = new int[numDisplays];
+ for (int i = 0; i < numDisplays; i++) {
+ final int screenState = screenStates[i];
+ oldScreenStates[i] = mPerDisplayBatteryStats[i].screenStateAtLastEnergyMeasurement;
+ mPerDisplayBatteryStats[i].screenStateAtLastEnergyMeasurement = screenState;
+ }
+
+ if (!mOnBatteryInternal) {
// There's nothing further to update.
return;
}
@@ -12627,17 +12980,31 @@ public class BatteryStatsImpl extends BatteryStats {
return;
}
- mGlobalMeasuredEnergyStats.updateStandardBucket(powerBucket, chargeUC);
+ long totalScreenOnChargeUC = 0;
+ for (int i = 0; i < numDisplays; i++) {
+ final long chargeUC = chargesUC[i];
+ if (chargeUC <= 0) {
+ // There's nothing further to update.
+ continue;
+ }
+
+ final @StandardPowerBucket int powerBucket =
+ MeasuredEnergyStats.getDisplayPowerBucket(oldScreenStates[i]);
+ mGlobalMeasuredEnergyStats.updateStandardBucket(powerBucket, chargeUC);
+ if (powerBucket == MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON) {
+ totalScreenOnChargeUC += chargeUC;
+ }
+ }
// Now we blame individual apps, but only if the display was ON.
- if (powerBucket != MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON) {
+ if (totalScreenOnChargeUC <= 0) {
return;
}
// TODO(b/175726779): Consider unifying the code with the non-rail display power blaming.
// NOTE: fg time is NOT pooled. If two uids are both somehow in fg, then that time is
// 'double counted' and will simply exceed the realtime that elapsed.
- // If multidisplay becomes a reality, this is probably more reasonable than pooling.
+ // TODO(b/175726779): collect per display uid visibility for display power attribution.
// Collect total time since mark so that we can normalize power.
final SparseDoubleArray fgTimeUsArray = new SparseDoubleArray();
@@ -12650,7 +13017,8 @@ public class BatteryStatsImpl extends BatteryStats {
if (fgTimeUs == 0) continue;
fgTimeUsArray.put(uid.getUid(), (double) fgTimeUs);
}
- distributeEnergyToUidsLocked(powerBucket, chargeUC, fgTimeUsArray, 0);
+ distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON,
+ totalScreenOnChargeUC, fgTimeUsArray, 0);
}
/**
@@ -14556,7 +14924,12 @@ public class BatteryStatsImpl extends BatteryStats {
public void initMeasuredEnergyStatsLocked(@Nullable boolean[] supportedStandardBuckets,
String[] customBucketNames) {
boolean supportedBucketMismatch = false;
- mScreenStateAtLastEnergyMeasurement = mScreenState;
+
+ final int numDisplays = mPerDisplayBatteryStats.length;
+ for (int i = 0; i < numDisplays; i++) {
+ final int screenState = mPerDisplayBatteryStats[i].screenState;
+ mPerDisplayBatteryStats[i].screenStateAtLastEnergyMeasurement = screenState;
+ }
if (supportedStandardBuckets == null) {
if (mGlobalMeasuredEnergyStats != null) {
diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java
index 4979ecbae8cb..93d562c571f8 100644
--- a/core/java/com/android/internal/os/PowerCalculator.java
+++ b/core/java/com/android/internal/os/PowerCalculator.java
@@ -133,32 +133,6 @@ public abstract class PowerCalculator {
}
/**
- * Returns either the measured energy converted to mAh or a usage-based estimate.
- */
- protected static double getMeasuredOrEstimatedPower(@BatteryConsumer.PowerModel int powerModel,
- long measuredEnergyUC, UsageBasedPowerEstimator powerEstimator, long durationMs) {
- switch (powerModel) {
- case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
- return uCtoMah(measuredEnergyUC);
- case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
- default:
- return powerEstimator.calculatePower(durationMs);
- }
- }
-
- /**
- * Returns either the measured energy converted to mAh or a usage-based estimate.
- */
- protected static double getMeasuredOrEstimatedPower(
- long measuredEnergyUC, UsageBasedPowerEstimator powerEstimator, long durationMs) {
- if (measuredEnergyUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
- return uCtoMah(measuredEnergyUC);
- } else {
- return powerEstimator.calculatePower(durationMs);
- }
- }
-
- /**
* Prints formatted amount of power in milli-amp-hours.
*/
public static void printPowerMah(PrintWriter pw, double powerMah) {
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index add2304afe9d..4d19b35b1e16 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -17,10 +17,12 @@
package com.android.internal.os;
+import android.annotation.StringDef;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
+import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
@@ -30,6 +32,8 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
@@ -40,6 +44,8 @@ import java.util.HashMap;
*/
public class PowerProfile {
+ public static final String TAG = "PowerProfile";
+
/*
* POWER_CPU_SUSPEND: Power consumption when CPU is in power collapse mode.
* POWER_CPU_IDLE: Power consumption when CPU is awake (when a wake lock is held). This should
@@ -145,12 +151,18 @@ public class PowerProfile {
/**
* Power consumption when screen is in doze/ambient/always-on mode, including backlight power.
+ *
+ * @deprecated Use {@link #POWER_GROUP_DISPLAY_AMBIENT} instead.
*/
+ @Deprecated
public static final String POWER_AMBIENT_DISPLAY = "ambient.on";
/**
* Power consumption when screen is on, not including the backlight power.
+ *
+ * @deprecated Use {@link #POWER_GROUP_DISPLAY_SCREEN_ON} instead.
*/
+ @Deprecated
@UnsupportedAppUsage
public static final String POWER_SCREEN_ON = "screen.on";
@@ -175,7 +187,10 @@ public class PowerProfile {
/**
* Power consumption at full backlight brightness. If the backlight is at
* 50% brightness, then this should be multiplied by 0.5
+ *
+ * @deprecated Use {@link #POWER_GROUP_DISPLAY_SCREEN_FULL} instead.
*/
+ @Deprecated
@UnsupportedAppUsage
public static final String POWER_SCREEN_FULL = "screen.full";
@@ -221,6 +236,29 @@ public class PowerProfile {
public static final String POWER_BATTERY_CAPACITY = "battery.capacity";
/**
+ * Power consumption when a screen is in doze/ambient/always-on mode, including backlight power.
+ */
+ public static final String POWER_GROUP_DISPLAY_AMBIENT = "ambient.on.display";
+
+ /**
+ * Power consumption when a screen is on, not including the backlight power.
+ */
+ public static final String POWER_GROUP_DISPLAY_SCREEN_ON = "screen.on.display";
+
+ /**
+ * Power consumption of a screen at full backlight brightness.
+ */
+ public static final String POWER_GROUP_DISPLAY_SCREEN_FULL = "screen.full.display";
+
+ @StringDef(prefix = { "POWER_GROUP_" }, value = {
+ POWER_GROUP_DISPLAY_AMBIENT,
+ POWER_GROUP_DISPLAY_SCREEN_ON,
+ POWER_GROUP_DISPLAY_SCREEN_FULL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PowerGroup {}
+
+ /**
* A map from Power Use Item to its power consumption.
*/
static final HashMap<String, Double> sPowerItemMap = new HashMap<>();
@@ -255,6 +293,7 @@ public class PowerProfile {
readPowerValuesFromXml(context, forTest);
}
initCpuClusters();
+ initDisplays();
}
}
@@ -424,6 +463,58 @@ public class PowerProfile {
return 0;
}
+ private int mNumDisplays;
+
+ private void initDisplays() {
+ // Figure out how many displays are listed in the power profile.
+ mNumDisplays = 0;
+ while (!Double.isNaN(
+ getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, mNumDisplays, Double.NaN))
+ || !Double.isNaN(
+ getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, mNumDisplays, Double.NaN))
+ || !Double.isNaN(
+ getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, mNumDisplays,
+ Double.NaN))) {
+ mNumDisplays++;
+ }
+
+ // Handle legacy display power constants.
+ final Double deprecatedAmbientDisplay = sPowerItemMap.get(POWER_AMBIENT_DISPLAY);
+ boolean legacy = false;
+ if (deprecatedAmbientDisplay != null && mNumDisplays == 0) {
+ final String key = getOrdinalPowerType(POWER_GROUP_DISPLAY_AMBIENT, 0);
+ Slog.w(TAG, POWER_AMBIENT_DISPLAY + " is deprecated! Use " + key + " instead.");
+ sPowerItemMap.put(key, deprecatedAmbientDisplay);
+ legacy = true;
+ }
+
+ final Double deprecatedScreenOn = sPowerItemMap.get(POWER_SCREEN_ON);
+ if (deprecatedScreenOn != null && mNumDisplays == 0) {
+ final String key = getOrdinalPowerType(POWER_GROUP_DISPLAY_SCREEN_ON, 0);
+ Slog.w(TAG, POWER_SCREEN_ON + " is deprecated! Use " + key + " instead.");
+ sPowerItemMap.put(key, deprecatedScreenOn);
+ legacy = true;
+ }
+
+ final Double deprecatedScreenFull = sPowerItemMap.get(POWER_SCREEN_FULL);
+ if (deprecatedScreenFull != null && mNumDisplays == 0) {
+ final String key = getOrdinalPowerType(POWER_GROUP_DISPLAY_SCREEN_FULL, 0);
+ Slog.w(TAG, POWER_SCREEN_FULL + " is deprecated! Use " + key + " instead.");
+ sPowerItemMap.put(key, deprecatedScreenFull);
+ legacy = true;
+ }
+ if (legacy) {
+ mNumDisplays = 1;
+ }
+ }
+
+ /**
+ * Returns the number built in displays on the device as defined in the power_profile.xml.
+ */
+ public int getNumDisplays() {
+ return mNumDisplays;
+ }
+
/**
* Returns the number of memory bandwidth buckets defined in power_profile.xml, or a
* default value if the subsystem has no recorded value.
@@ -496,6 +587,32 @@ public class PowerProfile {
}
/**
+ * Returns the average current in mA consumed by an ordinaled subsystem, or the given
+ * default value if the subsystem has no recorded value.
+ *
+ * @param group the subsystem {@link PowerGroup}.
+ * @param ordinal which entity in the {@link PowerGroup}.
+ * @param defaultValue the value to return if the subsystem has no recorded value.
+ * @return the average current in milliAmps.
+ */
+ public double getAveragePowerForOrdinal(@PowerGroup String group, int ordinal,
+ double defaultValue) {
+ final String type = getOrdinalPowerType(group, ordinal);
+ return getAveragePowerOrDefault(type, defaultValue);
+ }
+
+ /**
+ * Returns the average current in mA consumed by an ordinaled subsystem.
+ *
+ * @param group the subsystem {@link PowerGroup}.
+ * @param ordinal which entity in the {@link PowerGroup}.
+ * @return the average current in milliAmps.
+ */
+ public double getAveragePowerForOrdinal(@PowerGroup String group, int ordinal) {
+ return getAveragePowerForOrdinal(group, ordinal, 0);
+ }
+
+ /**
* Returns the battery capacity, if available, in milli Amp Hours. If not available,
* it returns zero.
*
@@ -682,4 +799,9 @@ public class PowerProfile {
}
}
}
+
+ // Creates the key for an ordinaled power constant from the group and ordinal.
+ private static String getOrdinalPowerType(@PowerGroup String group, int ordinal) {
+ return group + ordinal;
+ }
}
diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java
index 1b3bc234fc0f..2b634598bbbc 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -16,6 +16,9 @@
package com.android.internal.os;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON;
+
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
@@ -41,8 +44,8 @@ public class ScreenPowerCalculator extends PowerCalculator {
// Minimum amount of time the screen should be on to start smearing drain to apps
public static final long MIN_ACTIVE_TIME_FOR_SMEARING = 10 * DateUtils.MINUTE_IN_MILLIS;
- private final UsageBasedPowerEstimator mScreenOnPowerEstimator;
- private final UsageBasedPowerEstimator mScreenFullPowerEstimator;
+ private final UsageBasedPowerEstimator[] mScreenOnPowerEstimators;
+ private final UsageBasedPowerEstimator[] mScreenFullPowerEstimators;
private static class PowerAndDuration {
public long durationMs;
@@ -50,10 +53,16 @@ public class ScreenPowerCalculator extends PowerCalculator {
}
public ScreenPowerCalculator(PowerProfile powerProfile) {
- mScreenOnPowerEstimator = new UsageBasedPowerEstimator(
- powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON));
- mScreenFullPowerEstimator = new UsageBasedPowerEstimator(
- powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL));
+ final int numDisplays = powerProfile.getNumDisplays();
+ mScreenOnPowerEstimators = new UsageBasedPowerEstimator[numDisplays];
+ mScreenFullPowerEstimators = new UsageBasedPowerEstimator[numDisplays];
+ for (int display = 0; display < numDisplays; display++) {
+ mScreenOnPowerEstimators[display] = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, display));
+ mScreenFullPowerEstimators[display] = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL,
+ display));
+ }
}
@Override
@@ -168,7 +177,7 @@ public class ScreenPowerCalculator extends PowerCalculator {
case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
default:
totalPowerAndDuration.powerMah = calculateTotalPowerFromBrightness(batteryStats,
- rawRealtimeUs, statsType, totalPowerAndDuration.durationMs);
+ rawRealtimeUs);
}
}
@@ -190,19 +199,25 @@ public class ScreenPowerCalculator extends PowerCalculator {
return batteryStats.getScreenOnTime(rawRealtimeUs, statsType) / 1000;
}
- private double calculateTotalPowerFromBrightness(BatteryStats batteryStats, long rawRealtimeUs,
- int statsType, long durationMs) {
- double power = mScreenOnPowerEstimator.calculatePower(durationMs);
- for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- final long brightnessTime =
- batteryStats.getScreenBrightnessTime(i, rawRealtimeUs, statsType) / 1000;
- final double binPowerMah = mScreenFullPowerEstimator.calculatePower(brightnessTime)
- * (i + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
- if (DEBUG && binPowerMah != 0) {
- Slog.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
- + " power=" + formatCharge(binPowerMah));
+ private double calculateTotalPowerFromBrightness(BatteryStats batteryStats,
+ long rawRealtimeUs) {
+ final int numDisplays = mScreenOnPowerEstimators.length;
+ double power = 0;
+ for (int display = 0; display < numDisplays; display++) {
+ final long displayTime = batteryStats.getDisplayScreenOnTime(display, rawRealtimeUs)
+ / 1000;
+ power += mScreenOnPowerEstimators[display].calculatePower(displayTime);
+ for (int bin = 0; bin < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; bin++) {
+ final long brightnessTime = batteryStats.getDisplayScreenBrightnessTime(display,
+ bin, rawRealtimeUs) / 1000;
+ final double binPowerMah = mScreenFullPowerEstimators[display].calculatePower(
+ brightnessTime) * (bin + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
+ if (DEBUG && binPowerMah != 0) {
+ Slog.d(TAG, "Screen bin #" + bin + ": time=" + brightnessTime
+ + " power=" + formatCharge(binPowerMah));
+ }
+ power += binPowerMah;
}
- power += binPowerMah;
}
return power;
}
diff --git a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
index 8e454db4cb04..419b1f8feac7 100644
--- a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
@@ -20,5 +20,4 @@ interface IKeyguardStateCallback {
void onSimSecureStateChanged(boolean simSecure);
void onInputRestrictedStateChanged(boolean inputRestricted);
void onTrustedChanged(boolean trusted);
- void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper);
} \ No newline at end of file
diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
index 52172cf04362..ec6283922807 100644
--- a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
+++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
@@ -16,7 +16,9 @@
package com.android.internal.policy;
+import android.content.Context;
import android.content.res.Resources;
+import android.view.RoundedCorners;
import com.android.internal.R;
@@ -29,23 +31,28 @@ public class ScreenDecorationsUtils {
* Corner radius that should be used on windows in order to cover the display.
* These values are expressed in pixels because they should not respect display or font
* scaling, this means that we don't have to reload them on config changes.
+ *
+ * Note that if the context is not an UI context(not associated with Display), it will use
+ * default display.
*/
- public static float getWindowCornerRadius(Resources resources) {
+ public static float getWindowCornerRadius(Context context) {
+ final Resources resources = context.getResources();
if (!supportsRoundedCornersOnWindows(resources)) {
return 0f;
}
-
+ // Use Context#getDisplayNoVerify() in case the context is not an UI context.
+ final String displayUniqueId = context.getDisplayNoVerify().getUniqueId();
// Radius that should be used in case top or bottom aren't defined.
- float defaultRadius = resources.getDimension(R.dimen.rounded_corner_radius)
- - resources.getDimension(R.dimen.rounded_corner_radius_adjustment);
+ float defaultRadius = RoundedCorners.getRoundedCornerRadius(resources, displayUniqueId)
+ - RoundedCorners.getRoundedCornerRadiusAdjustment(resources, displayUniqueId);
- float topRadius = resources.getDimension(R.dimen.rounded_corner_radius_top)
- - resources.getDimension(R.dimen.rounded_corner_radius_top_adjustment);
+ float topRadius = RoundedCorners.getRoundedCornerTopRadius(resources, displayUniqueId)
+ - RoundedCorners.getRoundedCornerRadiusTopAdjustment(resources, displayUniqueId);
if (topRadius == 0f) {
topRadius = defaultRadius;
}
- float bottomRadius = resources.getDimension(R.dimen.rounded_corner_radius_bottom)
- - resources.getDimension(R.dimen.rounded_corner_radius_bottom_adjustment);
+ float bottomRadius = RoundedCorners.getRoundedCornerBottomRadius(resources, displayUniqueId)
+ - RoundedCorners.getRoundedCornerRadiusBottomAdjustment(resources, displayUniqueId);
if (bottomRadius == 0f) {
bottomRadius = defaultRadius;
}
diff --git a/core/java/com/android/internal/policy/SystemBarUtils.java b/core/java/com/android/internal/policy/SystemBarUtils.java
new file mode 100644
index 000000000000..6bf1333097f7
--- /dev/null
+++ b/core/java/com/android/internal/policy/SystemBarUtils.java
@@ -0,0 +1,94 @@
+/*
+ * 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.internal.policy;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Insets;
+import android.util.RotationUtils;
+import android.view.DisplayCutout;
+import android.view.Surface;
+
+import com.android.internal.R;
+
+/**
+ * Utility functions for system bars used by both window manager and System UI.
+ *
+ * @hide
+ */
+public final class SystemBarUtils {
+
+ /**
+ * Gets the status bar height.
+ */
+ public static int getStatusBarHeight(Context context) {
+ return getStatusBarHeight(context.getResources(), context.getDisplay().getCutout());
+ }
+
+ /**
+ * Gets the status bar height with a specific display cutout.
+ */
+ public static int getStatusBarHeight(Resources res, DisplayCutout cutout) {
+ final int defaultSize = res.getDimensionPixelSize(R.dimen.status_bar_height);
+ final int safeInsetTop = cutout == null ? 0 : cutout.getSafeInsetTop();
+ final int waterfallInsetTop = cutout == null ? 0 : cutout.getWaterfallInsets().top;
+ // The status bar height should be:
+ // Max(top cutout size, (status bar default height + waterfall top size))
+ return Math.max(safeInsetTop, defaultSize + waterfallInsetTop);
+ }
+
+ /**
+ * Gets the status bar height for a specific rotation.
+ */
+ public static int getStatusBarHeightForRotation(
+ Context context, @Surface.Rotation int targetRot) {
+ final int rotation = context.getDisplay().getRotation();
+ final DisplayCutout cutout = context.getDisplay().getCutout();
+
+ Insets insets = cutout == null ? Insets.NONE : Insets.of(cutout.getSafeInsets());
+ Insets waterfallInsets = cutout == null ? Insets.NONE : cutout.getWaterfallInsets();
+ // rotate insets to target rotation if needed.
+ if (rotation != targetRot) {
+ if (!insets.equals(Insets.NONE)) {
+ insets = RotationUtils.rotateInsets(
+ insets, RotationUtils.deltaRotation(rotation, targetRot));
+ }
+ if (!waterfallInsets.equals(Insets.NONE)) {
+ waterfallInsets = RotationUtils.rotateInsets(
+ waterfallInsets, RotationUtils.deltaRotation(rotation, targetRot));
+ }
+ }
+ final int defaultSize =
+ context.getResources().getDimensionPixelSize(R.dimen.status_bar_height);
+ // The status bar height should be:
+ // Max(top cutout size, (status bar default height + waterfall top size))
+ return Math.max(insets.top, defaultSize + waterfallInsets.top);
+ }
+
+ /**
+ * Gets the height of area above QQS where battery/time go in notification panel. The height
+ * equals to status bar height if status bar height is bigger than the
+ * {@link R.dimen#quick_qs_offset_height}.
+ */
+ public static int getQuickQsOffsetHeight(Context context) {
+ final int defaultSize = context.getResources().getDimensionPixelSize(
+ R.dimen.quick_qs_offset_height);
+ final int statusBarHeight = getStatusBarHeight(context);
+ // Equals to status bar height if status bar height is bigger.
+ return Math.max(defaultSize, statusBarHeight);
+ }
+}
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 60a8d802861f..d3224b13e312 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -16,16 +16,20 @@
package com.android.internal.policy;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -34,12 +38,18 @@ import android.content.res.Configuration;
import android.content.res.ResourceId;
import android.content.res.Resources;
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.SystemProperties;
import android.util.Slog;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.TransitionOldType;
+import android.view.WindowManager.TransitionType;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
@@ -56,11 +66,17 @@ import java.util.List;
/** @hide */
public class TransitionAnimation {
+ public static final int WALLPAPER_TRANSITION_NONE = 0;
+ public static final int WALLPAPER_TRANSITION_OPEN = 1;
+ public static final int WALLPAPER_TRANSITION_CLOSE = 2;
+ public static final int WALLPAPER_TRANSITION_INTRA_OPEN = 3;
+ public static final int WALLPAPER_TRANSITION_INTRA_CLOSE = 4;
+
// These are the possible states for the enter/exit activities during a thumbnail transition
- public static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0;
- public static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1;
- public static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2;
- public static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3;
+ private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0;
+ private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1;
+ private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2;
+ private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3;
/**
* Maximum duration for the clip reveal animation. This is used when there is a lot of movement
@@ -72,9 +88,15 @@ public class TransitionAnimation {
public static final int DEFAULT_APP_TRANSITION_DURATION = 336;
+ /** Fraction of animation at which the recents thumbnail stays completely transparent */
+ private static final float RECENTS_THUMBNAIL_FADEIN_FRACTION = 0.5f;
/** Fraction of animation at which the recents thumbnail becomes completely transparent */
private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.5f;
+ /** Interpolator to be used for animations that respond directly to a touch */
+ static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
+ new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+
private static final String DEFAULT_PACKAGE = "android";
private final Context mContext;
@@ -86,7 +108,9 @@ public class TransitionAnimation {
new PathInterpolator(0.3f, 0f, 0.1f, 1f);
private final Interpolator mClipHorizontalInterpolator = new PathInterpolator(0, 0, 0.4f, 1f);
private final Interpolator mDecelerateInterpolator;
+ private final Interpolator mFastOutLinearInInterpolator;
private final Interpolator mLinearOutSlowInInterpolator;
+ private final Interpolator mThumbnailFadeInInterpolator;
private final Interpolator mThumbnailFadeOutInterpolator;
private final Rect mTmpFromClipRect = new Rect();
private final Rect mTmpToClipRect = new Rect();
@@ -107,8 +131,19 @@ public class TransitionAnimation {
mDecelerateInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.decelerate_cubic);
+ mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
+ com.android.internal.R.interpolator.fast_out_linear_in);
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.linear_out_slow_in);
+ mThumbnailFadeInInterpolator = input -> {
+ // Linear response for first fraction, then complete after that.
+ if (input < RECENTS_THUMBNAIL_FADEIN_FRACTION) {
+ return 0f;
+ }
+ float t = (input - RECENTS_THUMBNAIL_FADEIN_FRACTION)
+ / (1f - RECENTS_THUMBNAIL_FADEIN_FRACTION);
+ return mFastOutLinearInInterpolator.getInterpolation(t);
+ };
mThumbnailFadeOutInterpolator = input -> {
// Linear response for first fraction, then complete after that.
if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) {
@@ -181,6 +216,13 @@ public class TransitionAnimation {
DEFAULT_PACKAGE, com.android.internal.R.anim.cross_profile_apps_thumbnail_enter);
}
+ @Nullable
+ public Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
+ final Animation animation = loadCrossProfileAppThumbnailEnterAnimation();
+ return prepareThumbnailAnimationWithDuration(animation, appRect.width(),
+ appRect.height(), 0, null);
+ }
+
/** Load animation by resource Id from specific package. */
@Nullable
public Animation loadAnimationRes(String packageName, int resId) {
@@ -347,8 +389,15 @@ public class TransitionAnimation {
}
}
- public Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame,
- Rect displayFrame, Rect startRect) {
+ public Animation createClipRevealAnimationLocked(@TransitionType int transit,
+ int wallpaperTransit, boolean enter, Rect appFrame, Rect displayFrame, Rect startRect) {
+ return createClipRevealAnimationLockedCompat(
+ getTransitCompatType(transit, wallpaperTransit), enter, appFrame, displayFrame,
+ startRect);
+ }
+
+ public Animation createClipRevealAnimationLockedCompat(@TransitionOldType int transit,
+ boolean enter, Rect appFrame, Rect displayFrame, Rect startRect) {
final Animation anim;
if (enter) {
final int appWidth = appFrame.width();
@@ -458,8 +507,14 @@ public class TransitionAnimation {
return anim;
}
- public Animation createScaleUpAnimationLocked(int transit, boolean enter,
- Rect containingFrame, Rect startRect) {
+ public Animation createScaleUpAnimationLocked(@TransitionType int transit, int wallpaperTransit,
+ boolean enter, Rect containingFrame, Rect startRect) {
+ return createScaleUpAnimationLockedCompat(getTransitCompatType(transit, wallpaperTransit),
+ enter, containingFrame, startRect);
+ }
+
+ public Animation createScaleUpAnimationLockedCompat(@TransitionOldType int transit,
+ boolean enter, Rect containingFrame, Rect startRect) {
Animation a;
setupDefaultNextAppTransitionStartRect(startRect, mTmpRect);
final int appWidth = containingFrame.width();
@@ -514,12 +569,19 @@ public class TransitionAnimation {
return a;
}
+ public Animation createThumbnailEnterExitAnimationLocked(boolean enter, boolean scaleUp,
+ Rect containingFrame, @TransitionType int transit, int wallpaperTransit,
+ HardwareBuffer thumbnailHeader, Rect startRect) {
+ return createThumbnailEnterExitAnimationLockedCompat(enter, scaleUp, containingFrame,
+ getTransitCompatType(transit, wallpaperTransit), thumbnailHeader, startRect);
+ }
+
/**
* This animation is created when we are doing a thumbnail transition, for the activity that is
* leaving, and the activity that is entering.
*/
- public Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState,
- Rect containingFrame, int transit, HardwareBuffer thumbnailHeader,
+ public Animation createThumbnailEnterExitAnimationLockedCompat(boolean enter, boolean scaleUp,
+ Rect containingFrame, @TransitionOldType int transit, HardwareBuffer thumbnailHeader,
Rect startRect) {
final int appWidth = containingFrame.width();
final int appHeight = containingFrame.height();
@@ -529,6 +591,7 @@ public class TransitionAnimation {
final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight;
final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
+ final int thumbTransitState = getThumbnailTransitionState(enter, scaleUp);
switch (thumbTransitState) {
case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
@@ -587,8 +650,8 @@ public class TransitionAnimation {
* This alternate animation is created when we are doing a thumbnail transition, for the
* activity that is leaving, and the activity that is entering.
*/
- public Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState,
- int orientation, int transit, Rect containingFrame, Rect contentInsets,
+ public Animation createAspectScaledThumbnailEnterExitAnimationLocked(boolean enter,
+ boolean scaleUp, int orientation, int transit, Rect containingFrame, Rect contentInsets,
@Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean freeform,
Rect startRect, Rect defaultStartRect) {
Animation a;
@@ -601,11 +664,11 @@ public class TransitionAnimation {
final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
final int thumbStartX = mTmpRect.left - containingFrame.left - contentInsets.left;
final int thumbStartY = mTmpRect.top - containingFrame.top;
+ final int thumbTransitState = getThumbnailTransitionState(enter, scaleUp);
switch (thumbTransitState) {
case THUMBNAIL_TRANSITION_ENTER_SCALE_UP:
case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
- final boolean scaleUp = thumbTransitState == THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
if (freeform && scaleUp) {
a = createAspectScaledThumbnailEnterFreeformAnimationLocked(
containingFrame, surfaceInsets, startRect, defaultStartRect);
@@ -720,10 +783,151 @@ public class TransitionAnimation {
}
/**
+ * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
+ * when a thumbnail is specified with the pending animation override.
+ */
+ public Animation createThumbnailAspectScaleAnimationLocked(Rect appRect,
+ @Nullable Rect contentInsets, HardwareBuffer thumbnailHeader, int orientation,
+ Rect startRect, Rect defaultStartRect, boolean scaleUp) {
+ 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(startRect, defaultStartRect, mTmpRect);
+ final float fromX;
+ float fromY;
+ final float toX;
+ float toY;
+ final float pivotX;
+ final float pivotY;
+ if (shouldScaleDownThumbnailTransition(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;
+ }
+ if (scaleUp) {
+ // Animation up from the thumbnail to the full screen
+ Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, pivotX, pivotY);
+ scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+ scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+ Animation alpha = new AlphaAnimation(1f, 0f);
+ alpha.setInterpolator(mThumbnailFadeOutInterpolator);
+ alpha.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+ Animation translate = createCurvedMotion(fromX, toX, fromY, toY);
+ translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+ translate.setDuration(THUMBNAIL_APP_TRANSITION_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(TOUCH_RESPONSE_INTERPOLATOR);
+ clipAnim.setDuration(THUMBNAIL_APP_TRANSITION_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(TOUCH_RESPONSE_INTERPOLATOR);
+ scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+ Animation alpha = new AlphaAnimation(0f, 1f);
+ alpha.setInterpolator(mThumbnailFadeInInterpolator);
+ alpha.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+ Animation translate = createCurvedMotion(toX, fromX, toY, fromY);
+ translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+ translate.setDuration(THUMBNAIL_APP_TRANSITION_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);
+ }
+
+ /**
+ * Creates an overlay with a background color and a thumbnail for the cross profile apps
+ * animation.
+ */
+ public 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 = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.cross_profile_apps_thumbnail_size);
+ final Drawable drawable = 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();
+ }
+
+ /**
* Prepares the specified animation with a standard duration, interpolator, etc.
*/
private Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight,
- int transit) {
+ @TransitionOldType 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
// task transition duration.
@@ -820,6 +1024,22 @@ public class TransitionAnimation {
return anim;
}
+ private static @TransitionOldType int getTransitCompatType(@TransitionType int transit,
+ int wallpaperTransit) {
+ if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
+ return TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
+ return TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
+ } else if (transit == TRANSIT_OPEN) {
+ return TRANSIT_OLD_ACTIVITY_OPEN;
+ } else if (transit == TRANSIT_CLOSE) {
+ return TRANSIT_OLD_ACTIVITY_CLOSE;
+ }
+
+ // We only do some special handle for above type, so use type NONE for default behavior.
+ return TRANSIT_OLD_NONE;
+ }
+
/**
* Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that
* the start rect is outside of the target rect, and there is a lot of movement going on.
@@ -843,10 +1063,33 @@ public class TransitionAnimation {
}
/**
+ * Return the current thumbnail transition state.
+ */
+ private int getThumbnailTransitionState(boolean enter, boolean scaleUp) {
+ if (enter) {
+ if (scaleUp) {
+ return THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
+ } else {
+ return THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN;
+ }
+ } else {
+ if (scaleUp) {
+ return THUMBNAIL_TRANSITION_EXIT_SCALE_UP;
+ } else {
+ return THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN;
+ }
+ }
+ }
+
+ /**
* Prepares the specified animation with a standard duration, interpolator, etc.
*/
- private static Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth,
+ public static Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth,
int appHeight, long duration, Interpolator interpolator) {
+ if (a == null) {
+ return null;
+ }
+
if (duration > 0) {
a.setDuration(duration);
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index ce3efd35ee48..954204fe781c 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -80,6 +80,11 @@ public enum ProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM),
WM_DEBUG_WINDOW_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM),
+ WM_DEBUG_WINDOW_INSETS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM),
+ WM_DEBUG_LAYER_MIRRORING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ Consts.TAG_WM),
+ WM_DEBUG_WALLPAPER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM),
TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
private final boolean mEnabled;
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 10224a4b9db6..353c6c083d9d 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -28,7 +28,7 @@ import java.io.File;
*/
public class ProtoLogImpl extends BaseProtoLogImpl {
private static final int BUFFER_CAPACITY = 1024 * 1024;
- private static final String LOG_FILENAME = "/data/misc/wmtrace/wm_log.pb";
+ private static final String LOG_FILENAME = "/data/misc/wmtrace/wm_log.winscope";
private static final String VIEWER_CONFIG_FILENAME = "/system/etc/protolog.conf.json.gz";
private static ProtoLogImpl sServiceInstance = null;
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index ed6415d749a3..b427e8be142d 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -25,6 +25,7 @@ import android.hardware.fingerprint.IUdfpsHbmListener;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.service.notification.StatusBarNotification;
+import android.view.InsetsVisibilities;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.view.AppearanceRegion;
@@ -182,7 +183,7 @@ oneway interface IStatusBar
/**
* Notifies System UI side of system bar attribute change on the specified display.
*
- * @param displayId the ID of the display to notify
+ * @param displayId the ID of the display to notify.
* @param appearance the appearance of the focused window. The light top bar appearance is not
* controlled here, but primaryAppearance and secondaryAppearance.
* @param appearanceRegions a set of appearances which will be only applied in their own bounds.
@@ -191,11 +192,12 @@ oneway interface IStatusBar
* stacks.
* @param navbarColorManagedByIme {@code true} if navigation bar color is managed by IME.
* @param behavior the behavior of the focused window.
- * @param isFullscreen whether any of status or navigation bar is requested invisible.
+ * @param requestedVisibilities the collection of the requested visibilities of system insets.
+ * @param packageName the package name of the focused app.
*/
void onSystemBarAttributesChanged(int displayId, int appearance,
in AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- int behavior, boolean isFullscreen);
+ int behavior, in InsetsVisibilities requestedVisibilities, String packageName);
/**
* Notifies System UI to show transient bars. The transient bars are system bars, e.g., status
diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
index 8fb2f9cd8bf9..4dcc82e2e572 100644
--- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
+++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
@@ -21,6 +21,7 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
+import android.view.InsetsVisibilities;
import com.android.internal.view.AppearanceRegion;
@@ -39,14 +40,15 @@ public final class RegisterStatusBarResult implements Parcelable {
public final IBinder mImeToken;
public final boolean mNavbarColorManagedByIme;
public final int mBehavior;
- public final boolean mAppFullscreen;
+ public final InsetsVisibilities mRequestedVisibilities;
+ public final String mPackageName;
public final int[] mTransientBarTypes;
public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1,
int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis,
int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, IBinder imeToken,
- boolean navbarColorManagedByIme, int behavior, boolean appFullscreen,
- @NonNull int[] transientBarTypes) {
+ boolean navbarColorManagedByIme, int behavior, InsetsVisibilities requestedVisibilities,
+ String packageName, @NonNull int[] transientBarTypes) {
mIcons = new ArrayMap<>(icons);
mDisabledFlags1 = disabledFlags1;
mAppearance = appearance;
@@ -58,7 +60,8 @@ public final class RegisterStatusBarResult implements Parcelable {
mImeToken = imeToken;
mNavbarColorManagedByIme = navbarColorManagedByIme;
mBehavior = behavior;
- mAppFullscreen = appFullscreen;
+ mRequestedVisibilities = requestedVisibilities;
+ mPackageName = packageName;
mTransientBarTypes = transientBarTypes;
}
@@ -80,7 +83,8 @@ public final class RegisterStatusBarResult implements Parcelable {
dest.writeStrongBinder(mImeToken);
dest.writeBoolean(mNavbarColorManagedByIme);
dest.writeInt(mBehavior);
- dest.writeBoolean(mAppFullscreen);
+ dest.writeTypedObject(mRequestedVisibilities, 0);
+ dest.writeString(mPackageName);
dest.writeIntArray(mTransientBarTypes);
}
@@ -104,12 +108,14 @@ public final class RegisterStatusBarResult implements Parcelable {
final IBinder imeToken = source.readStrongBinder();
final boolean navbarColorManagedByIme = source.readBoolean();
final int behavior = source.readInt();
- final boolean appFullscreen = source.readBoolean();
+ final InsetsVisibilities requestedVisibilities =
+ source.readTypedObject(InsetsVisibilities.CREATOR);
+ final String packageName = source.readString();
final int[] transientBarTypes = source.createIntArray();
return new RegisterStatusBarResult(icons, disabledFlags1, appearance,
appearanceRegions, imeWindowVis, imeBackDisposition, showImeSwitcher,
disabledFlags2, imeToken, navbarColorManagedByIme, behavior,
- appFullscreen, transientBarTypes);
+ requestedVisibilities, packageName, transientBarTypes);
}
@Override
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index f040462dafdc..4c519f4c779f 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -14,15 +14,20 @@
package com.android.internal.util;
+import static android.os.Trace.TRACE_TAG_APP;
+
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.os.Build;
import android.os.SystemClock;
import android.os.Trace;
import android.provider.DeviceConfig;
+import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
-import android.util.SparseLongArray;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.EventLogTags;
@@ -31,6 +36,7 @@ import com.android.internal.os.BackgroundThread;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
/**
* Class to track various latencies in SystemUI. It then writes the latency to statsd and also
@@ -44,6 +50,7 @@ public class LatencyTracker {
private static final String TAG = "LatencyTracker";
private static final String SETTINGS_ENABLED_KEY = "enabled";
private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval";
+ private static final boolean DEBUG = false;
/** Default to being enabled on debug builds. */
private static final boolean DEFAULT_ENABLED = Build.IS_DEBUGGABLE;
/** Default to collecting data for 1/5 of all actions (randomly sampled). */
@@ -110,6 +117,11 @@ public class LatencyTracker {
*/
public static final int ACTION_LOCKSCREEN_UNLOCK = 11;
+ /**
+ * Time it takes to switch users.
+ */
+ public static final int ACTION_USER_SWITCH = 12;
+
private static final int[] ACTIONS_ALL = {
ACTION_EXPAND_PANEL,
ACTION_TOGGLE_RECENTS,
@@ -122,7 +134,8 @@ public class LatencyTracker {
ACTION_START_RECENTS_ANIMATION,
ACTION_ROTATE_SCREEN_SENSOR,
ACTION_ROTATE_SCREEN_CAMERA_CHECK,
- ACTION_LOCKSCREEN_UNLOCK
+ ACTION_LOCKSCREEN_UNLOCK,
+ ACTION_USER_SWITCH
};
/** @hide */
@@ -138,7 +151,8 @@ public class LatencyTracker {
ACTION_START_RECENTS_ANIMATION,
ACTION_ROTATE_SCREEN_SENSOR,
ACTION_ROTATE_SCREEN_CAMERA_CHECK,
- ACTION_LOCKSCREEN_UNLOCK
+ ACTION_LOCKSCREEN_UNLOCK,
+ ACTION_USER_SWITCH
})
@Retention(RetentionPolicy.SOURCE)
public @interface Action {
@@ -156,13 +170,15 @@ public class LatencyTracker {
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK,
- FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK
+ FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK,
+ FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH
};
private static LatencyTracker sLatencyTracker;
private final Object mLock = new Object();
- private final SparseLongArray mStartRtc = new SparseLongArray();
+ @GuardedBy("mLock")
+ private final SparseArray<Session> mSessions = new SparseArray<>();
@GuardedBy("mLock")
private final int[] mTraceThresholdPerAction = new int[ACTIONS_ALL.length];
@GuardedBy("mLock")
@@ -239,13 +255,19 @@ public class LatencyTracker {
return "ACTION_ROTATE_SCREEN_SENSOR";
case 12:
return "ACTION_LOCKSCREEN_UNLOCK";
+ case 13:
+ return "ACTION_USER_SWITCH";
default:
throw new IllegalArgumentException("Invalid action");
}
}
- private static String getTraceNameOfAction(@Action int action) {
- return "L<" + getNameOfAction(STATSD_ACTION[action]) + ">";
+ private static String getTraceNameOfAction(@Action int action, String tag) {
+ if (TextUtils.isEmpty(tag)) {
+ return "L<" + getNameOfAction(STATSD_ACTION[action]) + ">";
+ } else {
+ return "L<" + getNameOfAction(STATSD_ACTION[action]) + "::" + tag + ">";
+ }
}
private static String getTraceTriggerNameForAction(@Action int action) {
@@ -263,35 +285,82 @@ public class LatencyTracker {
}
/**
- * Notifies that an action is starting. This needs to be called from the main thread.
+ * Notifies that an action is starting. <s>This needs to be called from the main thread.</s>
*
* @param action The action to start. One of the ACTION_* values.
*/
public void onActionStart(@Action int action) {
- if (!isEnabled()) {
- return;
+ onActionStart(action, null);
+ }
+
+ /**
+ * Notifies that an action is starting. <s>This needs to be called from the main thread.</s>
+ *
+ * @param action The action to start. One of the ACTION_* values.
+ * @param tag The brief description of the action.
+ */
+ public void onActionStart(@Action int action, String tag) {
+ synchronized (mLock) {
+ if (!isEnabled()) {
+ return;
+ }
+ // skip if the action is already instrumenting.
+ if (mSessions.get(action) != null) {
+ return;
+ }
+ Session session = new Session(action, tag);
+ session.begin(() -> onActionCancel(action));
+ mSessions.put(action, session);
+
+ if (DEBUG) {
+ Log.d(TAG, "onActionStart: " + session.name() + ", start=" + session.mStartRtc);
+ }
}
- Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, getTraceNameOfAction(action), 0);
- mStartRtc.put(action, SystemClock.elapsedRealtime());
}
/**
- * Notifies that an action has ended. This needs to be called from the main thread.
+ * Notifies that an action has ended. <s>This needs to be called from the main thread.</s>
*
* @param action The action to end. One of the ACTION_* values.
*/
public void onActionEnd(@Action int action) {
- if (!isEnabled()) {
- return;
+ synchronized (mLock) {
+ if (!isEnabled()) {
+ return;
+ }
+ Session session = mSessions.get(action);
+ if (session == null) {
+ return;
+ }
+ session.end();
+ mSessions.delete(action);
+ logAction(action, session.duration());
+
+ if (DEBUG) {
+ Log.d(TAG, "onActionEnd:" + session.name() + ", duration=" + session.duration());
+ }
}
- long endRtc = SystemClock.elapsedRealtime();
- long startRtc = mStartRtc.get(action, -1);
- if (startRtc == -1) {
- return;
+ }
+
+ /**
+ * Notifies that an action has canceled. <s>This needs to be called from the main thread.</s>
+ *
+ * @param action The action to cancel. One of the ACTION_* values.
+ * @hide
+ */
+ public void onActionCancel(@Action int action) {
+ synchronized (mLock) {
+ Session session = mSessions.get(action);
+ if (session == null) {
+ return;
+ }
+ session.cancel();
+ mSessions.delete(action);
+
+ if (DEBUG) {
+ Log.d(TAG, "onActionCancel: " + session.name());
+ }
}
- mStartRtc.delete(action);
- Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, getTraceNameOfAction(action), 0);
- logAction(action, (int) (endRtc - startRtc));
}
/**
@@ -332,4 +401,57 @@ public class LatencyTracker {
FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED, STATSD_ACTION[action], duration);
}
}
+
+ static class Session {
+ @Action
+ private final int mAction;
+ private final String mTag;
+ private final String mName;
+ private Runnable mTimeoutRunnable;
+ private long mStartRtc = -1;
+ private long mEndRtc = -1;
+
+ Session(@Action int action, @Nullable String tag) {
+ mAction = action;
+ mTag = tag;
+ mName = TextUtils.isEmpty(mTag)
+ ? getNameOfAction(STATSD_ACTION[mAction])
+ : getNameOfAction(STATSD_ACTION[mAction]) + "::" + mTag;
+ }
+
+ String name() {
+ return mName;
+ }
+
+ String traceName() {
+ return getTraceNameOfAction(mAction, mTag);
+ }
+
+ void begin(@NonNull Runnable timeoutAction) {
+ mStartRtc = SystemClock.elapsedRealtime();
+ Trace.asyncTraceBegin(TRACE_TAG_APP, traceName(), 0);
+
+ // start counting timeout.
+ mTimeoutRunnable = timeoutAction;
+ BackgroundThread.getHandler()
+ .postDelayed(mTimeoutRunnable, TimeUnit.SECONDS.toMillis(15));
+ }
+
+ void end() {
+ mEndRtc = SystemClock.elapsedRealtime();
+ Trace.asyncTraceEnd(TRACE_TAG_APP, traceName(), 0);
+ BackgroundThread.getHandler().removeCallbacks(mTimeoutRunnable);
+ mTimeoutRunnable = null;
+ }
+
+ void cancel() {
+ Trace.asyncTraceEnd(TRACE_TAG_APP, traceName(), 0);
+ BackgroundThread.getHandler().removeCallbacks(mTimeoutRunnable);
+ mTimeoutRunnable = null;
+ }
+
+ int duration() {
+ return (int) (mEndRtc - mStartRtc);
+ }
+ }
}
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 8d82e33dc29f..5354afbd667b 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -35,7 +35,7 @@ import com.android.internal.view.InlineSuggestionsRequestInfo;
* {@hide}
*/
oneway interface IInputMethod {
- void initializeInternal(IBinder token, int displayId, IInputMethodPrivilegedOperations privOps,
+ void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
int configChanges);
void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo,
diff --git a/core/java/com/android/internal/view/ScrollCaptureInternal.java b/core/java/com/android/internal/view/ScrollCaptureInternal.java
index e3a9fda7b000..72b5488f4bac 100644
--- a/core/java/com/android/internal/view/ScrollCaptureInternal.java
+++ b/core/java/com/android/internal/view/ScrollCaptureInternal.java
@@ -25,6 +25,7 @@ import android.util.Log;
import android.view.ScrollCaptureCallback;
import android.view.View;
import android.view.ViewGroup;
+import android.webkit.WebView;
import android.widget.ListView;
/**
@@ -43,7 +44,7 @@ public class ScrollCaptureInternal {
private static final int DOWN = 1;
/**
- * Not a ViewGroup, or cannot scroll according to View APIs.
+ * Cannot scroll according to {@link View#canScrollVertically}.
*/
public static final int TYPE_FIXED = 0;
@@ -60,7 +61,7 @@ public class ScrollCaptureInternal {
public static final int TYPE_RECYCLING = 2;
/**
- * The ViewGroup scrolls, but has no child views in
+ * Unknown scrollable view with no child views (or not a subclass of ViewGroup).
*/
private static final int TYPE_OPAQUE = 3;
@@ -73,16 +74,6 @@ public class ScrollCaptureInternal {
* as excluded during scroll capture search.
*/
private static int detectScrollingType(View view) {
- // Must be a ViewGroup
- if (!(view instanceof ViewGroup)) {
- if (DEBUG_VERBOSE) {
- Log.v(TAG, "hint: not a subclass of ViewGroup");
- }
- return TYPE_FIXED;
- }
- if (DEBUG_VERBOSE) {
- Log.v(TAG, "hint: is a subclass of ViewGroup");
- }
// Confirm that it can scroll.
if (!(view.canScrollVertically(DOWN) || view.canScrollVertically(UP))) {
// Nothing to scroll here, move along.
@@ -94,6 +85,17 @@ public class ScrollCaptureInternal {
if (DEBUG_VERBOSE) {
Log.v(TAG, "hint: can be scrolled up or down");
}
+ // Must be a ViewGroup
+ if (!(view instanceof ViewGroup)) {
+ if (DEBUG_VERBOSE) {
+ Log.v(TAG, "hint: not a subclass of ViewGroup");
+ }
+ return TYPE_OPAQUE;
+ }
+ if (DEBUG_VERBOSE) {
+ Log.v(TAG, "hint: is a subclass of ViewGroup");
+ }
+
// ScrollViews accept only a single child.
if (((ViewGroup) view).getChildCount() > 1) {
if (DEBUG_VERBOSE) {
@@ -188,6 +190,18 @@ public class ScrollCaptureInternal {
}
return new ScrollCaptureViewSupport<>((ViewGroup) view,
new RecyclerViewCaptureHelper());
+ case TYPE_OPAQUE:
+ if (DEBUG) {
+ Log.d(TAG, "scroll capture: FOUND " + view.getClass().getName()
+ + "[" + resolveId(view.getContext(), view.getId()) + "]"
+ + " -> TYPE_OPAQUE");
+ }
+ if (view instanceof WebView) {
+ Log.d(TAG, "scroll capture: Using WebView support");
+ return new ScrollCaptureViewSupport<>((WebView) view,
+ new WebViewCaptureHelper());
+ }
+ break;
case TYPE_FIXED:
// ignore
break;
diff --git a/core/java/com/android/internal/view/WebViewCaptureHelper.java b/core/java/com/android/internal/view/WebViewCaptureHelper.java
new file mode 100644
index 000000000000..e6a311cfcda5
--- /dev/null
+++ b/core/java/com/android/internal/view/WebViewCaptureHelper.java
@@ -0,0 +1,100 @@
+/*
+ * 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.internal.view;
+
+import static android.util.MathUtils.constrain;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+import android.annotation.NonNull;
+import android.graphics.Rect;
+import android.webkit.WebView;
+
+/**
+ * ScrollCapture for WebView.
+ */
+class WebViewCaptureHelper implements ScrollCaptureViewHelper<WebView> {
+ private static final String TAG = "WebViewScrollCapture";
+
+ private final Rect mRequestWebViewLocal = new Rect();
+ private final Rect mWebViewBounds = new Rect();
+
+ private int mOriginScrollY;
+ private int mOriginScrollX;
+
+ @Override
+ public boolean onAcceptSession(@NonNull WebView view) {
+ return view.isVisibleToUser()
+ && (view.getContentHeight() * view.getScale()) > view.getHeight();
+ }
+
+ @Override
+ public void onPrepareForStart(@NonNull WebView view, @NonNull Rect scrollBounds) {
+ mOriginScrollX = view.getScrollX();
+ mOriginScrollY = view.getScrollY();
+ }
+
+ @NonNull
+ @Override
+ public ScrollResult onScrollRequested(@NonNull WebView view, @NonNull Rect scrollBounds,
+ @NonNull Rect requestRect) {
+
+ int scrollDelta = view.getScrollY() - mOriginScrollY;
+
+ ScrollResult result = new ScrollResult();
+ result.requestedArea = new Rect(requestRect);
+ result.availableArea = new Rect();
+ result.scrollDelta = scrollDelta;
+
+ mWebViewBounds.set(0, 0, view.getWidth(), view.getHeight());
+
+ if (!view.isVisibleToUser()) {
+ return result;
+ }
+
+ // Map the request into local coordinates
+ mRequestWebViewLocal.set(requestRect);
+ mRequestWebViewLocal.offset(0, -scrollDelta);
+
+ // Offset to center the rect vertically, clamp to available content
+ int upLimit = min(0, -view.getScrollY());
+ int contentHeightPx = (int) (view.getContentHeight() * view.getScale());
+ int downLimit = max(0, (contentHeightPx - view.getHeight()) - view.getScrollY());
+ int scrollToCenter = mRequestWebViewLocal.centerY() - mWebViewBounds.centerY();
+ int scrollMovement = constrain(scrollToCenter, upLimit, downLimit);
+
+ // Scroll and update relative based on the new position
+ view.scrollBy(mOriginScrollX, scrollMovement);
+ scrollDelta = view.getScrollY() - mOriginScrollY;
+ mRequestWebViewLocal.offset(0, -scrollMovement);
+ result.scrollDelta = scrollDelta;
+
+ if (mRequestWebViewLocal.intersect(mWebViewBounds)) {
+ result.availableArea = new Rect(mRequestWebViewLocal);
+ result.availableArea.offset(0, result.scrollDelta);
+ }
+ return result;
+ }
+
+ @Override
+ public void onPrepareForEnd(@NonNull WebView view) {
+ view.scrollTo(mOriginScrollX, mOriginScrollY);
+ }
+
+}
+
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index fd6038fd655d..4fc135c515b0 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -19,6 +19,7 @@ package com.android.internal.widget;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Canvas;
+import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.Path;
@@ -136,6 +137,7 @@ public class PointerLocationView extends View implements InputDeviceListener,
private final FontMetricsInt mTextMetrics = new FontMetricsInt();
private int mHeaderBottom;
private int mHeaderPaddingTop = 0;
+ private Insets mWaterfallInsets = Insets.NONE;
@UnsupportedAppUsage
private boolean mCurDown;
@UnsupportedAppUsage
@@ -229,8 +231,10 @@ public class PointerLocationView extends View implements InputDeviceListener,
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
if (insets.getDisplayCutout() != null) {
mHeaderPaddingTop = insets.getDisplayCutout().getSafeInsetTop();
+ mWaterfallInsets = insets.getDisplayCutout().getWaterfallInsets();
} else {
mHeaderPaddingTop = 0;
+ mWaterfallInsets = Insets.NONE;
}
return super.onApplyWindowInsets(insets);
}
@@ -266,11 +270,6 @@ public class PointerLocationView extends View implements InputDeviceListener,
@Override
protected void onDraw(Canvas canvas) {
- final int w = getWidth();
- final int itemW = w/7;
- final int base = mHeaderPaddingTop-mTextMetrics.ascent+1;
- final int bottom = mHeaderBottom;
-
final int NP = mPointers.size();
if (!mSystemGestureExclusion.isEmpty()) {
@@ -286,71 +285,7 @@ public class PointerLocationView extends View implements InputDeviceListener,
}
// Labels
- if (mActivePointerId >= 0) {
- final PointerState ps = mPointers.get(mActivePointerId);
-
- canvas.drawRect(0, mHeaderPaddingTop, itemW-1, bottom,mTextBackgroundPaint);
- canvas.drawText(mText.clear()
- .append("P: ").append(mCurNumPointers)
- .append(" / ").append(mMaxNumPointers)
- .toString(), 1, base, mTextPaint);
-
- final int N = ps.mTraceCount;
- if ((mCurDown && ps.mCurDown) || N == 0) {
- canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom,
- mTextBackgroundPaint);
- canvas.drawText(mText.clear()
- .append("X: ").append(ps.mCoords.x, 1)
- .toString(), 1 + itemW, base, mTextPaint);
- canvas.drawRect(itemW * 2, mHeaderPaddingTop, (itemW * 3) - 1, bottom,
- mTextBackgroundPaint);
- canvas.drawText(mText.clear()
- .append("Y: ").append(ps.mCoords.y, 1)
- .toString(), 1 + itemW * 2, base, mTextPaint);
- } else {
- float dx = ps.mTraceX[N - 1] - ps.mTraceX[0];
- float dy = ps.mTraceY[N - 1] - ps.mTraceY[0];
- canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom,
- Math.abs(dx) < mVC.getScaledTouchSlop()
- ? mTextBackgroundPaint : mTextLevelPaint);
- canvas.drawText(mText.clear()
- .append("dX: ").append(dx, 1)
- .toString(), 1 + itemW, base, mTextPaint);
- canvas.drawRect(itemW * 2, mHeaderPaddingTop, (itemW * 3) - 1, bottom,
- Math.abs(dy) < mVC.getScaledTouchSlop()
- ? mTextBackgroundPaint : mTextLevelPaint);
- canvas.drawText(mText.clear()
- .append("dY: ").append(dy, 1)
- .toString(), 1 + itemW * 2, base, mTextPaint);
- }
-
- canvas.drawRect(itemW * 3, mHeaderPaddingTop, (itemW * 4) - 1, bottom,
- mTextBackgroundPaint);
- canvas.drawText(mText.clear()
- .append("Xv: ").append(ps.mXVelocity, 3)
- .toString(), 1 + itemW * 3, base, mTextPaint);
-
- canvas.drawRect(itemW * 4, mHeaderPaddingTop, (itemW * 5) - 1, bottom,
- mTextBackgroundPaint);
- canvas.drawText(mText.clear()
- .append("Yv: ").append(ps.mYVelocity, 3)
- .toString(), 1 + itemW * 4, base, mTextPaint);
-
- canvas.drawRect(itemW * 5, mHeaderPaddingTop, (itemW * 6) - 1, bottom,
- mTextBackgroundPaint);
- canvas.drawRect(itemW * 5, mHeaderPaddingTop,
- (itemW * 5) + (ps.mCoords.pressure * itemW) - 1, bottom, mTextLevelPaint);
- canvas.drawText(mText.clear()
- .append("Prs: ").append(ps.mCoords.pressure, 2)
- .toString(), 1 + itemW * 5, base, mTextPaint);
-
- canvas.drawRect(itemW * 6, mHeaderPaddingTop, w, bottom, mTextBackgroundPaint);
- canvas.drawRect(itemW * 6, mHeaderPaddingTop,
- (itemW * 6) + (ps.mCoords.size * itemW) - 1, bottom, mTextLevelPaint);
- canvas.drawText(mText.clear()
- .append("Size: ").append(ps.mCoords.size, 2)
- .toString(), 1 + itemW * 6, base, mTextPaint);
- }
+ drawLabels(canvas);
// Pointer trace.
for (int p = 0; p < NP; p++) {
@@ -463,6 +398,84 @@ public class PointerLocationView extends View implements InputDeviceListener,
}
}
+ private void drawLabels(Canvas canvas) {
+ if (mActivePointerId < 0) {
+ return;
+ }
+
+ final int w = getWidth() - mWaterfallInsets.left - mWaterfallInsets.right;
+ final int itemW = w / 7;
+ final int base = mHeaderPaddingTop - mTextMetrics.ascent + 1;
+ final int bottom = mHeaderBottom;
+
+ canvas.save();
+ canvas.translate(mWaterfallInsets.left, 0);
+ final PointerState ps = mPointers.get(mActivePointerId);
+
+ canvas.drawRect(0, mHeaderPaddingTop, itemW - 1, bottom, mTextBackgroundPaint);
+ canvas.drawText(mText.clear()
+ .append("P: ").append(mCurNumPointers)
+ .append(" / ").append(mMaxNumPointers)
+ .toString(), 1, base, mTextPaint);
+
+ final int count = ps.mTraceCount;
+ if ((mCurDown && ps.mCurDown) || count == 0) {
+ canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom,
+ mTextBackgroundPaint);
+ canvas.drawText(mText.clear()
+ .append("X: ").append(ps.mCoords.x, 1)
+ .toString(), 1 + itemW, base, mTextPaint);
+ canvas.drawRect(itemW * 2, mHeaderPaddingTop, (itemW * 3) - 1, bottom,
+ mTextBackgroundPaint);
+ canvas.drawText(mText.clear()
+ .append("Y: ").append(ps.mCoords.y, 1)
+ .toString(), 1 + itemW * 2, base, mTextPaint);
+ } else {
+ float dx = ps.mTraceX[count - 1] - ps.mTraceX[0];
+ float dy = ps.mTraceY[count - 1] - ps.mTraceY[0];
+ canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom,
+ Math.abs(dx) < mVC.getScaledTouchSlop()
+ ? mTextBackgroundPaint : mTextLevelPaint);
+ canvas.drawText(mText.clear()
+ .append("dX: ").append(dx, 1)
+ .toString(), 1 + itemW, base, mTextPaint);
+ canvas.drawRect(itemW * 2, mHeaderPaddingTop, (itemW * 3) - 1, bottom,
+ Math.abs(dy) < mVC.getScaledTouchSlop()
+ ? mTextBackgroundPaint : mTextLevelPaint);
+ canvas.drawText(mText.clear()
+ .append("dY: ").append(dy, 1)
+ .toString(), 1 + itemW * 2, base, mTextPaint);
+ }
+
+ canvas.drawRect(itemW * 3, mHeaderPaddingTop, (itemW * 4) - 1, bottom,
+ mTextBackgroundPaint);
+ canvas.drawText(mText.clear()
+ .append("Xv: ").append(ps.mXVelocity, 3)
+ .toString(), 1 + itemW * 3, base, mTextPaint);
+
+ canvas.drawRect(itemW * 4, mHeaderPaddingTop, (itemW * 5) - 1, bottom,
+ mTextBackgroundPaint);
+ canvas.drawText(mText.clear()
+ .append("Yv: ").append(ps.mYVelocity, 3)
+ .toString(), 1 + itemW * 4, base, mTextPaint);
+
+ canvas.drawRect(itemW * 5, mHeaderPaddingTop, (itemW * 6) - 1, bottom,
+ mTextBackgroundPaint);
+ canvas.drawRect(itemW * 5, mHeaderPaddingTop,
+ (itemW * 5) + (ps.mCoords.pressure * itemW) - 1, bottom, mTextLevelPaint);
+ canvas.drawText(mText.clear()
+ .append("Prs: ").append(ps.mCoords.pressure, 2)
+ .toString(), 1 + itemW * 5, base, mTextPaint);
+
+ canvas.drawRect(itemW * 6, mHeaderPaddingTop, w, bottom, mTextBackgroundPaint);
+ canvas.drawRect(itemW * 6, mHeaderPaddingTop,
+ (itemW * 6) + (ps.mCoords.size * itemW) - 1, bottom, mTextLevelPaint);
+ canvas.drawText(mText.clear()
+ .append("Size: ").append(ps.mCoords.size, 2)
+ .toString(), 1 + itemW * 6, base, mTextPaint);
+ canvas.restore();
+ }
+
private void logMotionEvent(String type, MotionEvent event) {
final int action = event.getAction();
final int N = event.getHistorySize();
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 6b9d3754c247..4f27d218f05c 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -224,6 +224,7 @@ cc_library_shared {
"fd_utils.cpp",
"android_hardware_input_InputWindowHandle.cpp",
"android_hardware_input_InputApplicationHandle.cpp",
+ "android_window_WindowInfosListener.cpp",
],
static_libs: [
@@ -231,15 +232,19 @@ cc_library_shared {
"libbinderthreadstateutils",
"libdmabufinfo",
"libgif",
+ "libgui_window_info_static",
"libseccomp_policy",
"libgrallocusage",
"libscrypt_static",
"libstatssocket_lazy",
+ "libskia",
],
shared_libs: [
"audioclient-types-aidl-cpp",
"audioflinger-aidl-cpp",
+ "audiopolicy-types-aidl-cpp",
+ "spatializer-aidl-cpp",
"av-types-aidl-cpp",
"android.hardware.camera.device@3.2",
"libandroidicu",
@@ -370,6 +375,7 @@ cc_library_shared {
"libinput",
"libbinderthreadstateutils",
"libsqlite",
+ "libgui_window_info_static",
],
shared_libs: [
// libbinder needs to be shared since it has global state
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 2fd1e543cc5b..c18d227fa674 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -207,6 +207,7 @@ extern int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv *env);
extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env);
extern int register_com_android_internal_security_VerityUtils(JNIEnv* env);
extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
+extern int register_android_window_WindowInfosListener(JNIEnv* env);
// Namespace for Android Runtime flags applied during boot time.
static const char* RUNTIME_NATIVE_BOOT_NAMESPACE = "runtime_native_boot";
@@ -1649,6 +1650,8 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader),
REG_JNI(register_com_android_internal_os_KernelSingleProcessCpuThreadReader),
REG_JNI(register_com_android_internal_os_KernelSingleUidTimeReader),
+
+ REG_JNI(register_android_window_WindowInfosListener),
};
/*
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 666ab957b8ce..af77cb7aae8d 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -22,6 +22,7 @@ per-file android_view_PointerIcon.* = file:/services/core/java/com/android/serve
# WindowManager
per-file android_graphics_BLASTBufferQueue.cpp = file:/services/core/java/com/android/server/wm/OWNERS
per-file android_view_Surface* = file:/services/core/java/com/android/server/wm/OWNERS
+per-file android_window_WindowInfosListener.cpp = file:/services/core/java/com/android/server/wm/OWNERS
# Resources
per-file android_content_res_* = file:/core/java/android/content/res/OWNERS
diff --git a/core/jni/android_hardware_input_InputApplicationHandle.cpp b/core/jni/android_hardware_input_InputApplicationHandle.cpp
index 995bfa97ab2e..24d35316ef20 100644
--- a/core/jni/android_hardware_input_InputApplicationHandle.cpp
+++ b/core/jni/android_hardware_input_InputApplicationHandle.cpp
@@ -28,6 +28,8 @@
namespace android {
static struct {
+ jclass clazz;
+ jmethodID ctor;
jfieldID ptr;
jfieldID name;
jfieldID dispatchingTimeoutMillis;
@@ -101,6 +103,15 @@ std::shared_ptr<InputApplicationHandle> android_view_InputApplicationHandle_getH
return *handle;
}
+jobject android_view_InputApplicationHandle_fromInputApplicationInfo(
+ JNIEnv* env, gui::InputApplicationInfo inputApplicationInfo) {
+ jobject binderObject = javaObjectForIBinder(env, inputApplicationInfo.token);
+ ScopedLocalRef<jstring> name(env, env->NewStringUTF(inputApplicationInfo.name.data()));
+ return env->NewObject(gInputApplicationHandleClassInfo.clazz,
+ gInputApplicationHandleClassInfo.ctor, binderObject, name.get(),
+ inputApplicationInfo.dispatchingTimeoutMillis);
+}
+
// --- JNI ---
static void android_view_InputApplicationHandle_nativeDispose(JNIEnv* env, jobject obj) {
@@ -131,6 +142,10 @@ static const JNINativeMethod gInputApplicationHandleMethods[] = {
var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
+#define GET_METHOD_ID(var, clazz, methodName, methodSignature) \
+ var = env->GetMethodID(clazz, methodName, methodSignature); \
+ LOG_ALWAYS_FATAL_IF(!(var), "Unable to find method " methodName);
+
int register_android_view_InputApplicationHandle(JNIEnv* env) {
int res = jniRegisterNativeMethods(env, "android/view/InputApplicationHandle",
gInputApplicationHandleMethods, NELEM(gInputApplicationHandleMethods));
@@ -139,6 +154,10 @@ int register_android_view_InputApplicationHandle(JNIEnv* env) {
jclass clazz;
FIND_CLASS(clazz, "android/view/InputApplicationHandle");
+ gInputApplicationHandleClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+
+ GET_METHOD_ID(gInputApplicationHandleClassInfo.ctor, clazz, "<init>",
+ "(Landroid/os/IBinder;Ljava/lang/String;J)V");
GET_FIELD_ID(gInputApplicationHandleClassInfo.ptr, clazz,
"ptr", "J");
diff --git a/core/jni/android_hardware_input_InputApplicationHandle.h b/core/jni/android_hardware_input_InputApplicationHandle.h
index ec99d6da5b8e..5d88d8e25160 100644
--- a/core/jni/android_hardware_input_InputApplicationHandle.h
+++ b/core/jni/android_hardware_input_InputApplicationHandle.h
@@ -19,7 +19,7 @@
#include <string>
-#include <input/InputApplication.h>
+#include <gui/InputApplication.h>
#include <nativehelper/JNIHelp.h>
#include "jni.h"
@@ -42,6 +42,9 @@ private:
extern std::shared_ptr<InputApplicationHandle> android_view_InputApplicationHandle_getHandle(
JNIEnv* env, jobject inputApplicationHandleObj);
+extern jobject android_view_InputApplicationHandle_fromInputApplicationInfo(
+ JNIEnv* env, gui::InputApplicationInfo inputApplicationInfo);
+
} // namespace android
#endif // _ANDROID_VIEW_INPUT_APPLICATION_HANDLE_H
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 463d909821b1..e4ef7d39d77c 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -26,14 +26,19 @@
#include <ui/Region.h>
#include <utils/threads.h>
+#include <android/graphics/matrix.h>
+#include <gui/WindowInfo.h>
+#include "SkRegion.h"
#include "android_hardware_input_InputApplicationHandle.h"
#include "android_util_Binder.h"
#include "core_jni_helpers.h"
-#include "input/InputWindow.h"
#include "jni.h"
namespace android {
+using gui::TouchOcclusionMode;
+using gui::WindowInfo;
+
struct WeakRefHandleField {
jfieldID ctrl;
jmethodID get;
@@ -41,6 +46,8 @@ struct WeakRefHandleField {
};
static struct {
+ jclass clazz;
+ jmethodID ctor;
jfieldID ptr;
jfieldID inputApplicationHandle;
jfieldID token;
@@ -66,11 +73,18 @@ static struct {
jfieldID packageName;
jfieldID inputFeatures;
jfieldID displayId;
- jfieldID portalToDisplayId;
jfieldID replaceTouchableRegionWithCrop;
WeakRefHandleField touchableRegionSurfaceControl;
+ jfieldID transform;
+ jfieldID windowToken;
} gInputWindowHandleClassInfo;
+static struct {
+ jclass clazz;
+ jmethodID ctor;
+ jfieldID nativeRegion;
+} gRegionClassInfo;
+
static Mutex gHandleMutex;
@@ -115,9 +129,9 @@ bool NativeInputWindowHandle::updateInfo() {
mInfo.name = getStringField(env, obj, gInputWindowHandleClassInfo.name, "<null>");
- mInfo.flags = Flags<InputWindowInfo::Flag>(
+ mInfo.flags = Flags<WindowInfo::Flag>(
env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsFlags));
- mInfo.type = static_cast<InputWindowInfo::Type>(
+ mInfo.type = static_cast<WindowInfo::Type>(
env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsType));
mInfo.dispatchingTimeout = std::chrono::milliseconds(
env->GetLongField(obj, gInputWindowHandleClassInfo.dispatchingTimeoutMillis));
@@ -159,12 +173,10 @@ bool NativeInputWindowHandle::updateInfo() {
mInfo.ownerUid = env->GetIntField(obj,
gInputWindowHandleClassInfo.ownerUid);
mInfo.packageName = getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>");
- mInfo.inputFeatures = static_cast<InputWindowInfo::Feature>(
+ mInfo.inputFeatures = static_cast<WindowInfo::Feature>(
env->GetIntField(obj, gInputWindowHandleClassInfo.inputFeatures));
mInfo.displayId = env->GetIntField(obj,
gInputWindowHandleClassInfo.displayId);
- mInfo.portalToDisplayId = env->GetIntField(obj,
- gInputWindowHandleClassInfo.portalToDisplayId);
jobject inputApplicationHandleObj = env->GetObjectField(obj,
gInputWindowHandleClassInfo.inputApplicationHandle);
@@ -204,6 +216,14 @@ bool NativeInputWindowHandle::updateInfo() {
mInfo.touchableRegionCropHandle.clear();
}
+ jobject windowTokenObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.windowToken);
+ if (windowTokenObj) {
+ mInfo.windowToken = ibinderForJavaObject(env, windowTokenObj);
+ env->DeleteLocalRef(windowTokenObj);
+ } else {
+ mInfo.windowToken.clear();
+ }
+
env->DeleteLocalRef(obj);
return true;
}
@@ -233,6 +253,81 @@ sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(
return handle;
}
+jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env, gui::WindowInfo windowInfo) {
+ ScopedLocalRef<jobject>
+ applicationHandle(env,
+ android_view_InputApplicationHandle_fromInputApplicationInfo(
+ env, windowInfo.applicationInfo));
+
+ jobject inputWindowHandle =
+ env->NewObject(gInputWindowHandleClassInfo.clazz, gInputWindowHandleClassInfo.ctor,
+ applicationHandle.get(), windowInfo.displayId);
+ env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.token,
+ javaObjectForIBinder(env, windowInfo.token));
+ env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.name,
+ env->NewStringUTF(windowInfo.name.data()));
+ env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.layoutParamsFlags,
+ static_cast<uint32_t>(windowInfo.flags.get()));
+ env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.layoutParamsType,
+ static_cast<int32_t>(windowInfo.type));
+ env->SetLongField(inputWindowHandle, gInputWindowHandleClassInfo.dispatchingTimeoutMillis,
+ std::chrono::duration_cast<std::chrono::milliseconds>(
+ windowInfo.dispatchingTimeout)
+ .count());
+ env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.frameLeft,
+ windowInfo.frameLeft);
+ env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.frameTop, windowInfo.frameTop);
+ env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.frameRight,
+ windowInfo.frameRight);
+ env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.frameBottom,
+ windowInfo.frameBottom);
+ env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.surfaceInset,
+ windowInfo.surfaceInset);
+ env->SetFloatField(inputWindowHandle, gInputWindowHandleClassInfo.scaleFactor,
+ windowInfo.globalScaleFactor);
+
+ SkRegion* region = new SkRegion();
+ for (const auto& r : windowInfo.touchableRegion) {
+ region->op({r.left, r.top, r.right, r.bottom}, SkRegion::kUnion_Op);
+ }
+ ScopedLocalRef<jobject> regionObj(env,
+ env->NewObject(gRegionClassInfo.clazz,
+ gRegionClassInfo.ctor));
+ env->SetLongField(regionObj.get(), gRegionClassInfo.nativeRegion,
+ reinterpret_cast<jlong>(region));
+ env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.touchableRegion,
+ regionObj.get());
+
+ env->SetBooleanField(inputWindowHandle, gInputWindowHandleClassInfo.visible,
+ windowInfo.visible);
+ env->SetBooleanField(inputWindowHandle, gInputWindowHandleClassInfo.focusable,
+ windowInfo.focusable);
+ env->SetBooleanField(inputWindowHandle, gInputWindowHandleClassInfo.hasWallpaper,
+ windowInfo.hasWallpaper);
+ env->SetBooleanField(inputWindowHandle, gInputWindowHandleClassInfo.paused, windowInfo.paused);
+ env->SetBooleanField(inputWindowHandle, gInputWindowHandleClassInfo.trustedOverlay,
+ windowInfo.trustedOverlay);
+ env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.touchOcclusionMode,
+ static_cast<int32_t>(windowInfo.touchOcclusionMode));
+ env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.ownerPid, windowInfo.ownerPid);
+ env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.ownerUid, windowInfo.ownerUid);
+ env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.packageName,
+ env->NewStringUTF(windowInfo.packageName.data()));
+ env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.inputFeatures,
+ static_cast<int32_t>(windowInfo.inputFeatures.get()));
+
+ float transformVals[9];
+ for (int i = 0; i < 9; i++) {
+ transformVals[i] = windowInfo.transform[i % 3][i / 3];
+ }
+ ScopedLocalRef<jobject> matrixObj(env, AMatrix_newInstance(env, transformVals));
+ env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.transform, matrixObj.get());
+
+ env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.windowToken,
+ javaObjectForIBinder(env, windowInfo.windowToken));
+
+ return inputWindowHandle;
+}
// --- JNI ---
@@ -275,6 +370,10 @@ int register_android_view_InputWindowHandle(JNIEnv* env) {
jclass clazz;
FIND_CLASS(clazz, "android/view/InputWindowHandle");
+ gInputWindowHandleClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+
+ GET_METHOD_ID(gInputWindowHandleClassInfo.ctor, clazz, "<init>",
+ "(Landroid/view/InputApplicationHandle;I)V");
GET_FIELD_ID(gInputWindowHandleClassInfo.ptr, clazz,
"ptr", "J");
@@ -348,12 +447,15 @@ int register_android_view_InputWindowHandle(JNIEnv* env) {
GET_FIELD_ID(gInputWindowHandleClassInfo.displayId, clazz,
"displayId", "I");
- GET_FIELD_ID(gInputWindowHandleClassInfo.portalToDisplayId, clazz,
- "portalToDisplayId", "I");
-
GET_FIELD_ID(gInputWindowHandleClassInfo.replaceTouchableRegionWithCrop, clazz,
"replaceTouchableRegionWithCrop", "Z");
+ GET_FIELD_ID(gInputWindowHandleClassInfo.transform, clazz, "transform",
+ "Landroid/graphics/Matrix;");
+
+ GET_FIELD_ID(gInputWindowHandleClassInfo.windowToken, clazz, "windowToken",
+ "Landroid/os/IBinder;");
+
jclass weakRefClazz;
FIND_CLASS(weakRefClazz, "java/lang/ref/Reference");
@@ -368,6 +470,11 @@ int register_android_view_InputWindowHandle(JNIEnv* env) {
GET_FIELD_ID(gInputWindowHandleClassInfo.touchableRegionSurfaceControl.mNativeObject,
surfaceControlClazz, "mNativeObject", "J");
+ jclass regionClazz;
+ FIND_CLASS(regionClazz, "android/graphics/Region");
+ gRegionClassInfo.clazz = MakeGlobalRefOrDie(env, regionClazz);
+ GET_METHOD_ID(gRegionClassInfo.ctor, gRegionClassInfo.clazz, "<init>", "()V");
+ GET_FIELD_ID(gRegionClassInfo.nativeRegion, gRegionClassInfo.clazz, "mNativeRegion", "J");
return 0;
}
diff --git a/core/jni/android_hardware_input_InputWindowHandle.h b/core/jni/android_hardware_input_InputWindowHandle.h
index de5bd6ef97f4..408e0f1bfa36 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.h
+++ b/core/jni/android_hardware_input_InputWindowHandle.h
@@ -17,14 +17,14 @@
#ifndef _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H
#define _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H
-#include <input/InputWindow.h>
+#include <gui/WindowInfo.h>
#include <nativehelper/JNIHelp.h>
#include "jni.h"
namespace android {
-class NativeInputWindowHandle : public InputWindowHandle {
+class NativeInputWindowHandle : public gui::WindowInfoHandle {
public:
NativeInputWindowHandle(jweak objWeak);
virtual ~NativeInputWindowHandle();
@@ -37,10 +37,12 @@ private:
jweak mObjWeak;
};
-
extern sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(
JNIEnv* env, jobject inputWindowHandleObj);
+extern jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env,
+ gui::WindowInfo windowInfo);
+
} // namespace android
#endif // _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H
diff --git a/core/jni/android_media_AudioDeviceAttributes.cpp b/core/jni/android_media_AudioDeviceAttributes.cpp
index 2a16dce99125..6879a6008f5a 100644
--- a/core/jni/android_media_AudioDeviceAttributes.cpp
+++ b/core/jni/android_media_AudioDeviceAttributes.cpp
@@ -24,6 +24,11 @@ using namespace android;
static jclass gAudioDeviceAttributesClass;
static jmethodID gAudioDeviceAttributesCstor;
+static struct {
+ jfieldID mAddress;
+ jfieldID mNativeType;
+ // other fields unused by JNI
+} gAudioDeviceAttributesFields;
namespace android {
@@ -33,12 +38,25 @@ jint createAudioDeviceAttributesFromNative(JNIEnv *env, jobject *jAudioDeviceAtt
jint jNativeType = (jint)devTypeAddr->mType;
ScopedLocalRef<jstring> jAddress(env, env->NewStringUTF(devTypeAddr->getAddress()));
- *jAudioDeviceAttributes = env->NewObject(gAudioDeviceAttributesClass, gAudioDeviceAttributesCstor,
- jNativeType, jAddress.get());
+ *jAudioDeviceAttributes =
+ env->NewObject(gAudioDeviceAttributesClass, gAudioDeviceAttributesCstor,
+ jNativeType, jAddress.get());
return jStatus;
}
+jint createAudioDeviceTypeAddrFromJava(JNIEnv *env, AudioDeviceTypeAddr *devTypeAddr,
+ const jobject jAudioDeviceAttributes) {
+ devTypeAddr->mType = (audio_devices_t)env->GetIntField(jAudioDeviceAttributes,
+ gAudioDeviceAttributesFields.mNativeType);
+
+ jstring jAddress = (jstring)env->GetObjectField(jAudioDeviceAttributes,
+ gAudioDeviceAttributesFields.mAddress);
+ devTypeAddr->setAddress(ScopedUtfChars(env, jAddress).c_str());
+
+ return AUDIO_JAVA_SUCCESS;
+}
+
} // namespace android
int register_android_media_AudioDeviceAttributes(JNIEnv *env) {
@@ -48,5 +66,10 @@ int register_android_media_AudioDeviceAttributes(JNIEnv *env) {
gAudioDeviceAttributesCstor =
GetMethodIDOrDie(env, audioDeviceTypeAddressClass, "<init>", "(ILjava/lang/String;)V");
+ gAudioDeviceAttributesFields.mNativeType =
+ GetFieldIDOrDie(env, gAudioDeviceAttributesClass, "mNativeType", "I");
+ gAudioDeviceAttributesFields.mAddress =
+ GetFieldIDOrDie(env, gAudioDeviceAttributesClass, "mAddress", "Ljava/lang/String;");
+
return 0;
}
diff --git a/core/jni/android_media_AudioDeviceAttributes.h b/core/jni/android_media_AudioDeviceAttributes.h
index b49d9ba515b8..4a1f40d9bb7c 100644
--- a/core/jni/android_media_AudioDeviceAttributes.h
+++ b/core/jni/android_media_AudioDeviceAttributes.h
@@ -28,6 +28,9 @@ namespace android {
extern jint createAudioDeviceAttributesFromNative(JNIEnv *env, jobject *jAudioDeviceAttributes,
const AudioDeviceTypeAddr *devTypeAddr);
+
+extern jint createAudioDeviceTypeAddrFromJava(JNIEnv *env, AudioDeviceTypeAddr *devTypeAddr,
+ const jobject jAudioDeviceAttributes);
} // namespace android
#endif \ No newline at end of file
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 4b93363b5b90..509b7ad74a29 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -27,6 +27,8 @@
#include "core_jni_helpers.h"
#include <android/media/AudioVibratorInfo.h>
+#include <android/media/INativeSpatializerCallback.h>
+#include <android/media/ISpatializer.h>
#include <audiomanager/AudioManager.h>
#include <media/AudioPolicy.h>
#include <media/AudioSystem.h>
@@ -207,6 +209,7 @@ static struct {
jmethodID getId;
jmethodID getResonantFrequency;
jmethodID getQFactor;
+ jmethodID getMaxAmplitude;
} gVibratorMethods;
static Mutex gLock;
@@ -2022,6 +2025,18 @@ android_media_AudioSystem_registerRoutingCallback(JNIEnv *env, jobject thiz)
AudioSystem::setRoutingCallback(android_media_AudioSystem_routing_callback);
}
+void javaAudioFormatToNativeAudioConfig(JNIEnv *env, audio_config_t *nConfig,
+ const jobject jFormat, bool isInput) {
+ *nConfig = AUDIO_CONFIG_INITIALIZER;
+ nConfig->format = audioFormatToNative(env->GetIntField(jFormat, gAudioFormatFields.mEncoding));
+ nConfig->sample_rate = env->GetIntField(jFormat, gAudioFormatFields.mSampleRate);
+ jint jChannelMask = env->GetIntField(jFormat, gAudioFormatFields.mChannelMask);
+ if (isInput) {
+ nConfig->channel_mask = inChannelMaskToNative(jChannelMask);
+ } else {
+ nConfig->channel_mask = outChannelMaskToNative(jChannelMask);
+ }
+}
static jint convertAudioMixToNative(JNIEnv *env,
AudioMix *nAudioMix,
@@ -2042,13 +2057,7 @@ static jint convertAudioMixToNative(JNIEnv *env,
nAudioMix->mCbFlags = env->GetIntField(jAudioMix, gAudioMixFields.mCallbackFlags);
jobject jFormat = env->GetObjectField(jAudioMix, gAudioMixFields.mFormat);
- nAudioMix->mFormat = AUDIO_CONFIG_INITIALIZER;
- nAudioMix->mFormat.sample_rate = env->GetIntField(jFormat,
- gAudioFormatFields.mSampleRate);
- nAudioMix->mFormat.channel_mask = outChannelMaskToNative(env->GetIntField(jFormat,
- gAudioFormatFields.mChannelMask));
- nAudioMix->mFormat.format = audioFormatToNative(env->GetIntField(jFormat,
- gAudioFormatFields.mEncoding));
+ javaAudioFormatToNativeAudioConfig(env, &nAudioMix->mFormat, jFormat, false /*isInput*/);
env->DeleteLocalRef(jFormat);
jobject jRule = env->GetObjectField(jAudioMix, gAudioMixFields.mRule);
@@ -2704,11 +2713,65 @@ static jint android_media_AudioSystem_setVibratorInfos(JNIEnv *env, jobject thiz
vibratorInfo.resonantFrequency =
env->CallFloatMethod(jVibrator.get(), gVibratorMethods.getResonantFrequency);
vibratorInfo.qFactor = env->CallFloatMethod(jVibrator.get(), gVibratorMethods.getQFactor);
+ vibratorInfo.maxAmplitude =
+ env->CallFloatMethod(jVibrator.get(), gVibratorMethods.getMaxAmplitude);
vibratorInfos.push_back(vibratorInfo);
}
return (jint)check_AudioSystem_Command(AudioSystem::setVibratorInfos(vibratorInfos));
}
+static jobject android_media_AudioSystem_getSpatializer(JNIEnv *env, jobject thiz,
+ jobject jISpatializerCallback) {
+ sp<media::INativeSpatializerCallback> nISpatializerCallback
+ = interface_cast<media::INativeSpatializerCallback>(
+ ibinderForJavaObject(env, jISpatializerCallback));
+ sp<media::ISpatializer> nSpatializer;
+ status_t status = AudioSystem::getSpatializer(nISpatializerCallback,
+ &nSpatializer);
+ if (status != NO_ERROR) {
+ return nullptr;
+ }
+ return javaObjectForIBinder(env, IInterface::asBinder(nSpatializer));
+}
+
+static jboolean android_media_AudioSystem_canBeSpatialized(JNIEnv *env, jobject thiz,
+ jobject jaa, jobject jFormat,
+ jobjectArray jDeviceArray) {
+ JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
+ jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
+ if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+ return false;
+ }
+
+ AudioDeviceTypeAddrVector nDevices;
+
+ const size_t numDevices = env->GetArrayLength(jDeviceArray);
+ for (size_t i = 0; i < numDevices; ++i) {
+ AudioDeviceTypeAddr device;
+ jobject jDevice = env->GetObjectArrayElement(jDeviceArray, i);
+ if (jDevice == nullptr) {
+ return false;
+ }
+ jStatus = createAudioDeviceTypeAddrFromJava(env, &device, jDevice);
+ if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+ return false;
+ }
+ nDevices.push_back(device);
+ }
+
+ audio_config_t nConfig;
+ javaAudioFormatToNativeAudioConfig(env, &nConfig, jFormat, false /*isInput*/);
+
+ bool canBeSpatialized;
+ status_t status =
+ AudioSystem::canBeSpatialized(paa.get(), &nConfig, nDevices, &canBeSpatialized);
+ if (status != NO_ERROR) {
+ ALOGW("%s native returned error %d", __func__, status);
+ return false;
+ }
+ return canBeSpatialized;
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gMethods[] =
@@ -2845,7 +2908,15 @@ static const JNINativeMethod gMethods[] =
(void *)android_media_AudioSystem_removeUserIdDeviceAffinities},
{"setCurrentImeUid", "(I)I", (void *)android_media_AudioSystem_setCurrentImeUid},
{"setVibratorInfos", "(Ljava/util/List;)I",
- (void *)android_media_AudioSystem_setVibratorInfos}};
+ (void *)android_media_AudioSystem_setVibratorInfos},
+ {"nativeGetSpatializer",
+ "(Landroid/media/INativeSpatializerCallback;)Landroid/os/IBinder;",
+ (void *)android_media_AudioSystem_getSpatializer},
+ {"canBeSpatialized",
+ "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;"
+ "[Landroid/media/AudioDeviceAttributes;)Z",
+ (void *)android_media_AudioSystem_canBeSpatialized}};
+
static const JNINativeMethod gEventHandlerMethods[] = {
{"native_setup",
@@ -3070,6 +3141,8 @@ int register_android_media_AudioSystem(JNIEnv *env)
gVibratorMethods.getResonantFrequency =
GetMethodIDOrDie(env, vibratorClass, "getResonantFrequency", "()F");
gVibratorMethods.getQFactor = GetMethodIDOrDie(env, vibratorClass, "getQFactor", "()F");
+ gVibratorMethods.getMaxAmplitude =
+ GetMethodIDOrDie(env, vibratorClass, "getHapticChannelMaximumAmplitude", "()F");
AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index e93b00d7b148..86d781033e5e 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -92,6 +92,7 @@ static struct configuration_offsets_t {
jfieldID mSmallestScreenWidthDpOffset;
jfieldID mScreenWidthDpOffset;
jfieldID mScreenHeightDpOffset;
+ jfieldID mScreenLayoutOffset;
} gConfigurationOffsets;
static struct arraymap_offsets_t {
@@ -1019,6 +1020,7 @@ static jobject ConstructConfigurationObject(JNIEnv* env, const ResTable_config&
config.smallestScreenWidthDp);
env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp);
env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp);
+ env->SetIntField(result, gConfigurationOffsets.mScreenLayoutOffset, config.screenLayout);
return result;
}
@@ -1553,6 +1555,8 @@ int register_android_content_AssetManager(JNIEnv* env) {
GetFieldIDOrDie(env, configurationClass, "screenWidthDp", "I");
gConfigurationOffsets.mScreenHeightDpOffset =
GetFieldIDOrDie(env, configurationClass, "screenHeightDp", "I");
+ gConfigurationOffsets.mScreenLayoutOffset =
+ GetFieldIDOrDie(env, configurationClass, "screenLayout", "I");
jclass arrayMapClass = FindClassOrDie(env, "android/util/ArrayMap");
gArrayMapOffsets.classObject = MakeGlobalRefOrDie(env, arrayMapClass);
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 45e3d1b97e83..16366a4e5bec 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -155,6 +155,7 @@ status_t NativeInputEventSender::sendMotionEvent(uint32_t seq, const MotionEvent
event->getYPrecision(),
event->getRawXCursorPosition(),
event->getRawYCursorPosition(),
+ event->getDisplayOrientation(),
event->getDisplaySize().x,
event->getDisplaySize().y, event->getDownTime(),
event->getHistoricalEventTime(i),
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 6971301cec32..cabf3abe350a 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -22,6 +22,7 @@
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
#include <attestation/HmacKeyManager.h>
+#include <gui/constants.h>
#include <input/Input.h>
#include <nativehelper/ScopedUtfChars.h>
#include <utils/Log.h>
@@ -56,6 +57,8 @@ static struct {
jfieldID toolMajor;
jfieldID toolMinor;
jfieldID orientation;
+ jfieldID relativeX;
+ jfieldID relativeY;
} gPointerCoordsClassInfo;
static struct {
@@ -212,6 +215,12 @@ static void pointerCoordsToNative(JNIEnv* env, jobject pointerCoordsObj,
env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.toolMinor));
outRawPointerCoords->setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.orientation));
+ outRawPointerCoords->setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X,
+ env->GetFloatField(pointerCoordsObj,
+ gPointerCoordsClassInfo.relativeX));
+ outRawPointerCoords->setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y,
+ env->GetFloatField(pointerCoordsObj,
+ gPointerCoordsClassInfo.relativeY));
BitSet64 bits =
BitSet64(env->GetLongField(pointerCoordsObj, gPointerCoordsClassInfo.mPackedAxisBits));
@@ -261,6 +270,12 @@ static void pointerCoordsFromNative(JNIEnv* env, const PointerCoords* rawPointer
float rawY = rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_Y);
vec2 transformed = transform.transform(rawX, rawY);
+ float rawRelX = rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+ float rawRelY = rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+ // Apply only rotation and scale, not translation.
+ const vec2 transformedOrigin = transform.transform(0, 0);
+ const vec2 transformedRel = transform.transform(rawRelX, rawRelY) - transformedOrigin;
+
env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.x, transformed.x);
env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.y, transformed.y);
env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.pressure,
@@ -277,6 +292,8 @@ static void pointerCoordsFromNative(JNIEnv* env, const PointerCoords* rawPointer
rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR));
env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.orientation,
rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+ env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.relativeX, transformedRel.x);
+ env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.relativeY, transformedRel.y);
uint64_t outBits = 0;
BitSet64 bits = BitSet64(rawPointerCoords->bits);
@@ -289,6 +306,8 @@ static void pointerCoordsFromNative(JNIEnv* env, const PointerCoords* rawPointer
bits.clearBit(AMOTION_EVENT_AXIS_TOOL_MAJOR);
bits.clearBit(AMOTION_EVENT_AXIS_TOOL_MINOR);
bits.clearBit(AMOTION_EVENT_AXIS_ORIENTATION);
+ bits.clearBit(AMOTION_EVENT_AXIS_RELATIVE_X);
+ bits.clearBit(AMOTION_EVENT_AXIS_RELATIVE_Y);
if (!bits.isEmpty()) {
uint32_t packedAxesCount = bits.count();
jfloatArray outValuesArray = obtainPackedAxisValuesArray(env, packedAxesCount,
@@ -378,8 +397,8 @@ static jlong android_view_MotionEvent_nativeInitialize(
flags, edgeFlags, metaState, buttonState,
static_cast<MotionClassification>(classification), transform, xPrecision,
yPrecision, AMOTION_EVENT_INVALID_CURSOR_POSITION,
- AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_DISPLAY_SIZE,
- AMOTION_EVENT_INVALID_DISPLAY_SIZE, downTimeNanos, eventTimeNanos,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
+ INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, downTimeNanos, eventTimeNanos,
pointerCount, pointerProperties, rawPointerCoords);
return reinterpret_cast<jlong>(event.release());
@@ -872,6 +891,8 @@ int register_android_view_MotionEvent(JNIEnv* env) {
gPointerCoordsClassInfo.toolMajor = GetFieldIDOrDie(env, clazz, "toolMajor", "F");
gPointerCoordsClassInfo.toolMinor = GetFieldIDOrDie(env, clazz, "toolMinor", "F");
gPointerCoordsClassInfo.orientation = GetFieldIDOrDie(env, clazz, "orientation", "F");
+ gPointerCoordsClassInfo.relativeX = GetFieldIDOrDie(env, clazz, "relativeX", "F");
+ gPointerCoordsClassInfo.relativeY = GetFieldIDOrDie(env, clazz, "relativeY", "F");
clazz = FindClassOrDie(env, "android/view/MotionEvent$PointerProperties");
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 8d12df226ffe..1452c67ae3c6 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -62,6 +62,8 @@
namespace android {
+using gui::FocusRequest;
+
static void doThrowNPE(JNIEnv* env) {
jniThrowNullPointerException(env, NULL);
}
@@ -868,6 +870,13 @@ static void nativeSetFixedTransformHint(JNIEnv* env, jclass clazz, jlong transac
transaction->setFixedTransformHint(ctrl, transformHint);
}
+static void nativeSetDropInputMode(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong nativeObject, jint mode) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+ transaction->setDropInputMode(ctrl, static_cast<gui::DropInputMode>(mode));
+}
+
static jlongArray nativeGetPhysicalDisplayIds(JNIEnv* env, jclass clazz) {
const auto displayIds = SurfaceComposerClient::getPhysicalDisplayIds();
jlongArray array = env->NewLongArray(displayIds.size());
@@ -889,6 +898,12 @@ static jlongArray nativeGetPhysicalDisplayIds(JNIEnv* env, jclass clazz) {
return array;
}
+static jlong nativeGetPrimaryPhysicalDisplayId(JNIEnv* env, jclass clazz) {
+ PhysicalDisplayId displayId;
+ SurfaceComposerClient::getPrimaryPhysicalDisplayId(&displayId);
+ return static_cast<jlong>(displayId.value);
+}
+
static jobject nativeGetPhysicalDisplayToken(JNIEnv* env, jclass clazz, jlong physicalDisplayId) {
sp<IBinder> token =
SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId(physicalDisplayId));
@@ -1014,6 +1029,17 @@ static void nativeSetDisplayLayerStack(JNIEnv* env, jclass clazz,
}
}
+static void nativeSetDisplayFlags(JNIEnv* env, jclass clazz, jlong transactionObj, jobject tokenObj,
+ jint flags) {
+ sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+ if (token == NULL) return;
+
+ {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ transaction->setDisplayFlags(token, flags);
+ }
+}
+
static void nativeSetDisplayProjection(JNIEnv* env, jclass clazz,
jlong transactionObj,
jobject tokenObj, jint orientation,
@@ -1789,16 +1815,18 @@ static void nativeSetTransformHint(JNIEnv* env, jclass clazz, jlong nativeSurfac
if (surface == nullptr) {
return;
}
- surface->setTransformHint(
- ui::Transform::toRotationFlags(static_cast<ui::Rotation>(transformHint)));
+ surface->setTransformHint(transformHint);
}
static jint nativeGetTransformHint(JNIEnv* env, jclass clazz, jlong nativeSurfaceControl) {
sp<SurfaceControl> surface(reinterpret_cast<SurfaceControl*>(nativeSurfaceControl));
- ui::Transform::RotationFlags transformHintRotationFlags =
- static_cast<ui::Transform::RotationFlags>(surface->getTransformHint());
+ return surface->getTransformHint();
+}
+
+static jint nativeGetLayerId(JNIEnv* env, jclass clazz, jlong nativeSurfaceControl) {
+ sp<SurfaceControl> surface(reinterpret_cast<SurfaceControl*>(nativeSurfaceControl));
- return toRotationInt(ui::Transform::toRotation((transformHintRotationFlags)));
+ return surface->getLayerId();
}
// ----------------------------------------------------------------------------
@@ -1879,6 +1907,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeReleaseFrameRateFlexibilityToken },
{"nativeGetPhysicalDisplayIds", "()[J",
(void*)nativeGetPhysicalDisplayIds },
+ {"nativeGetPrimaryPhysicalDisplayId", "()J",
+ (void*)nativeGetPrimaryPhysicalDisplayId },
{"nativeGetPhysicalDisplayToken", "(J)Landroid/os/IBinder;",
(void*)nativeGetPhysicalDisplayToken },
{"nativeCreateDisplay", "(Ljava/lang/String;Z)Landroid/os/IBinder;",
@@ -1889,6 +1919,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeSetDisplaySurface },
{"nativeSetDisplayLayerStack", "(JLandroid/os/IBinder;I)V",
(void*)nativeSetDisplayLayerStack },
+ {"nativeSetDisplayFlags", "(JLandroid/os/IBinder;I)V",
+ (void*)nativeSetDisplayFlags },
{"nativeSetDisplayProjection", "(JLandroid/os/IBinder;IIIIIIIII)V",
(void*)nativeSetDisplayProjection },
{"nativeSetDisplaySize", "(JLandroid/os/IBinder;II)V",
@@ -1994,6 +2026,10 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeGetTransformHint },
{"nativeSetTrustedOverlay", "(JJZ)V",
(void*)nativeSetTrustedOverlay },
+ {"nativeSetDropInputMode", "(JJI)V",
+ (void*)nativeSetDropInputMode },
+ {"nativeGetLayerId", "(J)I",
+ (void*)nativeGetLayerId },
// clang-format on
};
diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp
new file mode 100644
index 000000000000..ab88b537f96b
--- /dev/null
+++ b/core/jni/android_window_WindowInfosListener.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "WindowInfosListener"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <gui/SurfaceComposerClient.h>
+#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+
+#include "android_hardware_input_InputWindowHandle.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+using gui::WindowInfo;
+
+namespace {
+
+static struct {
+ jclass clazz;
+ jmethodID onWindowInfosChanged;
+} gListenerClassInfo;
+
+static jclass gInputWindowHandleClass;
+
+struct WindowInfosListener : public gui::WindowInfosListener {
+ WindowInfosListener(JNIEnv* env, jobject listener)
+ : mListener(env->NewWeakGlobalRef(listener)) {}
+
+ void onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos) override {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ LOG_ALWAYS_FATAL_IF(env == nullptr, "Unable to retrieve JNIEnv in onWindowInfoChanged.");
+
+ jobject listener = env->NewGlobalRef(mListener);
+ if (listener == nullptr) {
+ // Weak reference went out of scope
+ return;
+ }
+
+ jobjectArray jWindowHandlesArray =
+ env->NewObjectArray(windowInfos.size(), gInputWindowHandleClass, nullptr);
+ for (int i = 0; i < windowInfos.size(); i++) {
+ ScopedLocalRef<jobject>
+ jWindowHandle(env,
+ android_view_InputWindowHandle_fromWindowInfo(env,
+ windowInfos[i]));
+ env->SetObjectArrayElement(jWindowHandlesArray, i, jWindowHandle.get());
+ }
+
+ env->CallVoidMethod(listener, gListenerClassInfo.onWindowInfosChanged, jWindowHandlesArray);
+ env->DeleteGlobalRef(listener);
+
+ if (env->ExceptionCheck()) {
+ ALOGE("WindowInfosListener.onWindowInfosChanged() failed.");
+ LOGE_EX(env);
+ env->ExceptionClear();
+ }
+ }
+
+ ~WindowInfosListener() override {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->DeleteWeakGlobalRef(mListener);
+ }
+
+private:
+ jweak mListener;
+};
+
+jlong nativeCreate(JNIEnv* env, jclass clazz, jobject obj) {
+ WindowInfosListener* listener = new WindowInfosListener(env, obj);
+ listener->incStrong((void*)nativeCreate);
+ return reinterpret_cast<jlong>(listener);
+}
+
+void destroyNativeService(void* ptr) {
+ WindowInfosListener* listener = reinterpret_cast<WindowInfosListener*>(ptr);
+ listener->decStrong((void*)nativeCreate);
+}
+
+void nativeRegister(JNIEnv* env, jclass clazz, jlong ptr) {
+ sp<WindowInfosListener> listener = reinterpret_cast<WindowInfosListener*>(ptr);
+ SurfaceComposerClient::getDefault()->addWindowInfosListener(listener);
+}
+
+void nativeUnregister(JNIEnv* env, jclass clazz, jlong ptr) {
+ sp<WindowInfosListener> listener = reinterpret_cast<WindowInfosListener*>(ptr);
+ SurfaceComposerClient::getDefault()->removeWindowInfosListener(listener);
+}
+
+static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeService));
+}
+
+const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativeCreate", "(Landroid/window/WindowInfosListener;)J", (void*)nativeCreate},
+ {"nativeRegister", "(J)V", (void*)nativeRegister},
+ {"nativeUnregister", "(J)V", (void*)nativeUnregister},
+ {"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer}};
+
+} // namespace
+
+int register_android_window_WindowInfosListener(JNIEnv* env) {
+ int res = jniRegisterNativeMethods(env, "android/window/WindowInfosListener", gMethods,
+ NELEM(gMethods));
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+ jclass clazz = env->FindClass("android/window/WindowInfosListener");
+ gListenerClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gListenerClassInfo.onWindowInfosChanged =
+ env->GetMethodID(gListenerClassInfo.clazz, "onWindowInfosChanged",
+ "([Landroid/view/InputWindowHandle;)V");
+
+ clazz = env->FindClass("android/view/InputWindowHandle");
+ gInputWindowHandleClass = MakeGlobalRefOrDie(env, clazz);
+ return 0;
+}
+
+} // namespace android
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 1bba12ff7fa6..ba4a5b0ce222 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -79,7 +79,7 @@ message SecureSettingsProto {
optional SettingProto accessibility_magnification_mode = 34 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto button_targets = 35 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_magnification_capability = 36 [ (android.privacy).dest = DEST_AUTOMATIC ];
- // Settings for accessibility button mode (navigation bar or floating action menu).
+ // Settings for accessibility button related config
optional SettingProto accessibility_button_mode = 37 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_floating_menu_size = 38 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_floating_menu_icon_type = 39 [ (android.privacy).dest = DEST_AUTOMATIC ];
diff --git a/core/proto/android/server/accessibilitytrace.proto b/core/proto/android/server/accessibilitytrace.proto
index 1fc4a01936b1..41fecfd6cdb7 100644
--- a/core/proto/android/server/accessibilitytrace.proto
+++ b/core/proto/android/server/accessibilitytrace.proto
@@ -46,17 +46,17 @@ message AccessibilityTraceProto {
/* required: elapsed realtime in nanos since boot of when this entry was logged */
optional fixed64 elapsed_realtime_nanos = 1;
optional string calendar_time = 2;
-
- optional string process_name = 3;
- optional string thread_id_name = 4;
+ repeated string logging_type = 3;
+ optional string process_name = 4;
+ optional string thread_id_name = 5;
/* where the trace originated */
- optional string where = 5;
+ optional string where = 6;
- optional string calling_pkg = 6;
- optional string calling_params = 7;
- optional string calling_stacks = 8;
+ optional string calling_pkg = 7;
+ optional string calling_params = 8;
+ optional string calling_stacks = 9;
- optional AccessibilityDumpProto accessibility_service = 9;
- optional com.android.server.wm.WindowManagerServiceDumpProto window_manager_service = 10;
+ optional AccessibilityDumpProto accessibility_service = 10;
+ optional com.android.server.wm.WindowManagerServiceDumpProto window_manager_service = 11;
}
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index fa1e9d4afcdf..c750eadf845a 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -69,6 +69,7 @@ message RootWindowContainerProto {
// know what activity types to check for when invoking splitscreen multi-window.
optional bool is_home_recents_component = 6;
repeated IdentifierProto pending_activities = 7 [deprecated=true];
+ optional int32 default_min_size_resizable_task = 8;
}
message BarControllerProto {
@@ -201,7 +202,6 @@ message DisplayContentProto {
optional .com.android.server.wm.IdentifierProto resumed_activity = 24;
repeated TaskProto tasks = 25 [deprecated=true];
optional bool display_ready = 26;
-
optional WindowStateProto input_method_target = 27;
optional WindowStateProto input_method_input_target = 28;
optional WindowStateProto input_method_control_target = 29;
@@ -211,6 +211,8 @@ message DisplayContentProto {
optional DisplayRotationProto display_rotation = 33;
optional int32 ime_policy = 34;
+ optional bool is_sleeping = 36;
+ repeated string sleep_tokens = 37;
}
/* represents DisplayArea object */
@@ -278,7 +280,7 @@ message PinnedTaskControllerProto {
message TaskProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- optional WindowContainerProto window_container = 1;
+ optional WindowContainerProto window_container = 1 [deprecated=true];
optional int32 id = 2;
reserved 3; // activity
optional bool fills_parent = 4;
@@ -295,12 +297,12 @@ message TaskProto {
optional string real_activity = 13;
optional string orig_activity = 14;
- optional int32 display_id = 15;
+ optional int32 display_id = 15 [deprecated=true];
optional int32 root_task_id = 16;
- optional int32 activity_type = 17 [(.android.typedef) = "android.app.WindowConfiguration.ActivityType"];
+ optional int32 activity_type = 17 [(.android.typedef) = "android.app.WindowConfiguration.ActivityType", deprecated=true] ;
optional int32 resize_mode = 18 [(.android.typedef) = "android.appwidget.AppWidgetProviderInfo.ResizeModeFlags"];
- optional int32 min_width = 19;
- optional int32 min_height = 20;
+ optional int32 min_width = 19 [deprecated=true];
+ optional int32 min_height = 20 [deprecated=true];
optional .android.graphics.RectProto adjusted_bounds = 21;
optional .android.graphics.RectProto last_non_fullscreen_bounds = 22;
@@ -312,6 +314,18 @@ message TaskProto {
optional bool created_by_organizer = 28;
optional string affinity = 29;
optional bool has_child_pip_activity = 30;
+ optional TaskFragmentProto task_fragment = 31;
+}
+
+/* represents TaskFragment */
+message TaskFragmentProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional WindowContainerProto window_container = 1;
+ optional int32 display_id = 2;
+ optional int32 activity_type = 3 [(.android.typedef) = "android.app.WindowConfiguration.ActivityType"];
+ optional int32 min_width = 4;
+ optional int32 min_height = 5;
}
/* represents ActivityRecordProto */
@@ -351,6 +365,7 @@ message ActivityRecordProto {
optional bool translucent = 30;
optional bool pip_auto_enter_enabled = 31;
optional bool in_size_compat_mode = 32;
+ optional float min_aspect_ratio = 33;
}
/* represents WindowToken */
@@ -468,6 +483,7 @@ message WindowContainerProto {
optional SurfaceAnimatorProto surface_animator = 4;
repeated WindowContainerChildProto children = 5;
optional IdentifierProto identifier = 6;
+ optional .android.view.SurfaceControlProto surface_control = 7;
}
/* represents a generic child of a WindowContainer */
@@ -493,6 +509,8 @@ message WindowContainerChildProto {
optional WindowTokenProto window_token = 7;
/* represents a WindowState child */
optional WindowStateProto window = 8;
+ /* represents a WindowState child */
+ optional TaskFragmentProto task_fragment = 9;
}
/* represents ConfigurationContainer */
diff --git a/core/proto/android/view/surfacecontrol.proto b/core/proto/android/view/surfacecontrol.proto
index cbb243ba7872..5a5f035412b2 100644
--- a/core/proto/android/view/surfacecontrol.proto
+++ b/core/proto/android/view/surfacecontrol.proto
@@ -29,4 +29,5 @@ message SurfaceControlProto {
optional int32 hash_code = 1;
optional string name = 2 [ (android.privacy).dest = DEST_EXPLICIT ];
+ optional int32 layerId = 3;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index cd6af189a04a..3968193e044f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3471,6 +3471,13 @@
<permission android:name="android.permission.TRIGGER_SHELL_BUGREPORT"
android:protectionLevel="signature" />
+ <!-- Allows an application to trigger profcollect report upload via shell.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.TRIGGER_SHELL_PROFCOLLECT_UPLOAD"
+ android:protectionLevel="signature" />
+
<!-- Allows an application to be the status bar. Currently used only by SystemUI.apk
@hide
@SystemApi -->
@@ -4858,6 +4865,18 @@
<permission android:name="android.permission.WRITE_SETTINGS_HOMEPAGE_DATA"
android:protectionLevel="signature|privileged" />
+ <!-- An application needs this permission for
+ {@link android.provider.Settings#ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY} to show its
+ {@link android.app.Activity} embedded in Settings app. -->
+ <permission android:name="android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK"
+ android:protectionLevel="signature|preinstalled" />
+
+ <!-- @SystemApi {@link android.app.Activity} should require this permission to ensure that only
+ the settings app can embed it in a multi pane window.
+ @hide -->
+ <permission android:name="android.permission.ALLOW_PLACE_IN_MULTI_PANE_SETTINGS"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows applications to set a live wallpaper.
@hide XXX Change to signature once the picker is moved to its
own apk as Ghod Intended. -->
@@ -5860,7 +5879,6 @@
android:excludeFromRecents="true"
android:documentLaunchMode="never"
android:relinquishTaskIdentity="true"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
android:process=":ui"
android:visibleToInstantApps="true">
<intent-filter>
diff --git a/core/res/OWNERS b/core/res/OWNERS
index 7a8da36d8a7d..bd192088e80d 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -25,3 +25,9 @@ svetoslavganov@google.com
toddke@google.com
tsuji@google.com
yamasani@google.com
+
+# Multiuser
+per-file res/xml/config_user_types.xml = file:/MULTIUSER_OWNERS
+
+# Car
+per-file res/values/dimens_car.xml = file:/platform/packages/services/Car:/OWNERS
diff --git a/core/res/res/drawable-nodpi/default_wallpaper.png b/core/res/res/drawable-nodpi/default_wallpaper.png
index ce546f0a11e7..5152972d2a80 100644
--- a/core/res/res/drawable-nodpi/default_wallpaper.png
+++ b/core/res/res/drawable-nodpi/default_wallpaper.png
Binary files differ
diff --git a/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png b/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png
index af8e2512385a..26376fb87cbe 100644
--- a/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png
+++ b/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png
Binary files differ
diff --git a/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png b/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png
index cb00d82a826f..490ebeeb75c1 100644
--- a/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png
+++ b/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png
Binary files differ
diff --git a/core/res/res/drawable/ic_corp_badge.xml b/core/res/res/drawable/ic_corp_badge.xml
index 16df45290302..6c40f10a03c3 100644
--- a/core/res/res/drawable/ic_corp_badge.xml
+++ b/core/res/res/drawable/ic_corp_badge.xml
@@ -22,8 +22,5 @@ Copyright (C) 2018 The Android Open Source Project
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
- android:pathData="M20,6h-4V4c0-1.1-0.9-2-2-2h-4C8.9,2,8,2.9,8,4v2H4C2.9,6,2,6.9,2,8l0,11c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8 C22,6.9,21.1,6,20,6z M10,4h4v2h-4V4z M20,19H4V8h16V19z" />
- <path
- android:fillColor="@android:color/white"
- android:pathData="M 12 12 C 12.8284271247 12 13.5 12.6715728753 13.5 13.5 C 13.5 14.3284271247 12.8284271247 15 12 15 C 11.1715728753 15 10.5 14.3284271247 10.5 13.5 C 10.5 12.6715728753 11.1715728753 12 12 12 Z" />
+ android:pathData="@string/config_work_badge_path_24"/>
</vector> \ No newline at end of file
diff --git a/core/res/res/drawable/ic_corp_badge_case.xml b/core/res/res/drawable/ic_corp_badge_case.xml
index 1cd995eea171..838426ecd9a0 100644
--- a/core/res/res/drawable/ic_corp_badge_case.xml
+++ b/core/res/res/drawable/ic_corp_badge_case.xml
@@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
- android:viewportWidth="20.0"
- android:viewportHeight="20.0">
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
<path
- android:pathData="M14.67,6.5h-2.33V5.33c0,-0.65 -0.52,-1.17 -1.17,-1.17H8.83c-0.65,0 -1.17,0.52 -1.17,1.17V6.5H5.33c-0.65,0 -1.16,0.52 -1.16,1.17l-0.01,6.42c0,0.65 0.52,1.17 1.17,1.17h9.33c0.65,0 1.17,-0.52 1.17,-1.17V7.67C15.83,7.02 15.31,6.5 14.67,6.5zM10,11.75c-0.64,0 -1.17,-0.52 -1.17,-1.17c0,-0.64 0.52,-1.17 1.17,-1.17c0.64,0 1.17,0.52 1.17,1.17C11.17,11.22 10.64,11.75 10,11.75zM11.17,6.5H8.83V5.33h2.33V6.5z"
+ android:pathData="@string/config_work_badge_path_24"
android:fillColor="#1A73E8"/>
</vector>
diff --git a/core/res/res/drawable/ic_corp_badge_no_background.xml b/core/res/res/drawable/ic_corp_badge_no_background.xml
index 8f7fb70d5b83..e81e69f1b5d5 100644
--- a/core/res/res/drawable/ic_corp_badge_no_background.xml
+++ b/core/res/res/drawable/ic_corp_badge_no_background.xml
@@ -4,6 +4,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:pathData="M20,6h-4V4c0,-1.11 -0.89,-2 -2,-2h-4C8.89,2 8,2.89 8,4v2H4C2.89,6 2.01,6.89 2.01,8L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V8C22,6.89 21.11,6 20,6zM12,15c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2s2,0.9 2,2S13.1,15 12,15zM14,6h-4V4h4V6z"
+ android:pathData="@string/config_work_badge_path_24"
android:fillColor="#FFFFFF"/>
</vector>
diff --git a/core/res/res/drawable/ic_corp_icon.xml b/core/res/res/drawable/ic_corp_icon.xml
index 48531dd8c539..86bb98e0996b 100644
--- a/core/res/res/drawable/ic_corp_icon.xml
+++ b/core/res/res/drawable/ic_corp_icon.xml
@@ -4,6 +4,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:pathData="M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM12,15c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM14,6h-4L10,4h4v2z"
+ android:pathData="@string/config_work_badge_path_24"
android:fillColor="#000000"/>
</vector> \ No newline at end of file
diff --git a/core/res/res/drawable/ic_corp_icon_badge_case.xml b/core/res/res/drawable/ic_corp_icon_badge_case.xml
index 50551d401120..09cf9cb89abe 100644
--- a/core/res/res/drawable/ic_corp_icon_badge_case.xml
+++ b/core/res/res/drawable/ic_corp_icon_badge_case.xml
@@ -22,9 +22,15 @@ Copyright (C) 2016 The Android Open Source Project
<path
android:pathData="M 42 42 L 58 42 L 58 58 L 42 58 L 42 42 Z" />
- <path
- android:fillColor="#1A73E8"
- android:pathData="M55.33,46H52.67V44.67a1.33,1.33,0,0,0-1.33-1.33H48.67a1.33,1.33,0,0,0-1.33,1.33V46H44.67a1.32,1.32,0,0,0-1.33,1.33v7.33A1.33,1.33,0,0,0,44.67,56H55.33a1.33,1.33,0,0,0,1.33-1.33V47.33A1.33,1.33,0,0,0,55.33,46ZM50,52a1.33,1.33,0,1,1,1.33-1.33A1.34,1.34,0,0,1,50,52Zm1.33-6H48.67V44.67h2.67Z" />
+ <group
+ android:scaleX=".66"
+ android:scaleY=".66"
+ android:translateX="42"
+ android:translateY="42">
+ <path
+ android:pathData="@string/config_work_badge_path_24"
+ android:fillColor="#1A73E8"/>
+ </group>
<path
android:pathData="M 0 0 H 64 V 64 H 0 V 0 Z" />
</vector> \ No newline at end of file
diff --git a/core/res/res/drawable/ic_corp_statusbar_icon.xml b/core/res/res/drawable/ic_corp_statusbar_icon.xml
index 8f7fb70d5b83..e81e69f1b5d5 100644
--- a/core/res/res/drawable/ic_corp_statusbar_icon.xml
+++ b/core/res/res/drawable/ic_corp_statusbar_icon.xml
@@ -4,6 +4,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:pathData="M20,6h-4V4c0,-1.11 -0.89,-2 -2,-2h-4C8.89,2 8,2.89 8,4v2H4C2.89,6 2.01,6.89 2.01,8L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V8C22,6.89 21.11,6 20,6zM12,15c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2s2,0.9 2,2S13.1,15 12,15zM14,6h-4V4h4V6z"
+ android:pathData="@string/config_work_badge_path_24"
android:fillColor="#FFFFFF"/>
</vector>
diff --git a/core/res/res/drawable/ic_corp_user_badge.xml b/core/res/res/drawable/ic_corp_user_badge.xml
index a08f2d4845e1..76ba4506426d 100644
--- a/core/res/res/drawable/ic_corp_user_badge.xml
+++ b/core/res/res/drawable/ic_corp_user_badge.xml
@@ -8,9 +8,6 @@
android:pathData="M16.3,11.3h3.4v1.7h-3.4z"
android:fillColor="#FFFFFF"/>
<path
- android:pathData="M18,17.17c-0.92,0 -1.67,0.75 -1.67,1.67c0,0.92 0.75,1.67 1.67,1.67c0.92,0 1.67,-0.75 1.67,-1.67C19.67,17.92 18.92,17.17 18,17.17z"
- android:fillColor="#FFFFFF"/>
- <path
android:pathData="M18,0C8.06,0 0,8.06 0,18s8.06,18 18,18s18,-8.06 18,-18S27.94,0 18,0zM26.3,23.83c0,0.92 -0.71,1.67 -1.63,1.67H11.33c-0.93,0 -1.67,-0.74 -1.67,-1.67l0.01,-9.17c0,-0.92 0.73,-1.67 1.66,-1.67h3.37v-1.67c0,-0.93 0.71,-1.63 1.63,-1.63h3.33c0.93,0 1.63,0.71 1.63,1.63V13h3.37c0.93,0 1.63,0.74 1.63,1.67V23.83z"
android:fillColor="#FFFFFF"/>
</vector>
diff --git a/core/res/res/layout-car/car_alert_dialog.xml b/core/res/res/layout-car/car_alert_dialog.xml
index 569e5948e2e0..2e7b62ceb723 100644
--- a/core/res/res/layout-car/car_alert_dialog.xml
+++ b/core/res/res/layout-car/car_alert_dialog.xml
@@ -54,7 +54,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_view_start_margin"
android:layout_marginEnd="@dimen/text_view_end_margin"
- style="@style/CarBody2"/>
+ style="@style/CarBody4"/>
<!-- we don't need this spacer, but the id needs to be here for compatibility -->
<Space
diff --git a/core/res/res/layout-car/car_alert_dialog_button_bar.xml b/core/res/res/layout-car/car_alert_dialog_button_bar.xml
index 277b0dcca657..4f815b887088 100644
--- a/core/res/res/layout-car/car_alert_dialog_button_bar.xml
+++ b/core/res/res/layout-car/car_alert_dialog_button_bar.xml
@@ -36,6 +36,9 @@
<Button
android:id="@+id/button3"
style="@style/CarAction1"
+ android:minWidth="@dimen/car_touch_target_size"
+ android:paddingStart="@dimen/car_padding_2"
+ android:paddingEnd="@dimen/car_padding_2"
android:background="@drawable/car_dialog_button_background"
android:layout_marginRight="@dimen/button_end_margin"
android:layout_width="wrap_content"
@@ -44,6 +47,9 @@
<Button
android:id="@+id/button2"
style="@style/CarAction1"
+ android:minWidth="@dimen/car_touch_target_size"
+ android:paddingStart="@dimen/car_padding_2"
+ android:paddingEnd="@dimen/car_padding_2"
android:background="@drawable/car_dialog_button_background"
android:layout_marginRight="@dimen/button_end_margin"
android:layout_width="wrap_content"
@@ -52,6 +58,9 @@
<Button
android:id="@+id/button1"
style="@style/CarAction1"
+ android:minWidth="@dimen/car_touch_target_size"
+ android:paddingStart="@dimen/car_padding_2"
+ android:paddingEnd="@dimen/car_padding_2"
android:background="@drawable/car_dialog_button_background"
android:layout_width="wrap_content"
android:layout_height="@dimen/button_layout_height" />
diff --git a/core/res/res/layout/accessibility_shortcut_chooser_item.xml b/core/res/res/layout/accessibility_shortcut_chooser_item.xml
index 7cca1292af68..4d7946b2138b 100644
--- a/core/res/res/layout/accessibility_shortcut_chooser_item.xml
+++ b/core/res/res/layout/accessibility_shortcut_chooser_item.xml
@@ -39,15 +39,20 @@
android:layout_height="48dp"
android:scaleType="fitCenter"/>
- <TextView
- android:id="@+id/accessibility_shortcut_target_label"
+ <LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
- android:layout_weight="1"
- android:textSize="20sp"
- android:textColor="?attr/textColorPrimary"
- android:fontFamily="sans-serif-medium"/>
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+id/accessibility_shortcut_target_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="20sp"
+ android:textColor="?attr/textColorPrimary"
+ android:fontFamily="sans-serif-medium"/>
+ </LinearLayout>
<TextView
android:id="@+id/accessibility_shortcut_target_status"
diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml
index 59e56af01b8d..6869c5fb7935 100644
--- a/core/res/res/layout/alert_dialog.xml
+++ b/core/res/res/layout/alert_dialog.xml
@@ -124,21 +124,21 @@
android:layout_width="0dip"
android:layout_gravity="start"
android:layout_weight="1"
- style="?android:attr/buttonBarButtonStyle"
+ style="?android:attr/buttonBarPositiveButtonStyle"
android:maxLines="2"
android:layout_height="wrap_content" />
<Button android:id="@+id/button3"
android:layout_width="0dip"
android:layout_gravity="center_horizontal"
android:layout_weight="1"
- style="?android:attr/buttonBarButtonStyle"
+ style="?android:attr/buttonBarNeutralButtonStyle"
android:maxLines="2"
android:layout_height="wrap_content" />
<Button android:id="@+id/button2"
android:layout_width="0dip"
android:layout_gravity="end"
android:layout_weight="1"
- style="?android:attr/buttonBarButtonStyle"
+ style="?android:attr/buttonBarNegativeButtonStyle"
android:maxLines="2"
android:layout_height="wrap_content" />
<LinearLayout android:id="@+id/rightSpacer"
diff --git a/core/res/res/layout/transient_notification.xml b/core/res/res/layout/transient_notification.xml
index 8a3d73491af3..3259201f6e9b 100644
--- a/core/res/res/layout/transient_notification.xml
+++ b/core/res/res/layout/transient_notification.xml
@@ -40,6 +40,5 @@
android:maxLines="2"
android:paddingTop="12dp"
android:paddingBottom="12dp"
- android:lineHeight="20sp"
android:textAppearance="@style/TextAppearance.Toast"/>
</LinearLayout>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 4541366c31cb..7763416976c1 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Wanneer die kortpad aan is, sal \'n toeganklikheidkenmerk begin word as albei volumeknoppies 3 sekondes lank gedruk word."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Skakel kortpad vir toeganklikheidskenmerke aan?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"As jy albei volumesleutels vir \'n paar sekondes hou, skakel dit toeganklikheidkenmerke aan. Dit kan verander hoe jou toestel werk.\n\nHuidige kenmerke:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nJy kan geselekteerde kenmerke in Instellings en Toeganklikheid verander."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Skakel <xliff:g id="SERVICE">%1$s</xliff:g>-kortpad aan?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"As jy albei volumesleutels vir \'n paar sekondes hou, skakel dit <xliff:g id="SERVICE">%1$s</xliff:g>, \'n toeganklikheidkenmerk, aan. Dit kan verander hoe jou toestel werk.\n\nJy kan hierdie kortpad na \'n ander kenmerk in Instellings en Toeganklikheid verander."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Skakel aan"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 4da99e27ebf7..1c306403aaec 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"አቋራጩ ሲበራ ሁለቱንም የድምጽ አዝራሮች ለ3 ሰከንዶች ተጭኖ መቆየት የተደራሽነት ባህሪን ያስጀምረዋል።"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"የተደራሽነት ባህሪዎች አቋራጭ ይብራ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ሁለቱንም የድምፅ ቁልፎች ወደ ታች ለጥቂት ሰከንዶች መያዝ የተደራሽነት ባሕሪያትን ያበራል። ይህ የእርስዎ መሣሪያ እንዴት እንደሚሠራ ሊለውጥ ይችላል።\n\nየአሁን ባሕሪያት፦\n<xliff:g id="SERVICE">%1$s</xliff:g>\nበቅንብሮች &gt; ተደራሽነት ውስጥ የተመረጡትን ባሕሪያት መለወጥ ይችላሉ።"</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"የ<xliff:g id="SERVICE">%1$s</xliff:g> አቋራጭ ይብራ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ሁለቱንም የድምፅ ቁልፎች ወደ ታች ለጥቂት ሰከንዶች መያዝ የተደራሽነት ባሕሪያትን <xliff:g id="SERVICE">%1$s</xliff:g> ያበራል። ይህ የእርስዎ መሣሪያ እንዴት እንደሚሠራ ሊለውጥ ይችላል።\n\nበቅንብሮች &gt; ተደራሽነት ውስጥ ወደ ሌላ ባሕሪ ይህን አቋራጭ መለወጥ ይችላሉ።"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"አብራ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index dedb4ce2a726..c27ff94bfc94 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1781,7 +1781,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"عند تفعيل الاختصار، يؤدي الضغط على زرّي التحكّم في مستوى الصوت معًا لمدة 3 ثوانٍ إلى تفعيل إحدى ميزات إمكانية الوصول."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"هل تريد تفعيل الاختصار لميزات إمكانية الوصول؟"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"يؤدي الضغط مع الاستمرار على كلا مفتاحَي التحكّم في مستوى الصوت لبضع ثوانٍ إلى تفعيل ميزات إمكانية الوصول. قد يؤدي هذا الإجراء إلى تغيير طريقة عمل جهازك.\n\nالميزات الحالية:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nيمكنك تغيير الميزات المحددة في الإعدادات &gt; إمكانية الوصول."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"هل تريد تفعيل اختصار <xliff:g id="SERVICE">%1$s</xliff:g>؟"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"‏يؤدي الضغط مع الاستمرار لبضع ثوانٍ على كلا مفتاحَي التحكّم في مستوى الصوت إلى تفعيل <xliff:g id="SERVICE">%1$s</xliff:g> وهي إحدى ميزات إمكانية الوصول. يمكن أن يؤدي هذا الإجراء إلى تغيير كيفية عمل جهازك.\n\nيمكنك تغيير هذا الاختصار لاستخدامه مع ميزة أخرى في الإعدادات &gt; أدوات تمكين الوصول."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"تفعيل"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 87d3054f2640..25a0ec834316 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"শ্বৰ্টকাটটো অন হৈ থকাৰ সময়ত দুয়োটা ভলিউম বুটাম ৩ ছেকেণ্ডৰ বাবে হেঁচি ধৰি ৰাখিলে এটা সাধ্য সুবিধা আৰম্ভ হ’ব।"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"সাধ্য সুবিধাসমূহৰ বাবে শ্বৰ্টকাট অন কৰিবনে?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"দুয়োটা ভলিউম কী কিছুসময়ৰ বাবে ধৰি থাকিলে সাধ্য-সুবিধাসমূহ অন কৰে। এইটোৱে আপোনাৰ ডিভাইচটোৱে কাম কৰাৰ ধৰণ সলনি কৰিব পাৰে।\n\nবর্তমানৰ সুবিধাসমূহ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nআপুনি ছেটিং &gt; সাধ্য-সুবিধাত কিছুমান নিৰ্দিষ্ট সুবিধা সলনি কৰিব পাৰে।"</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g>ৰ শ্বৰ্টকাট অন কৰিবনে?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"দুয়োটা ভলিউম কী কিছুসময়ৰ বাবে ধৰি থাকিলে এটা সাধ্য- সুবিধা <xliff:g id="SERVICE">%1$s</xliff:g> অন কৰে। এইটোৱে আপোনাৰ ডিভাইচটোৱে কাম কৰাৰ ধৰণ সলনি কৰিব পাৰে।\n\nআপুনি ছেটিং &gt; সাধ্য-সুবিধাসমূহত এই শ্বৰ্টকাটটো অন্য এটা সুবিধালৈ সলনি কৰিব পাৰে।"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"অন কৰক"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 7d415342d1b8..4825382f72f5 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Qısayol aktiv olduqda, hər iki səs düyməsinə 3 saniyə basıb saxlamaqla əlçatımlılıq funksiyası başladılacaq."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Əlçatımlılıq funksiyaları üçün qısayol aktiv edilsin?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hər iki səs səviyyəsi düyməsinə bir neçə saniyə basıb saxladıqda əlçatımlılıq funksiyaları aktiv olur. Cihazınızın işləmə qaydasını dəyişə bilər.\n\nCari funksiyalar:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nAyarlar və Əlçatımlılıq bölməsində seçilmiş funksiyaları dəyişə bilərsiniz."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> qısayolu aktiv edilsin?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Hər iki səs səviyyəsi düyməsinə bir neçə saniyə basıb saxladıqda əlçatımlılıq funksiyası olan <xliff:g id="SERVICE">%1$s</xliff:g> aktiv olur. Cihazınızın işləmə qaydasını dəyişə bilər.\n\nAyarlar və Əlçatımlılıq bölməsində bu qısayolu başqa bir funksiyaya dəyişə bilərsiniz."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktiv edin"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 7f7d62e8ce7d..014215a364c1 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -1715,7 +1715,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kada je prečica uključena, pritisnite oba dugmeta za jačinu zvuka da biste pokrenuli funkciju pristupačnosti."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Želite da uključite prečicu za funkcije pristupačnosti?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ako zadržite oba tastera za jačinu zvuka par sekundi, uključiće se funkcije pristupačnosti. To može da promeni način rada uređaja.\n\nPostojeće funkcije:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nMožete da promenite izabrane funkcije u odeljku Podešavanja &gt; Pristupačnost."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Želite da uključite prečicu za uslugu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ako zadržite oba tastera za jačinu zvuka par sekundi, uključuje se <xliff:g id="SERVICE">%1$s</xliff:g>, funkcija pristupačnosti. To može da promeni način rada uređaja.\n\nMožete da promenite funkciju na koju se odnosi ova prečica u odeljku Podešavanja &gt; Pristupačnost."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Uključi"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 49938844104a..cb8fa332c062 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -1737,7 +1737,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Калі хуткі доступ уключаны, вы можаце націснуць абедзве кнопкі гучнасці і ўтрымліваць іх 3 секунды, каб запусціць функцыю спецыяльных магчымасцей."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Уключыць хуткі доступ да спецыяльных магчымасцей?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Утрымліванне націснутымі абедзвюх клавіш гучнасці на працягу некалькіх секунд уключае спецыяльныя магчымасці. У выніку ваша прылада можа пачаць працаваць па-іншаму.\n\nБягучыя функцыі:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nВыбраныя функцыі можна змяніць у меню \"Налады &gt; Спецыяльныя магчымасці\"."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Уключыць хуткі доступ да сэрвісу \"<xliff:g id="SERVICE">%1$s</xliff:g>\"?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Утрымліванне націснутымі абедзвюх клавіш гучнасці на працягу некалькіх секунд уключае службу \"<xliff:g id="SERVICE">%1$s</xliff:g>\", якая з\'яўляецца спецыяльнай магчымасцю. У выніку ваша прылада можа пачаць працаваць па-іншаму.\n\nВы можаце задаць гэта спалучэнне клавіш для іншай функцыі ў меню \"Налады &gt; Спецыяльныя магчымасці\"."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Уключыць"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 0baf2f1f0969..cdd27d4ac514 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Когато прекият път е включен, можете да стартирате дадена функция за достъпност, като натиснете двата бутона за силата на звука и ги задържите за 3 секунди."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Искате ли да включите прекия път за функциите за достъпност?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Натиснете двата бутона за силата на звука и ги задръжте за няколко секунди, за да включите функциите за достъпност. Това може да промени начина, по който работи устройството ви.\n\nТекущи функции:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nМожете да промените избраните функции от „Настройки“ &gt; „Достъпност“."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Искате ли да включите прекия път за <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Натиснете двата бутона за силата на звука и ги задръжте за няколко секунди, за да включите функцията за достъпност <xliff:g id="SERVICE">%1$s</xliff:g>. Това може да промени начина, по който работи устройството ви.\n\nМожете да зададете друга функция за този пряк път от „Настройки“ &gt; „Достъпност“."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Включване"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 5543d724becc..057a409c15c0 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"শর্টকাট চালু করা থাকাকালীন দুটি ভলিউম বোতাম একসাথে ৩ সেকেন্ড টিপে ধরে রাখলে একটি অ্যাকসেসিবিলিটি ফিচার চালু হবে।"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"অ্যাক্সেসিবিলিটি ফিচারের শর্টকাট বন্ধ করতে চান?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"উভয় ভলিউম কী কয়েক সেকেন্ড ধরে থাকলে অ্যাক্সেসিবিলিটি ফিচার চালু হয়ে যাবে। এর ফলে, আপনার ডিভাইস কীভাবে কাজ করবে সেটিতে পরিবর্তন হতে পারে।\n\nবর্তমান ফিচার:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nসেটিংস &gt; অ্যাক্সেসিবিলিটি বিকল্প থেকে আপনি বাছাই করা ফিচার পরিবর্তন করতে পারবেন।"</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> শর্টকাট চালু করতে চান?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"উভয় ভলিউম কী কয়েক সেকেন্ড ধরে থাকলে <xliff:g id="SERVICE">%1$s</xliff:g> চালু হয়ে যাবে। এটি একটি অ্যাক্সেসিবিলিটি ফিচার। এর ফলে, আপনার ডিভাইস কীভাবে কাজ করবে সেটিতে পরিবর্তন হতে পারে।\n\nসেটিংস &gt; অ্যাক্সেসিবিলিটি থেকে আপনি এই শর্টকাট পরিবর্তন করতে পারবেন।"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"চালু করুন"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 1e574bddc045..3fef88bd15bc 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -1715,7 +1715,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kada je prečica uključena, pritiskom i držanjem oba dugmeta za jačinu zvuka u trajanju od 3 sekunde pokrenut će se funkcija pristupačnosti."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Uključiti prečicu za funkcije pristupačnosti?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ako nekoliko sekundi držite pritisnute obje tipke za jačinu zvuka, uključit ćete funkcije pristupačnosti. Ovo može uticati na način rada uređaja.\n\nTrenutne funkcije:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nOdabrane funkcije možete promijeniti u odjeljku Postavke &gt; Pristupačnost."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Uključiti prečicu za uslugu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ako nekoliko sekundi držite pritisnute obje tipke za jačinu zvuka, uključit ćete funkciju pristupačnosti <xliff:g id="SERVICE">%1$s</xliff:g>. Ovo može promijeniti način rada uređaja.\n\nOvu prečicu možete zamijeniti drugom funkcijom u odjeljku Postavke &gt; Pristupačnost."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Uključi"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 6256eb4c4c89..7bc5905f5136 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Si la drecera està activada, prem els dos botons de volum durant 3 segons per iniciar una funció d\'accessibilitat."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vols desactivar la drecera de les funcions d\'accessibilitat?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Si mantens premudes les dues tecles de volum durant uns segons, s\'activaran les funcions d\'accessibilitat. Això podria canviar el funcionament del teu dispositiu.\n\nFuncions actuals:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPots canviar les funcions seleccionades a Configuració &gt; Accessibilitat."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vols activar la drecera de <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Si mantens premudes les dues tecles de volum durant uns segons, la funció d\'accessibilitat <xliff:g id="SERVICE">%1$s</xliff:g> s\'activarà. Això podria canviar el funcionament del teu dispositiu.\n\nPots canviar la funció d\'aquesta drecera a Configuració &gt; Accessibilitat."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activa"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 3469a6def770..2c8477c0b515 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1737,7 +1737,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Když je tato zkratka zapnutá, můžete funkci přístupnosti spustit tím, že na tři sekundy podržíte obě tlačítka hlasitosti."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Zapnout zkratku funkcí pro usnadnění přístupu?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Podržením obou tlačítek hlasitosti po dobu několika sekund zapnete funkce pro usnadnění přístupu. Tato funkce může změnit fungování zařízení.\n\nAktuální funkce:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nVybrané funkce můžete změnit v Nastavení &gt; Přístupnost."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Zapnout zkratku služby <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Podržením obou tlačítek hlasitosti po dobu několika sekund zapnete funkci pro usnadnění přístupu <xliff:g id="SERVICE">%1$s</xliff:g>. Tato funkce může změnit fungování zařízení.\n\nZkratku můžete nastavit na jinou funkci v Nastavení &gt; Přístupnost."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Zapnout"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index bcca73465b9a..353358d7658f 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Når genvejen er aktiveret, kan du starte en hjælpefunktion ved at trykke på begge lydstyrkeknapper i tre sekunder."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vil du aktivere genvejen til hjælpefunktioner?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hvis du holder begge lydstyrkeknapperne nede i et par sekunder, aktiveres hjælpefunktionerne. Det kan ændre på, hvordan din enhed fungerer.\n\nAktuelle funktioner:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nDu kan ændre de valgte funktioner i Indstillinger &gt; Hjælpefunktioner."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vil du aktivere <xliff:g id="SERVICE">%1$s</xliff:g>-genvejen?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Hvis du holder begge lydstyrkeknapperne nede i et par sekunder, aktiveres hjælpefunktionen <xliff:g id="SERVICE">%1$s</xliff:g>. Det kan ændre på, hvordan din enhed fungerer.\n\nDu kan ændre denne genvej til en anden funktion i Indstillinger &gt; Hjælpefunktioner."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktivér"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 795c42571261..a974e90736dd 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Wenn die Verknüpfung aktiviert ist, kannst du die beiden Lautstärketasten drei Sekunden lang gedrückt halten, um eine Bedienungshilfe zu starten."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Verknüpfung für Bedienungshilfen aktivieren?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Wenn du beide Lautstärketasten einige Sekunden lang gedrückt hältst, aktivierst du die Bedienungshilfen. Dadurch kann sich die Funktionsweise deines Geräts ändern.\n\nAktuelle Funktionen:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nDu kannst ausgewählte Funktionen unter \"Einstellungen\" &gt; \"Bedienungshilfen\" ändern."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Verknüpfung für <xliff:g id="SERVICE">%1$s</xliff:g> aktivieren?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Wenn du beide Lautstärketasten einige Sekunden lang gedrückt hältst, aktivierst du die Bedienungshilfe \"<xliff:g id="SERVICE">%1$s</xliff:g>\". Dadurch kann sich die Funktionsweise deines Geräts ändern.\n\nUnter \"Einstellungen &gt; \"Bedienungshilfen\" kannst du dieser Verknüpfung eine andere Funktion zuweisen."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktivieren"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index a8dcc4f835e8..03d978e6268b 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Όταν η συντόμευση είναι ενεργοποιημένη, το πάτημα και των δύο κουμπιών έντασης ήχου για 3 δευτερόλεπτα θα ξεκινήσει μια λειτουργία προσβασιμότητας."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Ενεργοποίηση συντόμευσης για λειτουργίες προσβασιμότητας;"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Για να ενεργοποιήσετε τις λειτουργίες προσβασιμότητας, πατήστε παρατεταμένα τα δύο πλήκτρα έντασης για μερικά δευτερόλεπτα. Αυτό ενδέχεται να αλλάξει τον τρόπο λειτουργίας της συσκευής σας.\n\nΤρέχουσες λειτουργίες:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nΜπορείτε να αλλάξετε τις επιλεγμένες λειτουργίες στις Ρυθμίσεις &gt; Προσβασιμότητα."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Ενεργοποίηση συντόμευσης <xliff:g id="SERVICE">%1$s</xliff:g>;"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Μπορείτε να ενεργοποιήσετε τη λειτουργία <xliff:g id="SERVICE">%1$s</xliff:g>, η οποία είναι μία από τις λειτουργίες προσβασιμότητας, πατώντας παρατεταμένα ταυτόχρονα τα δύο πλήκτρα έντασης ήχου για μερικά δευτερόλεπτα. Αυτό ενδέχεται να αλλάξει τον τρόπο λειτουργίας της συσκευής σας.\n\nΜπορείτε να αλλάξετε αυτή τη συντόμευση σε μια άλλη λειτουργία στις Ρυθμίσεις &gt; Προσβασιμότητα."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ενεργοποίηση"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index c268eda05323..04158b40e79e 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for three seconds will start an accessibility feature."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Turn on shortcut for accessibility features?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nYou can change selected features in Settings &gt; Accessibility."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g> shortcut?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Holding down both volume keys for a few seconds turns on <xliff:g id="SERVICE">%1$s</xliff:g>, an accessibility feature. This may change how your device works.\n\nYou can change this shortcut to another feature in Settings &gt; Accessibility."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Turn on"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 5e5ffbe49268..3b019f47045e 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for three seconds will start an accessibility feature."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Turn on shortcut for accessibility features?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nYou can change selected features in Settings &gt; Accessibility."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g> shortcut?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Holding down both volume keys for a few seconds turns on <xliff:g id="SERVICE">%1$s</xliff:g>, an accessibility feature. This may change how your device works.\n\nYou can change this shortcut to another feature in Settings &gt; Accessibility."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Turn on"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index c65b8bbdbfdc..91d39a97e3f8 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for three seconds will start an accessibility feature."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Turn on shortcut for accessibility features?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nYou can change selected features in Settings &gt; Accessibility."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g> shortcut?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Holding down both volume keys for a few seconds turns on <xliff:g id="SERVICE">%1$s</xliff:g>, an accessibility feature. This may change how your device works.\n\nYou can change this shortcut to another feature in Settings &gt; Accessibility."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Turn on"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index baa35172c964..fe23e6367f98 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for three seconds will start an accessibility feature."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Turn on shortcut for accessibility features?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nYou can change selected features in Settings &gt; Accessibility."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g> shortcut?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Holding down both volume keys for a few seconds turns on <xliff:g id="SERVICE">%1$s</xliff:g>, an accessibility feature. This may change how your device works.\n\nYou can change this shortcut to another feature in Settings &gt; Accessibility."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Turn on"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index dcf237ed3929..787de9b81d62 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‎‎‎‎‎‎‏‎‏‏‏‎‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‎When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.‎‏‎‎‏‎"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‎‏‎‎Turn on shortcut for accessibility features?‎‏‎‎‏‎"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‏‏‎‎‏‏‎‎‏‎‏‎‎‎‎‏‏‏‏‏‎Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Current features:‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="SERVICE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎You can change selected features in Settings &gt; Accessibility.‎‏‎‎‏‎"</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎‎‎‏‎‎‎‏‎‏‎‎‏‏‎‎‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‏‎‎‏‏‏‎‏‎‎‏‎‏‏‎‏‏‎ • ‎‏‎‎‏‏‎<xliff:g id="SERVICE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" ‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‏‎‎‎‏‎‏‎‏‎‏‎‏‎‎‏‎‎ • ‎‏‎‎‏‏‎<xliff:g id="SERVICE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‎‎‎‏‏‎‏‎‏‎‎‎‏‎‏‏‎‎‏‎‎Turn on ‎‏‎‎‏‏‎<xliff:g id="SERVICE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ shortcut?‎‏‎‎‏‎"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‏‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‎‎‏‎‏‎‎‏‎‏‏‏‏‏‏‎‎‎‎‏‎Holding down both volume keys for a few seconds turns on ‎‏‎‎‏‏‎<xliff:g id="SERVICE">%1$s</xliff:g>‎‏‎‎‏‏‏‎, an accessibility feature. This may change how your device works.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎You can change this shortcut to another feature in Settings &gt; Accessibility.‎‏‎‎‏‎"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‎‎‏‎‏‎‏‎‏‏‎‎‎‎‎‎‏‎‎‎‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎Turn on‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 8987cda96efa..2ec544d69ef3 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Cuando la combinación de teclas está activada, puedes presionar los botones de volumen durante 3 segundos para iniciar una función de accesibilidad."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"¿Quieres activar la combinación de teclas para las funciones de accesibilidad?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Si mantienes presionadas las dos teclas de volumen durante unos segundos, se activarán las funciones de accesibilidad. Esto puede cambiar el funcionamiento de tu dispositivo.\n\nFunciones actuales:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuedes cambiar las funciones seleccionadas en Configuración &gt; Accesibilidad."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"¿Quieres activar el acceso directo de <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Si mantienes presionadas ambas teclas de volumen durante unos segundos, se activará la función de accesibilidad <xliff:g id="SERVICE">%1$s</xliff:g>. Esto podría cambiar la forma en que funciona tu dispositivo.\n\nPuedes cambiar este acceso directo a otra función en Configuración &gt; Accesibilidad."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activar"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 81d36263fef0..afc064461f9e 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Si el acceso directo está activado, pulsa los dos botones de volumen durante 3 segundos para iniciar una función de accesibilidad."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"¿Quieres activar el acceso directo a las funciones de accesibilidad?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Al mantener pulsadas las dos teclas de volumen durante unos segundos, se activan las funciones de accesibilidad, que pueden cambiar el funcionamiento del dispositivo.\n\nFunciones actuales:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuedes cambiar las funciones seleccionadas en Ajustes &gt; Accesibilidad."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"¿Quieres activar el acceso directo a <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Al mantener pulsadas ambas teclas de volumen durante unos segundos se activa <xliff:g id="SERVICE">%1$s</xliff:g>, una función de accesibilidad. Esta función puede modificar el funcionamiento del dispositivo.\n\nPuedes asignar este acceso directo a otra función en Ajustes &gt; Accesibilidad."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activar"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index b76e7e3ddf7a..70de8d910116 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kui otsetee on sisse lülitatud, käivitab mõlema helitugevuse nupu kolm sekundit all hoidmine juurdepääsetavuse funktsiooni."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Kas lülitada juurdepääsufunktsioonide otsetee sisse?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hoidke juurdepääsufunktsioonide sisselülitamiseks mõlemat helitugevuse klahvi mõni sekund all. See võib teie seadme tööviisi muuta.\n\nPraegused funktsioonid:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nValitud funktsioone saab muuta jaotises Seaded &gt; Juurdepääsetavus."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Kas lülitada teenuse <xliff:g id="SERVICE">%1$s</xliff:g> otsetee sisse?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Kui hoiate mõlemat helitugevuse klahvi mõni sekund all, lülitatakse juurdepääsufunktsioon <xliff:g id="SERVICE">%1$s</xliff:g> sisse. See võib teie seadme tööviisi muuta.\n\nSelle otsetee saab asendada muu otseteega jaotises Seaded &gt; Juurdepääsetavus."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Lülita sisse"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index cc394dddc4fd..72f98c6da479 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Lasterbidea aktibatuta dagoenean, bi bolumen-botoiak hiru segundoz sakatuta abiaraziko da erabilerraztasun-eginbidea."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Erabilerraztasun-eginbideetarako lasterbidea aktibatu nahi duzu?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Eduki sakatuta bolumen-botoiak segundo batzuez erabilerraztasun-eginbideak aktibatzeko. Hori eginez gero, baliteke zure mugikorraren funtzionamendua aldatzea.\n\nEginbideak:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nHautatutako eginbideak aldatzeko, joan Ezarpenak &gt; Erabilerraztasuna atalera."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> zerbitzuaren lasterbidea aktibatu nahi duzu?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Eduki sakatuta bolumen-botoiak segundo batzuez <xliff:g id="SERVICE">%1$s</xliff:g> izeneko erabilerraztasun-eginbidea aktibatzeko. Honen bidez, baliteke zure mugikorraren funtzionamendua aldatzea.\n\nLasterbide hau beste eginbide batengatik aldatzeko, joan Ezarpenak &gt; Erabilerraztasuna atalera."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktibatu"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 5155dfa9071b..9f7204496f1d 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"وقتی میان‌بر روشن باشد، با فشار دادن هردو دکمه صدا به‌مدت ۳ ثانیه ویژگی دسترس‌پذیری فعال می‌شود."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"میان‌بر برای ویژگی‌های دسترس‌پذیری روشن شود؟"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"با پایین نگه داشتن هردو کلید میزان صدا به‌مدت چند ثانیه، ویژگی‌های دسترس‌پذیری روشن می‌شود. با این کار نحوه عملکرد دستگاهتان تغییر می‌کند.\n\nویژگی‌های فعلی:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nمی‌توانید ویژگی‌های انتخابی را در «تنظیمات &gt; دسترس‌پذیری» تغییر دهید."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"میان‌بر <xliff:g id="SERVICE">%1$s</xliff:g> روشن شود؟"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"با پایین نگه داشتن هردو کلید میزان صدا به‌مدت چند ثانیه، <xliff:g id="SERVICE">%1$s</xliff:g> (یکی از ویژگی‌های دسترس‌پذیری) روشن می‌شود. با این کار نحوه عملکرد دستگاهتان تغییر می‌کند.\n\nمی‌توانید در «تنظیمات &gt; دسترس‌پذیری»،‌این میان‌بر را به ویژگی دیگری تغییر دهید."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"روشن شود"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 1e491e5b7482..388a586ba2ff 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kun pikanäppäin on käytössä, voit käynnistää esteettömyystoiminnon pitämällä molempia äänenvoimakkuuspainikkeita painettuna kolmen sekunnin ajan."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Laitetaanko esteettömyysominaisuuksien pikavalinta päälle?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Molempien äänenvoimakkuuspainikkeiden painaminen muutaman sekunnin ajan laittaa esteettömyysominaisuudet päälle. Tämä voi muuttaa laitteesi toimintaa.\n\nNykyiset ominaisuudet:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nVoit muuttaa valittuja ominaisuuksia kohdassa Asetukset &gt; Esteettömyys."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Laitetaanko pikavalinta (<xliff:g id="SERVICE">%1$s</xliff:g>) päälle?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Molempien äänenvoimakkuuspainikkeiden pitkään painaminen laittaa päälle esteettömyysominaisuuden <xliff:g id="SERVICE">%1$s</xliff:g>. Tämä voi muuttaa laitteesi toimintaa.\n\nVoit muuttaa tätä pikanäppäintä kohdassa Asetukset &gt; Esteettömyys."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Laita päälle"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 46994f07b18c..d674a5d94a43 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quand le raccourci est activé, appuyez sur les deux boutons de volume pendant trois secondes pour lancer une fonctionnalité d\'accessibilité."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Activer le raccourci pour les fonctionnalités d\'accessibilité?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Si vous maintenez enfoncées les deux touches de volume pendant quelques secondes, vous activez les fonctionnalités d\'accessibilité. Cela peut modifier le fonctionnement de votre appareil.\n\nFonctionnalités actuellement utilisées :\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPour les modifier, sélectionnez Paramètres &gt; Accessibilité."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Activer le raccourci pour <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Si vous maintenez enfoncées les deux touches de volume pendant quelques secondes, vous activez la fonctionnalité d\'accessibilité <xliff:g id="SERVICE">%1$s</xliff:g>. Cela peut modifier le fonctionnement de votre appareil.\n\nPour attribuer ce raccourci à une autre fonctionnalité, sélectionnez Paramètres &gt; Accessibilité."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activer"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index b1a7eece28ed..59046eea2272 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quand le raccourci est activé, appuyez sur les deux boutons de volume pendant trois secondes pour démarrer une fonctionnalité d\'accessibilité."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Activer le raccourci pour accéder aux fonctionnalités d\'accessibilité ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Si vous appuyez sur les deux touches de volume pendant quelques secondes, vous activez des fonctionnalités d\'accessibilité. Cela peut affecter le fonctionnement de votre appareil.\n\nFonctionnalités actuellement utilisées :\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPour les modifier, accédez à Paramètres &gt; Accessibilité."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Activer le raccourci <xliff:g id="SERVICE">%1$s</xliff:g> ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Si vous appuyez sur les deux touches de volume pendant quelques secondes, vous activez la fonctionnalité d\'accessibilité <xliff:g id="SERVICE">%1$s</xliff:g>. Cela peut affecter le fonctionnement de votre appareil.\n\nPour attribuer ce raccourci à une autre fonctionnalité, accédez à Paramètres &gt; Accessibilité."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activer"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 50117568dad4..34f027caa32d 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Cando o atallo está activado, podes premer os dous botóns de volume durante 3 segundos para iniciar unha función de accesibilidade."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Queres activar as funcións de accesibilidade?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ao manter as dúas teclas de volume premidas durante uns segundos actívanse as funcións de accesibilidade. Esta acción pode cambiar o funcionamento do dispositivo.\n\nFuncións activadas actualmente:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPodes cambiar as funcións seleccionadas en Configuración &gt; Accesibilidade."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Queres activar o atallo a <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ao manter as dúas teclas de volume premidas durante uns segundos actívase <xliff:g id="SERVICE">%1$s</xliff:g>, unha función de accesibilidade. Esta acción pode cambiar o funcionamento do dispositivo.\n\nPodes cambiar o uso deste atallo para outra función en Configuración &gt; Accesibilidade."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activar"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index e1d89ab6ab73..45934d2bd296 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"જ્યારે શૉર્ટકટ ચાલુ હોય, ત્યારે બન્ને વૉલ્યૂમ બટનને 3 સેકન્ડ સુધી દબાવી રાખવાથી ઍક્સેસિબિલિટી સુવિધા શરૂ થઈ જશે."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ઍક્સેસિબિલિટી સુવિધાઓ માટે શૉર્ટકટ ચાલુ કરીએ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"બન્ને વૉલ્યૂમ કીને થોડી સેકન્ડ સુધી દબાવી રાખવાથી ઍક્સેસિબિલિટી સુવિધાઓ ચાલુ થઈ જાય છે. આનાથી તમારા ડિવાઇસની કામ કરવાની રીત બદલાઈ શકે છે.\n\nવર્તમાન સુવિધાઓ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nતમે સેટિંગ &gt; ઍક્સેસિબિલિટીમાં જઈને પસંદ કરેલી સુવિધાઓને બદલી શકો છો."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> શૉર્ટકટ ચાલુ કરીએ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"બન્ને વૉલ્યૂમ કીને થોડી સેકન્ડ સુધી દબાવી રાખવાથી ઍક્સેસિબિલિટી સુવિધા એવી <xliff:g id="SERVICE">%1$s</xliff:g> ચાલુ થઈ જાય છે. આનાથી તમારા ડિવાઇસની કામ કરવાની રીત બદલાઈ શકે છે.\n\nતમે સેટિંગ &gt; ઍક્સેસિબિલિટીમાં જઈને આ શૉર્ટકટને બીજી સુવિધામાં બદલી શકો છો."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ચાલુ કરો"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 8df37722e5d9..6cde84781e7e 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"शॉर्टकट के चालू होने पर, दाेनाें वॉल्यूम बटन (आवाज़ कम या ज़्यादा करने वाले बटन) को तीन सेकंड तक दबाने से, सुलभता सुविधा शुरू हाे जाएगी."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"क्या आप सुलभता सुविधाओं के लिए शॉर्टकट चालू करना चाहते हैं?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"आवाज़ कम और ज़्यादा करने वाले दोनों बटन को कुछ सेकंड तक दबाकर रखने से सुलभता सुविधाएं चालू हो जाती हैं. ऐसा करने से आपके डिवाइस के काम करने के तरीके में बदलाव हो सकता है.\n\nमौजूदा सुविधाएं:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nआप सेटिंग और सुलभता में जाकर चुनी हुई सुविधाएं बदल सकते हैं."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"क्या आप <xliff:g id="SERVICE">%1$s</xliff:g> शॉर्टकट चालू करना चाहते हैं?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"आवाज़ कम और ज़्यादा करने वाले दोनों बटन को कुछ सेकंड तक दबाकर रखने से <xliff:g id="SERVICE">%1$s</xliff:g> चालू हो जाती है, जो एक सुलभता सुविधा है. ऐसा करने से आपके डिवाइस के काम करने के तरीके में बदलाव हो सकता है.\n\nआप सेटिंग और सुलभता में जाकर इस शॉर्टकट को किसी दूसरी सुविधा के लिए बदल सकते हैं."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"चालू करें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 7dd2ad39891f..24e809c0b7ad 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1715,7 +1715,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kad je taj prečac uključen, pritiskom na obje tipke za glasnoću na tri sekunde pokrenut će se značajka pristupačnosti."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Želite li uključiti prečac za značajke pristupačnosti?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Značajke pristupačnosti uključuju se ako na nekoliko sekundi pritisnete obje tipke za glasnoću. Time se može promijeniti način na koji vaš uređaj radi.\n\nTrenutačne značajke:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nOdabrane značajke možete promijeniti u odjeljku Postavke &gt; Pristupačnost."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Želite li uključiti prečac za uslugu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ako na nekoliko sekundi pritisnete obje tipke za glasnoću, uključuje se značajka pristupačnosti <xliff:g id="SERVICE">%1$s</xliff:g>. Time se može promijeniti način na koji vaš uređaj radi.\n\nZnačajku na koju se taj prečac odnosi možete promijeniti u odjeljku Postavke &gt; Pristupačnost."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Uključi"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 0e20f2051c29..567783d2be7c 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Ha a gyorsparancs aktív, akkor a két hangerőgomb három másodpercig tartó együttes lenyomásával kisegítő funkciót indíthat el."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Bekapcsol gyorsparancsot a kisegítő lehetőségekhez?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"A kisegítő lehetőségek bekapcsolásához tartsa nyomva néhány másodpercig mindkét hangerőgombot. Ez hatással lehet az eszköz működésére.\n\nJelenlegi funkciók:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nA kiválasztott funkciókat a Beállítások &gt; Kisegítő lehetőségek pontban módosíthatja."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Bekapcsolja a(z) <xliff:g id="SERVICE">%1$s</xliff:g> szolgáltatás gyorsparancsát?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"A(z) <xliff:g id="SERVICE">%1$s</xliff:g> kisegítő lehetőség bekapcsolásához tartsa nyomva néhány másodpercig mindkét hangerőgombot. Ez hatással lehet az eszköz működésére.\n\nEzt a gyorsparancsot a Beállítások &gt; Kisegítő lehetőségek pontban módosíthatja másik funkció használatára."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Bekapcsolom"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index f94c8114a0e6..87523a4de717 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Հատուկ գործառույթն օգտագործելու համար սեղմեք և 3 վայրկյան սեղմած պահեք ձայնի ուժգնության երկու կոճակները, երբ գործառույթը միացված է:"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Միացնե՞լ հատուկ գործառույթների դյուրանցումը"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ձայնի կարգավորման երկու կոճակները մի քանի վայրկյան սեղմած պահելով կմիացնեք հատուկ գործառույթները։ Դրա արդյունքում սարքի աշխատաեղանակը կարող է փոխվել։\n\nԸնթացիկ գործառույթներ՝\n<xliff:g id="SERVICE">%1$s</xliff:g>\nԸնտրված գործառույթները փոխելու համար անցեք Կարգավորումներ &gt; Հատուկ գործառույթներ։"</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Միացնե՞լ <xliff:g id="SERVICE">%1$s</xliff:g>-ի դյուրանցումը"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ձայնի կարգավորման երկու կոճակները մի քանի վայրկյան սեղմած պահելով կմիացնեք <xliff:g id="SERVICE">%1$s</xliff:g> ծառայությունը, որը հատուկ գործառույթ է։ Դրա արդյունքում սարքի աշխատաեղանակը կարող է փոխվել։\n\nԱյս դյուրանցումը մեկ այլ գործառույթով փոխելու համար անցեք Կարգավորումներ &gt; Հատուկ գործառույթներ։"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Միացնել"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 5f9ad9d7d456..2a2762a73cf4 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Saat pintasan aktif, menekan kedua tombol volume selama 3 detik akan memulai fitur aksesibilitas."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Aktifkan pintasan untuk fitur aksesibilitas?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Menahan kedua tombol volume selama beberapa detik akan mengaktifkan fitur aksesibilitas. Tindakan ini dapat mengubah cara kerja perangkat Anda.\n\nFitur saat ini:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nAnda dapat mengubah fitur yang dipilih di Setelan &gt; Aksesibilitas."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Aktifkan pintasan <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Menahan kedua tombol volume selama beberapa detik akan mengaktifkan <xliff:g id="SERVICE">%1$s</xliff:g>, yang merupakan fitur aksesibilitas. Tindakan ini dapat mengubah cara kerja perangkat Anda.\n\nAnda dapat mengubah pintasan ini ke fitur lain di Setelan &gt; Aksesibilitas."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktifkan"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 76c583720c4d..8919bf76a998 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Þegar flýtileiðin er virk er kveikt á aðgengiseiginleikanum með því að halda báðum hljóðstyrkshnöppunum inni í þrjár sekúndur."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Kveikja á flýtileið fyrir aðgangseiginleika?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Kveikt er á aðgengiseiginleikum þegar báðum hljóðstyrkstökkunum er haldið inni í nokkrar sekúndur. Þetta getur breytt því hvernig tækið virkar.\n\nNúverandi eiginleikar:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nÞú getur breytt völdum eiginleikum í Stillingar &gt; Aðgengi."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Kveikja á flýtileið <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ef báðum hljóðstyrkstökkunum er haldið inni í nokkrar sekúndur er kveikt á aðgengiseiginleikanum <xliff:g id="SERVICE">%1$s</xliff:g>. Þetta getur breytt því hvernig tækið virkar.\n\nÞú getur breytt þessari flýtileið í annan eiginleika í Stillingar &gt; Aðgengi."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Kveikja"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index c4493a188a22..f60664319bf0 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quando la scorciatoia è attiva, puoi premere entrambi i pulsanti del volume per tre secondi per avviare una funzione di accessibilità."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vuoi attivare la scorciatoia per le funzioni di accessibilità?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Se tieni premuti entrambi i tasti del volume per qualche secondo, vengono attivate le funzioni di accessibilità. Questa operazione potrebbe modificare il funzionamento del dispositivo.\n\nFunzioni correnti:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuoi modificare le funzioni selezionate in Impostazioni &gt; Accessibilità."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vuoi attivare la scorciatoia per <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Se tieni premuti entrambi i tasti del volume per qualche secondo verrà attivata la funzione di accessibilità <xliff:g id="SERVICE">%1$s</xliff:g>. Questa operazione potrebbe modificare il funzionamento del dispositivo.\n\nPuoi associare questa scorciatoia a un\'altra funzionalità in Impostazioni &gt; Accessibilità."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Attiva"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 02ff0f561e58..0aa6ad8afb6d 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1737,7 +1737,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"כשקיצור הדרך מופעל, לחיצה על שני לחצני עוצמת הקול למשך שלוש שניות מפעילה את תכונת הנגישות."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"האם להפעיל את מקש הקיצור לתכונות הנגישות?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"לחיצה ארוכה על שני לחצני עוצמת הקול למשך מספר שניות מפעילה את תכונות הנגישות. בעקבות זאת, ייתכן שאופן הפעולה של המכשיר ישתנה.\n\nהתכונות הנוכחיות:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nניתן לשנות את התכונות שנבחרו ב\'הגדרות\' &gt; \'נגישות\'."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"האם להפעיל את מקש הקיצור של <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"לחיצה על שני מקשי עוצמת הקול למשך מספר שניות מפעילה את תכונת הנגישות <xliff:g id="SERVICE">%1$s</xliff:g>. ייתכן שאופן הפעולה של המכשיר ישתנה בעקבות זאת.\n\nאפשר לשנות את מקשי הקיצור האלה לתכונה אחרת ב\'הגדרות\' &gt; \'נגישות\'."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"אני רוצה להפעיל"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 0ef14c2c82a2..739e19e3ef33 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ショートカットが ON の場合、両方の音量ボタンを 3 秒ほど長押しするとユーザー補助機能が起動します。"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ユーザー補助機能のショートカットを ON にしますか?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"音量大と音量小の両方のボタンを数秒ほど長押しすると、ユーザー補助機能が ON になります。この機能が ON になると、デバイスの動作が変わることがあります。\n\n現在の機能:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n選択した機能は [設定] &gt; [ユーザー補助] で変更できます。"</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> のショートカットを ON にしますか?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"音量大と音量小の両方のボタンを数秒ほど長押しすると、ユーザー補助機能の <xliff:g id="SERVICE">%1$s</xliff:g> が ON になります。この機能が ON になると、デバイスの動作が変わることがあります。\n\nこのショートカットは [設定] &gt; [ユーザー補助] で別の機能に変更できます。"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ON にする"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index b29ce0aa3795..3e13fc6c51c0 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"თუ მალსახმობი ჩართულია, ხმის ორივე ღილაკზე 3 წამის განმავლობაში დაჭერით მარტივი წვდომის ფუნქცია ჩაირთვება."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ჩაირთოს მარტივი წვდომის ფუნქციების მალსახმობი?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ხმის ორივე ღილაკზე ხანგრძლივად დაჭერა რამდენიმე წამის განმავლობაში ჩართავს მარტივი წვდომის ფუნქციებს. ამ ქმედებამ შეიძლება შეცვალოს თქვენი მოწყობილობის მუშაობის პრინციპი.\n\nამჟამინდელი ფუნქციები:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nარჩეული ფუნქციების შეცვლა შესაძლებელია აქ: პარამეტრები &gt; მარტივი წვდომა."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"ჩაირთოს <xliff:g id="SERVICE">%1$s</xliff:g>-ის მალსახმობი?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ხმის ორივე ღილაკზე რამდენიმე წამის განმავლობაში დაჭერით ჩაირთვება <xliff:g id="SERVICE">%1$s</xliff:g>, რომელიც მარტივი წვდომის ფუნქციაა. ამან შეიძლება შეცვალოს თქვენი მოწყობილობის მუშაობის პრინციპი.\n\nამ მალსახმობის შეცვლა სხვა ფუნქციით შეგიძლიათ აქ: პარამეტრები &gt; აპები."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ჩართვა"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 086a033e44dd..94989805353a 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Түймелер тіркесімі қосулы кезде, екі дыбыс түймесін 3 секунд басып тұрсаңыз, \"Арнайы мүмкіндіктер\" функциясы іске қосылады."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Арнайы мүмкіндіктердің жылдам пәрмені іске қосылсын ба?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Дыбыс деңгейі пернелерін бірнеше секунд басып тұрсаңыз, арнайы мүмкіндіктер іске қосылады. Бұл – құрылғының жұмысына әсер етуі мүмкін.\n\nҚазіргі функциялар:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТаңдалған функцияларды \"Параметрлер &gt; Арнайы мүмкіндіктер\" бөлімінен өзгерте аласыз."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> жылдам пәрмені іске қосылсын ба?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Дыбыс деңгейі пернелерін бірнеше секунд басып тұрсаңыз, <xliff:g id="SERVICE">%1$s</xliff:g> арнайы қызметі іске қосылады. Бұл – құрылғының жүмысына әсер етуі мүмкін.\n\nБұл таңбашаны басқа функцияға \"Параметрлер &gt; Арнайы мүмкіндіктер\" бөлімінен өзгерте аласыз."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Қосылсын"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 8b4d9b63c3ea..841f5bf325fe 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"នៅពេលបើក​ផ្លូវកាត់ ការចុច​ប៊ូតុង​កម្រិតសំឡេង​ទាំងពីរ​រយៈពេល 3 វិនាទី​នឹង​ចាប់ផ្តើម​មុខងារ​ភាពងាយប្រើ។"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"បើក​ផ្លូវកាត់​សម្រាប់មុខងារ​ភាពងាយស្រួលឬ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ការសង្កត់​គ្រាប់ចុច​កម្រិតសំឡេង​ទាំងពីរ​ឱ្យជាប់​រយៈពេល​ពីរបីវិនាទី​នឹងបើក​មុខងារ​ភាពងាយប្រើ។ ការធ្វើ​បែបនេះ​អាចផ្លាស់ប្ដូរ​របៀបដែល​ឧបករណ៍​របស់អ្នក​ដំណើរការ។\n\nមុខងារ​បច្ចុប្បន្ន៖\n<xliff:g id="SERVICE">%1$s</xliff:g>\nអ្នកអាច​ប្ដូរ​មុខងារ​ដែលបាន​ជ្រើសរើស​នៅក្នុង​ការកំណត់ &gt; ភាពងាយស្រួល។"</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"បើក​ផ្លូវកាត់ <xliff:g id="SERVICE">%1$s</xliff:g> ឬ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ការសង្កត់​គ្រាប់ចុច​កម្រិតសំឡេង​ទាំងពីរ​ឱ្យជាប់​រយៈពេល​ពីរបីវិនាទី​នឹងបើក <xliff:g id="SERVICE">%1$s</xliff:g> ដែលជា​មុខងារ​ភាពងាយប្រើ។ ការធ្វើ​បែបនេះ​អាចផ្លាស់ប្ដូរ​របៀបដែល​ឧបករណ៍​របស់អ្នក​ដំណើរការ។\n\nអ្នកអាច​ប្ដូរផ្លូវកាត់​នេះទៅ​មុខងារ​ផ្សេងទៀត​នៅក្នុង​ការកំណត់ &gt; ភាពងាយស្រួល។"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"បើក"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index af6df158afbe..400b4218f129 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ಶಾರ್ಟ್‌ಕಟ್ ಆನ್ ಆಗಿರುವಾಗ, ಎರಡೂ ವಾಲ್ಯೂಮ್ ಬಟನ್‌ಗಳನ್ನು 3 ಸೆಕೆಂಡುಗಳ ಕಾಲ ಒತ್ತಿದರೆ ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯವೊಂದು ಪ್ರಾರಂಭವಾಗುತ್ತದೆ."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯಗಳಿಗಾಗಿ ಶಾರ್ಟ್‌ಕಟ್ ಆನ್ ಮಾಡಬೇಕೇ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ಎರಡೂ ವಾಲ್ಯೂಮ್ ಕೀಗಳನ್ನು ಕೆಲವು ಸೆಕೆಂಡುಗಳ ಕಾಲ ಹಿಡಿದಿಟ್ಟುಕೊಳ್ಳುವುದರಿಂದ ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯಗಳು ಆನ್ ಆಗುತ್ತವೆ. ಇದು ನಿಮ್ಮ ಸಾಧನವು ಹೇಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ ಎಂಬುದನ್ನು ಬದಲಾಯಿಸಬಹುದು.\n\n ಪ್ರಸ್ತುತ ವೈಶಿಷ್ಟ್ಯಗಳು:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nಸೆಟ್ಟಿಂಗ್‌ಗಳು ಮತ್ತು ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿಯಲ್ಲಿ ಆಯ್ದ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ನೀವು ಬದಲಾಯಿಸಬಹುದು."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"ಶಾರ್ಟ್‌ಕಟ್ <xliff:g id="SERVICE">%1$s</xliff:g>ಆನ್‌ ಮಾಡಬೇಕೇ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ಎರಡೂ ವಾಲ್ಯೂಮ್ ಕೀಗಳನ್ನು ಕೆಲವು ಸೆಕೆಂಡುಗಳ ಕಾಲ ಹಿಡಿದಿಟ್ಟುಕೊಳ್ಳುವುದರಿಂದ ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯವಾದ <xliff:g id="SERVICE">%1$s</xliff:g> ಆನ್ ಆಗುತ್ತದೆ. ಇದು ನಿಮ್ಮ ಸಾಧನವು ಹೇಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ ಎಂಬುದನ್ನು ಬದಲಾಯಿಸಬಹುದು.\n\nನೀವು ಈ ಶಾರ್ಟ್‌ಕಟ್ ಅನ್ನು ಸೆಟ್ಟಿಂಗ್‌ಗಳು ಮತ್ತು ಅಕ್ಸೆಸಿಬಿಲಿಟಿಯಲ್ಲಿನ ಮತ್ತೊಂದು ವೈಶಿಷ್ಟ್ಯಕ್ಕೆ ಬದಲಾಯಿಸಬಹುದು."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ಆನ್ ಮಾಡಿ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 2a261c220e45..78ec66185bcc 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"단축키가 사용 설정된 경우 볼륨 버튼 두 개를 동시에 3초간 누르면 접근성 기능이 시작됩니다."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"접근성 기능 바로가기를 사용 설정하시겠습니까?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"볼륨 키 2개를 몇 초 동안 길게 누르면 접근성 기능이 사용 설정됩니다. 이때 기기 작동 방식이 달라질 수 있습니다.\n\n현재 기능:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n설정 &gt; 접근성에서 선택한 기능을 변경할 수 있습니다."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> 바로가기를 사용 설정하시겠습니까?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"볼륨 키 2개를 몇 초 동안 길게 누르면 <xliff:g id="SERVICE">%1$s</xliff:g> 접근성 기능이 사용 설정됩니다. 이렇게 되면 기기 작동 방식이 달라질 수 있습니다.\n\n설정 &gt; 접근성에서 이 단축키를 다른 기능으로 변경할 수 있습니다."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"사용"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 89b6e1573e78..ef922074d745 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Атайын мүмкүнчүлүктөр функциясын пайдалануу үчүн ал күйгүзүлгөндө, үндү катуулатып/акырындаткан эки баскычты тең 3 секунддай коё бербей басып туруңуз."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Атайын мүмкүнчүлүктөрдүн ыкчам баскычын иштетесизби?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Атайын мүмкүнчүлүктөр функциясын иштетүү үчүн үндү чоңойтуп/кичирейтүү баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nУчурдагы функциялар:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТандалган функцияларды өзгөртүү үчүн Жөндөөлөр &gt; Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> ыкчам баскычын иштетесизби?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"<xliff:g id="SERVICE">%1$s</xliff:g> кызматын иштетүү үчүн үндү чоңойтуп/кичирейтүү баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nБаскычтардын ушул айкалышын башка функцияга дайындоо үчүн, Жөндөөлөр &gt; Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ооба"</string>
diff --git a/core/res/res/values-land/dimens.xml b/core/res/res/values-land/dimens.xml
index ca549aeba1f5..f1e5888d61b9 100644
--- a/core/res/res/values-land/dimens.xml
+++ b/core/res/res/values-land/dimens.xml
@@ -27,8 +27,6 @@
<dimen name="password_keyboard_spacebar_vertical_correction">2dip</dimen>
<dimen name="preference_widget_width">72dp</dimen>
- <!-- Height of the status bar -->
- <dimen name="status_bar_height">@dimen/status_bar_height_landscape</dimen>
<!-- Height of area above QQS where battery/time go -->
<dimen name="quick_qs_offset_height">48dp</dimen>
<!-- Default height of an action bar. -->
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 9849b2988aca..564997b0e26b 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ເມື່ອເປີດໃຊ້ທາງລັດແລ້ວ, ການກົດປຸ່ມລະດັບສຽງທັງສອງຄ້າງໄວ້ 3 ວິນາທີຈະເປັນການເລີ່ມຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງ."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ເປີດໃຊ້ທາງລັດສຳລັບຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງບໍ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ກົດປຸ່ມລະດັບສຽງທັງສອງຄ້າງໄວ້ສອງສາມວິນາທີເພື່ອເປີດໃຊ້ຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງ. ນີ້ອາດປ່ຽນວິທີການເຮັດວຽກຂອງອຸປະກອນທ່ານ.\n\nຄຸນສົມບັດປັດຈຸບັນ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nທ່ານສາມາດປ່ຽນຄຸນສົມບັດທີ່ເລືອກໄດ້ໃນການຕັ້ງຄ່າ &gt; ການຊ່ວຍເຂົ້າເຖິງ."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"ເປີດໃຊ້ທາງລັດ <xliff:g id="SERVICE">%1$s</xliff:g> ບໍ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ກົດປຸ່ມລະດັບສຽງທັງສອງຄ້າງໄວ້ສອງສາມວິນາທີເພື່ອເປີດໃຊ້ <xliff:g id="SERVICE">%1$s</xliff:g>, ຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງ. ນີ້ອາດປ່ຽນວິທີການເຮັດວຽກຂອງອຸປະກອນທ່ານ.\n\nທ່ານສາມາດປ່ຽນທາງລັດນີ້ເປັນຄຸນສົມບັດອື່ນໄດ້ໃນການຕັ້ງຄ່າ &gt; ການຊ່ວຍເຂົ້າເຖິງ."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ເປີດໃຊ້"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 32cf15edfd4a..d09866ce76a1 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1737,7 +1737,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kai spartusis klavišas įjungtas, paspaudus abu garsumo mygtukus ir palaikius 3 sekundes bus įjungta pritaikymo neįgaliesiems funkcija."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Įjungti pritaikymo neįgaliesiems funkcijų spartųjį klavišą?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Paspaudus abu garsumo klavišus ir palaikius kelias sekundes įjungiamos pritaikymo neįgaliesiems funkcijos. Tai gali pakeisti įrenginio veikimą.\n\nDabartinės funkcijos:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPasirinktas funkcijas galite pakeisti skiltyje „Nustatymai“ &gt; „Pritaikomumas“."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • „<xliff:g id="SERVICE">%1$s</xliff:g>“\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Įjungti „<xliff:g id="SERVICE">%1$s</xliff:g>“ spartųjį klavišą?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Paspaudus abu garsumo klavišus ir palaikius kelias sekundes įjungiama pritaikymo neįgaliesiems funkcija „<xliff:g id="SERVICE">%1$s</xliff:g>“. Tai gali pakeisti įrenginio veikimą.\n\nGalite pakeisti šį spartųjį klavišą į kitą funkciją skiltyje „Nustatymai“ &gt; „Pritaikomumas“."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Įjungti"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 48318cdef17b..a2aa0ce5b42f 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1715,7 +1715,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kad īsinājumtaustiņš ir ieslēgts, nospiežot abas skaļuma pogas un 3 sekundes turot tās, tiks aktivizēta pieejamības funkcija."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vai ieslēgt pieejamības funkciju saīsni?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Turot nospiestus abus skaļuma taustiņus dažas sekundes, tiek ieslēgtas pieejamības funkcijas. Tas var mainīt ierīces darbību.\n\nPašreizējās funkcijas:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nAtlasītās funkcijas varat mainīt šeit: Iestatījumi &gt; Pieejamība."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vai ieslēgt <xliff:g id="SERVICE">%1$s</xliff:g> saīsni?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Turot nospiestus abus skaļuma taustiņus dažas sekundes, tiek ieslēgta pieejamības funkcija <xliff:g id="SERVICE">%1$s</xliff:g>. Tas var mainīt ierīces darbību.\n\nŠo saīsni uz citu funkciju varat mainīt šeit: Iestatījumi &gt; Pieejamība."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ieslēgt"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 34a1d29d0810..9d46eccc2828 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Кога е вклучена кратенката, ако ги притиснете двете копчиња за јачина на звук во времетраење од 3 секунди, ќе се стартува функција за пристапност."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Да се вклучи кратенка за функциите за пристапност?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ако ги задржите притиснати двете копчиња за јачина на звук неколку секунди, ќе се вклучат функциите за пристапност. Ова може да го промени начинот на функционирање на уредот.\n\nТековни функции:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nМоже да ги промените избраните функции во „Поставки &gt; Пристапност“."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Да се вклучи кратенка за <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ако ги задржите притиснати двете копчиња за јачина на звук неколку секунди, ќе се вклучи функцијата за пристапност <xliff:g id="SERVICE">%1$s</xliff:g>. Ова може да го промени начинот на функционирање на уредот.\n\nМоже да ја измените кратенкава да биде за друга функција во „Поставки &gt; Пристапност“."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Вклучи"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 14aab7185c7b..a5e2cbc0e4be 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"കുറുക്കുവഴി ഓണായിരിക്കുമ്പോൾ, രണ്ട് വോളിയം ബട്ടണുകളും 3 സെക്കൻഡ് നേരത്തേക്ക് അമർത്തുന്നത് ഉപയോഗസഹായി ഫീച്ചർ ആരംഭിക്കും."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ഉപയോഗസഹായി ഫീച്ചറുകൾക്കുള്ള കുറുക്കുവഴി ഓണാക്കണോ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"രണ്ട് വോളിയം കീകളും അൽപ്പ നേരത്തേക്ക് അമർത്തിപ്പിടിക്കുന്നത്, ഉപയോഗസഹായി ഫീച്ചറുകൾ ഓണാക്കുന്നു. നിങ്ങളുടെ ഉപകരണം പ്രവർത്തിക്കുന്ന രീതിയെ ഇത് മാറ്റിയേക്കാം.\n\nനിലവിലുള്ള ഫീച്ചറുകൾ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nതിരഞ്ഞെടുത്ത ഫീച്ചറുകൾ ക്രമീകരണം &gt; ഉപയോഗസഹായി എന്നതിൽ മാറ്റാനാവും."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> കുറുക്കുവഴി ഓണാക്കണോ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"രണ്ട് വോളിയം കീകളും അൽപ്പ നേരത്തേക്ക് അമർത്തിപ്പിടിക്കുന്നത് ഉപയോഗസഹായി ഫീച്ചറായ <xliff:g id="SERVICE">%1$s</xliff:g> എന്നതിനെ ഓണാക്കുന്നു. നിങ്ങളുടെ ഉപകരണം പ്രവർത്തിക്കുന്ന വിധം ഇത് മാറ്റിയേക്കാം.\n\nക്രമീകരണം &gt; ഉപയോഗസഹായി എന്നതിലെ മറ്റൊരു ഫീച്ചറിലേക്ക് നിങ്ങൾക്ക് ഈ കുറുക്കുവഴി മാറ്റാനാവും."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ഓണാക്കുക"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index f7be6ee95bb5..47c856035c01 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Товчлол асаалттай үед дууны түвшний хоёр товчлуурыг хамтад нь 3 секунд дарснаар хандалтын онцлогийг эхлүүлнэ."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Хандалтын онцлогуудын товчлолыг асаах уу?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Дууны түвшний түлхүүрийг хэдэн секундийн турш зэрэг дарснаар хандалтын онцлогууд асна. Энэ нь таны төхөөрөмжийн ажиллах зарчмыг өөрчилж болзошгүй.\n\nОдоогийн онцлогууд:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТа сонгосон онцлогуудыг Тохиргоо &gt; Хандалт хэсэгт өөрчлөх боломжтой."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g>-н товчлолыг асаах уу?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Дууны түвшний түлхүүрийг хэдэн секундийн турш зэрэг дарах нь хандалтын онцлог болох <xliff:g id="SERVICE">%1$s</xliff:g>-г асаадаг. Энэ нь таны төхөөрөмжийн ажиллах зарчмыг өөрчилж болзошгүй.\n\nТа Тохиргоо &gt; Хандалт хэсэгт энэ товчлолыг өөр онцлогт оноож өөрчлөх боломжтой."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Асаах"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 0f7a8abc0103..8d4956d0cad0 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"शॉर्टकट सुरू असताना, दोन्ही व्‍हॉल्‍यूम बटणे तीन सेकंदांसाठी दाबून ठेवल्याने अ‍ॅक्सेसिबिलिटी वैशिष्ट्य सुरू होईल."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"अ‍ॅक्सेसिबिलिटी वैशिष्ट्यांसाठी शॉर्टकट सुरू करायचा आहे का?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"दोन्ही व्हॉल्यूम की काही सेकंद धरून ठेवल्याने अ‍ॅक्सेसिबिलिटी वैशिष्ट्ये सुरू होतात. यामुळे तुमचे डिव्हाइस कसे काम करते हे पूर्णपणे बदलते.\n\nसध्याची वैशिष्ट्ये:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nतुम्ही हा शॉर्टकट सेटिंग्ज &gt; अ‍ॅक्सेसिबिलिटी मध्ये बदलू शकता."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> शॉर्टकट सुरू करायचा आहे का?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"दोन्ही व्हॉल्यूम की काही सेकंद धरून ठेवल्याने <xliff:g id="SERVICE">%1$s</xliff:g>, एक अ‍ॅक्सेसिबिलिटी वैशिष्ट्य सुरू होते. यामुळे तुमचे डिव्हाइस कसे काम करते हे पूर्णपणे बदलते.\n\nतुम्ही हा शॉर्टकट सेटिंग्ज &gt; अ‍ॅक्सेसिबिलिटी मध्ये बदलू शकता."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"सुरू करा"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 2844e1e22f6d..4daa0512187b 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Apabila pintasan dihidupkan, tindakan menekan kedua-dua butang kelantangan selama 3 saat akan memulakan ciri kebolehaksesan."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Hidupkan pintasan untuk ciri kebolehaksesan?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Tindakan menahan kedua-dua kekunci kelantangan selama beberapa saat akan menghidupkan ciri kebolehaksesan. Hal ini mungkin mengubah cara peranti anda berfungsi.\n\nCiri semasa:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nAnda boleh menukar ciri yang dipilih dalam Tetapan &gt; Kebolehaksesan."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Hidupkan pintasan <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Tindakan menahan kedua-dua kekunci kelantangan selama beberapa saat akan menghidupkan <xliff:g id="SERVICE">%1$s</xliff:g>, iaitu satu ciri kebolehaksesan. Ini mungkin mengubah cara peranti anda berfungsi.\n\nAnda boleh menukar pintasan ini kepada ciri lain dalam Tetapan &gt; Kebolehaksesan."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Hidupkan"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index eca5558c942a..886f1eecb03d 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ဖြတ်လမ်းလင့်ခ်ကို ဖွင့်ထားစဉ် အသံထိန်းခလုတ် နှစ်ခုစလုံးကို ၃ စက္ကန့်ခန့် ဖိထားခြင်းဖြင့် အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုကို ဖွင့်နိုင်သည်။"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုများအတွက် ဖြတ်လမ်းကို ဖွင့်မလား။"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"အသံခလုတ်နှစ်ခုလုံးကို စက္ကန့်အနည်းငယ် ဖိထားခြင်းက အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုများ ဖွင့်ပေးသည်။ ဤလုပ်ဆောင်ချက်က သင့်စက်အလုပ်လုပ်ပုံကို ပြောင်းလဲနိုင်သည်။\n\nလက်ရှိ ဝန်ဆောင်မှုများ-\n<xliff:g id="SERVICE">%1$s</xliff:g>\n\'ဆက်တင်များ &gt; အများသုံးစွဲနိုင်မှု\' တွင် ရွေးထားသည့် ဝန်ဆောင်မှုများကို ပြောင်းနိုင်သည်။"</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> ဖြတ်လမ်းကို ဖွင့်မလား။"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"အသံခလုတ်နှစ်ခုလုံးကို စက္ကန့်အနည်းငယ် ဖိထားခြင်းက အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုဖြစ်သော <xliff:g id="SERVICE">%1$s</xliff:g> ကို ဖွင့်ပေးသည်။ ဤလုပ်ဆောင်ချက်က သင့်စက်အလုပ်လုပ်ပုံကို ပြောင်းလဲနိုင်သည်။\n\nဤဖြတ်လမ်းလင့်ခ်ကို \'ဆက်တင်များ &gt; အများသုံးစွဲနိုင်မှု\' တွင် နောက်ဝန်ဆောင်မှုတစ်ခုသို့ ပြောင်းနိုင်သည်။"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ဖွင့်ရန်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 0252eb354609..d873c99ae746 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Når snarveien er på, starter en tilgjengelighetsfunksjon når du trykker inn begge volumknappene i tre sekunder."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vil du slå på snarveien for tilgjengelighetsfunksjoner?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hvis du holder inne volumtastene i noen sekunder, slås tilgjengelighetsfunksjoner på. Dette kan endre hvordan enheten din fungerer.\n\nNåværende funksjoner:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nDu kan endre valgte funksjoner i Innstillinger &gt; Tilgjengelighet."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vil du slå på <xliff:g id="SERVICE">%1$s</xliff:g>-snarveien?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Hvis du holder inne begge volumtastene i noen sekunder, slår du på <xliff:g id="SERVICE">%1$s</xliff:g>, en tilgjengelighetsfunksjon. Dette kan endre hvordan enheten din fungerer.\n\nDu kan endre denne snarveien til en annen funksjon i Innstillinger &gt; Tilgjengelighet."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Slå på"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index fbf40cfa5fb7..ade87f53f32d 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"यो सर्टकट सक्रिय हुँदा, ३ सेकेन्डसम्म दुवै भोल्युम बटन थिच्नुले पहुँचसम्बन्धी कुनै सुविधा सुरु गर्ने छ।"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"एक्सेसिबिलिटीसम्बन्धी सुविधा प्रयोग गर्न सर्टकट अन गर्ने हो?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"केही सेकेन्डसम्म दुवै भोल्युम की थिचिराख्नुभयो भने पहुँचसम्बन्धी सुविधाहरू सक्रिय हुन्छ। यसले तपाईंको यन्त्रले काम गर्ने तरिका परिवर्तन गर्न सक्छ।\n\nहालका सुविधाहरू:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nतपाईं सेटिङ &gt; पहुँचमा गएर चयन गरिएका सुविधाहरू परिवर्तन गर्न सक्नुहुन्छ।"</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> प्रयोग गर्न सर्टकट अन गर्ने हो?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"केही सेकेन्डसम्म दुवै भोल्युम की थिचिराख्नुले <xliff:g id="SERVICE">%1$s</xliff:g> नामक पहुँचसम्बन्धी सुविधा सक्रिय गर्छ। यसले तपाईंको यन्त्रले काम गर्ने तरिका परिवर्तन गर्न सक्छ।\n\nतपाईं सेटिङ &gt; पहुँचमा गई यो सर्टकटमार्फत अर्को सुविधा खुल्ने बनाउन सक्नुहुन्छ।"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"सक्रिय गरियोस्"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index ef6a441897be..9203f39c8b34 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Als de snelkoppeling aanstaat, houd je beide volumeknoppen 3 seconden ingedrukt om een toegankelijkheidsfunctie te starten."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Snelkoppeling voor toegankelijkheidsfuncties aanzetten?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Als je beide volumetoetsen een paar seconden ingedrukt houdt, zet je de toegankelijkheidsfuncties aan. Hierdoor kan de manier veranderen waarop je apparaat werkt.\n\nHuidige functies:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nJe kunt de geselecteerde functies wijzigen via Instellingen &gt; Toegankelijkheid."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Snelkoppeling voor <xliff:g id="SERVICE">%1$s</xliff:g> aanzetten?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Als je beide volumetoetsen een paar seconden ingedrukt houdt, wordt de toegankelijkheidsfunctie <xliff:g id="SERVICE">%1$s</xliff:g> aangezet. Hierdoor kan de manier veranderen waarop je apparaat werkt.\n\nJe kunt deze sneltoets op een andere functie instellen via Instellingen &gt; Toegankelijkheid."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aanzetten"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 291c1cc13e88..bb689908662c 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ସର୍ଟକଟ୍ ଚାଲୁ ଥିବା ବେଳେ, ଉଭୟ ଭଲ୍ୟୁମ୍ ବଟନ୍ 3 ସେକେଣ୍ଡ ପାଇଁ ଦବାଇବା ଦ୍ୱାରା ଏକ ଆକ୍ସେସବିଲିଟି ଫିଚର୍ ଆରମ୍ଭ ହେବ।"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ଆକ୍ସେସିବିଲିଟୀ ଫିଚରଗୁଡ଼ିକ ପାଇଁ ସର୍ଟକଟ୍ ଚାଲୁ କରିବେ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"କିଛି ସେକେଣ୍ଡ ପାଇଁ ଉଭୟ ଭଲ୍ୟୁମ୍ କୀ’କୁ ଧରି ରଖିବା ଫଳରେ ଆକ୍ସେସିବିଲିଟୀ ଫିଚରଗୁଡ଼ିକ ଚାଲୁ ହୁଏ। ଏହା ଆପଣଙ୍କ ଡିଭାଇସ୍ କିପରି କାମ କରେ ତାହା ପରିବର୍ତ୍ତନ କରିପାରେ।\n\nବର୍ତ୍ତମାନର ଫିଚରଗୁଡ଼ିକ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n ଆପଣ ସେଟିଂସ୍ &amp;gt ଆକ୍ସେସିବିଲିଟୀରେ ଚୟନିତ ଫିଚରଗୁଡ଼ିକୁ ପରିବର୍ତ୍ତନ କରିପାରିବେ।"</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> ସର୍ଟକଟ୍ ଚାଲୁ କରିବେ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"କିଛି ସେକେଣ୍ଡ ପାଇଁ ଉଭୟ ଭଲ୍ୟୁମ୍ କୀ’କୁ ଧରି ରଖିବା ଫଳରେ ଏକ ଆକ୍ସେସିବିଲିଟୀ ଫିଚର୍ <xliff:g id="SERVICE">%1$s</xliff:g> ଚାଲୁ ହୁଏ। ଏହା ଆପଣଙ୍କ ଡିଭାଇସ୍ କିପରି କାମ କରେ ତାହା ପରିବର୍ତ୍ତନ କରିପାରେ।\n\nଆପଣ ସେଟିଂସ୍ &amp;gt ଆକ୍ସେସିବିଲିଟୀରେ ଏହି ସର୍ଚକଟକୁ ଅନ୍ୟ ଏକ ଫିଚରରେ ପରିବର୍ତ୍ତନ କରିପାରିବେ।"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ଚାଲୁ କରନ୍ତୁ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 8a2ee3f74f96..9b6129f7cf61 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ਸ਼ਾਰਟਕੱਟ ਚਾਲੂ ਹੋਣ \'ਤੇ, ਕਿਸੇ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਦੋਵੇਂ ਅਵਾਜ਼ ਬਟਨਾਂ ਨੂੰ 3 ਸਕਿੰਟ ਲਈ ਦਬਾ ਕੇ ਰੱਖੋ।"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ਕੀ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਲਈ ਸ਼ਾਰਟਕੱਟ ਚਾਲੂ ਕਰਨਾ ਹੈ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ਕੁਝ ਸਕਿੰਟਾਂ ਲਈ ਦੋਵੇਂ ਅਵਾਜ਼ੀ ਕੁੰਜੀਆਂ ਨੂੰ ਦਬਾਈ ਰੱਖਣਾ, ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਚਾਲੂ ਕਰ ਦਿੰਦਾ ਹੈ। ਇਹ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੇ ਕੰਮ ਕਰਨ ਦੇ ਤਰੀਕੇ ਨੂੰ ਬਦਲ ਸਕਦਾ ਹੈ।\n\nਮੌਜੂਦਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nਸੈਟਿੰਗਾਂ ਅਤੇ ਪਹੁੰਚਯੋਗਤਾ ਵਿੱਚ ਤੁਸੀਂ ਚੁਣੀਆਂ ਗਈਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"ਕੀ <xliff:g id="SERVICE">%1$s</xliff:g> ਸ਼ਾਰਟਕੱਟ ਚਾਲੂ ਕਰਨਾ ਹੈ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ਕੁਝ ਸਕਿੰਟਾਂ ਲਈ ਦੋਵੇਂ ਅਵਾਜ਼ੀ ਕੁੰਜੀਆਂ ਨੂੰ ਦਬਾਈ ਰੱਖਣਾ <xliff:g id="SERVICE">%1$s</xliff:g>, ਇੱਕ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਚਾਲੂ ਕਰ ਦਿੰਦਾ ਹੈ। ਇਹ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੇ ਕੰਮ ਕਰਨ ਦੇ ਤਰੀਕੇ ਨੂੰ ਬਦਲ ਸਕਦਾ ਹੈ।\n\nਸੈਟਿੰਗਾਂ ਅਤੇ ਪਹੁੰਚਯੋਗਤਾ ਵਿੱਚ ਤੁਸੀਂ ਇਸ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਕਿਸੇ ਹੋਰ ਵਿਸ਼ੇਸ਼ਤਾ ਵਿੱਚ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ਚਾਲੂ ਕਰੋ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 2d9160f3e5dc..586a03a9e7ef 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1737,7 +1737,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Gdy skrót jest włączony, jednoczesne naciskanie przez trzy sekundy obu przycisków głośności uruchamia funkcję ułatwień dostępu."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Włączyć skrót ułatwień dostępu?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Przytrzymanie obu klawiszy głośności przez kilka sekund włącza ułatwienia dostępu. Może to zmienić sposób działania urządzenia.\n\nBieżące funkcje:\n<xliff:g id="SERVICE">%1$s</xliff:g>\naby zmienić wybrane funkcje, kliknij Ustawienia &gt; Ułatwienia dostępu."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Włączyć skrót <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Przytrzymanie obu klawiszy głośności przez kilka sekund włącza usługę <xliff:g id="SERVICE">%1$s</xliff:g>, stanowiącą ułatwienie dostępu. Może to zmienić sposób działania urządzenia.\n\nAby zmienić ten skrót i wskazać inną funkcję, kliknij Ustawienia &gt; Ułatwienia dostępu."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Włącz"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 3d55de4bbf67..da3164a523ab 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quando o atalho estiver ativado, pressione os dois botões de volume por três segundos para iniciar um recurso de acessibilidade."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Ativar atalho para recursos de acessibilidade?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Manter as duas teclas de volume pressionadas por alguns segundos ativa os recursos de acessibilidade. Isso pode mudar a forma como seu dispositivo funciona.\n\nRecursos atuais:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nÉ possível mudar os recursos selecionados em \"Config. &gt; Acessibilidade\"."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Ativar atalho para <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Manter as duas teclas de volume pressionadas por alguns segundos ativa o serviço <xliff:g id="SERVICE">%1$s</xliff:g>, um recurso de acessibilidade. Isso pode mudar a forma como seu dispositivo funciona.\n\nÉ possível trocar o uso desse atalho para outro recurso em \"Config. &gt; Acessibilidade\"."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ativar"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 64d28ba50104..0f5ddd945959 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quando o atalho está ativado, premir ambos os botões de volume durante 3 segundos inicia uma funcionalidade de acessibilidade."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Pretende ativar o atalho das funcionalidades de acessibilidade?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Manter premidas ambas as teclas de volume durante alguns segundos ativa as funcionalidades de acessibilidade. Estas podem alterar a forma como o seu dispositivo funciona.\n\nFuncionalidades atuais:\n<xliff:g id="SERVICE">%1$s</xliff:g>\npode alterar as funcionalidades selecionadas em Definições &gt; Acessibilidade."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Pretende ativar o atalho do serviço <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Manter premidas ambas as teclas de volume durante alguns segundos ativa o serviço <xliff:g id="SERVICE">%1$s</xliff:g>, uma funcionalidade de acessibilidade. Esta pode alterar a forma como o seu dispositivo funciona.\n\nPode alterar este atalho para outra funcionalidade em Definições &gt; Acessibilidade."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ativar"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 3d55de4bbf67..da3164a523ab 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quando o atalho estiver ativado, pressione os dois botões de volume por três segundos para iniciar um recurso de acessibilidade."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Ativar atalho para recursos de acessibilidade?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Manter as duas teclas de volume pressionadas por alguns segundos ativa os recursos de acessibilidade. Isso pode mudar a forma como seu dispositivo funciona.\n\nRecursos atuais:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nÉ possível mudar os recursos selecionados em \"Config. &gt; Acessibilidade\"."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Ativar atalho para <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Manter as duas teclas de volume pressionadas por alguns segundos ativa o serviço <xliff:g id="SERVICE">%1$s</xliff:g>, um recurso de acessibilidade. Isso pode mudar a forma como seu dispositivo funciona.\n\nÉ possível trocar o uso desse atalho para outro recurso em \"Config. &gt; Acessibilidade\"."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ativar"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 9fb501c2c413..2febb26f6d20 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1715,7 +1715,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Atunci când comanda rapidă este activată, dacă apăsați ambele butoane de volum timp de trei secunde, veți lansa o funcție de accesibilitate."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Activați comanda rapidă pentru funcțiile de accesibilitate?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Dacă apăsați ambele taste de volum câteva secunde, activați funcțiile de accesibilitate. Acest lucru poate schimba funcționarea dispozitivului.\n\nFuncțiile actuale:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuteți schimba funcțiile selectate din Setări &gt; Accesibilitate."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Activați comanda rapidă <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Dacă apăsați ambele taste de volum câteva secunde, activați funcția de accesibilitate <xliff:g id="SERVICE">%1$s</xliff:g>. Acest lucru poate schimba funcționarea dispozitivului.\n\nPuteți alege altă funcție pentru această comandă în Setări &gt; Accesibilitate."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activați"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index c4c69c90b8d0..1055a6d3cb98 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1737,7 +1737,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Чтобы использовать функцию специальных возможностей, когда она включена, нажмите и удерживайте обе кнопки регулировки громкости в течение трех секунд."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Использовать быстрое включение?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Чтобы включить специальные возможности, нажмите обе кнопки регулировки громкости и удерживайте несколько секунд. Обратите внимание, что в работе устройства могут произойти изменения.\n\nТекущие функции:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nЧтобы изменить выбранные функции, перейдите в настройки и нажмите \"Специальные возможности\"."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Использовать быстрое включение сервиса \"<xliff:g id="SERVICE">%1$s</xliff:g>\"?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Чтобы включить функцию \"<xliff:g id="SERVICE">%1$s</xliff:g>\", нажмите обе кнопки регулировки громкости на несколько секунд. Обратите внимание, что в работе устройства могут произойти изменения.\n\nЧтобы назначить это сочетание клавиш другой функции, перейдите в настройки и выберите \"Специальные возможности\"."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Включить"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index fd2e43bed403..703ad1760d69 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"කෙටිමග ක්‍රියාත්මක විට, හඬ පරිමා බොත්තම් දෙකම තත්පර 3ක් තිස්සේ එබීමෙන් ප්‍රවේශ්‍යතා විශේෂාංගය ආරම්භ වනු ඇත."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ප්‍රවේශ්‍යතා විශේෂාංග සඳහා කෙටි මග ක්‍රියාත්මක කරන්නද?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"හඬ පරිමා යතුරු දෙකම තත්පර කීපයකට පහළට අල්ලාගෙන සිටීම ප්‍රවේශ්‍යතා විශේෂාංග ක්‍රියාත්මක කරයි. මෙය ඔබේ උපාංගය ක්‍රියා කරන ආකාරය වෙනස් කළ හැකිය.\n\nවත්මන් විශේෂාංග:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nඔබට තේරූ විශේෂාංග සැකසීම් &gt; ප්‍රවේශ්‍යතාව හි වෙනස් කළ හැකිය."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> කෙටි මග ක්‍රියාත්මක කරන්නද?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"හඬ පරිමා යතුරු දෙකම තත්පර කීපයකට පහළට අල්ලාගෙන සිටීම ප්‍රවේශ්‍යතා විශේෂාංගයක් වන <xliff:g id="SERVICE">%1$s</xliff:g> ක්‍රියාත්මක කරයි. මෙය ඔබේ උපාංගය ක්‍රියා කරන ආකාරය වෙනස් කළ හැකිය.\n\nඔබට මෙම කෙටිමග සැකසීම් &gt; ප්‍රවේශ්‍යතාව හි තවත් විශේෂාංගයකට වෙනස් කළ හැකිය."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ක්‍රියාත්මක කරන්න"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index c862bc269dcf..334203cd9997 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1737,7 +1737,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Keď je skratka zapnutá, stlačením obidvoch tlačidiel hlasitosti na tri sekundy spustíte funkciu dostupnosti."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Chcete zapnúť skratku pre funkcie dostupnosti?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Pridržaním oboch tlačidiel hlasitosti na niekoľko sekúnd zapnete funkcie dostupnosti. Môže sa tým zmeniť spôsob fungovania vášho zariadenia.\n\nAktuálne funkcie:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nVybrané funkcie môžete zmeniť v časti Nastavenia &gt; Dostupnosť."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Chcete zapnúť skratku na službu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Pridržaním oboch klávesov hlasitosti na niekoľko sekúnd zapnete funkciu dostupnosti <xliff:g id="SERVICE">%1$s</xliff:g>. Môže sa tým zmeniť spôsob fungovania vášho zariadenia.\n\nTúto skratku môžete zmeniť na inú funkciu v časti Nastavenia &gt; Dostupnosť."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Zapnúť"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 9df733fd9be6..2d74bf9e7424 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1737,7 +1737,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Ko je bližnjica vklopljena, pritisnite gumba za glasnost in ju pridržite tri sekunde, če želite zagnati funkcijo za ljudi s posebnimi potrebami."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Želite vklopiti bližnjico za funkcije za ljudi s posebnimi potrebami?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Če za nekaj sekund pridržite obe tipki za glasnost, boste vklopili funkcije za ljudi s posebnimi potrebami. To lahko spremeni način delovanja naprave.\n\nTrenutne funkcije:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nIzbrane funkcije lahko spremenite v meniju »Nastavitve« &gt; »Funkcije za ljudi s posebnimi potrebami«."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Želite vklopiti bližnjico za <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Če za nekaj sekund pridržite obe tipki za glasnost, boste vklopili storitev <xliff:g id="SERVICE">%1$s</xliff:g>, ki je funkcija za ljudi s posebnimi potrebami. To lahko spremeni način delovanja naprave.\n\nTo bližnjico lahko v meniju »Nastavitve« &gt; »Funkcije za ljudi s posebnimi potrebami« spremenite, da bo uporabljena za drugo funkcijo."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Vklopi"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 44c820a8a7f5..a30396302f31 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kur shkurtorja është e aktivizuar, shtypja e të dy butonave për 3 sekonda do të nisë një funksion qasshmërie."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Të aktivizohet shkurtorja për veçoritë e qasshmërisë?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Mbajtja shtypur e dy tasteve të volumit për pak sekonda aktivizon veçoritë e qasshmërisë. Kjo mund të ndryshojë mënyrën se si funksionon pajisja jote.\n\nVeçoritë aktuale:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nKe ndryshuar veçoritë e zgjedhura te Cilësimet &gt; Qasshmëria."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Të aktivizohet shkurtorja për <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Mbajtja shtypur e dy tasteve të volumit për pak sekonda aktivizon <xliff:g id="SERVICE">%1$s</xliff:g>, një veçori të qasshmërisë. Kjo mund të ndryshojë mënyrën se si funksionon pajisja jote.\n\nMund të ndryshosh këtë shkurtore te një veçori tjetër te Cilësimet &gt; Qasshmëria."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktivizo"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 911ba91d058e..7a097d3734a2 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1715,7 +1715,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Када је пречица укључена, притисните оба дугмета за јачину звука да бисте покренули функцију приступачности."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Желите да укључите пречицу за функције приступачности?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ако задржите оба тастера за јачину звука пар секунди, укључиће се функције приступачности. То може да промени начин рада уређаја.\n\nПостојеће функције:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nМожете да промените изабране функције у одељку Подешавања &gt; Приступачност."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Желите да укључите пречицу за услугу <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ако задржите оба тастера за јачину звука пар секунди, укључује се <xliff:g id="SERVICE">%1$s</xliff:g>, функција приступачности. То може да промени начин рада уређаја.\n\nМожете да промените функцију на коју се односи ова пречица у одељку Подешавања &gt; Приступачност."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Укључи"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 3c388f6cd308..1f1579dace33 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"När kortkommandot har aktiverats startar du en tillgänglighetsfunktion genom att trycka ned båda volymknapparna i tre sekunder."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vill du aktivera genvägen till tillgänglighetsfunktioner?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Om du trycker ned båda volymknapparna i ett par sekunder aktiveras tillgänglighetsfunktionerna. Det kan få enheten ett fungera annorlunda.\n\nAktuella funktioner:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nDu kan ändra vilka funktioner som aktiveras under Inställningar &gt; Tillgänglighet."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vill du aktivera genvägen till <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Om du trycker ned båda volymknapparna i ett par sekunder aktiveras <xliff:g id="SERVICE">%1$s</xliff:g>, en tillgänglighetsfunktion. Det kan leda till att enheten fungerar annorlunda.\n\nDu kan ändra vilken funktion som ska aktiveras med genvägen under Inställningar &gt; Tillgänglighet."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktivera"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index eb679ff6fa1e..8b2cf779a243 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Unapowasha kipengele cha njia ya mkato, hatua ya kubonyeza vitufe vyote viwili vya sauti kwa sekunde tatu itafungua kipengele cha ufikivu."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Ungependa kuwasha njia ya mkato ya vipengele vya ufikivu?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hatua ya kushikilia chini vitufe vyote viwili vya sauti kwa sekunde chache huwasha vipengele vya ufikivu. Huenda hatua hii ikabadilisha jinsi kifaa chako kinavyofanya kazi.\n\nVipengele vya sasa:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nUnaweza kubadilisha vipengele ulivyochagua katika Mipangilio &gt; Ufikivu."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Ungependa kuwasha njia ya mkato ya <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Hatua ya kushikilia chini vitufe vyote viwili vya sauti kwa sekunde chache huwasha <xliff:g id="SERVICE">%1$s</xliff:g>, kipengele cha ufikivu. Huenda hatua hii ikabadilisha jinsi kifaa chako kinavyofanya kazi.\n\nUnaweza kubadilisha njia hii ya mkato iwe kipengele kingine katika Mipangilio &gt; Ufikivu."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Washa"</string>
diff --git a/core/res/res/values-sw600dp/config.xml b/core/res/res/values-sw600dp/config.xml
index 34b6a54be493..861e329f2de9 100644
--- a/core/res/res/values-sw600dp/config.xml
+++ b/core/res/res/values-sw600dp/config.xml
@@ -51,5 +51,10 @@
<!-- If true, show multiuser switcher by default unless the user specifically disables it. -->
<bool name="config_showUserSwitcherByDefault">true</bool>
+
+ <!-- Enable dynamic keyguard positioning for large-width screens. This will cause the keyguard
+ to be aligned to one side of the screen when in landscape mode. -->
+ <bool name="config_enableDynamicKeyguardPositioning">true</bool>
+
</resources>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index de6e5b158de5..b27af3447e66 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ஷார்ட்கட் இயக்கத்தில் இருக்கும்போது ஒலியளவு பட்டன்கள் இரண்டையும் 3 வினாடிகளுக்கு அழுத்தினால் அணுகல்தன்மை அம்சம் இயக்கப்படும்."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"அணுகல்தன்மை அம்சங்களுக்கான ஷார்ட்கட்டை ஆன் செய்யவா?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"இரண்டு ஒலியளவு விசைகளையும் சில விநாடிகள் பிடித்திருந்தால் அணுகல்தன்மை அம்சங்கள் ஆன் செய்யப்படும். இதனால் உங்கள் சாதனம் வேலை செய்யும் முறை மாறக்கூடும்.\n\nதற்போதைய அம்சங்கள்:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nதேர்ந்தெடுத்த அம்சங்களை அமைப்புகள் &gt; அணுகல்தன்மைக்குச் சென்று உங்களால் மாற்ற முடியும்."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> அம்சத்துக்கான ஷார்ட்கட்டை ஆன் செய்யவா?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"இரண்டு ஒலியளவு விசைகளையும் சில விநாடிகள் பிடித்திருப்பதால் அணுகல்தன்மை அம்சமான <xliff:g id="SERVICE">%1$s</xliff:g> ஆன் ஆகும். இதனால் உங்கள் சாதனம் வேலை செய்யும் முறை மாறக்கூடும்.\n\nஅமைப்புகள் &gt; அணுகல்தன்மைக்குச் சென்று இந்த ஷார்ட்கட்டை வேறு அம்சத்திற்கு மாற்ற முடியும்."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ஆன் செய்"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index e8aa72a0ea0a..8c30ebc8124b 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"షార్ట్‌కట్ ఆన్ చేసి ఉన్నప్పుడు, రెండు వాల్యూమ్ బటన్‌లను 3 సెకన్ల పాటు నొక్కి ఉంచితే యాక్సెస్ సౌలభ్య ఫీచర్ ప్రారంభం అవుతుంది."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"యాక్సెస్ సౌలభ్య ఫీచర్‌ల కోసం షార్ట్‌కట్‌ను ఆన్ చేయాలా?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"రెండు వాల్యూమ్ కీలను కొంత సేపు నొక్కి పట్టుకుంటే యాక్సెసిబిలిటీ ఫీచ‌ర్‌లు ఆన్ అవుతాయి. ఇది మీ పరికరం పని చేసే విధానాన్ని మార్చవచ్చు.\n\nప్రస్తుత ఫీచర్లు:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nఎంపిక చేసిన ఫీచర్లను మీరు సెట్టింగ్‌లు&gt;యాక్సెసిబిలిటీలో మార్చవచ్చు."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> షార్ట్‌కట్‌ను ఆన్ చేయాలా?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"రెండు వాల్యూమ్ కీలను కొన్ని సెకన్ల పాటు నొక్కి పట్టుకోవడం ద్వారా యాక్సెసిబిలిటీ అయిన <xliff:g id="SERVICE">%1$s</xliff:g> ఆన్ అవుతుంది. ఇది మీ పరికరం పని చేసే విధానాన్ని మార్చవచ్చు.\n\nసెట్టింగ్‌లు &gt; యాక్సెసిబిలిటీలో, వేరొక ఫీచర్‌ను ప్రారంభించేలా ఈ షార్ట్ కట్‌ను మీరు మార్చవచ్చు."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ఆన్ చేయి"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 9c6505efc7da..24725dea3227 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"เมื่อทางลัดเปิดอยู่ การกดปุ่มปรับระดับเสียงทั้ง 2 ปุ่มนาน 3 วินาทีจะเริ่มฟีเจอร์การช่วยเหลือพิเศษ"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"เปิดใช้ทางลัดสำหรับฟีเจอร์การช่วยเหลือพิเศษใช่ไหม"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"การกดปุ่มปรับระดับเสียงทั้ง 2 ปุ่มค้างไว้ 2-3 วินาทีจะเปิดฟีเจอร์การช่วยเหลือพิเศษ การดำเนินการนี้อาจเปลี่ยนแปลงลักษณะการทำงานของอุปกรณ์\n\nฟีเจอร์ปัจจุบัน:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nคุณจะเปลี่ยนฟีเจอร์ที่เลือกไว้ได้ในการตั้งค่า &gt; การช่วยเหลือพิเศษ"</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"เปิดใช้ทางลัด <xliff:g id="SERVICE">%1$s</xliff:g> ใช่ไหม"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"การกดปุ่มปรับระดับเสียงทั้ง 2 ปุ่มค้างไว้ 2-3 วินาทีจะเปิด <xliff:g id="SERVICE">%1$s</xliff:g> ซึ่งเป็นฟีเจอร์การช่วยเหลือพิเศษ การดำเนินการนี้อาจเปลี่ยนแปลงลักษณะการทำงานของอุปกรณ์\n\nคุณแก้ไขทางลัดนี้ให้เปิดฟีเจอร์อื่นได้ในการตั้งค่า &gt; การช่วยเหลือพิเศษ"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"เปิด"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index c0bb5e3d4f96..c85977a86062 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kapag naka-on ang shortcut, magsisimula ang isang feature ng pagiging naa-access kapag pinindot ang parehong button ng volume."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"I-on ang shortcut para sa mga feature ng pagiging naa-access?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Mao-on ang mga feature ng accessibility kapag pinindot nang matagal ang parehong volume key nang ilang segundo. Posibleng mabago nito ang paggana ng iyong device.\n\nMga kasalukuyang feature:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuwede mong baguhin ang mga napiling feature sa Mga Setting &gt; Accessibility."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"I-on ang shortcut ng <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Mao-on ang feature ng accessibility na <xliff:g id="SERVICE">%1$s</xliff:g> kapag pinindot nang matagal ang parehong volume key nang ilang segundo. Posibleng mabago nito ang paggana ng iyong device.\n\nPuwede mong palitan ng ibang feature ang shortcut na ito sa Mga Setting &gt; Accessibility."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"I-on"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index d69635988c6e..c85b7f650252 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kısayol açıkken ses düğmelerinin ikisini birden 3 saniyeliğine basılı tutmanız bir erişilebilirlik özelliğini başlatır."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Erişilebilirlik özellikleri için kısayol açılsın mı?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ses tuşlarının ikisini birden birkaç saniyeliğine basılı tutmak, erişilebilirlik özelliklerini açar. Bu, cihazınızın çalışma şeklini değiştirebilir.\n\nGeçerli özellikler:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nSeçilen özellikleri Ayarlar &gt; Erişilebilirlik\'te değiştirebilirsiniz."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> kısayolu açılsın mı?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ses tuşlarının ikisini birden birkaç saniyeliğine basılı tutmak <xliff:g id="SERVICE">%1$s</xliff:g> erişilebilirlik özelliğini etkinleştirir. Bu, cihazınızın çalışma şeklini değiştirebilir.\n\nBu kısayolu, Ayarlar &gt; Erişilebilirlik\'te başka bir özellikle değiştirebilirsiniz."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Etkinleştir"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 4b7643d8a97a..85e3110d2d30 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1737,7 +1737,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Якщо цей засіб увімкнено, ви можете активувати спеціальні можливості, утримуючи обидві кнопки гучності протягом трьох секунд."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Увімкнути засіб спеціальних можливостей?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Якщо втримувати обидві клавіші гучності впродовж кількох секунд, вмикаються спеціальні можливості. Це впливає на роботу пристрою.\n\nПоточні функції:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nВибрані функції можна змінити в налаштуваннях у меню спеціальних можливостей."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Увімкнути засіб швидкого доступу до сервісу <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Якщо втримувати обидві клавіші гучності впродовж кількох секунд, буде ввімкнено спеціальні можливості – <xliff:g id="SERVICE">%1$s</xliff:g>. Це може вплинути на роботу пристрою.\n\nДля цієї комбінації клавіш можна вибрати іншу функцію в меню \"Налаштування &gt; Спеціальні можливості\"."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Увімкнути"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 16e4549debfc..a189441bbe4f 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"شارٹ کٹ آن ہونے پر، 3 سیکنڈ تک دونوں والیوم بٹنز کو دبانے سے ایک ایکسیسبیلٹی خصوصیت شروع ہو جائے گی۔"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ایکسیسبیلٹی خصوصیات کے لیے شارٹ کٹ آن کریں؟"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"دونوں والیوم کی کلیدوں کو کچھ سیکنڈز تک دبائیں رکھنے سے ایکسیسبیلٹی خصوصیات آن ہو جاتی ہیں۔ اس سے آپ کے آلے کے کام کرنے کا طریقہ تبدیل ہو سکتا ہے۔\n\nموجودہ خصوصیات:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nآپ ترتیبات اور ایکسیسبیلٹی میں منتخب کردہ خصوصیات کو تبدیل کر سکتے ہیں۔"</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> شارٹ کٹ آن کریں؟"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"والیوم کی دونوں کلیدوں کو کچھ سیکنڈز تک دبائے رکھنے سے <xliff:g id="SERVICE">%1$s</xliff:g> ایکسیسبیلٹی خصوصیت آن ہو جاتی ہے۔ اس سے آپ کے آلے کے کام کرنے کا طریقہ تبدیل ہو سکتا ہے۔\n\nآپ ترتیبات اور ایکسیسبیلٹی میں دیگر خصوصیت کے لیے اس شارٹ کٹ کو تبدیل کر سکتے ہیں۔"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"آن کریں"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 48841acdfa95..624cf74c30b7 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Maxsus imkoniyatlar funksiyasidan foydalanish uchun u yoniqligida ikkala tovush tugmasini 3 soniya bosib turing."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Maxsus imkoniyatlar uchun tezkor tugma yoqilsinmi?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Maxsus imkoniyatlarni yoqish uchun ikkala tovush tugmalarini bir necha soniya bosib turing. Qurilmangiz ishlashida oʻzgarish yuz berishi mumkin.\n\nJoriy funksiyalar:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nTanlangan funksiyalarni Sozlamalar ichidagi Maxsus imkoniyatlar ustiga bosib oʻzgartirishingiz mumkin."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> tezkor tugmasi yoqilsinmi?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"<xliff:g id="SERVICE">%1$s</xliff:g> funksiyasini yoqish uchun ikkala tovush tugmalarini bir necha soniya bosib turing. Qurilmangiz ishlashida oʻzgarish yuz berishi mumkin.\n\nBu tezkor tugmalarni boshqa funksiyaga Sozlamalar ichidagi Maxsus imkoniyatlar orqali tayinlash mumkin."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Yoqilsin"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index cd8b20e500e9..4ae1a17d9922 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Khi phím tắt này đang bật, thao tác nhấn cả hai nút âm lượng trong 3 giây sẽ mở tính năng hỗ trợ tiếp cận."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Bật phím tắt cho các tính năng hỗ trợ tiếp cận?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Thao tác nhấn và giữ cả hai phím âm lượng trong vài giây sẽ bật các tính năng hỗ trợ tiếp cận. Việc bật các tính năng này có thể thay đổi cách thiết bị của bạn hoạt động.\n\nCác tính năng hiện tại:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nBạn có thể thay đổi những tính năng đã chọn trong phần Cài đặt &gt; Hỗ trợ tiếp cận."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Bật phím tắt cho <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Thao tác nhấn và giữ cả hai phím âm lượng trong vài giây sẽ bật <xliff:g id="SERVICE">%1$s</xliff:g>, một tính năng hỗ trợ tiếp cận. Việc bật tính năng này có thể thay đổi cách thiết bị của bạn hoạt động.\n\nBạn có thể chuyển phím tắt này thành một tính năng khác trong phần Cài đặt &gt; Hỗ trợ tiếp cận."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Bật"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index c43ee058c9e3..d8707640717c 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"启用这项快捷方式后,同时按下两个音量按钮 3 秒钟即可启动无障碍功能。"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"要开启无障碍功能快捷方式吗?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"同时按住两个音量键几秒钟,即可开启无障碍功能。这样做可能会改变您设备的工作方式。\n\n当前功能:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n您可以在“设置”&gt;“无障碍”中更改所选功能。"</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"要开启<xliff:g id="SERVICE">%1$s</xliff:g>快捷方式吗?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"同时按住两个音量键几秒钟,即可开启<xliff:g id="SERVICE">%1$s</xliff:g>无障碍功能。这样做可能会改变您设备的工作方式。\n\n您可以在“设置”&gt;“无障碍”中将此快捷方式更改为开启另一项功能。"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"开启"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 36b616ae6ece..deae9a380dc6 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"啟用快速鍵後,同時按住音量按鈕 3 秒便可啟用無障礙功能。"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"要開啟無障礙功能捷徑嗎?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"同時按下兩個音量鍵幾秒,以開啟無障礙功能。這可能會變更裝置的運作。\n\n目前功能:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n您可在「設定」&gt;「無障礙功能」中變更所選功能。"</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"要開啟 <xliff:g id="SERVICE">%1$s</xliff:g> 捷徑嗎?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"同時按下兩個音量鍵幾秒,以開啟 <xliff:g id="SERVICE">%1$s</xliff:g> 無障礙功能。這可能會變更裝置的運作。\n\n您可在「設定」&gt;「無障礙功能」中變更此快速鍵。"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"開啟"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 8a522d65c1f3..f621a67a1bf1 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"啟用捷徑功能,只要同時按下兩個音量按鈕 3 秒,就能啟動無障礙功能。"</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"要開啟無障礙功能快速鍵嗎?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"同時按住音量調高鍵和調低鍵數秒,即可開啟無障礙功能。這麼做可能會改變裝置的運作方式。\n\n目前的功能:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n你可以在 [設定] &gt; [無障礙設定] 中變更選取的功能。"</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"要開啟「<xliff:g id="SERVICE">%1$s</xliff:g>」快速鍵嗎?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"同時按住音量調高鍵和調低鍵數秒,即可開啟「<xliff:g id="SERVICE">%1$s</xliff:g>」無障礙功能。這麼做可能會改變裝置的運作方式。\n\n你可以在 [設定] &gt; [無障礙設定] 中變更這個快速鍵觸發的功能。"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"開啟"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index db19c084316e..a193ef0e5cff 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1693,7 +1693,7 @@
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Uma isinqamuleli sivuliwe, ukucindezela zombili izinkinobho zevolumu amasekhondi angu-3 kuzoqalisa isici sokufinyelela."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vula isinqamuleli sezici zokufinyeleleka?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ukubambela phansi bobabili okhiye bevolumu amasekhondi ambalwa kuvula izici zokufinyelela. Lokhu kungashintsha indlela idivayisi yakho esebenza ngayo.\n\nIzici zamanje:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nUngashintsha izici ezikhethiwe Kuzilungiselelo &gt; Ukufinyeleleka."</string>
- <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
+ <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
<string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vula isinqamuleli se-<xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ukubambela phansi bobabili okhiye bevolumu amasekhondi ambalwa kuvula i-<xliff:g id="SERVICE">%1$s</xliff:g>, eyisici sokufinyelela Lokhu kungashintsha indlela idivayisi yakho esebenza ngayo.\n\nUngashintshela lesi sinqamuleli kwesinye isici Kuzilungiselelo &gt; Ukufinyeleleka."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Vula"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a5f505176d5d..ab39152fc10f 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8371,6 +8371,19 @@
@hide @SystemApi -->
<attr name="supportsAmbientMode" format="boolean" />
+ <!-- Indicates that this wallpaper service should receive zoom transition updates when
+ changing the structural state of the device (e.g. when folding or unfolding
+ a foldable device). When this value is set to true
+ {@link android.service.wallpaper.WallpaperService.Engine} could receive zoom updates
+ before or after changing the device state. Wallpapers receive zoom updates using
+ {@link android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float)} and
+ zoom rendering should be handled manually. Zoom updates are delivered only when
+ {@link android.service.wallpaper.WallpaperService.Engine} is created and not destroyed.
+ Default value is true.
+ Corresponds to
+ {@link android.app.WallpaperInfo#shouldUseDefaultUnfoldTransition()} -->
+ <attr name="shouldUseDefaultUnfoldTransition" format="boolean" />
+
<!-- Uri that specifies a settings Slice for this wallpaper. -->
<attr name="settingsSliceUri" format="string"/>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index b191584345ef..59d6005ba193 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -246,37 +246,37 @@
<!-- Lightest shade of the accent color used by the system. White.
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent1_0">#ffffff</color>
- <!-- Shade of the accent system color at 99% lightness.
+ <!-- Shade of the accent system color at 99% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent1_10">#F9FCFF</color>
- <!-- Shade of the accent system color at 95% lightness.
+ <!-- Shade of the accent system color at 95% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent1_50">#E0F3FF</color>
- <!-- Shade of the accent system color at 90% lightness.
+ <!-- Shade of the accent system color at 90% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent1_100">#C1E8FF</color>
- <!-- Shade of the accent system color at 80% lightness.
+ <!-- Shade of the accent system color at 80% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent1_200">#76D1FF</color>
- <!-- Shade of the accent system color at 70% lightness.
+ <!-- Shade of the accent system color at 70% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent1_300">#4BB6E8</color>
- <!-- Shade of the accent system color at 60% lightness.
+ <!-- Shade of the accent system color at 60% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent1_400">#219BCC</color>
- <!-- Shade of the accent system color at 49% lightness.
+ <!-- Shade of the accent system color at 49.6% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent1_500">#007FAC</color>
- <!-- Shade of the accent system color at 40% lightness.
+ <!-- Shade of the accent system color at 40% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent1_600">#00668B</color>
- <!-- Shade of the accent system color at 30% lightness.
+ <!-- Shade of the accent system color at 30% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent1_700">#004C69</color>
- <!-- Shade of the accent system color at 20% lightness.
+ <!-- Shade of the accent system color at 20% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent1_800">#003549</color>
- <!-- Shade of the accent system color at 10% lightness.
+ <!-- Shade of the accent system color at 10% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent1_900">#001E2C</color>
<!-- Darkest shade of the accent color used by the system. Black.
@@ -286,37 +286,37 @@
<!-- Lightest shade of the secondary accent color used by the system. White.
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent2_0">#ffffff</color>
- <!-- Shade of the secondary accent system color at 99% lightness.
+ <!-- Shade of the secondary accent system color at 99% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent2_10">#F9FCFF</color>
- <!-- Shade of the secondary accent system color at 95% lightness.
+ <!-- Shade of the secondary accent system color at 95% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent2_50">#E0F3FF</color>
- <!-- Shade of the secondary accent system color at 90% lightness.
+ <!-- Shade of the secondary accent system color at 90% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent2_100">#D1E5F4</color>
- <!-- Shade of the secondary accent system color at 80% lightness.
+ <!-- Shade of the secondary accent system color at 80% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent2_200">#B5CAD7</color>
- <!-- Shade of the secondary accent system color at 70% lightness.
+ <!-- Shade of the secondary accent system color at 70% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent2_300">#9AAEBB</color>
- <!-- Shade of the secondary accent system color at 60% lightness.
+ <!-- Shade of the secondary accent system color at 60% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent2_400">#8094A0</color>
- <!-- Shade of the secondary accent system color at 49% lightness.
+ <!-- Shade of the secondary accent system color at 49.6% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent2_500">#657985</color>
- <!-- Shade of the secondary accent system color at 40% lightness.
+ <!-- Shade of the secondary accent system color at 40% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent2_600">#4E616C</color>
- <!-- Shade of the secondary accent system color at 30% lightness.
+ <!-- Shade of the secondary accent system color at 30% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent2_700">#374955</color>
- <!-- Shade of the secondary accent system color at 20% lightness.
+ <!-- Shade of the secondary accent system color at 20% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent2_800">#20333D</color>
- <!-- Shade of the secondary accent system color at 10% lightness.
+ <!-- Shade of the secondary accent system color at 10% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent2_900">#091E28</color>
<!-- Darkest shade of the secondary accent color used by the system. Black.
@@ -326,37 +326,37 @@
<!-- Lightest shade of the tertiary accent color used by the system. White.
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent3_0">#ffffff</color>
- <!-- Shade of the tertiary accent system color at 99% lightness.
+ <!-- Shade of the tertiary accent system color at 99% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent3_10">#FFFBFF</color>
- <!-- Shade of the tertiary accent system color at 95% lightness.
+ <!-- Shade of the tertiary accent system color at 95% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent3_50">#F5EEFF</color>
- <!-- Shade of the tertiary accent system color at 90% lightness.
+ <!-- Shade of the tertiary accent system color at 90% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent3_100">#E6DEFF</color>
- <!-- Shade of the tertiary accent system color at 80% lightness.
+ <!-- Shade of the tertiary accent system color at 80% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent3_200">#CAC1EA</color>
- <!-- Shade of the tertiary accent system color at 70% lightness.
+ <!-- Shade of the tertiary accent system color at 70% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent3_300">#AEA6CE</color>
- <!-- Shade of the tertiary accent system color at 60% lightness.
+ <!-- Shade of the tertiary accent system color at 60% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent3_400">#938CB1</color>
- <!-- Shade of the tertiary accent system color at 49% lightness.
+ <!-- Shade of the tertiary accent system color at 49% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent3_500">#787296</color>
- <!-- Shade of the tertiary accent system color at 40% lightness.
+ <!-- Shade of the tertiary accent system color at 40% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent3_600">#605A7C</color>
- <!-- Shade of the tertiary accent system color at 30% lightness.
+ <!-- Shade of the tertiary accent system color at 30% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent3_700">#484264</color>
- <!-- Shade of the tertiary accent system color at 20% lightness.
+ <!-- Shade of the tertiary accent system color at 20% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent3_800">#322C4C</color>
- <!-- Shade of the tertiary accent system color at 10% lightness.
+ <!-- Shade of the tertiary accent system color at 10% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_accent3_900">#1D1736</color>
<!-- Darkest shade of the tertiary accent color used by the system. Black.
@@ -366,37 +366,37 @@
<!-- Lightest shade of the neutral color used by the system. White.
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral1_0">#ffffff</color>
- <!-- Shade of the neutral system color at 99% lightness.
+ <!-- Shade of the neutral system color at 99% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral1_10">#FCFCFF</color>
- <!-- Shade of the neutral system color at 95% lightness.
+ <!-- Shade of the neutral system color at 95% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral1_50">#F0F0F3</color>
- <!-- Shade of the neutral system color at 90% lightness.
+ <!-- Shade of the neutral system color at 90% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral1_100">#E1E3E5</color>
- <!-- Shade of the neutral system color at 80% lightness.
+ <!-- Shade of the neutral system color at 80% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral1_200">#C5C7C9</color>
- <!-- Shade of the neutral system color at 70% lightness.
+ <!-- Shade of the neutral system color at 70% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral1_300">#AAABAE</color>
- <!-- Shade of the neutral system color at 60% lightness.
+ <!-- Shade of the neutral system color at 60% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral1_400">#8F9193</color>
- <!-- Shade of the neutral system color at 49% lightness.
+ <!-- Shade of the neutral system color at 49% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral1_500">#747679</color>
- <!-- Shade of the neutral system color at 40% lightness.
+ <!-- Shade of the neutral system color at 40% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral1_600">#5C5F61</color>
- <!-- Shade of the neutral system color at 30% lightness.
+ <!-- Shade of the neutral system color at 30% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral1_700">#454749</color>
- <!-- Shade of the neutral system color at 20% lightness.
+ <!-- Shade of the neutral system color at 20% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral1_800">#2E3133</color>
- <!-- Shade of the neutral system color at 10% lightness.
+ <!-- Shade of the neutral system color at 10% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral1_900">#191C1E</color>
<!-- Darkest shade of the neutral color used by the system. Black.
@@ -406,37 +406,37 @@
<!-- Lightest shade of the secondary neutral color used by the system. White.
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral2_0">#ffffff</color>
- <!-- Shade of the secondary neutral system color at 99% lightness.
+ <!-- Shade of the secondary neutral system color at 99% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral2_10">#F9FCFF</color>
- <!-- Shade of the secondary neutral system color at 95% lightness.
+ <!-- Shade of the secondary neutral system color at 95% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral2_50">#EBF1F8</color>
- <!-- Shade of the secondary neutral system color at 90% lightness.
+ <!-- Shade of the secondary neutral system color at 90% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral2_100">#DCE3E9</color>
- <!-- Shade of the secondary neutral system color at 80% lightness.
+ <!-- Shade of the secondary neutral system color at 80% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral2_200">#C0C7CD</color>
- <!-- Shade of the secondary neutral system color at 70% lightness.
+ <!-- Shade of the secondary neutral system color at 70% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral2_300">#A5ACB2</color>
- <!-- Shade of the secondary neutral system color at 60% lightness.
+ <!-- Shade of the secondary neutral system color at 60% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral2_400">#8A9297</color>
- <!-- Shade of the secondary neutral system color at 49% lightness.
+ <!-- Shade of the secondary neutral system color at 49% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral2_500">#70777C</color>
- <!-- Shade of the secondary neutral system color at 40% lightness.
+ <!-- Shade of the secondary neutral system color at 40% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral2_600">#585F65</color>
- <!-- Shade of the secondary neutral system color at 30% lightness.
+ <!-- Shade of the secondary neutral system color at 30% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral2_700">#40484D</color>
- <!-- Shade of the secondary neutral system color at 20% lightness.
+ <!-- Shade of the secondary neutral system color at 20% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral2_800">#2A3136</color>
- <!-- Shade of the secondary neutral system color at 10% lightness.
+ <!-- Shade of the secondary neutral system color at 10% perceptual luminance (L* in L*a*b* color space).
This value can be overlaid at runtime by OverlayManager RROs. -->
<color name="system_neutral2_900">#161C20</color>
<!-- Darkest shade of the secondary neutral color used by the system. Black.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0abeff91e511..32db1866c151 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -654,15 +654,37 @@
-->
</integer-array>
+ <!-- When entering this device state (defined in device_state_configuration.xml),
+ we should wake the device. -1 to disable the feature (do not wake on any device-state
+ transition). -->
+ <integer name="config_deviceStateOnWhichToWakeUp">-1</integer>
+
<!-- Indicate the display area rect for foldable devices in folded state. -->
<string name="config_foldedArea"></string>
+ <!-- Indicates whether to enable an animation when unfolding a device or not -->
+ <bool name="config_unfoldTransitionEnabled">false</bool>
+
+ <!-- Indicates whether to enable hinge angle sensor when using unfold animation -->
+ <bool name="config_unfoldTransitionHingeAngle">false</bool>
+
<!-- Indicates that the device supports having more than one internal display on at the same
time. Only applicable to devices with more than one internal display. If this option is
set to false, DisplayManager will make additional effort to ensure no more than 1 internal
display is powered on at the same time. -->
<bool name="config_supportsConcurrentInternalDisplays">true</bool>
+ <!-- Map of DeviceState to rotation lock setting. Each entry must be in the format
+ "key:value", for example: "0:1".
+ The keys are device states, and the values are one of
+ Settings.Secure.DeviceStateRotationLockSetting.
+ Any device state that doesn't have a default set here will be treated as
+ DEVICE_STATE_ROTATION_LOCK_IGNORED meaning it will not have its own rotation lock setting.
+ If this map is missing, the feature is disabled and only one global rotation lock setting
+ will apply, regardless of device state. -->
+ <string-array name="config_perDeviceStateRotationLockDefaults" />
+
+
<!-- Desk dock behavior -->
<!-- The number of degrees to rotate the display when the device is in a desk dock.
@@ -2301,6 +2323,11 @@
<!-- Type of the tap sensor. Empty if tap is not supported. -->
<string name="config_dozeTapSensorType" translatable="false"></string>
+ <!-- Type of the ambient tap sensor per device posture (defined by WM Jetpack posture).
+ Unspecified values use config_dozeTapSensor -->
+ <string-array name="config_dozeTapSensorPostureMapping" translatable="false">
+ </string-array>
+
<!-- Type of the long press sensor. Empty if long press is not supported. -->
<string name="config_dozeLongPressSensorType" translatable="false"></string>
@@ -3202,6 +3229,11 @@
and one pSIM) -->
<integer name="config_num_physical_slots">1</integer>
+ <!-- When a radio power off request is received, we will delay completing the request until
+ either IMS moves to the deregistered state or the timeout defined by this configuration
+ elapses. If 0, this feature is disabled and we do not delay radio power off requests.-->
+ <integer name="config_delay_for_ims_dereg_millis">0</integer>
+
<!--Thresholds for LTE dbm in status bar-->
<integer-array translatable="false" name="config_lteDbmThresholds">
<item>-140</item> <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
@@ -3621,8 +3653,8 @@
-->
<integer name="config_largeScreenSmallestScreenWidthDp">600</integer>
- <!-- True if the device is using leagacy split. -->
- <bool name="config_useLegacySplit">true</bool>
+ <!-- True if the device is using legacy split. -->
+ <bool name="config_useLegacySplit">false</bool>
<!-- True if the device supports running activities on secondary displays. -->
<bool name="config_supportsMultiDisplay">true</bool>
@@ -4032,7 +4064,7 @@
<string translatable="false" name="config_inCallNotificationSound">/product/media/audio/ui/InCallNotification.ogg</string>
<!-- URI for default ringtone sound file to be used for silent ringer vibration -->
- <string translatable="false" name="config_defaultRingtoneVibrationSound">/product/media/audio/ui/AttentionalHaptics.ogg</string>
+ <string translatable="false" name="config_defaultRingtoneVibrationSound"></string>
<!-- Default number of notifications from the same app before they are automatically grouped by the OS -->
<integer translatable="false" name="config_autoGroupAtCount">4</integer>
@@ -4566,6 +4598,20 @@
-->
</integer-array>
+ <!-- An array of arrays of side fingerprint sensor properties relative to each display.
+ Note: this value is temporary and is expected to be queried directly
+ from the HAL in the future. -->
+ <array name="config_sfps_sensor_props" translatable="false">
+ <!--
+ <array>
+ <item>displayId</item>
+ <item>sensorLocationX</item>
+ <item>sensorLocationY</item>
+ <item>sensorRadius</item>
+ <array>
+ -->
+ </array>
+
<!-- How long it takes for the HW to start illuminating after the illumination is requested. -->
<integer name="config_udfps_illumination_transition_ms">50</integer>
@@ -4827,8 +4873,8 @@
<item name="config_fixedOrientationLetterboxAspectRatio" format="float" type="dimen">0.0</item>
<!-- Corners radius for activity presented the letterbox mode. Values < 0 will be ignored and
- corners of the activity won't be rounded. -->
- <integer name="config_letterboxActivityCornersRadius">0</integer>
+ min between device bottom corner radii will be used instead. -->
+ <integer name="config_letterboxActivityCornersRadius">-1</integer>
<!-- Blur radius for the Option 3 in R.integer.config_letterboxBackgroundType. Values < 0 are
ignored and 0 is used. -->
@@ -4862,15 +4908,28 @@
- Option 3 is selected for R.integer.config_letterboxBackgroundType and blur requested
but isn't supported on the device or both dark scrim alpha and blur radius aren't
provided.
- Defaults to black if not specified.
-->
- <color name="config_letterboxBackgroundColor">#000</color>
+ <color name="config_letterboxBackgroundColor">@android:color/system_neutral2_500</color>
<!-- Horizonal position of a center of the letterboxed app window.
0 corresponds to the left side of the screen and 1 to the right side. If given value < 0
or > 1, it is ignored and central positionis used (0.5). -->
<item name="config_letterboxHorizontalPositionMultiplier" format="float" type="dimen">0.5</item>
+ <!-- Whether reachability repositioning is allowed for letterboxed fullscreen apps in landscape
+ device orientation. -->
+ <bool name="config_letterboxIsReachabilityEnabled">false</bool>
+
+ <!-- Default horizonal position of the letterboxed app window when reachability is
+ enabled and an app is fullscreen in landscape device orientation. When reachability is
+ enabled, the position can change between left, center and right. This config defines the
+ default one:
+ - Option 0 - Left.
+ - Option 1 - Center.
+ - Option 2 - Right.
+ If given value is outside of this range, the option 1 (center) is assummed. -->
+ <integer name="config_letterboxDefaultPositionForReachability">1</integer>
+
<!-- If true, hide the display cutout with display area -->
<bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>
@@ -5072,4 +5131,149 @@
<!-- the number of the max cached processes in the system. -->
<integer name="config_customizedMaxCachedProcesses">32</integer>
+
+ <!-- The display cutout configs for secondary built-in display. -->
+ <string name="config_secondaryBuiltInDisplayCutout" translatable="false"></string>
+ <string name="config_secondaryBuiltInDisplayCutoutRectApproximation" translatable="false">
+ @string/config_secondaryBuiltInDisplayCutout
+ </string>
+ <bool name="config_fillSecondaryBuiltInDisplayCutout">false</bool>
+ <bool name="config_maskSecondaryBuiltInDisplayCutout">false</bool>
+
+ <!-- An array contains unique ids of all built-in displays and the unique id of a display can be
+ obtained from {@link Display#getUniqueId}. This array should be set for multi-display
+ devices if there are different display related configs(e.g. display cutout, rounded corner)
+ between each built-in display.
+ It is used as an index for multi-display related configs:
+ First look up the index of the unique id of the given built-in display unique id in this
+ array and use this index to get the info in corresponding config arrays such as:
+ - config_displayCutoutPathArray
+ - config_displayCutoutApproximationRectArray
+ - config_fillBuiltInDisplayCutoutArray
+ - config_maskBuiltInDisplayCutoutArray
+ - config_waterfallCutoutArray
+ - config_roundedCornerRadiusArray
+ - config_roundedCornerTopRadiusArray
+ - config_roundedCornerBottomRadiusArray
+ - config_builtInDisplayIsRoundArray (config in SystemUI resource)
+ - config_roundedCornerMultipleRadiusArray (config in SystemUI resource)
+ - config_roundedCornerDrawableArray (config in SystemUI resource)
+ - config_roundedCornerTopDrawableArray (config in SystemUI resource)
+ - config_roundedCornerBottomDrawableArray (config in SystemUI resource)
+
+ Leave this array empty for single display device and the system will load the default main
+ built-in related configs.
+ -->
+ <string-array name="config_displayUniqueIdArray" translatable="false">
+ <!-- Example:
+ <item>"local:1234567891"</item> // main built-in display
+ <item>"local:1234567892"</item> // secondary built-in display
+ -->
+ </string-array>
+
+ <!-- The display cutout path config for each display in a multi-display device. -->
+ <string-array name="config_displayCutoutPathArray" translatable="false">
+ <item>@string/config_mainBuiltInDisplayCutout</item>
+ <item>@string/config_secondaryBuiltInDisplayCutout</item>
+ </string-array>
+
+ <!-- The display cutout approximation rect config for each display in a multi-display device.
+ -->
+ <string-array name="config_displayCutoutApproximationRectArray" translatable="false">
+ <item>@string/config_mainBuiltInDisplayCutoutRectApproximation</item>
+ <item>@string/config_secondaryBuiltInDisplayCutoutRectApproximation</item>
+ </string-array>
+
+ <!-- The maskBuiltInDisplayCutout config for each display in a multi-display device. -->
+ <array name="config_maskBuiltInDisplayCutoutArray" translatable="false">
+ <item>@bool/config_maskMainBuiltInDisplayCutout</item>
+ <item>@bool/config_maskSecondaryBuiltInDisplayCutout</item>
+ </array>
+
+ <!-- The fillBuiltInDisplayCutout config for each display in a multi-display device. -->
+ <array name="config_fillBuiltInDisplayCutoutArray" translatable="false">
+ <item>@bool/config_fillMainBuiltInDisplayCutout</item>
+ <item>@bool/config_fillSecondaryBuiltInDisplayCutout</item>
+ </array>
+
+ <array name="config_mainBuiltInDisplayWaterfallCutout" translatable="false">
+ <item>@dimen/waterfall_display_left_edge_size</item>
+ <item>@dimen/waterfall_display_top_edge_size</item>
+ <item>@dimen/waterfall_display_right_edge_size</item>
+ <item>@dimen/waterfall_display_bottom_edge_size</item>
+ </array>
+
+ <array name="config_secondaryBuiltInDisplayWaterfallCutout" translatable="false">
+ <item>@dimen/secondary_waterfall_display_left_edge_size</item>
+ <item>@dimen/secondary_waterfall_display_top_edge_size</item>
+ <item>@dimen/secondary_waterfall_display_right_edge_size</item>
+ <item>@dimen/secondary_waterfall_display_bottom_edge_size</item>
+ </array>
+
+ <!-- The waterfall cutout config for each display in a multi-display device. -->
+ <array name="config_waterfallCutoutArray" translatable="false">
+ <item>@array/config_mainBuiltInDisplayWaterfallCutout</item>
+ <item>@array/config_secondaryBuiltInDisplayWaterfallCutout</item>
+ </array>
+
+ <!-- The component name of the activity for the companion-device-manager notification access
+ confirmation. -->
+ <string name="config_notificationAccessConfirmationActivity" translatable="false">
+ com.android.settings/com.android.settings.notification.NotificationAccessConfirmationActivity
+ </string>
+
+ <!-- Whether the airplane mode should be reset when device boots in non-safemode after exiting
+ from safemode.
+ This flag should be enabled only when the product does not have any UI to toggle airplane
+ mode like automotive devices.-->
+ <bool name="config_autoResetAirplaneMode">false</bool>
+
+ <bool name="config_secondaryBuiltInDisplayIsRound">@bool/config_windowIsRound</bool>
+
+ <!-- The display round config for each display in a multi-display device. -->
+ <array name="config_builtInDisplayIsRoundArray" translatable="false">
+ <item>@bool/config_mainBuiltInDisplayIsRound</item>
+ <item>@bool/config_secondaryBuiltInDisplayIsRound</item>
+ </array>
+
+ <!-- The rounded corner radius for each display in a multi-display device. -->
+ <array name="config_roundedCornerRadiusArray" translatable="false">
+ <item>@dimen/rounded_corner_radius</item>
+ <item>@dimen/secondary_rounded_corner_radius</item>
+ </array>
+
+ <!-- The top rounded corner radius for each display in a multi-display device. -->
+ <array name="config_roundedCornerTopRadiusArray" translatable="false">
+ <item>@dimen/rounded_corner_radius_top</item>
+ <item>@dimen/secondary_rounded_corner_radius_top</item>
+ </array>
+
+ <!-- The bottom rounded corner radius for each display in a multi-display device. -->
+ <array name="config_roundedCornerBottomRadiusArray" translatable="false">
+ <item>@dimen/rounded_corner_radius_bottom</item>
+ <item>@dimen/secondary_rounded_corner_radius_bottom</item>
+ </array>
+
+ <!-- The rounded corner radius adjustment for each display in a multi-display device. -->
+ <array name="config_roundedCornerRadiusAdjustmentArray" translatable="false">
+ <item>@dimen/rounded_corner_radius_adjustment</item>
+ <item>@dimen/secondary_rounded_corner_radius_adjustment</item>
+ </array>
+
+ <!-- The rounded corner radius top adjustment for each display in a multi-display device. -->
+ <array name="config_roundedCornerTopRadiusAdjustmentArray" translatable="false">
+ <item>@dimen/rounded_corner_radius_top_adjustment</item>
+ <item>@dimen/secondary_rounded_corner_radius_top_adjustment</item>
+ </array>
+
+ <!-- The rounded corner radius bottom adjustment for each display in a multi-display device. -->
+ <array name="config_roundedCornerBottomRadiusAdjustmentArray" translatable="false">
+ <item>@dimen/rounded_corner_radius_bottom_adjustment</item>
+ <item>@dimen/secondary_rounded_corner_radius_bottom_adjustment</item>
+ </array>
+
+ <!-- Shape of the work badge icon for viewport size 24. -->
+ <string name="config_work_badge_path_24" translatable="false">
+ M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM14,6h-4L10,4h4v2z
+ </string>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index a666a5b4b796..0706d8a8e4c6 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -39,15 +39,21 @@
<!-- Elevation of toast view -->
<dimen name="toast_elevation">2dp</dimen>
- <!-- Height of the status bar -->
- <dimen name="status_bar_height">@dimen/status_bar_height_portrait</dimen>
- <!-- Height of the status bar in portrait. The height should be
- Max((status bar content height + waterfall top size), top cutout size) -->
- <dimen name="status_bar_height_portrait">24dp</dimen>
- <!-- Height of the status bar in landscape. The height should be
- Max((status bar content height + waterfall top size), top cutout size) -->
+ <!-- Height of the status bar.
+ Do not read this dimen directly. Use {@link SystemBarUtils#getStatusBarHeight} instead.
+ -->
+ <dimen name="status_bar_height">24dp</dimen>
+ <!-- Height of the status bar in portrait.
+ Do not read this dimen directly. Use {@link SystemBarUtils#getStatusBarHeight} instead.
+ -->
+ <dimen name="status_bar_height_portrait">@dimen/status_bar_height</dimen>
+ <!-- Height of the status bar in landscape.
+ Do not read this dimen directly. Use {@link SystemBarUtils#getStatusBarHeight} instead.
+ -->
<dimen name="status_bar_height_landscape">@dimen/status_bar_height_portrait</dimen>
- <!-- Height of area above QQS where battery/time go -->
+ <!-- Height of area above QQS where battery/time go.
+ Do not read this dimen directly. Use {@link SystemBarUtils#getQuickQsOffsetHeight} instead.
+ -->
<dimen name="quick_qs_offset_height">48dp</dimen>
<!-- Height of the bottom navigation / system bar. -->
<dimen name="navigation_bar_height">48dp</dimen>
@@ -55,6 +61,8 @@
<dimen name="navigation_bar_height_landscape">48dp</dimen>
<!-- Width of the navigation bar when it is placed vertically on the screen -->
<dimen name="navigation_bar_width">48dp</dimen>
+ <!-- Height of the bottom taskbar not including decorations like rounded corners. -->
+ <dimen name="taskbar_frame_height">60dp</dimen>
<!-- How much we expand the touchable region of the status bar below the notch to catch touches
that just start below the notch. -->
<dimen name="display_cutout_touchable_region_size">12dp</dimen>
@@ -926,7 +934,7 @@
<dimen name="chooser_action_button_icon_size">18dp</dimen>
- <!-- For Waterfall Display -->
+ <!-- For main built-in Waterfall Display -->
<dimen name="waterfall_display_left_edge_size">0px</dimen>
<dimen name="waterfall_display_top_edge_size">0px</dimen>
<dimen name="waterfall_display_right_edge_size">0px</dimen>
@@ -949,4 +957,18 @@
<dimen name="starting_surface_icon_size">160dp</dimen>
<!-- The default width/height of the icon on the spec of adaptive icon drawable. -->
<dimen name="starting_surface_default_icon_size">108dp</dimen>
+
+ <!-- For secondary built-in Waterfall Display -->
+ <dimen name="secondary_waterfall_display_left_edge_size">0px</dimen>
+ <dimen name="secondary_waterfall_display_top_edge_size">0px</dimen>
+ <dimen name="secondary_waterfall_display_right_edge_size">0px</dimen>
+ <dimen name="secondary_waterfall_display_bottom_edge_size">0px</dimen>
+
+ <!-- Rounded corner settings for secondary built-in display -->
+ <dimen name="secondary_rounded_corner_radius">0px</dimen>
+ <dimen name="secondary_rounded_corner_radius_top">0px</dimen>
+ <dimen name="secondary_rounded_corner_radius_bottom">0px</dimen>
+ <dimen name="secondary_rounded_corner_radius_adjustment">0px</dimen>
+ <dimen name="secondary_rounded_corner_radius_top_adjustment">0px</dimen>
+ <dimen name="secondary_rounded_corner_radius_bottom_adjustment">0px</dimen>
</resources>
diff --git a/core/res/res/values/dimens_car.xml b/core/res/res/values/dimens_car.xml
index 0ef60c4c9531..c5dddb8f7d8a 100644
--- a/core/res/res/values/dimens_car.xml
+++ b/core/res/res/values/dimens_car.xml
@@ -17,7 +17,7 @@
-->
<resources>
<dimen name="car_large_avatar_size">96dp</dimen>
- <dimen name="car_large_avatar_badge_size">32dp</dimen>
+ <dimen name="car_large_avatar_badge_size">24dp</dimen>
<!-- Application Bar -->
<dimen name="car_app_bar_height">80dp</dimen>
<!-- Margin -->
@@ -110,10 +110,10 @@
<dimen name="car_textview_fading_edge_length">40dp</dimen>
<!-- Dialog start padding for button bar layout -->
- <dimen name="button_bar_layout_start_padding">@*android:dimen/car_keyline_1</dimen>
+ <dimen name="button_bar_layout_start_padding">@dimen/car_padding_2</dimen>
<!-- Dialog end padding for button bar layout -->
- <dimen name="button_bar_layout_end_padding">@*android:dimen/car_keyline_1</dimen>
+ <dimen name="button_bar_layout_end_padding">@dimen/car_padding_2</dimen>
<!-- Dialog top padding for button bar layout -->
<dimen name="button_bar_layout_top_padding">@*android:dimen/car_padding_2</dimen>
@@ -122,7 +122,7 @@
<dimen name="button_layout_height">@*android:dimen/car_card_action_bar_height</dimen>
<!-- Dialog button end margin -->
- <dimen name="button_end_margin">@*android:dimen/car_padding_4</dimen>
+ <dimen name="button_end_margin">@*android:dimen/car_padding_2</dimen>
<!-- Dialog top padding when there is no title -->
<dimen name="dialog_no_title_padding_top">@*android:dimen/car_padding_4</dimen>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index c4838b83347c..84f82fd5d99d 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -254,6 +254,15 @@
<!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_IME_ENTER}. -->
<item type="id" name="accessibilityActionImeEnter" />
+ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_START}. -->
+ <item type="id" name="accessibilityActionDragStart" />
+
+ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_DROP}. -->
+ <item type="id" name="accessibilityActionDragDrop" />
+
+ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_DRAG_CANCEL}. -->
+ <item type="id" name="accessibilityActionDragCancel" />
+
<!-- View tag for remote views to store the index of the next child when adding nested remote views dynamically. -->
<item type="id" name="remote_views_next_child" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2403a605972e..1e30131ae6f9 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1235,7 +1235,6 @@
<public type="attr" name="author" id="0x010102b4" />
<public type="attr" name="autoStart" id="0x010102b5" />
-
<!-- ===============================================================
Resources added in version 8 of the platform (Eclair MR2).
=============================================================== -->
@@ -3201,12 +3200,24 @@
<public type="string" name="config_defaultRingtoneVibrationSound" id="0x0104003b" />
<!-- ===============================================================
- DO NOT ADD UN-GROUPED ITEMS HERE
+ Resources added in version S-V2 of the platform
+ =============================================================== -->
+ <eat-comment />
- Any new items (attrs, styles, ids, etc.) *must* be added in a
- public-group block, as the preceding comment explains.
- Items added outside of a group may have their value recalculated
- every time something new is added to this file.
- =============================================================== -->
+ <staging-public-group-final type="attr" first-id="0x01ff0000">
+ <public name="shouldUseDefaultUnfoldTransition" />
+ </staging-public-group-final>
+
+ <public type="attr" name="shouldUseDefaultUnfoldTransition" id="0x0101064c" />
+
+ <staging-public-group-final type="id" first-id="0x01fe0000">
+ <public name="accessibilityActionDragStart" />
+ <public name="accessibilityActionDragDrop" />
+ <public name="accessibilityActionDragCancel" />
+ </staging-public-group-final>
+
+ <public type="id" name="accessibilityActionDragStart" id="0x01020055" />
+ <public type="id" name="accessibilityActionDragDrop" id="0x01020056" />
+ <public type="id" name="accessibilityActionDragCancel" id="0x01020057" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b58638cc3ade..bb56b8d21a03 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4558,7 +4558,7 @@
<string name="accessibility_shortcut_multiple_service_warning">Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="service" example="TalkBack">%1$s</xliff:g>\nYou can change selected features in Settings > Accessibility.</string>
<!-- Used in multiple service warning to list current features. [CHAR LIMIT=none] -->
- <string name="accessibility_shortcut_multiple_service_list">\t• <xliff:g id="service" example="TalkBack">%1$s</xliff:g>\n</string>
+ <string name="accessibility_shortcut_multiple_service_list">\u0020• <xliff:g id="service" example="TalkBack">%1$s</xliff:g>\n</string>
<!-- Dialog title for dialog shown when this accessibility shortcut is activated, and we want to confirm that the user understands what's going to happen. [CHAR LIMIT=none] -->
<string name="accessibility_shortcut_single_service_warning_title">Turn on <xliff:g id="service" example="TalkBack">%1$s</xliff:g> shortcut?</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index bac9cf23b176..9553f95fe5c9 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -966,6 +966,7 @@ please see styles_device_defaults.xml.
<style name="TextAppearance.Toast">
<item name="fontFamily">@*android:string/config_bodyFontFamily</item>
<item name="textSize">14sp</item>
+ <item name="lineHeight">20sp</item>
<item name="textColor">?android:attr/textColorPrimary</item>
</style>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0a98601969af..758990df7159 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -486,6 +486,7 @@
<java-symbol type="string" name="config_deviceSpecificDevicePolicyManagerService" />
<java-symbol type="string" name="config_deviceSpecificAudioService" />
<java-symbol type="integer" name="config_num_physical_slots" />
+ <java-symbol type="integer" name="config_delay_for_ims_dereg_millis" />
<java-symbol type="array" name="config_integrityRuleProviderPackages" />
<java-symbol type="bool" name="config_useAssistantVolume" />
<java-symbol type="string" name="config_bandwidthEstimateSource" />
@@ -1741,6 +1742,7 @@
<java-symbol type="dimen" name="navigation_bar_height_car_mode" />
<java-symbol type="dimen" name="navigation_bar_height_landscape_car_mode" />
<java-symbol type="dimen" name="navigation_bar_width_car_mode" />
+ <java-symbol type="dimen" name="taskbar_frame_height" />
<java-symbol type="dimen" name="status_bar_height" />
<java-symbol type="dimen" name="display_cutout_touchable_region_size" />
<java-symbol type="dimen" name="system_gestures_start_threshold" />
@@ -2220,6 +2222,8 @@
<java-symbol type="string" name="config_primaryLocationTimeZoneProviderPackageName" />
<java-symbol type="bool" name="config_enableSecondaryLocationTimeZoneProvider" />
<java-symbol type="string" name="config_secondaryLocationTimeZoneProviderPackageName" />
+ <java-symbol type="bool" name="config_autoResetAirplaneMode" />
+ <java-symbol type="string" name="config_notificationAccessConfirmationActivity" />
<java-symbol type="layout" name="resolver_list" />
<java-symbol type="id" name="resolver_list" />
@@ -2607,6 +2611,7 @@
<java-symbol type="array" name="config_biometric_sensors" />
<java-symbol type="bool" name="allow_test_udfps" />
<java-symbol type="array" name="config_udfps_sensor_props" />
+ <java-symbol type="array" name="config_sfps_sensor_props" />
<java-symbol type="integer" name="config_udfps_illumination_transition_ms" />
<java-symbol type="bool" name="config_is_powerbutton_fps" />
<java-symbol type="array" name="config_udfps_enroll_stage_thresholds" />
@@ -3251,6 +3256,7 @@
<java-symbol type="string" name="config_dozeDoubleTapSensorType" />
<java-symbol type="string" name="config_dozeTapSensorType" />
+ <java-symbol type="array" name="config_dozeTapSensorPostureMapping" />
<java-symbol type="bool" name="config_dozePulsePickup" />
<!-- Used for MimeIconUtils. -->
@@ -3842,8 +3848,13 @@
<!-- For Foldables -->
<java-symbol type="array" name="config_foldedDeviceStates" />
+ <java-symbol type="integer" name="config_deviceStateOnWhichToWakeUp" />
<java-symbol type="string" name="config_foldedArea" />
<java-symbol type="bool" name="config_supportsConcurrentInternalDisplays" />
+ <java-symbol type="bool" name="config_unfoldTransitionEnabled" />
+ <java-symbol type="bool" name="config_unfoldTransitionHingeAngle" />
+ <java-symbol type="array" name="config_perDeviceStateRotationLockDefaults" />
+
<java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" />
<java-symbol type="array" name="config_disableApkUnlessMatchedSku_skus_list" />
@@ -4252,6 +4263,8 @@
<java-symbol type="integer" name="config_letterboxBackgroundType" />
<java-symbol type="color" name="config_letterboxBackgroundColor" />
<java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
+ <java-symbol type="bool" name="config_letterboxIsReachabilityEnabled" />
+ <java-symbol type="integer" name="config_letterboxDefaultPositionForReachability" />
<java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
@@ -4436,4 +4449,36 @@
<java-symbol type="integer" name="config_customizedMaxCachedProcesses" />
<java-symbol type="color" name="overview_background"/>
+
+ <java-symbol type="string" name="config_secondaryBuiltInDisplayCutout" />
+ <java-symbol type="string" name="config_secondaryBuiltInDisplayCutoutRectApproximation" />
+ <java-symbol type="bool" name="config_fillSecondaryBuiltInDisplayCutout" />
+ <java-symbol type="bool" name="config_maskSecondaryBuiltInDisplayCutout" />
+ <java-symbol type="array" name="config_displayUniqueIdArray" />
+ <java-symbol type="array" name="config_displayCutoutPathArray" />
+ <java-symbol type="array" name="config_displayCutoutApproximationRectArray" />
+ <java-symbol type="array" name="config_fillBuiltInDisplayCutoutArray" />
+ <java-symbol type="array" name="config_maskBuiltInDisplayCutoutArray" />
+ <java-symbol type="dimen" name="secondary_waterfall_display_left_edge_size" />
+ <java-symbol type="dimen" name="secondary_waterfall_display_top_edge_size" />
+ <java-symbol type="dimen" name="secondary_waterfall_display_right_edge_size" />
+ <java-symbol type="dimen" name="secondary_waterfall_display_bottom_edge_size" />
+ <java-symbol type="array" name="config_mainBuiltInDisplayWaterfallCutout" />
+ <java-symbol type="array" name="config_secondaryBuiltInDisplayWaterfallCutout" />
+ <java-symbol type="array" name="config_waterfallCutoutArray" />
+
+ <java-symbol type="dimen" name="secondary_rounded_corner_radius" />
+ <java-symbol type="dimen" name="secondary_rounded_corner_radius_top" />
+ <java-symbol type="dimen" name="secondary_rounded_corner_radius_bottom" />
+ <java-symbol type="dimen" name="secondary_rounded_corner_radius_adjustment" />
+ <java-symbol type="dimen" name="secondary_rounded_corner_radius_top_adjustment" />
+ <java-symbol type="dimen" name="secondary_rounded_corner_radius_bottom_adjustment" />
+ <java-symbol type="array" name="config_roundedCornerRadiusArray" />
+ <java-symbol type="array" name="config_roundedCornerTopRadiusArray" />
+ <java-symbol type="array" name="config_roundedCornerBottomRadiusArray" />
+ <java-symbol type="array" name="config_roundedCornerRadiusAdjustmentArray" />
+ <java-symbol type="array" name="config_roundedCornerTopRadiusAdjustmentArray" />
+ <java-symbol type="array" name="config_roundedCornerBottomRadiusAdjustmentArray" />
+ <java-symbol type="bool" name="config_secondaryBuiltInDisplayIsRound" />
+ <java-symbol type="array" name="config_builtInDisplayIsRoundArray" />
</resources>
diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml
index 166edca3d046..d310736ae121 100644
--- a/core/res/res/xml/power_profile.xml
+++ b/core/res/res/xml/power_profile.xml
@@ -27,9 +27,33 @@
are totally dependent on the platform and can vary
significantly, so should be measured on the shipping platform
with a power meter. -->
- <item name="ambient.on">0.1</item> <!-- ~100mA -->
- <item name="screen.on">0.1</item> <!-- ~100mA -->
- <item name="screen.full">0.1</item> <!-- ~100mA -->
+
+ <!-- Display related values. -->
+ <!-- Average battery current draw of display0 while in ambient mode, including backlight.
+ There must be one of these for each display, labeled:
+ ambient.on.display0, ambient.on.display1, etc...
+
+ Each display suffix number should match it's ordinal in its display device config.
+ -->
+ <item name="ambient.on.display0">0.1</item> <!-- ~100mA -->
+ <!-- Average battery current draw of display0 while on without backlight.
+ There must be one of these for each display, labeled:
+ screen.on.display0, screen.on.display1, etc...
+
+ Each display suffix number should match it's ordinal in its display device config.
+ -->
+ <item name="screen.on.display0">0.1</item> <!-- ~100mA -->
+ <!-- Average battery current draw of the backlight at full brightness.
+ The full current draw of display N at full brightness should be the sum of screen.on.displayN
+ and screen.full.displayN
+
+ There must be one of these for each display, labeled:
+ screen.full.display0, screen.full.display1, etc...
+
+ Each display suffix number should match it's ordinal in its display device config.
+ -->
+ <item name="screen.full.display0">0.1</item> <!-- ~100mA -->
+
<item name="bluetooth.active">0.1</item> <!-- Bluetooth data transfer, ~10mA -->
<item name="bluetooth.on">0.1</item> <!-- Bluetooth on & connectable, but not connected, ~0.1mA -->
<item name="wifi.on">0.1</item> <!-- ~3mA -->
diff --git a/core/tests/BroadcastRadioTests/OWNERS b/core/tests/BroadcastRadioTests/OWNERS
index ea4421eae96a..3e360e7e992c 100644
--- a/core/tests/BroadcastRadioTests/OWNERS
+++ b/core/tests/BroadcastRadioTests/OWNERS
@@ -1,2 +1,3 @@
+keunyoung@google.com
+oscarazu@google.com
twasilczyk@google.com
-randolphs@google.com
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
index 7c6271cbdf61..c194989b2752 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
@@ -65,6 +65,7 @@ public class StartProgramListUpdatesFanoutTest {
@Mock ITunerSession mHalTunerSessionMock;
private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks;
+ private final Object mLock = new Object();
// RadioModule under test
private RadioModule mRadioModule;
@@ -96,7 +97,7 @@ public class StartProgramListUpdatesFanoutTest {
mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties(0, "",
0, "", "", "", "", 0, 0, false, false, null, false, new int[] {}, new int[] {},
- null, null));
+ null, null), mLock);
doAnswer((Answer) invocation -> {
mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java
index c65ef9a56cd8..53ba140e6aad 100644
--- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityServiceTest.java
@@ -16,18 +16,40 @@
package android.accessibilityservice;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
+import android.content.Context;
import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
+import android.window.WindowTokenClient;
import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -42,6 +64,8 @@ import org.mockito.MockitoAnnotations;
public class AccessibilityServiceTest {
private static final String TAG = "AccessibilityServiceTest";
private static final int CONNECTION_ID = 1;
+ private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(
+ TYPE_ACCESSIBILITY_OVERLAY);
private static class AccessibilityServiceTestClass extends AccessibilityService {
private IAccessibilityServiceClient mCallback;
@@ -49,7 +73,11 @@ public class AccessibilityServiceTest {
AccessibilityServiceTestClass() {
super();
- attachBaseContext(InstrumentationRegistry.getContext());
+ Context context = ApplicationProvider.getApplicationContext();
+ final Display display = context.getSystemService(DisplayManager.class)
+ .getDisplay(DEFAULT_DISPLAY);
+
+ attachBaseContext(context.createTokenContext(new WindowTokenClient(), display));
mLooper = InstrumentationRegistry.getContext().getMainLooper();
}
@@ -78,14 +106,33 @@ public class AccessibilityServiceTest {
private @Mock IBinder mMockIBinder;
private IAccessibilityServiceClient mServiceInterface;
private AccessibilityServiceTestClass mService;
+ private final SparseArray<IBinder> mWindowTokens = new SparseArray<>();
@Before
- public void setUp() throws RemoteException {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mService = new AccessibilityServiceTestClass();
+ mService.onCreate();
mService.setupCallback(mMockClientForCallback);
mServiceInterface = (IAccessibilityServiceClient) mService.onBind(new Intent());
mServiceInterface.init(mMockConnection, CONNECTION_ID, mMockIBinder);
+ doAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ final int displayId = (int) args[0];
+ final IBinder token = new Binder();
+ WindowManagerGlobal.getWindowManagerService().addWindowToken(token,
+ TYPE_ACCESSIBILITY_OVERLAY, displayId, null /* options */);
+ mWindowTokens.put(displayId, token);
+ return token;
+ }).when(mMockConnection).getOverlayWindowToken(anyInt());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ for (int i = mWindowTokens.size() - 1; i >= 0; --i) {
+ WindowManagerGlobal.getWindowManagerService().removeWindowToken(
+ mWindowTokens.valueAt(i), mWindowTokens.keyAt(i));
+ }
}
@Test
@@ -101,4 +148,79 @@ public class AccessibilityServiceTest {
verify(mMockConnection).getSystemActions();
}
+
+ @Test
+ public void testAddViewWithA11yServiceDerivedDisplayContext() throws Exception {
+ try (VirtualDisplaySession session = new VirtualDisplaySession()) {
+ final Context context = mService.createDisplayContext(session.getDisplay());
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> context.getSystemService(WindowManager.class)
+ .addView(new View(context), mParams)
+ );
+ }
+ }
+
+ @Test
+ public void testAddViewWithA11yServiceDerivedWindowContext() throws Exception {
+ try (VirtualDisplaySession session = new VirtualDisplaySession()) {
+ final Context context = mService.createDisplayContext(session.getDisplay())
+ .createWindowContext(TYPE_ACCESSIBILITY_OVERLAY, null /* options */);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> context.getSystemService(WindowManager.class)
+ .addView(new View(context), mParams)
+ );
+ }
+ }
+
+ @Test
+ public void testAddViewWithA11yServiceDerivedWindowContextWithDisplay() throws Exception {
+ try (VirtualDisplaySession session = new VirtualDisplaySession()) {
+ final Context context = mService.createWindowContext(session.getDisplay(),
+ TYPE_ACCESSIBILITY_OVERLAY, null /* options */);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> context.getSystemService(WindowManager.class)
+ .addView(new View(context), mParams)
+ );
+ }
+ }
+
+ @Test(expected = WindowManager.BadTokenException.class)
+ public void testAddViewWithA11yServiceDerivedWindowContextWithDifferentType()
+ throws Exception {
+ try (VirtualDisplaySession session = new VirtualDisplaySession()) {
+ final Context context = mService.createWindowContext(session.getDisplay(),
+ TYPE_APPLICATION_OVERLAY, null /* options */);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> context.getSystemService(WindowManager.class)
+ .addView(new View(context), mParams)
+ );
+ }
+ }
+
+
+ private static class VirtualDisplaySession implements AutoCloseable {
+ private final VirtualDisplay mVirtualDisplay;
+
+ VirtualDisplaySession() {
+ final DisplayManager displayManager = ApplicationProvider.getApplicationContext()
+ .getSystemService(DisplayManager.class);
+ final int width = 800;
+ final int height = 480;
+ final int density = 160;
+ ImageReader reader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888,
+ 2 /* maxImages */);
+ mVirtualDisplay = displayManager.createVirtualDisplay(
+ TAG, width, height, density, reader.getSurface(),
+ VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+ }
+
+ private Display getDisplay() {
+ return mVirtualDisplay.getDisplay();
+ }
+
+ @Override
+ public void close() throws Exception {
+ mVirtualDisplay.release();
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index fb820cb2f5e5..6f17ea994699 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -286,7 +286,6 @@ public class ActivityThreadTest {
}
@Test
- @FlakyTest(bugId = 194242735)
public void testHandleActivityConfigurationChanged_EnsureUpdatesProcessedInOrder()
throws Exception {
final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java
index 3d820acf2d22..6884f13d4cc9 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java
@@ -62,18 +62,4 @@ public class GenericDocumentTest {
assertThat(outDoc.getPropertyDocument("propDocument").getPropertyBytesArray("propBytes"))
.isEqualTo(new byte[][] {{3, 4}});
}
-
- @Test
- public void testPutLargeDocument_exceedLimit() throws Exception {
- // Create a String property that has a very large property.
- char[] chars = new char[10_000_000];
- String property = new StringBuilder().append(chars).append("the end.").toString();
-
- GenericDocument doc =
- new GenericDocument.Builder<>("namespace", "id1", "schema1")
- .setPropertyString("propString", property)
- .build();
-
- assertThat(doc.getPropertyString("propString")).isEqualTo(property);
- }
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 9915e3852b8d..3e261a7113ac 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -156,7 +156,8 @@ public class ObjectPoolTests {
.setProcState(procState).setState(bundle).setPersistentState(persistableBundle)
.setPendingResults(resultInfoList()).setPendingNewIntents(referrerIntentList())
.setIsForward(true).setAssistToken(assistToken)
- .setShareableActivityToken(shareableActivityToken).build();
+ .setShareableActivityToken(shareableActivityToken)
+ .build();
LaunchActivityItem emptyItem = new LaunchActivityItemBuilder().build();
LaunchActivityItem item = itemSupplier.get();
diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java
index d1776fb7e5c1..3d7d807ca53d 100644
--- a/core/tests/coretests/src/android/content/ContextTest.java
+++ b/core/tests/coretests/src/android/content/ContextTest.java
@@ -32,7 +32,6 @@ import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
-import android.inputmethodservice.InputMethodService;
import android.media.ImageReader;
import android.os.UserHandle;
import android.view.Display;
@@ -140,13 +139,6 @@ public class ContextTest {
}
@Test
- public void testIsUiContext_InputMethodService_returnsTrue() {
- final InputMethodService ims = new InputMethodService();
-
- assertTrue(ims.isUiContext());
- }
-
- @Test
public void testGetDisplayFromDisplayContextDerivedContextOnPrimaryDisplay() {
verifyGetDisplayFromDisplayContextDerivedContext(false /* onSecondaryDisplay */);
}
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 8fd1af801094..4f1da1b29616 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -111,7 +111,7 @@ public class InsetsAnimationControlImplTest {
new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
mMockController, 10 /* durationMs */, new LinearInterpolator(),
0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */);
- mController.mReadyDispatched = true;
+ mController.setReadyDispatched(true);
}
@Test
@@ -197,7 +197,7 @@ public class InsetsAnimationControlImplTest {
@Test
public void testCancelled_beforeReadyDispatched() {
- mController.mReadyDispatched = false;
+ mController.setReadyDispatched(false);
mController.cancel();
assertFalse(mController.isReady());
assertFalse(mController.isFinished());
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 6301f32169f7..227a86576113 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -19,13 +19,17 @@ package android.view;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
import static android.view.InsetsController.ANIMATION_TYPE_NONE;
+import static android.view.InsetsController.ANIMATION_TYPE_RESIZE;
import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
+import static android.view.InsetsController.AnimationType;
import static android.view.InsetsSourceConsumer.ShowResult.IME_SHOW_DELAYED;
import static android.view.InsetsSourceConsumer.ShowResult.SHOW_IMMEDIATELY;
+import static android.view.InsetsState.FIRST_TYPE;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
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 android.view.InsetsState.LAST_TYPE;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
@@ -662,6 +666,97 @@ public class InsetsControllerTest {
}
@Test
+ public void testResizeAnimation_insetsTypes() {
+ for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+ final @AnimationType int expectedAnimationType =
+ (InsetsState.toPublicType(type) & Type.systemBars()) != 0
+ ? ANIMATION_TYPE_RESIZE
+ : ANIMATION_TYPE_NONE;
+ doTestResizeAnimation_insetsTypes(type, expectedAnimationType);
+ }
+ }
+
+ private void doTestResizeAnimation_insetsTypes(@InternalInsetsType int type,
+ @AnimationType int expectedAnimationType) {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ final InsetsState state1 = new InsetsState();
+ state1.getSource(type).setVisible(true);
+ state1.getSource(type).setFrame(0, 0, 500, 50);
+ final InsetsState state2 = new InsetsState(state1, true /* copySources */);
+ state2.getSource(type).setFrame(0, 0, 500, 60);
+ final String message = "Animation type of " + InsetsState.typeToString(type) + ":";
+
+ // New insets source won't cause the resize animation.
+ mController.onStateChanged(state1);
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+
+ // Changing frame might cause the resize animation. This depends on the insets type.
+ mController.onStateChanged(state2);
+ assertEquals(message, expectedAnimationType, mController.getAnimationType(type));
+
+ // Cancel the existing animations for the next iteration.
+ mController.cancelExistingAnimations();
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ @Test
+ public void testResizeAnimation_displayFrame() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ final @InternalInsetsType int type = ITYPE_STATUS_BAR;
+ final InsetsState state1 = new InsetsState();
+ state1.setDisplayFrame(new Rect(0, 0, 500, 1000));
+ state1.getSource(type).setFrame(0, 0, 500, 50);
+ final InsetsState state2 = new InsetsState(state1, true /* copySources */);
+ state2.setDisplayFrame(new Rect(0, 0, 500, 1010));
+ state2.getSource(type).setFrame(0, 0, 500, 60);
+ final String message = "There must not be resize animation.";
+
+ // New insets source won't cause the resize animation.
+ mController.onStateChanged(state1);
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+
+ // Changing frame won't cause the resize animation if the display frame is also changed.
+ mController.onStateChanged(state2);
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ @Test
+ public void testResizeAnimation_visibility() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ final @InternalInsetsType int type = ITYPE_STATUS_BAR;
+ final InsetsState state1 = new InsetsState();
+ state1.getSource(type).setVisible(true);
+ state1.getSource(type).setFrame(0, 0, 500, 50);
+ final InsetsState state2 = new InsetsState(state1, true /* copySources */);
+ state2.getSource(type).setVisible(false);
+ state2.getSource(type).setFrame(0, 0, 500, 60);
+ final InsetsState state3 = new InsetsState(state2, true /* copySources */);
+ state3.getSource(type).setVisible(true);
+ state3.getSource(type).setFrame(0, 0, 500, 70);
+ final String message = "There must not be resize animation.";
+
+ // New insets source won't cause the resize animation.
+ mController.onStateChanged(state1);
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+
+ // Changing source visibility (visible --> invisible) won't cause the resize animation.
+ // The previous source and the current one must be both visible.
+ mController.onStateChanged(state2);
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+
+ // Changing source visibility (invisible --> visible) won't cause the resize animation.
+ // The previous source and the current one must be both visible.
+ mController.onStateChanged(state3);
+ assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ @Test
public void testCaptionInsetsStateAssemble() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.onFrameChanged(new Rect(0, 0, 100, 300));
@@ -698,15 +793,15 @@ public class InsetsControllerTest {
@Test
public void testRequestedState() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- final InsetsState state = mTestHost.getRequestedState();
+ final InsetsVisibilities request = mTestHost.getRequestedVisibilities();
mController.hide(statusBars() | navigationBars());
- assertFalse(state.getSourceOrDefaultVisibility(ITYPE_STATUS_BAR));
- assertFalse(state.getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
+ assertFalse(request.getVisibility(ITYPE_STATUS_BAR));
+ assertFalse(request.getVisibility(ITYPE_NAVIGATION_BAR));
mController.show(statusBars() | navigationBars());
- assertTrue(state.getSourceOrDefaultVisibility(ITYPE_STATUS_BAR));
- assertTrue(state.getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
+ assertTrue(request.getVisibility(ITYPE_STATUS_BAR));
+ assertTrue(request.getVisibility(ITYPE_NAVIGATION_BAR));
});
}
@@ -837,20 +932,20 @@ public class InsetsControllerTest {
public static class TestHost extends ViewRootInsetsControllerHost {
- private final InsetsState mRequestedState = new InsetsState();
+ private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
TestHost(ViewRootImpl viewRoot) {
super(viewRoot);
}
@Override
- public void onInsetsModified(InsetsState insetsState) {
- mRequestedState.set(insetsState, true);
- super.onInsetsModified(insetsState);
+ public void updateRequestedVisibilities(InsetsVisibilities visibilities) {
+ mRequestedVisibilities.set(visibilities);
+ super.updateRequestedVisibilities(visibilities);
}
- public InsetsState getRequestedState() {
- return mRequestedState;
+ public InsetsVisibilities getRequestedVisibilities() {
+ return mRequestedVisibilities;
}
}
}
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 3bd29398325f..bf8bb76891d7 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -216,9 +216,9 @@ public class InsetsStateTest {
mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(0, 0, 100, 300));
mState.getSource(ITYPE_CAPTION_BAR).setVisible(true);
- Rect visibleInsets = mState.calculateVisibleInsets(
+ Insets visibleInsets = mState.calculateVisibleInsets(
new Rect(0, 0, 100, 400), SOFT_INPUT_ADJUST_NOTHING);
- assertEquals(new Rect(0, 300, 0, 0), visibleInsets);
+ assertEquals(Insets.of(0, 300, 0, 0), visibleInsets);
}
@Test
@@ -226,9 +226,9 @@ public class InsetsStateTest {
mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(0, 0, 100, 300));
mState.getSource(ITYPE_CAPTION_BAR).setVisible(true);
- Rect visibleInsets = mState.calculateVisibleInsets(
+ Insets visibleInsets = mState.calculateVisibleInsets(
new Rect(0, 0, 150, 400), SOFT_INPUT_ADJUST_NOTHING);
- assertEquals(new Rect(0, 300, 0, 0), visibleInsets);
+ assertEquals(Insets.of(0, 300, 0, 0), visibleInsets);
}
@Test
@@ -413,9 +413,9 @@ public class InsetsStateTest {
// Make sure bottom gestures are ignored
mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
- Rect visibleInsets = mState.calculateVisibleInsets(
+ Insets visibleInsets = mState.calculateVisibleInsets(
new Rect(0, 0, 100, 300), SOFT_INPUT_ADJUST_PAN);
- assertEquals(new Rect(0, 100, 0, 100), visibleInsets);
+ assertEquals(Insets.of(0, 100, 0, 100), visibleInsets);
}
@Test
@@ -428,9 +428,9 @@ public class InsetsStateTest {
// Make sure bottom gestures are ignored
mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
- Rect visibleInsets = mState.calculateVisibleInsets(
+ Insets visibleInsets = mState.calculateVisibleInsets(
new Rect(0, 0, 100, 300), SOFT_INPUT_ADJUST_NOTHING);
- assertEquals(new Rect(0, 100, 0, 0), visibleInsets);
+ assertEquals(Insets.of(0, 100, 0, 0), visibleInsets);
}
@Test
diff --git a/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java b/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java
new file mode 100644
index 000000000000..5664e0b0aa0f
--- /dev/null
+++ b/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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 android.view;
+
+import static android.view.InsetsState.FIRST_TYPE;
+import static android.view.InsetsState.LAST_TYPE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+import android.view.InsetsState.InternalInsetsType;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link InsetsVisibilities}.
+ *
+ * <p>Build/Install/Run:
+ * atest FrameworksCoreTests:InsetsVisibilities
+ *
+ * <p>This test class is a part of Window Manager Service tests and specified in
+ * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class InsetsVisibilitiesTest {
+
+ @Test
+ public void testEquals() {
+ final InsetsVisibilities v1 = new InsetsVisibilities();
+ final InsetsVisibilities v2 = new InsetsVisibilities();
+ final InsetsVisibilities v3 = new InsetsVisibilities();
+ assertEquals(v1, v2);
+ assertEquals(v1, v3);
+
+ for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+ v1.setVisibility(type, false);
+ v2.setVisibility(type, false);
+ }
+ assertEquals(v1, v2);
+ assertNotEquals(v1, v3);
+
+ for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+ v1.setVisibility(type, true);
+ v2.setVisibility(type, true);
+ }
+ assertEquals(v1, v2);
+ assertNotEquals(v1, v3);
+ }
+
+ @Test
+ public void testSet() {
+ for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+ final InsetsVisibilities v1 = new InsetsVisibilities();
+ final InsetsVisibilities v2 = new InsetsVisibilities();
+
+ v1.setVisibility(type, true);
+ assertNotEquals(v1, v2);
+
+ v2.set(v1);
+ assertEquals(v1, v2);
+
+ v2.setVisibility(type, false);
+ assertNotEquals(v1, v2);
+
+ v1.set(v2);
+ assertEquals(v1, v2);
+ }
+ }
+
+ @Test
+ public void testCopyConstructor() {
+ for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+ final InsetsVisibilities v1 = new InsetsVisibilities();
+ v1.setVisibility(type, true);
+ final InsetsVisibilities v2 = new InsetsVisibilities(v1);
+ assertEquals(v1, v2);
+
+ v2.setVisibility(type, false);
+ assertNotEquals(v1, v2);
+ }
+ }
+
+ @Test
+ public void testGetterAndSetter() {
+ final InsetsVisibilities v1 = new InsetsVisibilities();
+ final InsetsVisibilities v2 = new InsetsVisibilities();
+
+ for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+ assertEquals(InsetsState.getDefaultVisibility(type), v1.getVisibility(type));
+ }
+
+ for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+ v1.setVisibility(type, true);
+ assertTrue(v1.getVisibility(type));
+
+ v2.setVisibility(type, false);
+ assertFalse(v2.getVisibility(type));
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/WindowInfoTest.java b/core/tests/coretests/src/android/view/WindowInfoTest.java
index 05e8bd8b6cab..0a99b08f58ff 100644
--- a/core/tests/coretests/src/android/view/WindowInfoTest.java
+++ b/core/tests/coretests/src/android/view/WindowInfoTest.java
@@ -25,6 +25,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
+import android.app.ActivityTaskManager;
import android.os.IBinder;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
@@ -83,6 +84,7 @@ public class WindowInfoTest {
assertEquals(0, w.layer);
assertEquals(AccessibilityNodeInfo.UNDEFINED_NODE_ID, w.accessibilityIdOfAnchor);
assertEquals(Display.INVALID_DISPLAY, w.displayId);
+ assertEquals(ActivityTaskManager.INVALID_TASK_ID, w.taskId);
assertNull(w.title);
assertNull(w.token);
assertNull(w.childTokens);
@@ -123,6 +125,7 @@ public class WindowInfoTest {
windowInfo.displayId = 2;
windowInfo.layer = 3;
windowInfo.accessibilityIdOfAnchor = 4L;
+ windowInfo.taskId = 5;
windowInfo.title = "title";
windowInfo.token = mock(IBinder.class);
windowInfo.childTokens = new ArrayList<>();
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 8643a37bba8d..b71d814c508d 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -166,4 +166,7 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon
public void logTrace(long timestamp, String where, String callingParams, int processId,
long threadId, int callingUid, Bundle callingStack) {}
+
+ public void logTrace(long timestamp, String where, long loggingTypes, String callingParams,
+ int processId, long threadId, int callingUid, Bundle serializedCallingStackInBundle) {}
}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureContextTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureContextTest.java
index ddb6729b55e1..4b19391da78e 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureContextTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureContextTest.java
@@ -39,9 +39,10 @@ public class ContentCaptureContextTest {
public void testConstructorAdditionalFlags() {
final ComponentName componentName = new ComponentName("component", "name");
final IBinder token = new Binder();
+ final IBinder windowToken = new Binder();
final ContentCaptureContext ctx = new ContentCaptureContext(/* clientContext= */ null,
new ActivityId(/* taskId= */ 666, token), componentName, /* displayId= */
- 42, /* flags= */ 1);
+ 42, windowToken, /* flags= */ 1);
final ContentCaptureContext newCtx = new ContentCaptureContext(ctx, /* extraFlags= */ 2);
assertThat(newCtx.getFlags()).isEqualTo(3);
assertThat(newCtx.getActivityComponent()).isEqualTo(componentName);
@@ -50,6 +51,7 @@ public class ContentCaptureContextTest {
assertThat(activityId.getTaskId()).isEqualTo(666);
assertThat(activityId.getToken()).isEqualTo(token);
assertThat(newCtx.getDisplayId()).isEqualTo(42);
+ assertThat(newCtx.getWindowToken()).isEqualTo(windowToken);
assertThat(newCtx.getExtras()).isNull();
assertThat(newCtx.getLocusId()).isNull();
assertThat(newCtx.getParentSessionId()).isNull();
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
index 073e46827bbb..a6e351d9cee7 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -22,6 +22,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -66,7 +67,7 @@ public class WindowContextControllerTest {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mController = new WindowContextController(mMockToken, mMockWms);
- doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt());
+ doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean());
doReturn(new Configuration()).when(mMockWms).attachWindowContextToDisplayArea(any(),
anyInt(), anyInt(), any());
}
@@ -92,7 +93,8 @@ public class WindowContextControllerTest {
null /* options */);
assertThat(mController.mAttachedToDisplayArea).isTrue();
- verify(mMockToken).onConfigurationChanged(any(), eq(DEFAULT_DISPLAY));
+ verify(mMockToken).onConfigurationChanged(any(), eq(DEFAULT_DISPLAY),
+ eq(false) /* shouldReportConfigChange */);
mController.detachIfNeeded();
diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
index 96b4316ffafc..7cd8197ce1e4 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -23,6 +23,7 @@ import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_DEADLINE_
import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
import static com.android.internal.jank.FrameTracker.ViewRootWrapper;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_WALLPAPER_TRANSITION;
import static com.google.common.truth.Truth.assertThat;
@@ -50,6 +51,7 @@ import androidx.test.rule.ActivityTestRule;
import com.android.internal.jank.FrameTracker.ChoreographerWrapper;
import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
+import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.internal.jank.InteractionJankMonitor.Session;
import org.junit.Before;
@@ -69,7 +71,6 @@ public class FrameTrackerTest {
public ActivityTestRule<ViewAttachTestActivity> mRule =
new ActivityTestRule<>(ViewAttachTestActivity.class);
- private FrameTracker mTracker;
private ThreadedRendererWrapper mRenderer;
private FrameMetricsWrapper mWrapper;
private SurfaceControlWrapper mSurfaceControlWrapper;
@@ -85,7 +86,6 @@ public class FrameTrackerTest {
View view = mActivity.getWindow().getDecorView();
assertThat(view.isAttachedToWindow()).isTrue();
- Handler handler = mRule.getActivity().getMainThreadHandler();
mWrapper = Mockito.spy(new FrameMetricsWrapper());
mRenderer = Mockito.spy(new ThreadedRendererWrapper(view.getThreadedRenderer()));
doNothing().when(mRenderer).addObserver(any());
@@ -103,229 +103,355 @@ public class FrameTrackerTest {
mListenerCapture.capture());
mChoreographer = mock(ChoreographerWrapper.class);
+ }
- Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX);
- mTracker = Mockito.spy(
+ private FrameTracker spyFrameTracker(int cuj, String postfix, boolean surfaceOnly) {
+ Handler handler = mRule.getActivity().getMainThreadHandler();
+ Session session = new Session(cuj, postfix);
+ Configuration config = mock(Configuration.class);
+ when(config.isSurfaceOnly()).thenReturn(surfaceOnly);
+ when(config.getSurfaceControl()).thenReturn(mSurfaceControl);
+ FrameTracker frameTracker = Mockito.spy(
new FrameTracker(session, handler, mRenderer, mViewRootWrapper,
mSurfaceControlWrapper, mChoreographer, mWrapper,
- /*traceThresholdMissedFrames=*/ 1, /*traceThresholdFrameTimeMillis=*/ -1,
- null));
- doNothing().when(mTracker).triggerPerfetto();
- doNothing().when(mTracker).postTraceStartMarker();
+ /* traceThresholdMissedFrames= */ 1,
+ /* traceThresholdFrameTimeMillis= */ -1,
+ /* FrameTrackerListener= */ null, config));
+ doNothing().when(frameTracker).triggerPerfetto();
+ doNothing().when(frameTracker).postTraceStartMarker();
+ return frameTracker;
}
@Test
public void testOnlyFirstWindowFrameOverThreshold() {
+ FrameTracker tracker = spyFrameTracker(
+ CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+
// Just provide current timestamp anytime mWrapper asked for VSYNC_TIMESTAMP
when(mWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP))
.then(unusedInvocation -> System.nanoTime());
when(mChoreographer.getVsyncId()).thenReturn(100L);
- mTracker.begin();
+ tracker.begin();
verify(mRenderer, only()).addObserver(any());
// send first frame with a long duration - should not be taken into account
- sendFirstWindowFrame(100, JANK_APP_DEADLINE_MISSED, 100L);
+ sendFirstWindowFrame(tracker, 100, JANK_APP_DEADLINE_MISSED, 100L);
// send another frame with a short duration - should not be considered janky
- sendFirstWindowFrame(5, JANK_NONE, 101L);
+ sendFirstWindowFrame(tracker, 5, JANK_NONE, 101L);
// end the trace session, the last janky frame is after the end() so is discarded.
when(mChoreographer.getVsyncId()).thenReturn(102L);
- mTracker.end(FrameTracker.REASON_END_NORMAL);
- sendFrame(5, JANK_NONE, 102L);
- sendFrame(500, JANK_APP_DEADLINE_MISSED, 103L);
+ tracker.end(FrameTracker.REASON_END_NORMAL);
+ sendFrame(tracker, 5, JANK_NONE, 102L);
+ sendFrame(tracker, 500, JANK_APP_DEADLINE_MISSED, 103L);
- verify(mTracker).removeObservers();
- verify(mTracker, never()).triggerPerfetto();
+ verify(tracker).removeObservers();
+ verify(tracker, never()).triggerPerfetto();
}
@Test
public void testSfJank() {
+ FrameTracker tracker = spyFrameTracker(
+ CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+
when(mChoreographer.getVsyncId()).thenReturn(100L);
- mTracker.begin();
+ tracker.begin();
verify(mRenderer, only()).addObserver(any());
// send first frame - not janky
- sendFrame(4, JANK_NONE, 100L);
+ sendFrame(tracker, 4, JANK_NONE, 100L);
// send another frame - should be considered janky
- sendFrame(40, JANK_SURFACEFLINGER_DEADLINE_MISSED, 101L);
+ sendFrame(tracker, 40, JANK_SURFACEFLINGER_DEADLINE_MISSED, 101L);
// end the trace session
when(mChoreographer.getVsyncId()).thenReturn(102L);
- mTracker.end(FrameTracker.REASON_END_NORMAL);
- sendFrame(4, JANK_NONE, 102L);
+ tracker.end(FrameTracker.REASON_END_NORMAL);
+ sendFrame(tracker, 4, JANK_NONE, 102L);
- verify(mTracker).removeObservers();
+ verify(tracker).removeObservers();
// We detected a janky frame - trigger Perfetto
- verify(mTracker).triggerPerfetto();
+ verify(tracker).triggerPerfetto();
}
@Test
public void testFirstFrameJankyNoTrigger() {
+ FrameTracker tracker = spyFrameTracker(
+ CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+
when(mChoreographer.getVsyncId()).thenReturn(100L);
- mTracker.begin();
+ tracker.begin();
verify(mRenderer, only()).addObserver(any());
// send first frame - janky
- sendFrame(40, JANK_APP_DEADLINE_MISSED, 100L);
+ sendFrame(tracker, 40, JANK_APP_DEADLINE_MISSED, 100L);
// send another frame - not jank
- sendFrame(4, JANK_NONE, 101L);
+ sendFrame(tracker, 4, JANK_NONE, 101L);
// end the trace session
when(mChoreographer.getVsyncId()).thenReturn(102L);
- mTracker.end(FrameTracker.REASON_END_NORMAL);
- sendFrame(4, JANK_NONE, 102L);
+ tracker.end(FrameTracker.REASON_END_NORMAL);
+ sendFrame(tracker, 4, JANK_NONE, 102L);
- verify(mTracker).removeObservers();
+ verify(tracker).removeObservers();
// We detected a janky frame - trigger Perfetto
- verify(mTracker, never()).triggerPerfetto();
+ verify(tracker, never()).triggerPerfetto();
}
@Test
public void testOtherFrameOverThreshold() {
+ FrameTracker tracker = spyFrameTracker(
+ CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+
when(mChoreographer.getVsyncId()).thenReturn(100L);
- mTracker.begin();
+ tracker.begin();
verify(mRenderer, only()).addObserver(any());
// send first frame - not janky
- sendFrame(4, JANK_NONE, 100L);
+ sendFrame(tracker, 4, JANK_NONE, 100L);
// send another frame - should be considered janky
- sendFrame(40, JANK_APP_DEADLINE_MISSED, 101L);
+ sendFrame(tracker, 40, JANK_APP_DEADLINE_MISSED, 101L);
// end the trace session
when(mChoreographer.getVsyncId()).thenReturn(102L);
- mTracker.end(FrameTracker.REASON_END_NORMAL);
- sendFrame(4, JANK_NONE, 102L);
+ tracker.end(FrameTracker.REASON_END_NORMAL);
+ sendFrame(tracker, 4, JANK_NONE, 102L);
- verify(mTracker).removeObservers();
+ verify(tracker).removeObservers();
// We detected a janky frame - trigger Perfetto
- verify(mTracker).triggerPerfetto();
+ verify(tracker).triggerPerfetto();
}
@Test
public void testLastFrameOverThresholdBeforeEnd() {
+ FrameTracker tracker = spyFrameTracker(
+ CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+
when(mChoreographer.getVsyncId()).thenReturn(100L);
- mTracker.begin();
+ tracker.begin();
verify(mRenderer, only()).addObserver(any());
// send first frame - not janky
- sendFrame(4, JANK_NONE, 100L);
+ sendFrame(tracker, 4, JANK_NONE, 100L);
// send another frame - not janky
- sendFrame(4, JANK_NONE, 101L);
+ sendFrame(tracker, 4, JANK_NONE, 101L);
// end the trace session, simulate one more valid callback came after the end call.
when(mChoreographer.getVsyncId()).thenReturn(102L);
- mTracker.end(FrameTracker.REASON_END_NORMAL);
- sendFrame(50, JANK_APP_DEADLINE_MISSED, 102L);
+ tracker.end(FrameTracker.REASON_END_NORMAL);
+ sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 102L);
// One more callback with VSYNC after the end() vsync id.
- sendFrame(4, JANK_NONE, 103L);
+ sendFrame(tracker, 4, JANK_NONE, 103L);
- verify(mTracker).removeObservers();
+ verify(tracker).removeObservers();
// We detected a janky frame - trigger Perfetto
- verify(mTracker).triggerPerfetto();
+ verify(tracker).triggerPerfetto();
}
@Test
public void testBeginCancel() {
+ FrameTracker tracker = spyFrameTracker(
+ CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+
when(mChoreographer.getVsyncId()).thenReturn(100L);
- mTracker.begin();
+ tracker.begin();
verify(mRenderer).addObserver(any());
// First frame - not janky
- sendFrame(4, JANK_NONE, 100L);
+ sendFrame(tracker, 4, JANK_NONE, 100L);
// normal frame - not janky
- sendFrame(4, JANK_NONE, 101L);
+ sendFrame(tracker, 4, JANK_NONE, 101L);
// a janky frame
- sendFrame(50, JANK_APP_DEADLINE_MISSED, 102L);
+ sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 102L);
- mTracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
- verify(mTracker).removeObservers();
+ tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
+ verify(tracker).removeObservers();
// Since the tracker has been cancelled, shouldn't trigger perfetto.
- verify(mTracker, never()).triggerPerfetto();
+ verify(tracker, never()).triggerPerfetto();
}
@Test
public void testCancelIfEndVsyncIdEqualsToBeginVsyncId() {
+ FrameTracker tracker = spyFrameTracker(
+ CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+
when(mChoreographer.getVsyncId()).thenReturn(100L);
- mTracker.begin();
+ tracker.begin();
verify(mRenderer, only()).addObserver(any());
// end the trace session
when(mChoreographer.getVsyncId()).thenReturn(101L);
- mTracker.end(FrameTracker.REASON_END_NORMAL);
+ tracker.end(FrameTracker.REASON_END_NORMAL);
// Since the begin vsync id (101) equals to the end vsync id (101), will be treat as cancel.
- verify(mTracker).cancel(FrameTracker.REASON_CANCEL_SAME_VSYNC);
+ verify(tracker).cancel(FrameTracker.REASON_CANCEL_SAME_VSYNC);
// Observers should be removed in this case, or FrameTracker object will be leaked.
- verify(mTracker).removeObservers();
+ verify(tracker).removeObservers();
// Should never trigger Perfetto since it is a cancel.
- verify(mTracker, never()).triggerPerfetto();
+ verify(tracker, never()).triggerPerfetto();
}
@Test
public void testCancelIfEndVsyncIdLessThanBeginVsyncId() {
+ FrameTracker tracker = spyFrameTracker(
+ CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+
when(mChoreographer.getVsyncId()).thenReturn(100L);
- mTracker.begin();
+ tracker.begin();
verify(mRenderer, only()).addObserver(any());
// end the trace session at the same vsync id, end vsync id will less than the begin one.
// Because the begin vsync id is supposed to the next frame,
- mTracker.end(FrameTracker.REASON_END_NORMAL);
+ tracker.end(FrameTracker.REASON_END_NORMAL);
// The begin vsync id (101) is larger than the end one (100), will be treat as cancel.
- verify(mTracker).cancel(FrameTracker.REASON_CANCEL_SAME_VSYNC);
+ verify(tracker).cancel(FrameTracker.REASON_CANCEL_SAME_VSYNC);
// Observers should be removed in this case, or FrameTracker object will be leaked.
- verify(mTracker).removeObservers();
+ verify(tracker).removeObservers();
// Should never trigger Perfetto since it is a cancel.
- verify(mTracker, never()).triggerPerfetto();
+ verify(tracker, never()).triggerPerfetto();
}
@Test
public void testCancelWhenSessionNeverBegun() {
- mTracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
- verify(mTracker).removeObservers();
+ FrameTracker tracker = spyFrameTracker(
+ CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+
+ tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
+ verify(tracker).removeObservers();
}
@Test
public void testEndWhenSessionNeverBegun() {
- mTracker.end(FrameTracker.REASON_END_NORMAL);
- verify(mTracker).removeObservers();
+ FrameTracker tracker = spyFrameTracker(
+ CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
+
+ tracker.end(FrameTracker.REASON_END_NORMAL);
+ verify(tracker).removeObservers();
}
- private void sendFirstWindowFrame(long durationMillis,
+ @Test
+ public void testSurfaceOnlyOtherFrameJanky() {
+ FrameTracker tracker = spyFrameTracker(
+ CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
+
+ when(mChoreographer.getVsyncId()).thenReturn(100L);
+ tracker.begin();
+ verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
+
+ // First frame - not janky
+ sendFrame(tracker, JANK_NONE, 100L);
+ // normal frame - not janky
+ sendFrame(tracker, JANK_NONE, 101L);
+ // a janky frame
+ sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 102L);
+
+ when(mChoreographer.getVsyncId()).thenReturn(102L);
+ tracker.end(FrameTracker.REASON_CANCEL_NORMAL);
+
+ // an extra frame to trigger finish
+ sendFrame(tracker, JANK_NONE, 103L);
+
+ verify(mSurfaceControlWrapper).removeJankStatsListener(any());
+ verify(tracker).triggerPerfetto();
+ }
+
+ @Test
+ public void testSurfaceOnlyFirstFrameJanky() {
+ FrameTracker tracker = spyFrameTracker(
+ CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
+
+ when(mChoreographer.getVsyncId()).thenReturn(100L);
+ tracker.begin();
+ verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
+
+ // First frame - janky
+ sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 100L);
+ // normal frame - not janky
+ sendFrame(tracker, JANK_NONE, 101L);
+ // normal frame - not janky
+ sendFrame(tracker, JANK_NONE, 102L);
+
+ when(mChoreographer.getVsyncId()).thenReturn(102L);
+ tracker.end(FrameTracker.REASON_CANCEL_NORMAL);
+
+ // an extra frame to trigger finish
+ sendFrame(tracker, JANK_NONE, 103L);
+
+ verify(mSurfaceControlWrapper).removeJankStatsListener(any());
+ verify(tracker, never()).triggerPerfetto();
+ }
+
+ @Test
+ public void testSurfaceOnlyLastFrameJanky() {
+ FrameTracker tracker = spyFrameTracker(
+ CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
+
+ when(mChoreographer.getVsyncId()).thenReturn(100L);
+ tracker.begin();
+ verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
+
+ // First frame - not janky
+ sendFrame(tracker, JANK_NONE, 100L);
+ // normal frame - not janky
+ sendFrame(tracker, JANK_NONE, 101L);
+ // normal frame - not janky
+ sendFrame(tracker, JANK_NONE, 102L);
+
+ when(mChoreographer.getVsyncId()).thenReturn(102L);
+ tracker.end(FrameTracker.REASON_CANCEL_NORMAL);
+
+ // janky frame, should be ignored, trigger finish
+ sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 103L);
+
+ verify(mSurfaceControlWrapper).removeJankStatsListener(any());
+ verify(tracker, never()).triggerPerfetto();
+ }
+
+ private void sendFirstWindowFrame(FrameTracker tracker, long durationMillis,
@JankType int jankType, long vsyncId) {
- sendFrame(durationMillis, jankType, vsyncId, true /* firstWindowFrame */);
+ sendFrame(tracker, durationMillis, jankType, vsyncId, /* firstWindowFrame= */ true);
}
- private void sendFrame(long durationMillis,
+ private void sendFrame(FrameTracker tracker, long durationMillis,
@JankType int jankType, long vsyncId) {
- sendFrame(durationMillis, jankType, vsyncId, false /* firstWindowFrame */);
+ sendFrame(tracker, durationMillis, jankType, vsyncId, /* firstWindowFrame= */ false);
+ }
+
+ /**
+ * Used for surface only test.
+ */
+ private void sendFrame(FrameTracker tracker, @JankType int jankType, long vsyncId) {
+ sendFrame(tracker, /* durationMillis= */ -1,
+ jankType, vsyncId, /* firstWindowFrame= */ false);
}
- private void sendFrame(long durationMillis,
+ private void sendFrame(FrameTracker tracker, long durationMillis,
@JankType int jankType, long vsyncId, boolean firstWindowFrame) {
- when(mWrapper.getTiming()).thenReturn(new long[] { 0, vsyncId });
- doReturn(firstWindowFrame ? 1L : 0L).when(mWrapper)
- .getMetric(FrameMetrics.FIRST_DRAW_FRAME);
- doReturn(TimeUnit.MILLISECONDS.toNanos(durationMillis))
- .when(mWrapper).getMetric(FrameMetrics.TOTAL_DURATION);
- mTracker.onFrameMetricsAvailable(0);
+ if (!tracker.mSurfaceOnly) {
+ when(mWrapper.getTiming()).thenReturn(new long[]{0, vsyncId});
+ doReturn(firstWindowFrame ? 1L : 0L).when(mWrapper)
+ .getMetric(FrameMetrics.FIRST_DRAW_FRAME);
+ doReturn(TimeUnit.MILLISECONDS.toNanos(durationMillis))
+ .when(mWrapper).getMetric(FrameMetrics.TOTAL_DURATION);
+ tracker.onFrameMetricsAvailable(0);
+ }
mListenerCapture.getValue().onJankDataAvailable(new JankData[] {
new JankData(vsyncId, jankType)
});
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index c153b38d3f02..0d2d047b7f0b 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -16,8 +16,8 @@
package com.android.internal.jank;
-import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
-import static com.android.internal.jank.FrameTracker.ViewRootWrapper;
+import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
+import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_TO_STATSD_INTERACTION_TYPE;
@@ -25,17 +25,18 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.Message;
+import android.os.SystemClock;
import android.provider.DeviceConfig;
import android.view.View;
import android.view.ViewAttachTestActivity;
@@ -43,8 +44,12 @@ import android.view.ViewAttachTestActivity;
import androidx.test.filters.SmallTest;
import androidx.test.rule.ActivityTestRule;
+import com.android.internal.jank.FrameTracker.ChoreographerWrapper;
import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
+import com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
+import com.android.internal.jank.FrameTracker.ViewRootWrapper;
+import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.internal.jank.InteractionJankMonitor.Session;
import org.junit.Before;
@@ -80,33 +85,23 @@ public class InteractionJankMonitorTest {
Handler handler = spy(new Handler(mActivity.getMainLooper()));
doReturn(true).when(handler).sendMessageAtTime(any(), anyLong());
- mWorker = spy(new HandlerThread("Interaction-jank-monitor-test"));
- doNothing().when(mWorker).start();
+ mWorker = mock(HandlerThread.class);
doReturn(handler).when(mWorker).getThreadHandler();
}
@Test
public void testBeginEnd() {
- // Should return false if the view is not attached.
- InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker));
- verify(mWorker).start();
-
- Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX);
- FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(),
- new ThreadedRendererWrapper(mView.getThreadedRenderer()),
- new ViewRootWrapper(mView.getViewRootImpl()), new SurfaceControlWrapper(),
- mock(FrameTracker.ChoreographerWrapper.class),
- new FrameMetricsWrapper(), /*traceThresholdMissedFrames=*/ 1,
- /*traceThresholdFrameTimeMillis=*/ -1, null));
+ InteractionJankMonitor monitor = createMockedInteractionJankMonitor();
+ FrameTracker tracker = createMockedFrameTracker(null);
doReturn(tracker).when(monitor).createFrameTracker(any(), any());
- doNothing().when(tracker).triggerPerfetto();
- doNothing().when(tracker).postTraceStartMarker();
+ doNothing().when(tracker).begin();
+ doReturn(true).when(tracker).end(anyInt());
// Simulate a trace session and see if begin / end are invoked.
- assertThat(monitor.begin(mView, session.getCuj())).isTrue();
+ assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
verify(tracker).begin();
- assertThat(monitor.end(session.getCuj())).isTrue();
- verify(tracker).end(FrameTracker.REASON_END_NORMAL);
+ assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
+ verify(tracker).end(REASON_END_NORMAL);
}
@Test
@@ -135,31 +130,23 @@ public class InteractionJankMonitorTest {
}
@Test
- public void testBeginCancel() {
- InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker));
-
- ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
-
- Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX);
- FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(),
- new ThreadedRendererWrapper(mView.getThreadedRenderer()),
- new ViewRootWrapper(mView.getViewRootImpl()), new SurfaceControlWrapper(),
- mock(FrameTracker.ChoreographerWrapper.class),
- new FrameMetricsWrapper(), /*traceThresholdMissedFrames=*/ 1,
- /*traceThresholdFrameTimeMillis=*/ -1, null));
+ public void testBeginTimeout() {
+ ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+ InteractionJankMonitor monitor = createMockedInteractionJankMonitor();
+ FrameTracker tracker = createMockedFrameTracker(null);
doReturn(tracker).when(monitor).createFrameTracker(any(), any());
- doNothing().when(tracker).triggerPerfetto();
- doNothing().when(tracker).postTraceStartMarker();
+ doNothing().when(tracker).begin();
+ doReturn(true).when(tracker).cancel(anyInt());
- assertThat(monitor.begin(mView, session.getCuj())).isTrue();
+ assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
verify(tracker).begin();
- verify(mWorker.getThreadHandler(), atLeastOnce()).sendMessageAtTime(captor.capture(),
- anyLong());
- Runnable runnable = captor.getValue().getCallback();
+ verify(monitor).scheduleTimeoutAction(anyInt(), anyLong(), captor.capture());
+ Runnable runnable = captor.getValue();
assertThat(runnable).isNotNull();
mWorker.getThreadHandler().removeCallbacks(runnable);
runnable.run();
- verify(tracker).cancel(FrameTracker.REASON_CANCEL_NORMAL);
+ verify(monitor).cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, REASON_CANCEL_TIMEOUT);
+ verify(tracker).cancel(REASON_CANCEL_TIMEOUT);
}
@Test
@@ -185,4 +172,43 @@ public class InteractionJankMonitorTest {
.isTrue();
}
}
+
+ private InteractionJankMonitor createMockedInteractionJankMonitor() {
+ InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker));
+ doReturn(true).when(monitor).shouldMonitor(anyInt());
+ doNothing().when(monitor).notifyEvents(any(), any(), any());
+ return monitor;
+ }
+
+ private FrameTracker createMockedFrameTracker(FrameTracker.FrameTrackerListener listener) {
+ Session session = spy(new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX));
+ doReturn(false).when(session).logToStatsd();
+
+ ThreadedRendererWrapper threadedRenderer = mock(ThreadedRendererWrapper.class);
+ doNothing().when(threadedRenderer).addObserver(any());
+ doNothing().when(threadedRenderer).removeObserver(any());
+
+ ViewRootWrapper viewRoot = spy(new ViewRootWrapper(mView.getViewRootImpl()));
+ doNothing().when(viewRoot).addSurfaceChangedCallback(any());
+
+ SurfaceControlWrapper surfaceControl = mock(SurfaceControlWrapper.class);
+ doNothing().when(surfaceControl).addJankStatsListener(any(), any());
+ doNothing().when(surfaceControl).removeJankStatsListener(any());
+
+ final ChoreographerWrapper choreographer = mock(ChoreographerWrapper.class);
+ doReturn(SystemClock.elapsedRealtime()).when(choreographer).getVsyncId();
+
+ Configuration configuration = mock(Configuration.class);
+ when(configuration.isSurfaceOnly()).thenReturn(false);
+
+ FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(),
+ threadedRenderer, viewRoot, surfaceControl, choreographer,
+ new FrameMetricsWrapper(), /* traceThresholdMissedFrames= */ 1,
+ /* traceThresholdFrameTimeMillis= */ -1, listener, configuration));
+
+ doNothing().when(tracker).postTraceStartMarker();
+ doNothing().when(tracker).triggerPerfetto();
+
+ return tracker;
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
index 79f7a5c9df18..130f552f6e3a 100644
--- a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
@@ -16,6 +16,8 @@
package com.android.internal.os;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT;
+
import static com.google.common.truth.Truth.assertThat;
import android.os.BatteryConsumer;
@@ -36,26 +38,28 @@ public class AmbientDisplayPowerCalculatorTest {
@Rule
public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
- .setAveragePower(PowerProfile.POWER_AMBIENT_DISPLAY, 10.0);
+ .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 0, 10.0)
+ .setNumDisplays(1);
@Test
public void testMeasuredEnergyBasedModel() {
mStatsRule.initMeasuredEnergyStatsLocked();
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
- stats.updateDisplayMeasuredEnergyStatsLocked(300_000_000, Display.STATE_ON, 0);
+ stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{300_000_000},
+ new int[]{Display.STATE_ON}, 0);
- stats.noteScreenStateLocked(Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
+ stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
30 * MINUTE_IN_MS);
- stats.updateDisplayMeasuredEnergyStatsLocked(200_000_000, Display.STATE_DOZE,
- 30 * MINUTE_IN_MS);
+ stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{200_000_000},
+ new int[]{Display.STATE_DOZE}, 30 * MINUTE_IN_MS);
- stats.noteScreenStateLocked(Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
+ stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
120 * MINUTE_IN_MS);
- stats.updateDisplayMeasuredEnergyStatsLocked(100_000_000, Display.STATE_OFF,
- 120 * MINUTE_IN_MS);
+ stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{100_000_000},
+ new int[]{Display.STATE_OFF}, 120 * MINUTE_IN_MS);
AmbientDisplayPowerCalculator calculator =
new AmbientDisplayPowerCalculator(mStatsRule.getPowerProfile());
@@ -73,12 +77,73 @@ public class AmbientDisplayPowerCalculatorTest {
}
@Test
+ public void testMeasuredEnergyBasedModel_multiDisplay() {
+ mStatsRule.initMeasuredEnergyStatsLocked()
+ .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 1, 20.0)
+ .setNumDisplays(2);
+ BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+
+ final int[] screenStates = new int[] {Display.STATE_OFF, Display.STATE_OFF};
+
+ stats.noteScreenStateLocked(0, screenStates[0], 0, 0, 0);
+ stats.noteScreenStateLocked(1, screenStates[1], 0, 0, 0);
+ stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{300, 400}, screenStates, 0);
+
+ // Switch display0 to doze
+ screenStates[0] = Display.STATE_DOZE;
+ stats.noteScreenStateLocked(0, screenStates[0], 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
+ 30 * MINUTE_IN_MS);
+ stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{200, 300},
+ screenStates, 30 * MINUTE_IN_MS);
+
+ // Switch display1 to doze
+ screenStates[1] = Display.STATE_DOZE;
+ stats.noteScreenStateLocked(1, Display.STATE_DOZE, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS,
+ 90 * MINUTE_IN_MS);
+ // 100,000,000 uC should be attributed to display 0 doze here.
+ stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{100_000_000, 700_000_000},
+ screenStates, 90 * MINUTE_IN_MS);
+
+ // Switch display0 to off
+ screenStates[0] = Display.STATE_OFF;
+ stats.noteScreenStateLocked(0, screenStates[0], 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
+ 120 * MINUTE_IN_MS);
+ // 40,000,000 and 70,000,000 uC should be attributed to display 0 and 1 doze here.
+ stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{40_000_000, 70_000_000},
+ screenStates, 120 * MINUTE_IN_MS);
+
+ // Switch display1 to off
+ screenStates[1] = Display.STATE_OFF;
+ stats.noteScreenStateLocked(1, screenStates[1], 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS,
+ 150 * MINUTE_IN_MS);
+ stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{100, 90_000_000}, screenStates,
+ 150 * MINUTE_IN_MS);
+ // 90,000,000 uC should be attributed to display 1 doze here.
+
+ AmbientDisplayPowerCalculator calculator =
+ new AmbientDisplayPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ BatteryConsumer consumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
+ .isEqualTo(120 * MINUTE_IN_MS);
+ // 100,000,000 + 40,000,000 + 70,000,000 + 90,000,000 uC / 1000 (micro-/milli-) / 3600
+ // (seconds/hour) = 27.777778 mAh
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
+ .isWithin(PRECISION).of(83.33333);
+ assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+ }
+
+ @Test
public void testPowerProfileBasedModel() {
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
- stats.noteScreenStateLocked(Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
+ stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
30 * MINUTE_IN_MS);
- stats.noteScreenStateLocked(Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
+ stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
120 * MINUTE_IN_MS);
AmbientDisplayPowerCalculator calculator =
@@ -94,4 +159,36 @@ public class AmbientDisplayPowerCalculatorTest {
assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
+
+ @Test
+ public void testPowerProfileBasedModel_multiDisplay() {
+ mStatsRule.setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 1, 20.0)
+ .setNumDisplays(2);
+
+ BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+ stats.noteScreenStateLocked(1, Display.STATE_OFF, 0, 0, 0);
+ stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
+ 30 * MINUTE_IN_MS);
+ stats.noteScreenStateLocked(1, Display.STATE_DOZE, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS,
+ 90 * MINUTE_IN_MS);
+ stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
+ 120 * MINUTE_IN_MS);
+ stats.noteScreenStateLocked(1, Display.STATE_OFF, 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS,
+ 150 * MINUTE_IN_MS);
+
+ AmbientDisplayPowerCalculator calculator =
+ new AmbientDisplayPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+
+ BatteryConsumer consumer = mStatsRule.getDeviceBatteryConsumer();
+ // Duration should only be the union of
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
+ .isEqualTo(120 * MINUTE_IN_MS);
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
+ .isWithin(PRECISION).of(35.0);
+ assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index d4799a8f5fd3..3e2885a74287 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -16,9 +16,13 @@
package com.android.internal.os;
+import static android.os.BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
import static android.os.BatteryStats.STATS_SINCE_CHARGED;
import static android.os.BatteryStats.WAKE_TYPE_PARTIAL;
+import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU;
+import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_DISPLAY;
+
import android.app.ActivityManager;
import android.os.BatteryStats;
import android.os.BatteryStats.HistoryItem;
@@ -37,8 +41,10 @@ import com.android.internal.power.MeasuredEnergyStats;
import junit.framework.TestCase;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.function.IntConsumer;
/**
* Test various BatteryStatsImpl noteStart methods.
@@ -317,18 +323,130 @@ public class BatteryStatsNoteTest extends TestCase {
public void testNoteScreenStateLocked() throws Exception {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
bi.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
- bi.noteScreenStateLocked(Display.STATE_ON);
- bi.noteScreenStateLocked(Display.STATE_DOZE);
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_OFF, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE_SUSPEND);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE_SUSPEND, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ // STATE_VR note should map to STATE_ON.
+ bi.noteScreenStateLocked(0, Display.STATE_VR);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ // STATE_ON_SUSPEND note should map to STATE_ON.
+ bi.noteScreenStateLocked(0, Display.STATE_ON_SUSPEND);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ // Transition from ON to ON state should not cause an External Sync
+ assertEquals(0, bi.getAndClearExternalStatsSyncFlags());
+ }
+
+ /**
+ * Test BatteryStatsImpl.noteScreenStateLocked sets timebases and screen states correctly for
+ * multi display devices
+ */
+ @SmallTest
+ public void testNoteScreenStateLocked_multiDisplay() throws Exception {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setDisplayCountLocked(2);
+ bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ bi.noteScreenStateLocked(1, Display.STATE_OFF);
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_OFF, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE_SUSPEND);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE_SUSPEND, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ // STATE_VR note should map to STATE_ON.
+ bi.noteScreenStateLocked(0, Display.STATE_VR);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ // STATE_ON_SUSPEND note should map to STATE_ON.
+ bi.noteScreenStateLocked(0, Display.STATE_ON_SUSPEND);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ // Transition from ON to ON state should not cause an External Sync
+ assertEquals(0, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(1, Display.STATE_DOZE);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ // Should remain STATE_ON since display0 is still on.
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ // Overall screen state did not change, so no need to sync CPU stats.
+ assertEquals(UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
- assertEquals(bi.getScreenState(), Display.STATE_DOZE);
- bi.noteScreenStateLocked(Display.STATE_ON);
+ assertEquals(Display.STATE_DOZE, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
- assertEquals(bi.getScreenState(), Display.STATE_ON);
- bi.noteScreenStateLocked(Display.STATE_OFF);
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_DOZE, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE_SUSPEND);
assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
- assertEquals(bi.getScreenState(), Display.STATE_OFF);
+ assertEquals(Display.STATE_DOZE, bi.getScreenState());
+ // Overall screen state did not change, so no need to sync CPU stats.
+ assertEquals(UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_VR);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags());
+
+ bi.noteScreenStateLocked(0, Display.STATE_ON_SUSPEND);
+ assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+ assertEquals(Display.STATE_ON, bi.getScreenState());
+ assertEquals(0, bi.getAndClearExternalStatsSyncFlags());
}
/*
@@ -352,32 +470,317 @@ public class BatteryStatsNoteTest extends TestCase {
bi.updateTimeBasesLocked(true, Display.STATE_UNKNOWN, 100_000, 100_000);
// Turn on display at 200us
clocks.realtime = clocks.uptime = 200;
- bi.noteScreenStateLocked(Display.STATE_ON);
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
assertEquals(150_000, bi.computeBatteryRealtime(250_000, STATS_SINCE_CHARGED));
assertEquals(100_000, bi.computeBatteryScreenOffRealtime(250_000, STATS_SINCE_CHARGED));
assertEquals(50_000, bi.getScreenOnTime(250_000, STATS_SINCE_CHARGED));
assertEquals(0, bi.getScreenDozeTime(250_000, STATS_SINCE_CHARGED));
+ assertEquals(50_000, bi.getDisplayScreenOnTime(0, 250_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(0, 250_000));
clocks.realtime = clocks.uptime = 310;
- bi.noteScreenStateLocked(Display.STATE_OFF);
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
assertEquals(250_000, bi.computeBatteryRealtime(350_000, STATS_SINCE_CHARGED));
assertEquals(140_000, bi.computeBatteryScreenOffRealtime(350_000, STATS_SINCE_CHARGED));
assertEquals(110_000, bi.getScreenOnTime(350_000, STATS_SINCE_CHARGED));
assertEquals(0, bi.getScreenDozeTime(350_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 350_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(0, 350_000));
clocks.realtime = clocks.uptime = 400;
- bi.noteScreenStateLocked(Display.STATE_DOZE);
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
assertEquals(400_000, bi.computeBatteryRealtime(500_000, STATS_SINCE_CHARGED));
assertEquals(290_000, bi.computeBatteryScreenOffRealtime(500_000, STATS_SINCE_CHARGED));
assertEquals(110_000, bi.getScreenOnTime(500_000, STATS_SINCE_CHARGED));
assertEquals(100_000, bi.getScreenDozeTime(500_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 500_000));
+ assertEquals(100_000, bi.getDisplayScreenDozeTime(0, 500_000));
clocks.realtime = clocks.uptime = 1000;
- bi.noteScreenStateLocked(Display.STATE_OFF);
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
assertEquals(1400_000, bi.computeBatteryRealtime(1500_000, STATS_SINCE_CHARGED));
assertEquals(1290_000, bi.computeBatteryScreenOffRealtime(1500_000, STATS_SINCE_CHARGED));
assertEquals(110_000, bi.getScreenOnTime(1500_000, STATS_SINCE_CHARGED));
assertEquals(600_000, bi.getScreenDozeTime(1500_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 1500_000));
+ assertEquals(600_000, bi.getDisplayScreenDozeTime(0, 1500_000));
+ }
+
+ /*
+ * Test BatteryStatsImpl.noteScreenStateLocked updates timers correctly for multi display
+ * devices.
+ */
+ @SmallTest
+ public void testNoteScreenStateTimersLocked_multiDisplay() throws Exception {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setDisplayCountLocked(2);
+
+ clocks.realtime = clocks.uptime = 100;
+ // Device startup, setOnBatteryLocked calls updateTimebases
+ bi.updateTimeBasesLocked(true, Display.STATE_UNKNOWN, 100_000, 100_000);
+ // Turn on display at 200us
+ clocks.realtime = clocks.uptime = 200;
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ bi.noteScreenStateLocked(1, Display.STATE_OFF);
+ assertEquals(150_000, bi.computeBatteryRealtime(250_000, STATS_SINCE_CHARGED));
+ assertEquals(100_000, bi.computeBatteryScreenOffRealtime(250_000, STATS_SINCE_CHARGED));
+ assertEquals(50_000, bi.getScreenOnTime(250_000, STATS_SINCE_CHARGED));
+ assertEquals(0, bi.getScreenDozeTime(250_000, STATS_SINCE_CHARGED));
+ assertEquals(50_000, bi.getDisplayScreenOnTime(0, 250_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(0, 250_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 250_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(1, 250_000));
+
+ clocks.realtime = clocks.uptime = 310;
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertEquals(250_000, bi.computeBatteryRealtime(350_000, STATS_SINCE_CHARGED));
+ assertEquals(140_000, bi.computeBatteryScreenOffRealtime(350_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getScreenOnTime(350_000, STATS_SINCE_CHARGED));
+ assertEquals(0, bi.getScreenDozeTime(350_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 350_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(0, 350_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 350_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(1, 350_000));
+
+ clocks.realtime = clocks.uptime = 400;
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ assertEquals(400_000, bi.computeBatteryRealtime(500_000, STATS_SINCE_CHARGED));
+ assertEquals(290_000, bi.computeBatteryScreenOffRealtime(500_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getScreenOnTime(500_000, STATS_SINCE_CHARGED));
+ assertEquals(100_000, bi.getScreenDozeTime(500_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 500_000));
+ assertEquals(100_000, bi.getDisplayScreenDozeTime(0, 500_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 500_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(1, 500_000));
+
+ clocks.realtime = clocks.uptime = 1000;
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertEquals(1000_000, bi.computeBatteryRealtime(1100_000, STATS_SINCE_CHARGED));
+ assertEquals(890_000, bi.computeBatteryScreenOffRealtime(1100_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getScreenOnTime(1100_000, STATS_SINCE_CHARGED));
+ assertEquals(600_000, bi.getScreenDozeTime(1100_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 1100_000));
+ assertEquals(600_000, bi.getDisplayScreenDozeTime(0, 1100_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 1100_000));
+ assertEquals(0, bi.getDisplayScreenDozeTime(1, 1100_000));
+
+ clocks.realtime = clocks.uptime = 1200;
+ // Change state of second display to doze
+ bi.noteScreenStateLocked(1, Display.STATE_DOZE);
+ assertEquals(1150_000, bi.computeBatteryRealtime(1250_000, STATS_SINCE_CHARGED));
+ assertEquals(1040_000, bi.computeBatteryScreenOffRealtime(1250_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getScreenOnTime(1250_000, STATS_SINCE_CHARGED));
+ assertEquals(650_000, bi.getScreenDozeTime(1250_000, STATS_SINCE_CHARGED));
+ assertEquals(110_000, bi.getDisplayScreenOnTime(0, 1250_000));
+ assertEquals(600_000, bi.getDisplayScreenDozeTime(0, 1250_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 1250_000));
+ assertEquals(50_000, bi.getDisplayScreenDozeTime(1, 1250_000));
+
+ clocks.realtime = clocks.uptime = 1310;
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ assertEquals(1250_000, bi.computeBatteryRealtime(1350_000, STATS_SINCE_CHARGED));
+ assertEquals(1100_000, bi.computeBatteryScreenOffRealtime(1350_000, STATS_SINCE_CHARGED));
+ assertEquals(150_000, bi.getScreenOnTime(1350_000, STATS_SINCE_CHARGED));
+ assertEquals(710_000, bi.getScreenDozeTime(1350_000, STATS_SINCE_CHARGED));
+ assertEquals(150_000, bi.getDisplayScreenOnTime(0, 1350_000));
+ assertEquals(600_000, bi.getDisplayScreenDozeTime(0, 1350_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 1350_000));
+ assertEquals(150_000, bi.getDisplayScreenDozeTime(1, 1350_000));
+
+ clocks.realtime = clocks.uptime = 1400;
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ assertEquals(1400_000, bi.computeBatteryRealtime(1500_000, STATS_SINCE_CHARGED));
+ assertEquals(1200_000, bi.computeBatteryScreenOffRealtime(1500_000, STATS_SINCE_CHARGED));
+ assertEquals(200_000, bi.getScreenOnTime(1500_000, STATS_SINCE_CHARGED));
+ assertEquals(810_000, bi.getScreenDozeTime(1500_000, STATS_SINCE_CHARGED));
+ assertEquals(200_000, bi.getDisplayScreenOnTime(0, 1500_000));
+ assertEquals(700_000, bi.getDisplayScreenDozeTime(0, 1500_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 1500_000));
+ assertEquals(300_000, bi.getDisplayScreenDozeTime(1, 1500_000));
+
+ clocks.realtime = clocks.uptime = 2000;
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertEquals(2000_000, bi.computeBatteryRealtime(2100_000, STATS_SINCE_CHARGED));
+ assertEquals(1800_000, bi.computeBatteryScreenOffRealtime(2100_000, STATS_SINCE_CHARGED));
+ assertEquals(200_000, bi.getScreenOnTime(2100_000, STATS_SINCE_CHARGED));
+ assertEquals(1410_000, bi.getScreenDozeTime(2100_000, STATS_SINCE_CHARGED));
+ assertEquals(200_000, bi.getDisplayScreenOnTime(0, 2100_000));
+ assertEquals(1200_000, bi.getDisplayScreenDozeTime(0, 2100_000));
+ assertEquals(0, bi.getDisplayScreenOnTime(1, 2100_000));
+ assertEquals(900_000, bi.getDisplayScreenDozeTime(1, 2100_000));
+
+
+ clocks.realtime = clocks.uptime = 2200;
+ // Change state of second display to on
+ bi.noteScreenStateLocked(1, Display.STATE_ON);
+ assertEquals(2150_000, bi.computeBatteryRealtime(2250_000, STATS_SINCE_CHARGED));
+ assertEquals(1900_000, bi.computeBatteryScreenOffRealtime(2250_000, STATS_SINCE_CHARGED));
+ assertEquals(250_000, bi.getScreenOnTime(2250_000, STATS_SINCE_CHARGED));
+ assertEquals(1510_000, bi.getScreenDozeTime(2250_000, STATS_SINCE_CHARGED));
+ assertEquals(200_000, bi.getDisplayScreenOnTime(0, 2250_000));
+ assertEquals(1200_000, bi.getDisplayScreenDozeTime(0, 2250_000));
+ assertEquals(50_000, bi.getDisplayScreenOnTime(1, 2250_000));
+ assertEquals(1000_000, bi.getDisplayScreenDozeTime(1, 2250_000));
+
+ clocks.realtime = clocks.uptime = 2310;
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ assertEquals(2250_000, bi.computeBatteryRealtime(2350_000, STATS_SINCE_CHARGED));
+ assertEquals(1900_000, bi.computeBatteryScreenOffRealtime(2350_000, STATS_SINCE_CHARGED));
+ assertEquals(350_000, bi.getScreenOnTime(2350_000, STATS_SINCE_CHARGED));
+ assertEquals(1510_000, bi.getScreenDozeTime(2350_000, STATS_SINCE_CHARGED));
+ assertEquals(240_000, bi.getDisplayScreenOnTime(0, 2350_000));
+ assertEquals(1200_000, bi.getDisplayScreenDozeTime(0, 2350_000));
+ assertEquals(150_000, bi.getDisplayScreenOnTime(1, 2350_000));
+ assertEquals(1000_000, bi.getDisplayScreenDozeTime(1, 2350_000));
+
+ clocks.realtime = clocks.uptime = 2400;
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ assertEquals(2400_000, bi.computeBatteryRealtime(2500_000, STATS_SINCE_CHARGED));
+ assertEquals(1900_000, bi.computeBatteryScreenOffRealtime(2500_000, STATS_SINCE_CHARGED));
+ assertEquals(500_000, bi.getScreenOnTime(2500_000, STATS_SINCE_CHARGED));
+ assertEquals(1510_000, bi.getScreenDozeTime(2500_000, STATS_SINCE_CHARGED));
+ assertEquals(290_000, bi.getDisplayScreenOnTime(0, 2500_000));
+ assertEquals(1300_000, bi.getDisplayScreenDozeTime(0, 2500_000));
+ assertEquals(300_000, bi.getDisplayScreenOnTime(1, 2500_000));
+ assertEquals(1000_000, bi.getDisplayScreenDozeTime(1, 2500_000));
+
+ clocks.realtime = clocks.uptime = 3000;
+ bi.noteScreenStateLocked(0, Display.STATE_OFF);
+ assertEquals(3000_000, bi.computeBatteryRealtime(3100_000, STATS_SINCE_CHARGED));
+ assertEquals(1900_000, bi.computeBatteryScreenOffRealtime(3100_000, STATS_SINCE_CHARGED));
+ assertEquals(1100_000, bi.getScreenOnTime(3100_000, STATS_SINCE_CHARGED));
+ assertEquals(1510_000, bi.getScreenDozeTime(3100_000, STATS_SINCE_CHARGED));
+ assertEquals(290_000, bi.getDisplayScreenOnTime(0, 3100_000));
+ assertEquals(1800_000, bi.getDisplayScreenDozeTime(0, 3100_000));
+ assertEquals(900_000, bi.getDisplayScreenOnTime(1, 3100_000));
+ assertEquals(1000_000, bi.getDisplayScreenDozeTime(1, 3100_000));
+ }
+
+
+ /**
+ * Test BatteryStatsImpl.noteScreenBrightnessLocked updates timers correctly.
+ */
+ @SmallTest
+ public void testScreenBrightnessLocked_multiDisplay() throws Exception {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+ final int numDisplay = 2;
+ bi.setDisplayCountLocked(numDisplay);
+
+
+ final long[] overallExpected = new long[NUM_SCREEN_BRIGHTNESS_BINS];
+ final long[][] perDisplayExpected = new long[numDisplay][NUM_SCREEN_BRIGHTNESS_BINS];
+ class Bookkeeper {
+ public long currentTimeMs = 100;
+ public int overallActiveBin = -1;
+ public int[] perDisplayActiveBin = new int[numDisplay];
+ }
+ final Bookkeeper bk = new Bookkeeper();
+ Arrays.fill(bk.perDisplayActiveBin, -1);
+
+ IntConsumer incrementTime = inc -> {
+ bk.currentTimeMs += inc;
+ if (bk.overallActiveBin >= 0) {
+ overallExpected[bk.overallActiveBin] += inc;
+ }
+ for (int i = 0; i < numDisplay; i++) {
+ final int bin = bk.perDisplayActiveBin[i];
+ if (bin >= 0) {
+ perDisplayExpected[i][bin] += inc;
+ }
+ }
+ clocks.realtime = clocks.uptime = bk.currentTimeMs;
+ };
+
+ bi.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ bi.noteScreenStateLocked(1, Display.STATE_ON);
+
+ incrementTime.accept(100);
+ bi.noteScreenBrightnessLocked(0, 25);
+ bi.noteScreenBrightnessLocked(1, 25);
+ // floor(25/256*5) = bin 0
+ bk.overallActiveBin = 0;
+ bk.perDisplayActiveBin[0] = 0;
+ bk.perDisplayActiveBin[1] = 0;
+
+ incrementTime.accept(50);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(13);
+ bi.noteScreenBrightnessLocked(0, 100);
+ // floor(25/256*5) = bin 1
+ bk.overallActiveBin = 1;
+ bk.perDisplayActiveBin[0] = 1;
+
+ incrementTime.accept(44);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(22);
+ bi.noteScreenBrightnessLocked(1, 200);
+ // floor(200/256*5) = bin 3
+ bk.overallActiveBin = 3;
+ bk.perDisplayActiveBin[1] = 3;
+
+ incrementTime.accept(33);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(77);
+ bi.noteScreenBrightnessLocked(0, 150);
+ // floor(150/256*5) = bin 2
+ // Overall active bin should not change
+ bk.perDisplayActiveBin[0] = 2;
+
+ incrementTime.accept(88);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(11);
+ bi.noteScreenStateLocked(1, Display.STATE_OFF);
+ // Display 1 should timers should stop incrementing
+ // Overall active bin should fallback to display 0's bin
+ bk.overallActiveBin = 2;
+ bk.perDisplayActiveBin[1] = -1;
+
+ incrementTime.accept(99);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(200);
+ bi.noteScreenBrightnessLocked(0, 255);
+ // floor(150/256*5) = bin 4
+ bk.overallActiveBin = 4;
+ bk.perDisplayActiveBin[0] = 4;
+
+ incrementTime.accept(300);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(200);
+ bi.noteScreenStateLocked(0, Display.STATE_DOZE);
+ // No displays are on. No brightness timers should be active.
+ bk.overallActiveBin = -1;
+ bk.perDisplayActiveBin[0] = -1;
+
+ incrementTime.accept(300);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(400);
+ bi.noteScreenStateLocked(1, Display.STATE_ON);
+ // Display 1 turned back on.
+ bk.overallActiveBin = 3;
+ bk.perDisplayActiveBin[1] = 3;
+
+ incrementTime.accept(500);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
+
+ incrementTime.accept(600);
+ bi.noteScreenStateLocked(0, Display.STATE_ON);
+ // Display 0 turned back on.
+ bk.overallActiveBin = 4;
+ bk.perDisplayActiveBin[0] = 4;
+
+ incrementTime.accept(700);
+ checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs);
}
@SmallTest
@@ -595,7 +998,7 @@ public class BatteryStatsNoteTest extends TestCase {
bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
clocks.realtime = 0;
- int screen = Display.STATE_OFF;
+ int[] screen = new int[]{Display.STATE_OFF};
boolean battery = false;
final int uid1 = 10500;
@@ -605,35 +1008,35 @@ public class BatteryStatsNoteTest extends TestCase {
long globalDoze = 0;
// Case A: uid1 off, uid2 off, battery off, screen off
- bi.updateTimeBasesLocked(battery, screen, clocks.realtime*1000, 0);
+ bi.updateTimeBasesLocked(battery, screen[0], clocks.realtime * 1000, 0);
bi.setOnBatteryInternal(battery);
- bi.updateDisplayMeasuredEnergyStatsLocked(500_000, screen, clocks.realtime);
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{500_000}, screen, clocks.realtime);
checkMeasuredCharge("A", uid1, blame1, uid2, blame2, globalDoze, bi);
// Case B: uid1 off, uid2 off, battery ON, screen off
clocks.realtime += 17;
battery = true;
- bi.updateTimeBasesLocked(battery, screen, clocks.realtime*1000, 0);
+ bi.updateTimeBasesLocked(battery, screen[0], clocks.realtime * 1000, 0);
bi.setOnBatteryInternal(battery);
clocks.realtime += 19;
- bi.updateDisplayMeasuredEnergyStatsLocked(510_000, screen, clocks.realtime);
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{510_000}, screen, clocks.realtime);
checkMeasuredCharge("B", uid1, blame1, uid2, blame2, globalDoze, bi);
// Case C: uid1 ON, uid2 off, battery on, screen off
clocks.realtime += 18;
setFgState(uid1, true, bi);
clocks.realtime += 18;
- bi.updateDisplayMeasuredEnergyStatsLocked(520_000, screen, clocks.realtime);
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{520_000}, screen, clocks.realtime);
checkMeasuredCharge("C", uid1, blame1, uid2, blame2, globalDoze, bi);
// Case D: uid1 on, uid2 off, battery on, screen ON
clocks.realtime += 17;
- screen = Display.STATE_ON;
- bi.updateDisplayMeasuredEnergyStatsLocked(521_000, screen, clocks.realtime);
+ screen[0] = Display.STATE_ON;
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{521_000}, screen, clocks.realtime);
blame1 += 0; // Screen had been off during the measurement period
checkMeasuredCharge("D.1", uid1, blame1, uid2, blame2, globalDoze, bi);
clocks.realtime += 101;
- bi.updateDisplayMeasuredEnergyStatsLocked(530_000, screen, clocks.realtime);
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{530_000}, screen, clocks.realtime);
blame1 += 530_000;
checkMeasuredCharge("D.2", uid1, blame1, uid2, blame2, globalDoze, bi);
@@ -641,33 +1044,33 @@ public class BatteryStatsNoteTest extends TestCase {
clocks.realtime += 20;
setFgState(uid2, true, bi);
clocks.realtime += 40;
- bi.updateDisplayMeasuredEnergyStatsLocked(540_000, screen, clocks.realtime);
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{540_000}, screen, clocks.realtime);
// In the past 60ms, sum of fg is 20+40+40=100ms. uid1 is blamed for 60/100; uid2 for 40/100
blame1 += 540_000 * (20 + 40) / (20 + 40 + 40);
- blame2 += 540_000 * ( 0 + 40) / (20 + 40 + 40);
+ blame2 += 540_000 * (0 + 40) / (20 + 40 + 40);
checkMeasuredCharge("E", uid1, blame1, uid2, blame2, globalDoze, bi);
// Case F: uid1 on, uid2 OFF, battery on, screen on
clocks.realtime += 40;
setFgState(uid2, false, bi);
clocks.realtime += 120;
- bi.updateDisplayMeasuredEnergyStatsLocked(550_000, screen, clocks.realtime);
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{550_000}, screen, clocks.realtime);
// In the past 160ms, sum f fg is 200ms. uid1 is blamed for 40+120 of it; uid2 for 40 of it.
blame1 += 550_000 * (40 + 120) / (40 + 40 + 120);
- blame2 += 550_000 * (40 + 0 ) / (40 + 40 + 120);
+ blame2 += 550_000 * (40 + 0) / (40 + 40 + 120);
checkMeasuredCharge("F", uid1, blame1, uid2, blame2, globalDoze, bi);
// Case G: uid1 on, uid2 off, battery on, screen DOZE
clocks.realtime += 5;
- screen = Display.STATE_DOZE;
- bi.updateDisplayMeasuredEnergyStatsLocked(570_000, screen, clocks.realtime);
+ screen[0] = Display.STATE_DOZE;
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{570_000}, screen, clocks.realtime);
blame1 += 570_000; // All of this pre-doze time is blamed on uid1.
checkMeasuredCharge("G", uid1, blame1, uid2, blame2, globalDoze, bi);
// Case H: uid1 on, uid2 off, battery on, screen ON
clocks.realtime += 6;
- screen = Display.STATE_ON;
- bi.updateDisplayMeasuredEnergyStatsLocked(580_000, screen, clocks.realtime);
+ screen[0] = Display.STATE_ON;
+ bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{580_000}, screen, clocks.realtime);
blame1 += 0; // The screen had been doze during the energy period
globalDoze += 580_000;
checkMeasuredCharge("H", uid1, blame1, uid2, blame2, globalDoze, bi);
@@ -822,4 +1225,19 @@ public class BatteryStatsNoteTest extends TestCase {
assertEquals("Wrong uid2 blame in bucket 1 for Case " + caseName, blame2B, actualUid2[1]);
}
+
+ private void checkScreenBrightnesses(long[] overallExpected, long[][] perDisplayExpected,
+ BatteryStatsImpl bi, long currentTimeMs) {
+ final int numDisplay = bi.getDisplayCount();
+ for (int bin = 0; bin < NUM_SCREEN_BRIGHTNESS_BINS; bin++) {
+ for (int display = 0; display < numDisplay; display++) {
+ assertEquals("Failure for display " + display + " screen brightness bin " + bin,
+ perDisplayExpected[display][bin] * 1000,
+ bi.getDisplayScreenBrightnessTime(display, bin, currentTimeMs * 1000));
+ }
+ assertEquals("Failure for overall screen brightness bin " + bin,
+ overallExpected[bin] * 1000,
+ bi.getScreenBrightnessTime(bin, currentTimeMs * 1000, STATS_SINCE_CHARGED));
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
index 083090c54619..ac87806b1639 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -110,6 +110,20 @@ public class BatteryUsageStatsRule implements TestRule {
return this;
}
+ public BatteryUsageStatsRule setAveragePowerForOrdinal(String group, int ordinal,
+ double value) {
+ when(mPowerProfile.getAveragePowerForOrdinal(group, ordinal)).thenReturn(value);
+ when(mPowerProfile.getAveragePowerForOrdinal(eq(group), eq(ordinal),
+ anyDouble())).thenReturn(value);
+ return this;
+ }
+
+ public BatteryUsageStatsRule setNumDisplays(int value) {
+ when(mPowerProfile.getNumDisplays()).thenReturn(value);
+ mBatteryStats.setDisplayCountLocked(value);
+ return this;
+ }
+
/** Call only after setting the power profile information. */
public BatteryUsageStatsRule initMeasuredEnergyStatsLocked() {
return initMeasuredEnergyStatsLocked(new String[0]);
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index cee1a0352a7e..cfecf15b55ef 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -39,13 +39,14 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
public BatteryStatsImpl.Clocks clocks;
public boolean mForceOnBattery;
private NetworkStats mNetworkStats;
+ private DummyExternalStatsSync mExternalStatsSync = new DummyExternalStatsSync();
MockBatteryStatsImpl(Clocks clocks) {
super(clocks);
this.clocks = mClocks;
initTimersAndCounters();
- setExternalStatsSyncLocked(new DummyExternalStatsSync());
+ setExternalStatsSyncLocked(mExternalStatsSync);
informThatAllExternalStatsAreFlushed();
// A no-op handler.
@@ -182,7 +183,15 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
return mPendingUids;
}
+ public int getAndClearExternalStatsSyncFlags() {
+ final int flags = mExternalStatsSync.flags;
+ mExternalStatsSync.flags = 0;
+ return flags;
+ }
+
private class DummyExternalStatsSync implements ExternalStatsSync {
+ public int flags = 0;
+
@Override
public Future<?> scheduleSync(String reason, int flags) {
return null;
@@ -211,8 +220,9 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
}
@Override
- public Future<?> scheduleSyncDueToScreenStateChange(
- int flag, boolean onBattery, boolean onBatteryScreenOff, int screenState) {
+ public Future<?> scheduleSyncDueToScreenStateChange(int flag, boolean onBattery,
+ boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
+ flags |= flag;
return null;
}
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
index 5862368f44d2..88ee405483db 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
@@ -17,6 +17,10 @@
package com.android.internal.os;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON;
+
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -53,7 +57,12 @@ public class PowerProfileTest extends TestCase {
assertEquals(4, mProfile.getNumSpeedStepsInCpuCluster(1));
assertEquals(60.0, mProfile.getAveragePowerForCpuCore(1, 3));
assertEquals(3000.0, mProfile.getBatteryCapacity());
- assertEquals(0.5, mProfile.getAveragePower(PowerProfile.POWER_AMBIENT_DISPLAY));
+ assertEquals(0.5,
+ mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 0));
+ assertEquals(100.0,
+ mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 0));
+ assertEquals(800.0,
+ mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0));
assertEquals(100.0, mProfile.getAveragePower(PowerProfile.POWER_AUDIO));
assertEquals(150.0, mProfile.getAveragePower(PowerProfile.POWER_VIDEO));
}
diff --git a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
index c695fc9eb87d..eee5d57c7bc6 100644
--- a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
@@ -16,6 +16,9 @@
package com.android.internal.os;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON;
+
import static com.google.common.truth.Truth.assertThat;
import android.app.ActivityManager;
@@ -39,24 +42,27 @@ public class ScreenPowerCalculatorTest {
private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 43;
private static final long MINUTE_IN_MS = 60 * 1000;
private static final long MINUTE_IN_US = 60 * 1000 * 1000;
+ private static final long HOUR_IN_MS = 60 * MINUTE_IN_MS;
@Rule
public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
- .setAveragePower(PowerProfile.POWER_SCREEN_ON, 36.0)
- .setAveragePower(PowerProfile.POWER_SCREEN_FULL, 48.0);
+ .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 0, 36.0)
+ .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0, 48.0)
+ .setNumDisplays(1);
@Test
public void testMeasuredEnergyBasedModel() {
mStatsRule.initMeasuredEnergyStatsLocked();
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
- batteryStats.noteScreenStateLocked(Display.STATE_ON, 0, 0, 0);
- batteryStats.updateDisplayMeasuredEnergyStatsLocked(0, Display.STATE_ON, 0);
+ batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0);
+ batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{0},
+ new int[]{Display.STATE_ON}, 0);
setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true,
0, 0);
- batteryStats.updateDisplayMeasuredEnergyStatsLocked(200_000_000, Display.STATE_ON,
- 15 * MINUTE_IN_MS);
+ batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{200_000_000},
+ new int[]{Display.STATE_ON}, 15 * MINUTE_IN_MS);
setProcState(APP_UID1, ActivityManager.PROCESS_STATE_CACHED_EMPTY, false,
20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
@@ -64,16 +70,16 @@ public class ScreenPowerCalculatorTest {
setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true,
20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
- batteryStats.updateDisplayMeasuredEnergyStatsLocked(300_000_000, Display.STATE_ON,
- 60 * MINUTE_IN_MS);
+ batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{300_000_000},
+ new int[]{Display.STATE_ON}, 60 * MINUTE_IN_MS);
- batteryStats.noteScreenStateLocked(Display.STATE_OFF,
+ batteryStats.noteScreenStateLocked(0, Display.STATE_OFF,
80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false,
80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
- batteryStats.updateDisplayMeasuredEnergyStatsLocked(100_000_000, Display.STATE_DOZE,
- 120 * MINUTE_IN_MS);
+ batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{100_000_000},
+ new int[]{Display.STATE_DOZE}, 120 * MINUTE_IN_MS);
mStatsRule.setTime(120 * MINUTE_IN_US, 120 * MINUTE_IN_US);
@@ -126,24 +132,122 @@ public class ScreenPowerCalculatorTest {
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
}
+
+ @Test
+ public void testMeasuredEnergyBasedModel_multiDisplay() {
+ mStatsRule.initMeasuredEnergyStatsLocked()
+ .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 1, 60.0)
+ .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 1, 100.0)
+ .setNumDisplays(2);
+
+ BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+ final int[] screenStates = new int[]{Display.STATE_ON, Display.STATE_OFF};
+
+ batteryStats.noteScreenStateLocked(0, screenStates[0], 0, 0, 0);
+ batteryStats.noteScreenStateLocked(1, screenStates[1], 0, 0, 0);
+ batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0);
+ setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true, 0, 0);
+ batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{300, 400}, screenStates, 0);
+
+ batteryStats.noteScreenBrightnessLocked(0, 100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(0, 200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+
+ setProcState(APP_UID1, ActivityManager.PROCESS_STATE_CACHED_EMPTY, false,
+ 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
+ setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true,
+ 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
+
+ screenStates[0] = Display.STATE_OFF;
+ screenStates[1] = Display.STATE_ON;
+ batteryStats.noteScreenStateLocked(0, screenStates[0],
+ 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+ batteryStats.noteScreenStateLocked(1, screenStates[1], 80 * MINUTE_IN_MS,
+ 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+ batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{600_000_000, 500},
+ screenStates, 80 * MINUTE_IN_MS);
+
+ batteryStats.noteScreenBrightnessLocked(1, 25, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(1, 250, 86 * MINUTE_IN_MS, 86 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(1, 75, 98 * MINUTE_IN_MS, 98 * MINUTE_IN_MS);
+
+ screenStates[1] = Display.STATE_OFF;
+ batteryStats.noteScreenStateLocked(1, screenStates[1], 110 * MINUTE_IN_MS,
+ 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
+ batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{700, 800_000_000},
+ screenStates, 110 * MINUTE_IN_MS);
+
+ setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false,
+ 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
+
+ mStatsRule.setTime(120 * MINUTE_IN_US, 120 * MINUTE_IN_US);
+
+ ScreenPowerCalculator calculator =
+ new ScreenPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(calculator);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(110 * MINUTE_IN_MS);
+ // (600000000 + 800000000) uAs * (1 mA / 1000 uA) * (1 h / 3600 s) = 166.66666 mAh
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(388.88888);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
+ assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(20 * MINUTE_IN_MS);
+
+ // Uid1 ran for 20 out of 80 min during the first Display update.
+ // It also ran for 5 out of 45 min during the second Display update:
+ // Uid1 charge = 20 / 80 * 600000000 mAs = 41.66666 mAh
+ assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(41.66666);
+ assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+ assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(90 * MINUTE_IN_MS);
+
+ // Uid2 ran for 60 out of 80 min during the first Display update.
+ // It also ran for all of the second Display update:
+ // Uid1 charge = 60 / 80 * 600000000 + 800000000 mAs = 347.22222 mAh
+ assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(347.22222);
+ assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(110 * MINUTE_IN_MS);
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(388.88888);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ }
+
@Test
public void testPowerProfileBasedModel() {
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
- batteryStats.noteScreenStateLocked(Display.STATE_ON, 0, 0, 0);
- batteryStats.noteScreenBrightnessLocked(255, 0, 0);
+ batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0);
+ batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0);
setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true,
0, 0);
- batteryStats.noteScreenBrightnessLocked(100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS);
- batteryStats.noteScreenBrightnessLocked(200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(0, 100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(0, 200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
setProcState(APP_UID1, ActivityManager.PROCESS_STATE_CACHED_EMPTY, false,
20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true,
20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
- batteryStats.noteScreenStateLocked(Display.STATE_OFF,
+ batteryStats.noteScreenStateLocked(0, Display.STATE_OFF,
80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false,
80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
@@ -194,6 +298,95 @@ public class ScreenPowerCalculatorTest {
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
+
+ @Test
+ public void testPowerProfileBasedModel_multiDisplay() {
+ mStatsRule.setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 1, 60.0)
+ .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 1, 100.0)
+ .setNumDisplays(2);
+
+ BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+ batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0);
+ batteryStats.noteScreenStateLocked(1, Display.STATE_OFF, 0, 0, 0);
+ batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0);
+ setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true,
+ 0, 0);
+
+ batteryStats.noteScreenBrightnessLocked(0, 100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(0, 200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+
+ setProcState(APP_UID1, ActivityManager.PROCESS_STATE_CACHED_EMPTY, false,
+ 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
+ setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true,
+ 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
+
+ batteryStats.noteScreenStateLocked(0, Display.STATE_OFF,
+ 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+ batteryStats.noteScreenStateLocked(1, Display.STATE_ON, 80 * MINUTE_IN_MS,
+ 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(1, 20, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+
+ batteryStats.noteScreenBrightnessLocked(1, 250, 86 * MINUTE_IN_MS, 86 * MINUTE_IN_MS);
+ batteryStats.noteScreenBrightnessLocked(1, 75, 98 * MINUTE_IN_MS, 98 * MINUTE_IN_MS);
+ batteryStats.noteScreenStateLocked(1, Display.STATE_OFF, 110 * MINUTE_IN_MS,
+ 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
+
+ setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false,
+ 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
+
+ mStatsRule.setTime(120 * MINUTE_IN_US, 120 * MINUTE_IN_US);
+ ScreenPowerCalculator calculator =
+ new ScreenPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(110 * MINUTE_IN_MS);
+ // First display consumed 92 mAh.
+ // Second display ran for 0.5 hours at a base drain rate of 60 mA.
+ // 6 minutes (0.1 hours) spent in the first brightness level which drains an extra 10 mA.
+ // 12 minutes (0.2 hours) spent in the fifth brightness level which drains an extra 90 mA.
+ // 12 minutes (0.2 hours) spent in the second brightness level which drains an extra 30 mA.
+ // 92 + 60 * 0.5 + 10 * 0.1 + 90 * 0.2 + 30 * 0.2 = 147
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(147);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
+ assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(20 * MINUTE_IN_MS);
+
+ // Uid1 took 20 out of the total of 110 min of foreground activity
+ // Uid1 charge = 20 / 110 * 147.0 = 23.0 mAh
+ assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(26.72727);
+ assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+ assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(90 * MINUTE_IN_MS);
+
+ // Uid2 took 90 out of the total of 110 min of foreground activity
+ // Uid2 charge = 90 / 110 * 92.0 = 69.0 mAh
+ assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(120.272727);
+ assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(110 * MINUTE_IN_MS);
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(147);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ }
+
private void setProcState(int uid, int procState, boolean resumed, long realtimeMs,
long uptimeMs) {
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
index 272f2287dd6e..0f05be06bff6 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
@@ -24,6 +24,7 @@ import android.os.Binder;
import android.os.Parcel;
import android.os.UserHandle;
import android.util.ArrayMap;
+import android.view.InsetsVisibilities;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -59,7 +60,8 @@ public class RegisterStatusBarResultTest {
new Binder() /* imeToken */,
true /* navbarColorManagedByIme */,
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
- true /* appFullscreen */,
+ new InsetsVisibilities() /* requestedVisibilities */,
+ "test" /* packageName */,
new int[0] /* transientBarTypes */);
final RegisterStatusBarResult copy = clone(original);
@@ -79,7 +81,8 @@ public class RegisterStatusBarResultTest {
assertThat(copy.mImeToken).isSameInstanceAs(original.mImeToken);
assertThat(copy.mNavbarColorManagedByIme).isEqualTo(original.mNavbarColorManagedByIme);
assertThat(copy.mBehavior).isEqualTo(original.mBehavior);
- assertThat(copy.mAppFullscreen).isEqualTo(original.mAppFullscreen);
+ assertThat(copy.mRequestedVisibilities).isEqualTo(original.mRequestedVisibilities);
+ assertThat(copy.mPackageName).isEqualTo(original.mPackageName);
assertThat(copy.mTransientBarTypes).isEqualTo(original.mTransientBarTypes);
}
diff --git a/core/tests/mockingcoretests/src/android/window/ConfigurationHelperTest.java b/core/tests/mockingcoretests/src/android/window/ConfigurationHelperTest.java
new file mode 100644
index 000000000000..996d7b435e5a
--- /dev/null
+++ b/core/tests/mockingcoretests/src/android/window/ConfigurationHelperTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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 android.window;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+
+import android.app.ResourcesManager;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+/**
+ * Tests for {@link ConfigurationHelper}
+ *
+ * <p>Build/Install/Run:
+ * atest FrameworksMockingCoreTests:ConfigurationHelperTest
+ *
+ * <p>This test class is a part of Window Manager Service tests and specified in
+ * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class ConfigurationHelperTest {
+ MockitoSession mMockitoSession;
+ ResourcesManager mResourcesManager;
+
+ @Before
+ public void setUp() {
+ mMockitoSession = mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(ResourcesManager.class)
+ .startMocking();
+ doReturn(mock(ResourcesManager.class)).when(ResourcesManager::getInstance);
+ mResourcesManager = ResourcesManager.getInstance();
+ }
+
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
+
+ @Test
+ public void testShouldUpdateResources_NullConfig_ReturnsTrue() {
+ assertThat(ConfigurationHelper.shouldUpdateResources(new Binder(), null /* config */,
+ new Configuration(), new Configuration(), false /* displayChanged */,
+ null /* configChanged */)).isTrue();
+ }
+
+ @Test
+ public void testShouldUpdateResources_DisplayChanged_ReturnsTrue() {
+ assertThat(ConfigurationHelper.shouldUpdateResources(new Binder(), new Configuration(),
+ new Configuration(), new Configuration(), true /* displayChanged */,
+ null /* configChanged */)).isTrue();
+ }
+
+ @Test
+ public void testShouldUpdateResources_DifferentResources_ReturnsTrue() {
+ doReturn(false).when(mResourcesManager).isSameResourcesOverrideConfig(any(), any());
+
+ assertThat(ConfigurationHelper.shouldUpdateResources(new Binder(), new Configuration(),
+ new Configuration(), new Configuration(), false /* displayChanged */,
+ null /* configChanged */)).isTrue();
+ }
+
+ @Test
+ public void testShouldUpdateResources_DifferentBounds_ReturnsTrue() {
+ doReturn(true).when(mResourcesManager).isSameResourcesOverrideConfig(any(), any());
+
+ final Configuration config = new Configuration();
+ config.windowConfiguration.setBounds(new Rect(0, 0, 10, 10));
+ config.windowConfiguration.setMaxBounds(new Rect(0, 0, 20, 20));
+
+ final Configuration newConfig = new Configuration();
+ newConfig.windowConfiguration.setBounds(new Rect(0, 0, 20, 20));
+ newConfig.windowConfiguration.setMaxBounds(new Rect(0, 0, 20, 20));
+
+ assertThat(ConfigurationHelper.shouldUpdateResources(new Binder(), config, newConfig,
+ new Configuration(), false /* displayChanged */, null /* configChanged */))
+ .isTrue();
+ }
+
+ @Test
+ public void testShouldUpdateResources_SameConfig_ReturnsFalse() {
+ doReturn(true).when(mResourcesManager).isSameResourcesOverrideConfig(any(), any());
+
+ final Configuration config = new Configuration();
+ final Configuration newConfig = new Configuration();
+
+ assertThat(ConfigurationHelper.shouldUpdateResources(new Binder(), config, newConfig,
+ new Configuration(), false /* displayChanged */, null /* configChanged */))
+ .isFalse();
+ }
+
+ @Test
+ public void testShouldUpdateResources_DifferentConfig_ReturnsTrue() {
+ doReturn(true).when(mResourcesManager).isSameResourcesOverrideConfig(any(), any());
+
+ final Configuration config = new Configuration();
+ final Configuration newConfig = new Configuration();
+ newConfig.setToDefaults();
+
+ assertThat(ConfigurationHelper.shouldUpdateResources(new Binder(), config, newConfig,
+ new Configuration(), false /* displayChanged */, null /* configChanged */))
+ .isTrue();
+ }
+
+ @Test
+ public void testShouldUpdateResources_DifferentNonPublicConfig_ReturnsTrue() {
+ doReturn(true).when(mResourcesManager).isSameResourcesOverrideConfig(any(), any());
+
+ final Configuration config = new Configuration();
+ final Configuration newConfig = new Configuration();
+ newConfig.windowConfiguration.setAppBounds(new Rect(0, 0, 10, 10));
+
+ assertThat(ConfigurationHelper.shouldUpdateResources(new Binder(), config, newConfig,
+ new Configuration(), false /* displayChanged */, null /* configChanged */))
+ .isTrue();
+ }
+
+ @Test
+ public void testShouldUpdateResources_OverrideConfigChanged_ReturnsFalse() {
+ doReturn(true).when(mResourcesManager).isSameResourcesOverrideConfig(any(), any());
+
+ final Configuration config = new Configuration();
+ final Configuration newConfig = new Configuration();
+ final boolean configChanged = true;
+
+ assertThat(ConfigurationHelper.shouldUpdateResources(new Binder(), config, newConfig,
+ new Configuration(), false /* displayChanged */, configChanged))
+ .isEqualTo(configChanged);
+ }
+}
diff --git a/core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java b/core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java
new file mode 100644
index 000000000000..fa4aa803c75e
--- /dev/null
+++ b/core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java
@@ -0,0 +1,379 @@
+/*
+ * 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 android.window;
+
+import static android.content.pm.ActivityInfo.CONFIG_LOCALE;
+import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
+import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
+import static android.content.res.Configuration.SCREENLAYOUT_COMPAT_NEEDED;
+import static android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_LTR;
+import static android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_RTL;
+import static android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_UNDEFINED;
+import static android.content.res.Configuration.SCREENLAYOUT_LONG_NO;
+import static android.content.res.Configuration.SCREENLAYOUT_LONG_YES;
+import static android.content.res.Configuration.SCREENLAYOUT_ROUND_NO;
+import static android.content.res.Configuration.SCREENLAYOUT_ROUND_UNDEFINED;
+import static android.content.res.Configuration.SCREENLAYOUT_ROUND_YES;
+import static android.content.res.Configuration.SCREENLAYOUT_SIZE_LARGE;
+import static android.content.res.Configuration.SCREENLAYOUT_SIZE_NORMAL;
+import static android.content.res.Configuration.SCREENLAYOUT_SIZE_SMALL;
+import static android.content.res.Configuration.SCREENLAYOUT_SIZE_XLARGE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.res.Configuration;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link SizeConfigurationBuckets}
+ *
+ * Build/Install/Run:
+ * atest FrameworksMockingCoreTests:SizeConfigurationBucketsTest
+ */
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class SizeConfigurationBucketsTest {
+
+ /**
+ * Tests that a change in any of the non-size-related screen layout fields results in
+ * {@link SizeConfigurationBuckets#areNonSizeLayoutFieldsUnchanged} returning false.
+ */
+ @Test
+ public void testNonSizeRelatedScreenLayoutFields() {
+ // Test layout direction
+ assertEquals(true, SizeConfigurationBuckets
+ .areNonSizeLayoutFieldsUnchanged(0, SCREENLAYOUT_LAYOUTDIR_UNDEFINED));
+ assertEquals(false, SizeConfigurationBuckets
+ .areNonSizeLayoutFieldsUnchanged(0, SCREENLAYOUT_LAYOUTDIR_LTR));
+ assertEquals(false, SizeConfigurationBuckets
+ .areNonSizeLayoutFieldsUnchanged(0, SCREENLAYOUT_LAYOUTDIR_RTL));
+
+ // Test layout roundness
+ assertEquals(true, SizeConfigurationBuckets
+ .areNonSizeLayoutFieldsUnchanged(0, SCREENLAYOUT_ROUND_UNDEFINED));
+ assertEquals(false, SizeConfigurationBuckets
+ .areNonSizeLayoutFieldsUnchanged(0, SCREENLAYOUT_ROUND_NO));
+ assertEquals(false, SizeConfigurationBuckets
+ .areNonSizeLayoutFieldsUnchanged(0, SCREENLAYOUT_ROUND_YES));
+
+ // Test layout compat needed
+ assertEquals(false, SizeConfigurationBuckets
+ .areNonSizeLayoutFieldsUnchanged(0, SCREENLAYOUT_COMPAT_NEEDED));
+ }
+
+ /**
+ * Tests that null size configuration buckets unflips the correct configuration flags.
+ */
+ @Test
+ public void testNullSizeConfigurationBuckets() {
+ // Check that all 3 size configurations are filtered out of the diff if the buckets are null
+ // and non-size attributes of screen layout are unchanged. Add a non-size related config
+ // change (i.e. CONFIG_LOCALE) to test that the diff is not set to zero.
+ final int diff = CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_SCREEN_LAYOUT
+ | CONFIG_LOCALE;
+ final int filteredDiffNonSizeLayoutUnchanged = SizeConfigurationBuckets.filterDiff(diff,
+ Configuration.EMPTY, Configuration.EMPTY, null);
+ assertEquals(CONFIG_LOCALE, filteredDiffNonSizeLayoutUnchanged);
+
+ // Check that only screen size and smallest screen size are filtered out of the diff if the
+ // buckets are null and non-size attributes of screen layout are changed.
+ final Configuration newConfig = new Configuration();
+ newConfig.screenLayout |= SCREENLAYOUT_ROUND_YES;
+ final int filteredDiffNonSizeLayoutChanged = SizeConfigurationBuckets.filterDiff(diff,
+ Configuration.EMPTY, newConfig, null);
+ assertEquals(CONFIG_SCREEN_LAYOUT | CONFIG_LOCALE, filteredDiffNonSizeLayoutChanged);
+ }
+
+ /**
+ * Tests that {@link SizeConfigurationBuckets.crossesSizeThreshold()} correctly checks whether
+ * the bucket thresholds have or have not been crossed. This test includes boundary checks
+ * to ensure that arithmetic is inclusive and exclusive in the right places.
+ */
+ @Test
+ public void testCrossesSizeThreshold() {
+ final int[] thresholds = new int[] { 360, 600 };
+ final int nThresholds = thresholds.length;
+ for (int i = -1; i < nThresholds; i++) {
+ final int minValueInBucket = i < 0 ? 0 : thresholds[i];
+ final int maxValueInBucket = i < nThresholds - 1
+ ? thresholds[i + 1] - 1 : Integer.MAX_VALUE;
+ final int bucketRange = maxValueInBucket - minValueInBucket;
+ // Set old value to 1/4 in between the two thresholds.
+ final int oldValue = (int) (minValueInBucket + bucketRange * 0.25);
+ // Test 3 values of new value spread across bucket range: minValueInBucket, bucket
+ // midpoint, and max value in bucket. In all 3 cases, the bucket has not changed so
+ // {@link SizeConfigurationBuckets#crossedSizeThreshold()} should return false.
+ checkCrossesSizeThreshold(thresholds, oldValue, minValueInBucket, false);
+ checkCrossesSizeThreshold(thresholds, oldValue,
+ (int) (minValueInBucket + bucketRange * 0.5), false);
+ checkCrossesSizeThreshold(thresholds, oldValue, maxValueInBucket, false);
+ // Test 4 values of size spread outside of bucket range: more than 1 less than min
+ // value, 1 less than min value, 1 more than max value, and more than 1 more than max
+ // value. In all 4 cases, the bucket has changed so
+ // {@link SizeConfigurationBuckets#crossedSizeThreshold()} should return true.
+ // Only test less than min value if min value > 0.
+ if (minValueInBucket > 0) {
+ checkCrossesSizeThreshold(thresholds, oldValue, minValueInBucket - 20, true);
+ checkCrossesSizeThreshold(thresholds, oldValue, minValueInBucket - 1, true);
+ }
+ // Only test greater than max value if not in highest bucket.
+ if (i < nThresholds - 1) {
+ checkCrossesSizeThreshold(thresholds, oldValue, maxValueInBucket + 1, true);
+ checkCrossesSizeThreshold(thresholds, oldValue, maxValueInBucket + 20, true);
+ }
+ }
+ }
+
+ /**
+ * Tests that if screen layout size changed but did not cross a threshold, the filtered diff
+ * does not include screen layout.
+ */
+ @Test
+ public void testScreenLayoutFilteredIfSizeDidNotCrossThreshold() {
+ // Set only small and large sizes
+ final Configuration[] sizeConfigs = new Configuration[2];
+ sizeConfigs[0] = new Configuration();
+ sizeConfigs[0].screenLayout |= SCREENLAYOUT_SIZE_SMALL;
+ sizeConfigs[1] = new Configuration();
+ sizeConfigs[1].screenLayout |= SCREENLAYOUT_SIZE_LARGE;
+ final SizeConfigurationBuckets sizeBuckets = new SizeConfigurationBuckets(sizeConfigs);
+
+ // Change screen layout size from small to normal and check that screen layout flag is
+ // not part of the diff because a threshold was not crossed.
+ final int diff = CONFIG_SCREEN_LAYOUT;
+ final Configuration oldConfig = new Configuration();
+ oldConfig.screenLayout |= SCREENLAYOUT_SIZE_SMALL;
+ final Configuration newConfig = new Configuration();
+ newConfig.screenLayout |= SCREENLAYOUT_SIZE_NORMAL;
+ final int filteredDiff = SizeConfigurationBuckets.filterDiff(diff, oldConfig, newConfig,
+ sizeBuckets);
+ assertEquals(0, filteredDiff);
+
+ // If a non-size attribute of screen layout changed, then screen layout should not be
+ // filtered from the diff.
+ newConfig.screenLayout |= SCREENLAYOUT_ROUND_YES;
+ final int filteredDiffNonSizeLayoutChanged = SizeConfigurationBuckets.filterDiff(diff,
+ oldConfig, newConfig, sizeBuckets);
+ assertEquals(CONFIG_SCREEN_LAYOUT, filteredDiffNonSizeLayoutChanged);
+ }
+
+ /**
+ * Tests that if screen layout size changed and did cross a threshold, the filtered diff
+ * includes screen layout.
+ */
+ @Test
+ public void testScreenLayoutNotFilteredIfSizeCrossedThreshold() {
+ // Set only small and normal sizes
+ final Configuration[] sizeConfigs = new Configuration[2];
+ sizeConfigs[0] = new Configuration();
+ sizeConfigs[0].screenLayout |= SCREENLAYOUT_SIZE_SMALL;
+ sizeConfigs[1] = new Configuration();
+ sizeConfigs[1].screenLayout |= SCREENLAYOUT_SIZE_NORMAL;
+ final SizeConfigurationBuckets sizeBuckets = new SizeConfigurationBuckets(sizeConfigs);
+
+ // Change screen layout size from small to normal and check that screen layout flag is
+ // still part of the diff because a threshold was crossed.
+ final int diff = CONFIG_SCREEN_LAYOUT;
+ final Configuration oldConfig = new Configuration();
+ oldConfig.screenLayout |= SCREENLAYOUT_SIZE_SMALL;
+ final Configuration newConfig = new Configuration();
+ newConfig.screenLayout |= SCREENLAYOUT_SIZE_NORMAL;
+ final int filteredDiff = SizeConfigurationBuckets.filterDiff(diff, oldConfig, newConfig,
+ sizeBuckets);
+ assertEquals(CONFIG_SCREEN_LAYOUT, filteredDiff);
+ }
+
+ /**
+ * Tests that anytime screen layout size is decreased, the filtered diff still includes screen
+ * layout.
+ */
+ @Test
+ public void testScreenLayoutNotFilteredIfSizeDecreased() {
+ // The size thresholds can be anything, but can't be null
+ final int[] horizontalThresholds = new int[] { 360, 600 };
+ final SizeConfigurationBuckets sizeBuckets = new SizeConfigurationBuckets(
+ horizontalThresholds, null /* vertical */, null /* smallest */,
+ null /* screenLayoutSize */, false /* screenLayoutLongSet */);
+ final int[] sizeValuesInOrder = new int[] {
+ SCREENLAYOUT_SIZE_SMALL, SCREENLAYOUT_SIZE_NORMAL, SCREENLAYOUT_SIZE_LARGE,
+ SCREENLAYOUT_SIZE_XLARGE
+ };
+ final int nSizes = sizeValuesInOrder.length;
+ for (int larger = nSizes - 1; larger > 0; larger--) {
+ for (int smaller = larger - 1; smaller >= 0; smaller--) {
+ final Configuration oldConfig = new Configuration();
+ oldConfig.screenLayout |= sizeValuesInOrder[larger];
+ final Configuration newConfig = new Configuration();
+ newConfig.screenLayout |= sizeValuesInOrder[smaller];
+ assertTrue(String.format("oldSize=%d, newSize=%d", oldConfig.screenLayout,
+ newConfig.screenLayout),
+ sizeBuckets.crossesScreenLayoutSizeThreshold(oldConfig, newConfig));
+ }
+ }
+ }
+
+ /**
+ * Tests that if screen layout long changed but did not cross a threshold, the filtered diff
+ * does not include screen layout.
+ */
+ @Test
+ public void testScreenLayoutFilteredIfLongDidNotCrossThreshold() {
+ // Do not set any long threshold
+ final Configuration[] sizeConfigs = new Configuration[1];
+ sizeConfigs[0] = Configuration.EMPTY;
+ final SizeConfigurationBuckets sizeBuckets = new SizeConfigurationBuckets(sizeConfigs);
+
+ // Change screen layout long from not long to long and check that screen layout flag is
+ // not part of the diff because a threshold was not crossed.
+ final int diff = CONFIG_SCREEN_LAYOUT;
+ final Configuration oldConfig = new Configuration();
+ oldConfig.screenLayout |= SCREENLAYOUT_LONG_NO;
+ final Configuration newConfig = new Configuration();
+ newConfig.screenLayout |= SCREENLAYOUT_LONG_YES;
+ final int filteredDiff = SizeConfigurationBuckets.filterDiff(diff, oldConfig, newConfig,
+ sizeBuckets);
+ assertEquals(0, filteredDiff);
+
+ // If a non-size attribute of screen layout changed, then screen layout should not be
+ // filtered from the diff.
+ newConfig.screenLayout |= SCREENLAYOUT_ROUND_YES;
+ final int filteredDiffNonSizeLayoutChanged = SizeConfigurationBuckets.filterDiff(diff,
+ oldConfig, newConfig, sizeBuckets);
+ assertEquals(CONFIG_SCREEN_LAYOUT, filteredDiffNonSizeLayoutChanged);
+ }
+
+ /**
+ * Tests that if screen layout long changed and did cross a threshold, the filtered diff
+ * includes screen layout.
+ */
+ @Test
+ public void testScreenLayoutNotFilteredIfLongCrossedThreshold() {
+ // Set only small and normal sizes
+ final Configuration[] sizeConfigs = new Configuration[1];
+ sizeConfigs[0] = new Configuration();
+ sizeConfigs[0].screenLayout |= SCREENLAYOUT_LONG_NO;
+ final SizeConfigurationBuckets sizeBuckets = new SizeConfigurationBuckets(sizeConfigs);
+
+ // Change screen layout long from not long to long and check that screen layout flag is
+ // still part of the diff because a threshold was crossed.
+ final int diff = CONFIG_SCREEN_LAYOUT;
+ final Configuration oldConfig = new Configuration();
+ oldConfig.screenLayout |= SCREENLAYOUT_LONG_NO;
+ final Configuration newConfig = new Configuration();
+ newConfig.screenLayout |= SCREENLAYOUT_LONG_YES;
+ final int filteredDiff = SizeConfigurationBuckets.filterDiff(diff, oldConfig, newConfig,
+ sizeBuckets);
+ assertEquals(CONFIG_SCREEN_LAYOUT, filteredDiff);
+ }
+
+ /**
+ * Tests that horizontal buckets are correctly checked in
+ * {@link SizeConfigurationBuckets#filterDiff()}.
+ */
+ @Test
+ public void testHorizontalSizeThresholds() {
+ final int[] horizontalThresholds = new int[] { 360, 600 };
+ final SizeConfigurationBuckets sizeBuckets = new SizeConfigurationBuckets(
+ horizontalThresholds, null /* vertical */, null /* smallest */,
+ null /* screenLayoutSize */, false /* screenLayoutLongSet */);
+
+ final Configuration oldConfig = new Configuration();
+ final Configuration newConfig = new Configuration();
+
+ oldConfig.screenWidthDp = 480;
+ // Test that value within bucket filters out screen size config
+ newConfig.screenWidthDp = 520;
+ assertEquals(0, SizeConfigurationBuckets.filterDiff(CONFIG_SCREEN_SIZE, oldConfig,
+ newConfig, sizeBuckets));
+ // Test that value outside bucket does not filter out screen size config
+ newConfig.screenWidthDp = 640;
+ assertEquals(CONFIG_SCREEN_SIZE, SizeConfigurationBuckets.filterDiff(CONFIG_SCREEN_SIZE,
+ oldConfig, newConfig, sizeBuckets));
+ }
+
+ /**
+ * Tests that vertical buckets are correctly checked in
+ * {@link SizeConfigurationBuckets#filterDiff()}.
+ */
+ @Test
+ public void testVerticalSizeThresholds() {
+ final int[] verticalThresholds = new int[] { 360, 600 };
+ final SizeConfigurationBuckets sizeBuckets = new SizeConfigurationBuckets(
+ null, verticalThresholds /* vertical */, null /* smallest */,
+ null /* screenLayoutSize */, false /* screenLayoutLongSet */);
+
+ final Configuration oldConfig = new Configuration();
+ final Configuration newConfig = new Configuration();
+
+ oldConfig.screenHeightDp = 480;
+ // Test that value within bucket filters out screen size config
+ newConfig.screenHeightDp = 520;
+ assertEquals(0, SizeConfigurationBuckets.filterDiff(CONFIG_SCREEN_SIZE, oldConfig,
+ newConfig, sizeBuckets));
+ // Test that value outside bucket does not filter out screen size config
+ newConfig.screenHeightDp = 640;
+ assertEquals(CONFIG_SCREEN_SIZE, SizeConfigurationBuckets.filterDiff(CONFIG_SCREEN_SIZE,
+ oldConfig, newConfig, sizeBuckets));
+ }
+
+ /**
+ * Tests that smallest width buckets are correctly checked in
+ * {@link SizeConfigurationBuckets#filterDiff()}.
+ */
+ @Test
+ public void testSmallestWidthSizeThresholds() {
+ final int[] smallestWidthThresholds = new int[] { 360, 600 };
+ final SizeConfigurationBuckets sizeBuckets = new SizeConfigurationBuckets(
+ null, null /* vertical */, smallestWidthThresholds /* smallest */,
+ null /* screenLayoutSize */, false /* screenLayoutLongSet */);
+
+ final Configuration oldConfig = new Configuration();
+ final Configuration newConfig = new Configuration();
+
+ oldConfig.smallestScreenWidthDp = 480;
+ // Test that value within bucket filters out smallest screen size config
+ newConfig.smallestScreenWidthDp = 520;
+ assertEquals(0, SizeConfigurationBuckets.filterDiff(CONFIG_SMALLEST_SCREEN_SIZE, oldConfig,
+ newConfig, sizeBuckets));
+ // Test that value outside bucket does not filter out smallest screen size config
+ newConfig.smallestScreenWidthDp = 640;
+ assertEquals(CONFIG_SMALLEST_SCREEN_SIZE, SizeConfigurationBuckets.filterDiff(
+ CONFIG_SMALLEST_SCREEN_SIZE, oldConfig, newConfig, sizeBuckets));
+ }
+
+ private void checkCrossesSizeThreshold(int[] thresholds, int oldValue, int newValue,
+ boolean expected) {
+ final String errorString = String.format(
+ "thresholds=%s, oldValue=%d, newValue=%d, expected=%b", Arrays.toString(thresholds),
+ oldValue, newValue, expected);
+ final boolean actual = SizeConfigurationBuckets.crossesSizeThreshold(thresholds, oldValue,
+ newValue);
+ assertEquals(errorString, expected, actual);
+ }
+}
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index 084e1dbced4a..d1e4322ab612 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -130,6 +130,13 @@ prebuilt_etc {
}
prebuilt_etc {
+ name: "allowed_privapp_com.google.android.car.adaslocation",
+ sub_dir: "permissions",
+ src: "com.google.android.car.adaslocation.xml",
+ filename_from_src: true,
+}
+
+prebuilt_etc {
name: "allowed_privapp_com.google.android.car.kitchensink",
sub_dir: "permissions",
src: "com.google.android.car.kitchensink.xml",
diff --git a/data/etc/car/com.android.car.carlauncher.xml b/data/etc/car/com.android.car.carlauncher.xml
index abde232a8a5a..53d02a4a309e 100644
--- a/data/etc/car/com.android.car.carlauncher.xml
+++ b/data/etc/car/com.android.car.carlauncher.xml
@@ -17,11 +17,13 @@
<permissions>
<privapp-permissions package="com.android.car.carlauncher">
<permission name="android.permission.ACTIVITY_EMBEDDING"/>
+ <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
<permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
<permission name="android.permission.PACKAGE_USAGE_STATS"/>
<permission name="android.car.permission.ACCESS_CAR_PROJECTION_STATUS"/>
+ <permission name="android.car.permission.CONTROL_CAR_APP_LAUNCH"/>
</privapp-permissions>
</permissions>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-nb/strings.xml b/data/etc/car/com.google.android.car.adaslocation.xml
index 6ecb7674d65b..cc1ef3c3e160 100644
--- a/packages/overlays/NoCutoutOverlay/res/values-nb/strings.xml
+++ b/data/etc/car/com.google.android.car.adaslocation.xml
@@ -1,5 +1,5 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?>
+<!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,9 +13,9 @@
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Skjul"</string>
-</resources>
+ -->
+<permissions>
+ <privapp-permissions package="com.google.android.car.adaslocation">
+ <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+ </privapp-permissions>
+</permissions>
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index eeb65ae8e129..2c59c7390ebf 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -67,6 +67,7 @@
<permission name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"/>
<permission name="android.car.permission.CAR_MILEAGE"/>
<permission name="android.car.permission.CAR_MOCK_VEHICLE_HAL"/>
+ <permission name="android.car.permission.CAR_MONITOR_CLUSTER_NAVIGATION_STATE"/>
<permission name="android.car.permission.CAR_NAVIGATION_MANAGER"/>
<permission name="android.car.permission.CAR_POWER"/>
<permission name="android.car.permission.CAR_PROJECTION"/>
@@ -74,6 +75,8 @@
<permission name="android.car.permission.CAR_TEST_SERVICE"/>
<permission name="android.car.permission.CAR_UX_RESTRICTIONS_CONFIGURATION"/>
<permission name="android.car.permission.CAR_VENDOR_EXTENSION"/>
+ <!-- use for AndroidCarApiTest -->
+ <permission name="android.car.permission.CONTROL_CAR_APP_LAUNCH"/>
<permission name="android.car.permission.CONTROL_CAR_CLIMATE"/>
<permission name="android.car.permission.CONTROL_CAR_DOORS"/>
<permission name="android.car.permission.CONTROL_CAR_EXTERIOR_LIGHTS"/>
@@ -92,5 +95,6 @@
<permission name="android.car.permission.CONTROL_CAR_EVS_ACTIVITY" />
<permission name="android.car.permission.USE_CAR_EVS_CAMERA" />
<permission name="android.car.permission.MONITOR_CAR_EVS_STATUS" />
+ <permission name="android.car.permission.USE_CAR_TELEMETRY_SERVICE" />
</privapp-permissions>
</permissions>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index b67988ee9646..8b3780590d44 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -79,12 +79,6 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
- "-2029985709": {
- "message": "setFocusedTask: taskId=%d",
- "level": "DEBUG",
- "group": "WM_DEBUG_FOCUS",
- "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
- },
"-2024464438": {
"message": "app-onAnimationFinished(): mOuter=%s",
"level": "DEBUG",
@@ -103,11 +97,11 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
- "-2006946193": {
- "message": "setClientVisible: %s clientVisible=%b Callers=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_APP_TRANSITIONS",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ "-2010331310": {
+ "message": "resumeTopActivity: Top activity resumed (dontWaitForPause) %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
},
"-2002500255": {
"message": "Defer removing snapshot surface in %dms",
@@ -181,6 +175,12 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-1924376693": {
+ "message": " Setting Ready-group to %b. group=%s from %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-1918702467": {
"message": "onSyncFinishedDrawing %s",
"level": "VERBOSE",
@@ -205,12 +205,24 @@
"group": "WM_DEBUG_SYNC_ENGINE",
"at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
},
+ "-1898316768": {
+ "message": "Unable to retrieve window container to start layer mirroring for display %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-1895337367": {
"message": "Delete root task display=%d winMode=%d",
"level": "VERBOSE",
"group": "WM_DEBUG_WINDOW_ORGANIZER",
"at": "com\/android\/server\/wm\/TaskOrganizerController.java"
},
+ "-1886145147": {
+ "message": "resumeTopActivity: Going to sleep and all paused",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"-1884933373": {
"message": "enableScreenAfterBoot: mDisplayEnabled=%b mForceDisplayEnabled=%b mShowingBootMessages=%b mSystemBooted=%b. %s",
"level": "INFO",
@@ -247,12 +259,6 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/AppTransition.java"
},
- "-1861864501": {
- "message": "resumeTopActivityLocked: Going to sleep and all paused",
- "level": "DEBUG",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"-1844540996": {
"message": " Initial targets: %s",
"level": "VERBOSE",
@@ -325,12 +331,6 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
- "-1768090656": {
- "message": "Re-launching after pause: %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"-1750384749": {
"message": "Launch on display check: allow launch on public display",
"level": "DEBUG",
@@ -415,12 +415,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-1655805455": {
- "message": "Enqueue pending stop if needed: %s wasStopping=%b visibleRequested=%b",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"-1647332198": {
"message": "remove RecentTask %s when finishing user %d",
"level": "INFO",
@@ -433,6 +427,12 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/ResetTargetTaskHelper.java"
},
+ "-1633115609": {
+ "message": "Key dispatch not paused for screen off",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"-1632122349": {
"message": "Changing surface while display frozen: %s",
"level": "VERBOSE",
@@ -487,6 +487,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
+ "-1564228464": {
+ "message": "App died while pausing: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"-1559645910": {
"message": "Looking for task of type=%s, taskAffinity=%s, intent=%s, info=%s, preferredTDA=%s",
"level": "DEBUG",
@@ -499,6 +505,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-1556507536": {
+ "message": "Passing transform hint %d for window %s%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"-1554521902": {
"message": "showInsets(ime) was requested by different window: %s ",
"level": "WARN",
@@ -541,6 +553,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "-1501564055": {
+ "message": "Organized TaskFragment is not ready= %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_APP_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/AppTransitionController.java"
+ },
"-1499134947": {
"message": "Removing starting %s from %s",
"level": "VERBOSE",
@@ -565,11 +583,11 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityStarter.java"
},
- "-1492696222": {
- "message": "App died during pause, not stopping: %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
+ "-1483435730": {
+ "message": "InsetsSource setWin %s for type %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WINDOW_INSETS",
+ "at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
},
"-1480772131": {
"message": "No app or window is requesting an orientation, return %d for display id=%d",
@@ -577,6 +595,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-1478175541": {
+ "message": "No longer animating wallpaper targets!",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-1474602871": {
"message": "Launch on display check: disallow launch on virtual display for not-embedded activity.",
"level": "DEBUG",
@@ -613,6 +637,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1442613680": {
+ "message": " Creating Ready-group for Transition %d with root=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-1438175584": {
"message": "Input focus has changed to %s display=%d",
"level": "VERBOSE",
@@ -637,12 +667,24 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1421296808": {
+ "message": "Moving to RESUMED: %s (in existing)",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"-1419762046": {
"message": "moveRootTaskToDisplay: moving taskId=%d to displayId=%d",
"level": "DEBUG",
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
+ "-1419461256": {
+ "message": "resumeTopActivity: Resumed %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"-1413901262": {
"message": "startRecentsActivity(): intent=%s",
"level": "DEBUG",
@@ -655,6 +697,12 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java"
},
+ "-1394745488": {
+ "message": "ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s",
+ "level": "INFO",
+ "group": "WM_DEBUG_WINDOW_INSETS",
+ "at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
+ },
"-1391944764": {
"message": "SURFACE DESTROY: %s. %s",
"level": "INFO",
@@ -703,11 +751,11 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "-1312861660": {
- "message": "notifyInsetsControlChanged for %s ",
- "level": "DEBUG",
- "group": "WM_DEBUG_IME",
- "at": "com\/android\/server\/wm\/WindowState.java"
+ "-1311436264": {
+ "message": "Unregister task fragment organizer=%s uid=%d pid=%d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_ORGANIZER",
+ "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
},
"-1305966693": {
"message": "Sending position change to %s, onTop: %b",
@@ -805,6 +853,18 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-1187377055": {
+ "message": "Enqueue pending stop if needed: %s wasStopping=%b visibleRequested=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
+ "-1185473319": {
+ "message": "ControlAdapter startAnimation mSource: %s controlTarget: %s",
+ "level": "INFO",
+ "group": "WM_DEBUG_WINDOW_INSETS",
+ "at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
+ },
"-1176488860": {
"message": "SURFACE isSecure=%b: %s",
"level": "INFO",
@@ -919,12 +979,6 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
},
- "-1066383762": {
- "message": "Sleep still waiting to pause %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"-1060365734": {
"message": "Attempted to add QS dialog window with bad token %s. Aborting.",
"level": "WARN",
@@ -985,6 +1039,12 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "-957060823": {
+ "message": "Moving to PAUSING: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"-951939129": {
"message": "Unregister task organizer=%s uid=%d",
"level": "VERBOSE",
@@ -1051,6 +1111,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-904499590": {
+ "message": "Provided surface for layer mirroring on display %d is not present, so do not update the surface",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-883738232": {
"message": "Adding more than one toast window for UID at a time.",
"level": "WARN",
@@ -1075,6 +1141,12 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-863438038": {
+ "message": "Aborting Transition: %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-861859917": {
"message": "Attempted to add window to a display that does not exist: %d. Aborting.",
"level": "WARN",
@@ -1153,6 +1225,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-779535710": {
+ "message": "Transition %d: Set %s as transient-launch",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-775004869": {
"message": "Not a match: %s",
"level": "DEBUG",
@@ -1201,6 +1279,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-706481945": {
+ "message": "TaskFragment parent info changed name=%s parentTaskId=%d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_ORGANIZER",
+ "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+ },
"-705939410": {
"message": "Waiting for pause to complete...",
"level": "VERBOSE",
@@ -1213,6 +1297,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
+ "-702650156": {
+ "message": "Override with TaskFragment remote animation for transit=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_APP_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/AppTransitionController.java"
+ },
"-701167286": {
"message": "applyAnimation: transit=%s, enter=%b, wc=%s",
"level": "VERBOSE",
@@ -1237,12 +1327,6 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
- "-672228342": {
- "message": "resumeTopActivityLocked: Top activity resumed %s",
- "level": "DEBUG",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"-668956537": {
"message": " THUMBNAIL %s: CREATE",
"level": "INFO",
@@ -1255,6 +1339,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "-663411559": {
+ "message": "Going ahead with updating layer mirroring for display %d to new bounds %s and\/or orientation %d.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-655104359": {
"message": "Frontmost changed immersion: %s",
"level": "DEBUG",
@@ -1267,11 +1357,11 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
- "-650261962": {
- "message": "Sleep needs to pause %s",
+ "-648891906": {
+ "message": "Activity not running or entered PiP, resuming next.",
"level": "VERBOSE",
"group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
},
"-641258376": {
"message": "realStartActivityLocked: Skipping start of r=%s some activities pausing...",
@@ -1309,12 +1399,6 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-606328116": {
- "message": "resumeTopActivityLocked: Top activity resumed (dontWaitForPause) %s",
- "level": "DEBUG",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"-597091183": {
"message": "Delete TaskDisplayArea uid=%d",
"level": "VERBOSE",
@@ -1375,11 +1459,11 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowAnimator.java"
},
- "-533690126": {
- "message": "resumeTopActivityLocked: Resumed %s",
- "level": "DEBUG",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
+ "-542756093": {
+ "message": "TaskFragment vanished name=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_ORGANIZER",
+ "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
},
"-532081937": {
"message": " Commit activity becoming invisible: %s",
@@ -1387,11 +1471,11 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
- "-527683022": {
- "message": "resumeTopActivityLocked: Skip resume: some activity pausing.",
+ "-521613870": {
+ "message": "App died during pause, not stopping: %s",
"level": "VERBOSE",
"group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
},
"-519504830": {
"message": "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s isEntrance=%b Callers=%s",
@@ -1477,17 +1561,11 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayRotation.java"
},
- "-427457280": {
- "message": "App died while pausing: %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
- "-417514857": {
- "message": "Key dispatch not paused for screen off",
- "level": "VERBOSE",
+ "-436553282": {
+ "message": "Remove sleep token: tag=%s, displayId=%d",
+ "level": "DEBUG",
"group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
"-415865166": {
"message": "findFocusedWindow: Found new focus @ %s",
@@ -1519,12 +1597,6 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
- "-395922585": {
- "message": "InsetsSource setWin %s",
- "level": "DEBUG",
- "group": "WM_DEBUG_IME",
- "at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
- },
"-393505149": {
"message": "unable to update pointer icon",
"level": "WARN",
@@ -1537,6 +1609,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-384564722": {
+ "message": "Unable to start layer mirroring for display %d since the surface is not available.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-381475323": {
"message": "DisplayContent: boot is waiting for window of type %d to be drawn",
"level": "DEBUG",
@@ -1561,6 +1639,12 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "-360208282": {
+ "message": "Animating wallpapers: old: %s hidden=%b new: %s hidden=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-354571697": {
"message": "Existence Changed in transition %d: %s",
"level": "VERBOSE",
@@ -1591,17 +1675,41 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "-317761482": {
+ "message": "Create sleep token: tag=%s, displayId=%d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
"-317194205": {
"message": "clearLockedTasks: %s",
"level": "INFO",
"group": "WM_DEBUG_LOCKTASK",
"at": "com\/android\/server\/wm\/LockTaskController.java"
},
- "-303497363": {
- "message": "reparent: moving activity=%s to task=%d at %d",
+ "-312353598": {
+ "message": "Executing finish of activity: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
+ "-310337305": {
+ "message": "Activity config changed during resume: %s, new next: %s",
"level": "INFO",
- "group": "WM_DEBUG_ADD_REMOVE",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
+ "-309399422": {
+ "message": "Display %d state is now (%d), so update layer mirroring?",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
+ "-304728471": {
+ "message": "New wallpaper: target=%s prev=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
},
"-302468788": {
"message": "Expected target rootTask=%s to be top most but found rootTask=%s",
@@ -1621,11 +1729,11 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-279436615": {
- "message": "Moving to PAUSING: %s",
+ "-275077723": {
+ "message": "New animation: %s old animation: %s",
"level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
},
"-262984451": {
"message": "Relaunch failed %s",
@@ -1639,6 +1747,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-248761393": {
+ "message": "startPausing: taskFrag =%s mResumedActivity=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"-240296576": {
"message": "handleAppTransitionReady: displayId=%d appTransition={%s} openingApps=[%s] closingApps=[%s] transit=%s",
"level": "VERBOSE",
@@ -1651,12 +1765,6 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "-234244777": {
- "message": "Activity config changed during resume: %s, new next: %s",
- "level": "INFO",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"-230587670": {
"message": "SyncGroup %d: Unfinished container: %s",
"level": "VERBOSE",
@@ -1675,6 +1783,18 @@
"group": "WM_DEBUG_WINDOW_MOVEMENT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-190034097": {
+ "message": "Unable to retrieve window container to update layer mirroring for display %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
+ "-182877285": {
+ "message": "Wallpaper layer changed: assigning layers + relayout",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-177040661": {
"message": "Start rotation animation. customAnim=%s, mCurRotation=%s, mOriginalRotation=%s",
"level": "DEBUG",
@@ -1723,24 +1843,12 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "-118786523": {
- "message": "Resume failed; resetting state to %s: %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"-116086365": {
"message": "******************** ENABLING SCREEN!",
"level": "INFO",
"group": "WM_DEBUG_SCREEN_ON",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-112805366": {
- "message": "InsetsSource updateVisibility serverVisible: %s clientVisible: %s",
- "level": "DEBUG",
- "group": "WM_DEBUG_IME",
- "at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
- },
"-108977760": {
"message": "Sandbox max bounds for uid %s to bounds %s. config to never sandbox = %s, config to always sandbox = %s, letterboxing from mismatch with parent bounds = %s, has mCompatDisplayInsets = %s, should create compatDisplayInsets = %s",
"level": "DEBUG",
@@ -1777,6 +1885,30 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/Session.java"
},
+ "-80004683": {
+ "message": "Resume failed; resetting state to %s: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
+ "-79877120": {
+ "message": "Display %d has content (%b) so disable layer mirroring",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
+ "-70719599": {
+ "message": "Unregister remote animations for organizer=%s uid=%d pid=%d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_ORGANIZER",
+ "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+ },
+ "-55185509": {
+ "message": "setFocusedTask: taskId=%d touchedActivity=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_FOCUS",
+ "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+ },
"-50336993": {
"message": "moveFocusableActivityToTop: activity=%s",
"level": "DEBUG",
@@ -1831,12 +1963,6 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
- "29780972": {
- "message": "InsetsSource Control %s for target %s",
- "level": "DEBUG",
- "group": "WM_DEBUG_IME",
- "at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
- },
"35398067": {
"message": "goodToGo(): onAnimationStart, transit=%s, apps=%d, wallpapers=%d, nonApps=%d",
"level": "DEBUG",
@@ -1879,24 +2005,12 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
- "73987756": {
- "message": "ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s",
- "level": "INFO",
- "group": "WM_DEBUG_IME",
- "at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
- },
"74885950": {
"message": "Waiting for top state to be released by %s",
"level": "VERBOSE",
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
},
- "75707221": {
- "message": "ControlAdapter startAnimation mSource: %s controlTarget: %s",
- "level": "INFO",
- "group": "WM_DEBUG_IME",
- "at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
- },
"83950285": {
"message": "removeAnimation(%d)",
"level": "DEBUG",
@@ -1909,12 +2023,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowContextListenerController.java"
},
- "94402792": {
- "message": "Moving to RESUMED: %s (in existing)",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"95216706": {
"message": "hideIme target: %s ",
"level": "DEBUG",
@@ -1933,12 +2041,24 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
+ "102618780": {
+ "message": "resumeTopActivity: Pausing %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"108170907": {
"message": "Add starting %s: startingData=%s",
"level": "VERBOSE",
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "114070759": {
+ "message": "New wallpaper target: %s prevTarget: %s caller=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"115358443": {
"message": "Focus changing: %s -> %s",
"level": "INFO",
@@ -2173,6 +2293,18 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "327461496": {
+ "message": "Complete pause: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
+ "341055768": {
+ "message": "resumeTopActivity: Skip resume: need to start pausing",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"342460966": {
"message": "DRAG %s: pos=(%d,%d)",
"level": "INFO",
@@ -2191,6 +2323,12 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "352982444": {
+ "message": " allReady query: used=%b override=%b states=[%s]",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"355720268": {
"message": "stopFreezingDisplayLocked: Unfreezing now",
"level": "DEBUG",
@@ -2227,11 +2365,11 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "391189028": {
- "message": "pauseBackTasks: task=%s mResumedActivity=%s",
- "level": "DEBUG",
+ "378825104": {
+ "message": "Enqueueing pending pause: %s",
+ "level": "VERBOSE",
"group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/TaskDisplayArea.java"
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
},
"397105698": {
"message": "grantEmbeddedWindowFocus remove request for win=%s dropped since no candidate was found",
@@ -2251,12 +2389,24 @@
"group": "WM_SHOW_SURFACE_ALLOC",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "416924848": {
+ "message": "InsetsSource Control %s for target %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WINDOW_INSETS",
+ "at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
+ },
"417311568": {
"message": "onResize: Resizing %s",
"level": "DEBUG",
"group": "WM_DEBUG_RESIZE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "422634333": {
+ "message": "First draw done in potential wallpaper target %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"424524729": {
"message": "Attempted to add wallpaper window with unknown token %s. Aborting.",
"level": "WARN",
@@ -2281,12 +2431,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "457951957": {
- "message": "\tNot visible=%s",
- "level": "DEBUG",
- "group": "WM_DEBUG_REMOTE_ANIMATIONS",
- "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
- },
"463993897": {
"message": "Aborted waiting for drawn: %s",
"level": "WARN",
@@ -2311,6 +2455,12 @@
"group": "WM_DEBUG_WINDOW_ORGANIZER",
"at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
},
+ "504397469": {
+ "message": "Unable to update layer mirroring for display %d to new bounds %s and\/or orientation %d, since the surface is not available.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"508887531": {
"message": "applyAnimation voice: anim=%s transit=%s isEntrance=%b Callers=%s",
"level": "VERBOSE",
@@ -2329,6 +2479,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowContainerThumbnail.java"
},
+ "535103992": {
+ "message": "Wallpaper may change! Adjusting",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
"539077569": {
"message": "Clear freezing of %s force=%b",
"level": "VERBOSE",
@@ -2365,6 +2521,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowSurfaceController.java"
},
+ "573582981": {
+ "message": "reparent: moving activity=%s to new task fragment in task=%d at %d",
+ "level": "INFO",
+ "group": "WM_DEBUG_ADD_REMOVE",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"579298675": {
"message": "Moving to DESTROYED: %s (removed from history)",
"level": "VERBOSE",
@@ -2467,6 +2629,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "660908897": {
+ "message": "Auto-PIP allowed, entering PIP mode directly: %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"662572728": {
"message": "Attempted to add a toast window with bad token %s. Aborting.",
"level": "WARN",
@@ -2485,12 +2653,24 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "669361121": {
+ "message": "Sleep still need to stop %d activities",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"674932310": {
"message": "Setting Intent of %s to target %s",
"level": "VERBOSE",
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/Task.java"
},
+ "675705156": {
+ "message": "resumeTopActivity: Top activity resumed %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"685047360": {
"message": "Resizing window %s",
"level": "VERBOSE",
@@ -2521,12 +2701,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
},
- "709500946": {
- "message": "resumeTopActivityLocked: Skip resume: need to start pausing",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"715749922": {
"message": "Allowlisting %d:%s",
"level": "WARN",
@@ -2539,12 +2713,24 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "733466617": {
+ "message": "Wallpaper token %s visible=%b",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperWindowToken.java"
+ },
"736692676": {
"message": "Config is relaunching %s",
"level": "VERBOSE",
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "743418423": {
+ "message": "Sending TaskFragment error exception=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_ORGANIZER",
+ "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+ },
"744171317": {
"message": " SKIP: %s",
"level": "VERBOSE",
@@ -2629,12 +2815,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
- "897964776": {
- "message": "Complete pause: %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"898863925": {
"message": "Attempted to add QS dialog window with unknown token %s. Aborting.",
"level": "WARN",
@@ -2659,6 +2839,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowStateAnimator.java"
},
+ "935418348": {
+ "message": "resumeTopActivity: Skip resume: some activity pausing.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"950074526": {
"message": "setLockTaskMode: Can't lock due to auth",
"level": "WARN",
@@ -2707,11 +2893,11 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
},
- "988389910": {
- "message": "resumeTopActivityLocked: Pausing %s",
- "level": "DEBUG",
+ "987903142": {
+ "message": "Sleep needs to pause %s",
+ "level": "VERBOSE",
"group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
},
"996960396": {
"message": "Starting Transition %d",
@@ -2719,24 +2905,36 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
- "1001509841": {
- "message": "Auto-PIP allowed, entering PIP mode directly: %s",
- "level": "DEBUG",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"1001904964": {
"message": "***** BOOT TIMEOUT: forcing display enabled",
"level": "WARN",
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1011462000": {
+ "message": "Re-launching after pause: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
+ "1022095595": {
+ "message": "TaskFragment info changed name=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_ORGANIZER",
+ "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+ },
"1023413388": {
"message": "Finish waiting for pause of: %s",
"level": "VERBOSE",
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1030898920": {
+ "message": "notifyInsetsControlChanged for %s ",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WINDOW_INSETS",
+ "at": "com\/android\/server\/wm\/WindowState.java"
+ },
"1033274509": {
"message": "moveWindowTokenToDisplay: Attempted to move non-existing token: %s",
"level": "WARN",
@@ -2755,6 +2953,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1047505501": {
+ "message": "notifyInsetsChanged for %s ",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WINDOW_INSETS",
+ "at": "com\/android\/server\/wm\/WindowState.java"
+ },
"1047769218": {
"message": "Finishing activity r=%s, result=%d, data=%s, reason=%s",
"level": "VERBOSE",
@@ -2851,6 +3055,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "1178653181": {
+ "message": "Old wallpaper still the target.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"1186730970": {
"message": " no common mode yet, so set it",
"level": "VERBOSE",
@@ -2875,6 +3085,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1210037962": {
+ "message": "Register remote animations for organizer=%s uid=%d pid=%d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_ORGANIZER",
+ "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+ },
"1219600119": {
"message": "addWindow: win=%s Callers=%s",
"level": "DEBUG",
@@ -2917,6 +3133,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1284122013": {
+ "message": "TaskFragment appeared name=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_ORGANIZER",
+ "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+ },
"1288731814": {
"message": "WindowState.hideLw: setting mFocusMayChange true",
"level": "INFO",
@@ -3013,6 +3235,12 @@
"group": "WM_DEBUG_SCREEN_ON",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1407569006": {
+ "message": "Display %d was already layer mirroring, so apply transformations if necessary",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"1417601133": {
"message": "Enqueueing ADD_STARTING",
"level": "VERBOSE",
@@ -3073,6 +3301,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
+ "1494644409": {
+ "message": " Rejecting as detached: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"1495525537": {
"message": "createWallpaperAnimations()",
"level": "DEBUG",
@@ -3133,12 +3367,6 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "1533154777": {
- "message": "notifyInsetsChanged for %s ",
- "level": "DEBUG",
- "group": "WM_DEBUG_IME",
- "at": "com\/android\/server\/wm\/WindowState.java"
- },
"1557732761": {
"message": "For Intent %s bringing to top: %s",
"level": "DEBUG",
@@ -3163,12 +3391,6 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/WindowContainer.java"
},
- "1585450696": {
- "message": "resumeTopActivityLocked: Restarting %s",
- "level": "DEBUG",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"1589610525": {
"message": "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS: anim=%s transit=%s isEntrance=true Callers=%s",
"level": "VERBOSE",
@@ -3217,6 +3439,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "1653025361": {
+ "message": "Register task fragment organizer=%s uid=%d pid=%d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_ORGANIZER",
+ "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+ },
"1653210583": {
"message": "Removing app %s delayed=%b animation=%s animating=%b",
"level": "VERBOSE",
@@ -3229,6 +3457,12 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/InsetsStateController.java"
},
+ "1670933628": {
+ "message": " Setting allReady override",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"1671994402": {
"message": "Nulling last startingData",
"level": "VERBOSE",
@@ -3253,6 +3487,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1687376052": {
+ "message": "Display %d has no content and is on, so start layer mirroring for state %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_LAYER_MIRRORING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"1689989893": {
"message": "SyncGroup %d: Set ready",
"level": "VERBOSE",
@@ -3337,6 +3577,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1805116444": {
+ "message": "We don't support remote animation for Task with multiple TaskFragmentOrganizers.",
+ "level": "ERROR",
+ "group": "WM_DEBUG_APP_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/AppTransitionController.java"
+ },
"1810019902": {
"message": "TRANSIT_FLAG_OPEN_BEHIND, adding %s to mOpeningApps",
"level": "DEBUG",
@@ -3385,18 +3631,6 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "1837992242": {
- "message": "Executing finish of activity: %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
- "1847414670": {
- "message": "Activity not running or entered PiP, resuming next.",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"1853793312": {
"message": "Notify removed startingWindow %s",
"level": "VERBOSE",
@@ -3409,6 +3643,12 @@
"group": "WM_DEBUG_FOCUS",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1856783490": {
+ "message": "resumeTopActivity: Restarting %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"1865125884": {
"message": "finishScreenTurningOn: mAwake=%b, mScreenOnEarly=%b, mScreenOnFully=%b, mKeyguardDrawComplete=%b, mWindowManagerDrawComplete=%b",
"level": "DEBUG",
@@ -3421,30 +3661,24 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "1884961873": {
- "message": "Sleep still need to stop %d activities",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"1891501279": {
"message": "cancelAnimation(): reason=%s",
"level": "DEBUG",
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
- "1894239744": {
- "message": "Enqueueing pending pause: %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"1903353011": {
"message": "notifyAppStopped: %s",
"level": "VERBOSE",
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1912291550": {
+ "message": "Sleep still waiting to pause %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"1918448345": {
"message": "Task appeared taskId=%d",
"level": "VERBOSE",
@@ -3505,6 +3739,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowAnimator.java"
},
+ "1984843251": {
+ "message": "Hiding wallpaper %s from %s target=%s prev=%s callers=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"1995093920": {
"message": "Checking to restart %s: changed=0x%s, handles=0x%s, mLastReportedConfiguration=%s",
"level": "VERBOSE",
@@ -3535,6 +3775,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "2024493888": {
+ "message": "\tWallpaper of display=%s is not visible",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+ "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
+ },
"2028163120": {
"message": "applyAnimation: anim=%s nextAppTransition=ANIM_SCALE_UP transit=%s isEntrance=%s Callers=%s",
"level": "VERBOSE",
@@ -3559,18 +3805,18 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
- "2057434754": {
- "message": "\tvisible=%s",
- "level": "DEBUG",
- "group": "WM_DEBUG_REMOTE_ANIMATIONS",
- "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
- },
"2060978050": {
"message": "moveWindowTokenToDisplay: Attempted to move token: %s to non-exiting displayId=%d",
"level": "WARN",
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "2070726247": {
+ "message": "InsetsSource updateVisibility for %s, serverVisible: %s clientVisible: %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WINDOW_INSETS",
+ "at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
+ },
"2083556954": {
"message": "Set mOrientationChanging of %s",
"level": "VERBOSE",
@@ -3663,6 +3909,9 @@
"WM_DEBUG_KEEP_SCREEN_ON": {
"tag": "WindowManager"
},
+ "WM_DEBUG_LAYER_MIRRORING": {
+ "tag": "WindowManager"
+ },
"WM_DEBUG_LOCKTASK": {
"tag": "WindowManager"
},
@@ -3696,6 +3945,12 @@
"WM_DEBUG_TASKS": {
"tag": "WindowManager"
},
+ "WM_DEBUG_WALLPAPER": {
+ "tag": "WindowManager"
+ },
+ "WM_DEBUG_WINDOW_INSETS": {
+ "tag": "WindowManager"
+ },
"WM_DEBUG_WINDOW_MOVEMENT": {
"tag": "WindowManager"
},
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java
deleted file mode 100644
index b7a60392c512..000000000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java
+++ /dev/null
@@ -1,41 +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 androidx.window.extensions;
-
-import android.content.Context;
-
-/**
- * Provider class that will instantiate the library implementation. It must be included in the
- * vendor library, and the vendor implementation must match the signature of this class.
- */
-public class ExtensionProvider {
- /**
- * Provides a simple implementation of {@link ExtensionInterface} that can be replaced by
- * an OEM by overriding this method.
- */
- public static ExtensionInterface getExtensionImpl(Context context) {
- return new SampleExtensionImpl(context);
- }
-
- /**
- * The support library will use this method to check API version compatibility.
- * @return API version string in MAJOR.MINOR.PATCH-description format.
- */
- public static String getApiVersion() {
- return "1.0.0-settings_sample";
- }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java
deleted file mode 100644
index 6a53efee0e74..000000000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java
+++ /dev/null
@@ -1,72 +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 androidx.window.extensions;
-
-import android.app.Activity;
-
-import androidx.annotation.NonNull;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Basic implementation of the {@link ExtensionInterface}. An OEM can choose to use it as the base
- * class for their implementation.
- */
-abstract class StubExtension implements ExtensionInterface {
-
- private ExtensionCallback mExtensionCallback;
- private final Set<Activity> mWindowLayoutChangeListenerActivities = new HashSet<>();
-
- StubExtension() {
- }
-
- @Override
- public void setExtensionCallback(@NonNull ExtensionCallback extensionCallback) {
- this.mExtensionCallback = extensionCallback;
- }
-
- @Override
- public void onWindowLayoutChangeListenerAdded(@NonNull Activity activity) {
- this.mWindowLayoutChangeListenerActivities.add(activity);
- this.onListenersChanged();
- }
-
- @Override
- public void onWindowLayoutChangeListenerRemoved(@NonNull Activity activity) {
- this.mWindowLayoutChangeListenerActivities.remove(activity);
- this.onListenersChanged();
- }
-
- void updateWindowLayout(@NonNull Activity activity,
- @NonNull ExtensionWindowLayoutInfo newLayout) {
- if (this.mExtensionCallback != null) {
- mExtensionCallback.onWindowLayoutChanged(activity, newLayout);
- }
- }
-
- @NonNull
- Set<Activity> getActivitiesListeningForLayoutChanges() {
- return mWindowLayoutChangeListenerActivities;
- }
-
- protected boolean hasListeners() {
- return !mWindowLayoutChangeListenerActivities.isEmpty();
- }
-
- protected abstract void onListenersChanged();
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
new file mode 100644
index 000000000000..bdf703c9bd38
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -0,0 +1,78 @@
+/*
+ * 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 androidx.window.extensions;
+
+import android.app.ActivityThread;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
+import androidx.window.extensions.embedding.SplitController;
+import androidx.window.extensions.layout.WindowLayoutComponent;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+
+/**
+ * The reference implementation of {@link WindowExtensions} that implements the initial API version.
+ */
+public class WindowExtensionsImpl implements WindowExtensions {
+
+ private final Object mLock = new Object();
+ private volatile WindowLayoutComponent mWindowLayoutComponent;
+ private volatile SplitController mSplitController;
+
+ @Override
+ public int getVendorApiLevel() {
+ return 1;
+ }
+
+ /**
+ * Returns a reference implementation of {@link WindowLayoutComponent} if available,
+ * {@code null} otherwise. The implementation must match the API level reported in
+ * {@link WindowExtensions#getWindowLayoutComponent()}.
+ * @return {@link WindowLayoutComponent} OEM implementation
+ */
+ @Override
+ public WindowLayoutComponent getWindowLayoutComponent() {
+ if (mWindowLayoutComponent == null) {
+ synchronized (mLock) {
+ if (mWindowLayoutComponent == null) {
+ Context context = ActivityThread.currentApplication();
+ mWindowLayoutComponent = new WindowLayoutComponentImpl(context);
+ }
+ }
+ }
+ return mWindowLayoutComponent;
+ }
+
+ /**
+ * Returns a reference implementation of {@link ActivityEmbeddingComponent} if available,
+ * {@code null} otherwise. The implementation must match the API level reported in
+ * {@link WindowExtensions#getWindowLayoutComponent()}.
+ * @return {@link ActivityEmbeddingComponent} OEM implementation.
+ */
+ @NonNull
+ public ActivityEmbeddingComponent getActivityEmbeddingComponent() {
+ if (mSplitController == null) {
+ synchronized (mLock) {
+ if (mSplitController == null) {
+ mSplitController = new SplitController();
+ }
+ }
+ }
+ return mSplitController;
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsProvider.java
new file mode 100644
index 000000000000..f9e1f077cffc
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsProvider.java
@@ -0,0 +1,38 @@
+/*
+ * 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 androidx.window.extensions;
+
+import android.annotation.NonNull;
+
+/**
+ * Provides the OEM implementation of {@link WindowExtensions}.
+ */
+public class WindowExtensionsProvider {
+
+ private static final WindowExtensions sWindowExtensions = new WindowExtensionsImpl();
+
+ /**
+ * Returns the OEM implementation of {@link WindowExtensions}. This method is implemented in
+ * the library provided on the device and overwrites one in the Jetpack library included in
+ * apps.
+ * @return the OEM implementation of {@link WindowExtensions}
+ */
+ @NonNull
+ public static WindowExtensions getWindowExtensions() {
+ return sWindowExtensions;
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
new file mode 100644
index 000000000000..85ef270ac49d
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -0,0 +1,304 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import android.app.Activity;
+import android.app.WindowConfiguration.WindowingMode;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.ArrayMap;
+import android.view.SurfaceControl;
+import android.window.TaskFragmentAppearedInfo;
+import android.window.TaskFragmentCreationParams;
+import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentOrganizer;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Platform default Extensions implementation of {@link TaskFragmentOrganizer} to organize
+ * task fragments.
+ *
+ * All calls into methods of this class are expected to be on the UI thread.
+ */
+class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
+
+ /** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */
+ private final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>();
+
+ /** Mapping from the client assigned unique token to the TaskFragment {@link SurfaceControl}. */
+ private final Map<IBinder, SurfaceControl> mFragmentLeashes = new ArrayMap<>();
+
+ /**
+ * Mapping from the client assigned unique token to the TaskFragment parent
+ * {@link Configuration}.
+ */
+ final Map<IBinder, Configuration> mFragmentParentConfigs = new ArrayMap<>();
+
+ private final TaskFragmentCallback mCallback;
+ private TaskFragmentAnimationController mAnimationController;
+
+ /**
+ * Callback that notifies the controller about changes to task fragments.
+ */
+ interface TaskFragmentCallback {
+ void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo);
+ void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo);
+ void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo);
+ void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
+ @NonNull Configuration parentConfig);
+ }
+
+ /**
+ * @param executor callbacks from WM Core are posted on this executor. It should be tied to the
+ * UI thread that all other calls into methods of this class are also on.
+ */
+ JetpackTaskFragmentOrganizer(@NonNull Executor executor, TaskFragmentCallback callback) {
+ super(executor);
+ mCallback = callback;
+ }
+
+ @Override
+ public void registerOrganizer() {
+ if (mAnimationController != null) {
+ throw new IllegalStateException("Must unregister the organizer before re-register.");
+ }
+ super.registerOrganizer();
+ mAnimationController = new TaskFragmentAnimationController(this);
+ mAnimationController.registerRemoteAnimations();
+ }
+
+ @Override
+ public void unregisterOrganizer() {
+ if (mAnimationController != null) {
+ mAnimationController.unregisterRemoteAnimations();
+ mAnimationController = null;
+ }
+ super.unregisterOrganizer();
+ }
+
+ /**
+ * Starts a new Activity and puts it into split with an existing Activity side-by-side.
+ * @param launchingFragmentToken token for the launching TaskFragment. If it exists, it will
+ * be resized based on {@param launchingFragmentBounds}.
+ * Otherwise, we will create a new TaskFragment with the given
+ * token for the {@param launchingActivity}.
+ * @param launchingFragmentBounds the initial bounds for the launching TaskFragment.
+ * @param launchingActivity the Activity to put on the left hand side of the split as the
+ * primary.
+ * @param secondaryFragmentToken token to create the secondary TaskFragment with.
+ * @param secondaryFragmentBounds the initial bounds for the secondary TaskFragment
+ * @param activityIntent Intent to start the secondary Activity with.
+ * @param activityOptions ActivityOptions to start the secondary Activity with.
+ */
+ void startActivityToSide(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder launchingFragmentToken, @NonNull Rect launchingFragmentBounds,
+ @NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken,
+ @NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent,
+ @Nullable Bundle activityOptions, @NonNull SplitRule rule) {
+ final IBinder ownerToken = launchingActivity.getActivityToken();
+
+ // Create or resize the launching TaskFragment.
+ if (mFragmentInfos.containsKey(launchingFragmentToken)) {
+ resizeTaskFragment(wct, launchingFragmentToken, launchingFragmentBounds);
+ } else {
+ createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken,
+ launchingFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, launchingActivity);
+ }
+
+ // Create a TaskFragment for the secondary activity.
+ createTaskFragmentAndStartActivity(wct, secondaryFragmentToken, ownerToken,
+ secondaryFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, activityIntent,
+ activityOptions);
+
+ // Set adjacent to each other so that the containers below will be invisible.
+ setAdjacentTaskFragments(wct, launchingFragmentToken, secondaryFragmentToken, rule);
+ }
+
+ /**
+ * Expands an existing TaskFragment to fill parent.
+ * @param wct WindowContainerTransaction in which the task fragment should be resized.
+ * @param fragmentToken token of an existing TaskFragment.
+ */
+ void expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
+ resizeTaskFragment(wct, fragmentToken, new Rect());
+ setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */);
+ }
+
+ /**
+ * Expands an existing TaskFragment to fill parent.
+ * @param fragmentToken token of an existing TaskFragment.
+ */
+ void expandTaskFragment(IBinder fragmentToken) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ expandTaskFragment(wct, fragmentToken);
+ applyTransaction(wct);
+ }
+
+ /**
+ * Expands an Activity to fill parent by moving it to a new TaskFragment.
+ * @param fragmentToken token to create new TaskFragment with.
+ * @param activity activity to move to the fill-parent TaskFragment.
+ */
+ void expandActivity(IBinder fragmentToken, Activity activity) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ createTaskFragmentAndReparentActivity(
+ wct, fragmentToken, activity.getActivityToken(), new Rect(),
+ WINDOWING_MODE_UNDEFINED, activity);
+ applyTransaction(wct);
+ }
+
+ /**
+ * @param ownerToken The token of the activity that creates this task fragment. It does not
+ * have to be a child of this task fragment, but must belong to the same task.
+ */
+ void createTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken,
+ IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
+ final TaskFragmentCreationParams fragmentOptions =
+ createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
+ wct.createTaskFragment(fragmentOptions);
+ }
+
+ /**
+ * @param ownerToken The token of the activity that creates this task fragment. It does not
+ * have to be a child of this task fragment, but must belong to the same task.
+ */
+ private void createTaskFragmentAndReparentActivity(
+ WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
+ @NonNull Rect bounds, @WindowingMode int windowingMode, Activity activity) {
+ createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
+ wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
+ }
+
+ /**
+ * @param ownerToken The token of the activity that creates this task fragment. It does not
+ * have to be a child of this task fragment, but must belong to the same task.
+ */
+ private void createTaskFragmentAndStartActivity(
+ WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
+ @NonNull Rect bounds, @WindowingMode int windowingMode, Intent activityIntent,
+ @Nullable Bundle activityOptions) {
+ createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
+ wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions);
+ }
+
+ void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule) {
+ WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null;
+ final boolean finishSecondaryWithPrimary =
+ splitRule != null && SplitContainer.shouldFinishSecondaryWithPrimary(splitRule);
+ final boolean finishPrimaryWithSecondary =
+ splitRule != null && SplitContainer.shouldFinishPrimaryWithSecondary(splitRule);
+ if (finishSecondaryWithPrimary || finishPrimaryWithSecondary) {
+ adjacentParams = new WindowContainerTransaction.TaskFragmentAdjacentParams();
+ adjacentParams.setShouldDelayPrimaryLastActivityRemoval(finishSecondaryWithPrimary);
+ adjacentParams.setShouldDelaySecondaryLastActivityRemoval(finishPrimaryWithSecondary);
+ }
+ wct.setAdjacentTaskFragments(primary, secondary, adjacentParams);
+ }
+
+ TaskFragmentCreationParams createFragmentOptions(IBinder fragmentToken, IBinder ownerToken,
+ Rect bounds, @WindowingMode int windowingMode) {
+ if (mFragmentInfos.containsKey(fragmentToken)) {
+ throw new IllegalArgumentException(
+ "There is an existing TaskFragment with fragmentToken=" + fragmentToken);
+ }
+
+ return new TaskFragmentCreationParams.Builder(
+ getOrganizerToken(),
+ fragmentToken,
+ ownerToken)
+ .setInitialBounds(bounds)
+ .setWindowingMode(windowingMode)
+ .build();
+ }
+
+ void resizeTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken,
+ @Nullable Rect bounds) {
+ if (!mFragmentInfos.containsKey(fragmentToken)) {
+ throw new IllegalArgumentException(
+ "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
+ }
+ if (bounds == null) {
+ bounds = new Rect();
+ }
+ wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds);
+ }
+
+ void deleteTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
+ if (!mFragmentInfos.containsKey(fragmentToken)) {
+ throw new IllegalArgumentException(
+ "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
+ }
+ wct.deleteTaskFragment(mFragmentInfos.get(fragmentToken).getToken());
+ }
+
+ @Override
+ public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) {
+ final TaskFragmentInfo info = taskFragmentAppearedInfo.getTaskFragmentInfo();
+ final IBinder fragmentToken = info.getFragmentToken();
+ final SurfaceControl leash = taskFragmentAppearedInfo.getLeash();
+ mFragmentInfos.put(fragmentToken, info);
+ mFragmentLeashes.put(fragmentToken, leash);
+
+ if (mCallback != null) {
+ mCallback.onTaskFragmentAppeared(taskFragmentAppearedInfo);
+ }
+ }
+
+ @Override
+ public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+ mFragmentInfos.put(fragmentToken, taskFragmentInfo);
+
+ if (mCallback != null) {
+ mCallback.onTaskFragmentInfoChanged(taskFragmentInfo);
+ }
+ }
+
+ @Override
+ public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ mFragmentInfos.remove(taskFragmentInfo.getFragmentToken());
+ mFragmentLeashes.remove(taskFragmentInfo.getFragmentToken());
+ mFragmentParentConfigs.remove(taskFragmentInfo.getFragmentToken());
+
+ if (mCallback != null) {
+ mCallback.onTaskFragmentVanished(taskFragmentInfo);
+ }
+ }
+
+ @Override
+ public void onTaskFragmentParentInfoChanged(
+ @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {
+ mFragmentParentConfigs.put(fragmentToken, parentConfig);
+
+ if (mCallback != null) {
+ mCallback.onTaskFragmentParentInfoChanged(fragmentToken, parentConfig);
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
new file mode 100644
index 000000000000..06e7d1457417
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -0,0 +1,86 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+
+/**
+ * Client-side descriptor of a split that holds two containers.
+ */
+class SplitContainer {
+ private final TaskFragmentContainer mPrimaryContainer;
+ private final TaskFragmentContainer mSecondaryContainer;
+ private final SplitRule mSplitRule;
+
+ SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
+ @NonNull Activity primaryActivity,
+ @NonNull TaskFragmentContainer secondaryContainer,
+ @NonNull SplitRule splitRule) {
+ mPrimaryContainer = primaryContainer;
+ mSecondaryContainer = secondaryContainer;
+ mSplitRule = splitRule;
+
+ if (shouldFinishPrimaryWithSecondary(splitRule)) {
+ if (mPrimaryContainer.getRunningActivityCount() == 1
+ && mPrimaryContainer.hasActivity(primaryActivity.getActivityToken())) {
+ mSecondaryContainer.addContainerToFinishOnExit(mPrimaryContainer);
+ } else {
+ // Only adding the activity to be finished vs. the entire TaskFragment while
+ // the secondary container exits because there are other unrelated activities in the
+ // primary TaskFragment.
+ mSecondaryContainer.addActivityToFinishOnExit(primaryActivity);
+ }
+ }
+ if (shouldFinishSecondaryWithPrimary(splitRule)) {
+ mPrimaryContainer.addContainerToFinishOnExit(mSecondaryContainer);
+ }
+ }
+
+ @NonNull
+ TaskFragmentContainer getPrimaryContainer() {
+ return mPrimaryContainer;
+ }
+
+ @NonNull
+ TaskFragmentContainer getSecondaryContainer() {
+ return mSecondaryContainer;
+ }
+
+ @NonNull
+ SplitRule getSplitRule() {
+ return mSplitRule;
+ }
+
+ boolean isPlaceholderContainer() {
+ return (mSplitRule instanceof SplitPlaceholderRule);
+ }
+
+ static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
+ final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule;
+ final boolean shouldFinishPrimaryWithSecondary = (splitRule instanceof SplitPairRule)
+ && ((SplitPairRule) splitRule).shouldFinishPrimaryWithSecondary();
+ return shouldFinishPrimaryWithSecondary || isPlaceholderContainer;
+ }
+
+ static boolean shouldFinishSecondaryWithPrimary(@NonNull SplitRule splitRule) {
+ final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule;
+ final boolean shouldFinishSecondaryWithPrimary = (splitRule instanceof SplitPairRule)
+ && ((SplitPairRule) splitRule).shouldFinishSecondaryWithPrimary();
+ return shouldFinishSecondaryWithPrimary || isPlaceholderContainer;
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
new file mode 100644
index 000000000000..20515e71a91b
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -0,0 +1,802 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityClient;
+import android.app.ActivityOptions;
+import android.app.ActivityThread;
+import android.app.Application.ActivityLifecycleCallbacks;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.window.TaskFragmentAppearedInfo;
+import android.window.TaskFragmentInfo;
+import android.window.WindowContainerTransaction;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Main controller class that manages split states and presentation.
+ */
+public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
+ ActivityEmbeddingComponent {
+
+ private final SplitPresenter mPresenter;
+
+ // Currently applied split configuration.
+ private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
+ private final List<TaskFragmentContainer> mContainers = new ArrayList<>();
+ private final List<SplitContainer> mSplitContainers = new ArrayList<>();
+
+ // Callback to Jetpack to notify about changes to split states.
+ private @NonNull Consumer<List<SplitInfo>> mEmbeddingCallback;
+ private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
+
+ public SplitController() {
+ mPresenter = new SplitPresenter(new MainThreadExecutor(), this);
+ ActivityThread activityThread = ActivityThread.currentActivityThread();
+ // Register a callback to be notified about activities being created.
+ activityThread.getApplication().registerActivityLifecycleCallbacks(
+ new LifecycleCallbacks());
+ // Intercept activity starts to route activities to new containers if necessary.
+ Instrumentation instrumentation = activityThread.getInstrumentation();
+ instrumentation.addMonitor(new ActivityStartMonitor());
+ }
+
+ /** Updates the embedding rules applied to future activity launches. */
+ @Override
+ public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) {
+ mSplitRules.clear();
+ mSplitRules.addAll(rules);
+ }
+
+ @NonNull
+ public List<EmbeddingRule> getSplitRules() {
+ return mSplitRules;
+ }
+
+ /**
+ * Starts an activity to side of the launchingActivity with the provided split config.
+ */
+ public void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent,
+ @Nullable Bundle options, @NonNull SplitRule sideRule,
+ @NonNull Consumer<Exception> failureCallback) {
+ try {
+ mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule);
+ } catch (Exception e) {
+ failureCallback.accept(e);
+ }
+ }
+
+ /**
+ * Registers the split organizer callback to notify about changes to active splits.
+ */
+ @Override
+ public void setSplitInfoCallback(@NonNull Consumer<List<SplitInfo>> callback) {
+ mEmbeddingCallback = callback;
+ updateCallbackIfNecessary();
+ }
+
+ @Override
+ public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) {
+ TaskFragmentContainer container = getContainer(
+ taskFragmentAppearedInfo.getTaskFragmentInfo().getFragmentToken());
+ if (container == null) {
+ return;
+ }
+
+ container.setInfo(taskFragmentAppearedInfo.getTaskFragmentInfo());
+ if (container.isFinished()) {
+ mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+ }
+ updateCallbackIfNecessary();
+ }
+
+ @Override
+ public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
+ if (container == null) {
+ return;
+ }
+
+ container.setInfo(taskFragmentInfo);
+ // Check if there are no running activities - consider the container empty if there are no
+ // non-finishing activities left.
+ if (!taskFragmentInfo.hasRunningActivity()) {
+ // Do not finish the dependents if this TaskFragment was cleared due to launching
+ // activity in the Task.
+ final boolean shouldFinishDependent =
+ !taskFragmentInfo.isTaskClearedForReuse();
+ mPresenter.cleanupContainer(container, shouldFinishDependent);
+ }
+ updateCallbackIfNecessary();
+ }
+
+ @Override
+ public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
+ if (container == null) {
+ return;
+ }
+
+ mPresenter.cleanupContainer(container, true /* shouldFinishDependent */);
+ updateCallbackIfNecessary();
+ }
+
+ @Override
+ public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
+ @NonNull Configuration parentConfig) {
+ TaskFragmentContainer container = getContainer(fragmentToken);
+ if (container != null) {
+ mPresenter.updateContainer(container);
+ updateCallbackIfNecessary();
+ }
+ }
+
+ void onActivityCreated(@NonNull Activity launchedActivity) {
+ handleActivityCreated(launchedActivity);
+ updateCallbackIfNecessary();
+ }
+
+ /**
+ * Checks if the activity start should be routed to a particular container. It can create a new
+ * container for the activity and a new split container if necessary.
+ */
+ // TODO(b/190433398): Break down into smaller functions.
+ void handleActivityCreated(@NonNull Activity launchedActivity) {
+ final List<EmbeddingRule> splitRules = getSplitRules();
+ final TaskFragmentContainer currentContainer = getContainerWithActivity(
+ launchedActivity.getActivityToken());
+
+ // Check if the activity is configured to always be expanded.
+ if (shouldExpand(launchedActivity, null, splitRules)) {
+ if (shouldContainerBeExpanded(currentContainer)) {
+ // Make sure that the existing container is expanded
+ mPresenter.expandTaskFragment(currentContainer.getTaskFragmentToken());
+ } else {
+ // Put activity into a new expanded container
+ final TaskFragmentContainer newContainer = newContainer(launchedActivity);
+ mPresenter.expandActivity(newContainer.getTaskFragmentToken(),
+ launchedActivity);
+ }
+ return;
+ }
+
+ // Check if activity requires a placeholder
+ if (launchPlaceholderIfNecessary(launchedActivity)) {
+ return;
+ }
+
+ // TODO(b/190433398): Check if it is a placeholder and there is already another split
+ // created by the primary activity. This is necessary for the case when the primary activity
+ // launched another secondary in the split, but the placeholder was still launched by the
+ // logic above. We didn't prevent the placeholder launcher because we didn't know that
+ // another secondary activity is coming up.
+
+ // Check if the activity should form a split with the activity below in the same task
+ // fragment.
+ Activity activityBelow = null;
+ if (currentContainer != null) {
+ final List<Activity> containerActivities = currentContainer.collectActivities();
+ final int index = containerActivities.indexOf(launchedActivity);
+ if (index > 0) {
+ activityBelow = containerActivities.get(index - 1);
+ }
+ }
+ if (activityBelow == null) {
+ IBinder belowToken = ActivityClient.getInstance().getActivityTokenBelow(
+ launchedActivity.getActivityToken());
+ if (belowToken != null) {
+ activityBelow = ActivityThread.currentActivityThread().getActivity(belowToken);
+ }
+ }
+ if (activityBelow == null) {
+ return;
+ }
+
+ // Check if the split is already set.
+ final TaskFragmentContainer activityBelowContainer = getContainerWithActivity(
+ activityBelow.getActivityToken());
+ if (currentContainer != null && activityBelowContainer != null) {
+ final SplitContainer existingSplit = getActiveSplitForContainers(currentContainer,
+ activityBelowContainer);
+ if (existingSplit != null) {
+ // There is already an active split with the activity below.
+ return;
+ }
+ }
+
+ final SplitPairRule splitPairRule = getSplitRule(activityBelow, launchedActivity,
+ splitRules);
+ if (splitPairRule == null) {
+ return;
+ }
+
+ mPresenter.createNewSplitContainer(activityBelow, launchedActivity,
+ splitPairRule);
+ }
+
+ private void onActivityConfigurationChanged(@NonNull Activity activity) {
+ final TaskFragmentContainer currentContainer = getContainerWithActivity(
+ activity.getActivityToken());
+
+ if (currentContainer != null) {
+ // Changes to activities in controllers are handled in
+ // onTaskFragmentParentInfoChanged
+ return;
+ }
+
+ // Check if activity requires a placeholder
+ launchPlaceholderIfNecessary(activity);
+ }
+
+ /**
+ * Returns a container that this activity is registered with. An activity can only belong to one
+ * container, or no container at all.
+ */
+ @Nullable
+ TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
+ for (TaskFragmentContainer container : mContainers) {
+ if (container.hasActivity(activityToken)) {
+ return container;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Creates and registers a new organized container with an optional activity that will be
+ * re-parented to it in a WCT.
+ */
+ TaskFragmentContainer newContainer(@Nullable Activity activity) {
+ TaskFragmentContainer container = new TaskFragmentContainer(activity);
+ mContainers.add(container);
+ return container;
+ }
+
+ /**
+ * Creates and registers a new split with the provided containers and configuration. Finishes
+ * existing secondary containers if found for the given primary container.
+ */
+ void registerSplit(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity,
+ @NonNull TaskFragmentContainer secondaryContainer,
+ @NonNull SplitRule splitRule) {
+ if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
+ removeExistingSecondaryContainers(wct, primaryContainer);
+ }
+ SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity,
+ secondaryContainer, splitRule);
+ mSplitContainers.add(splitContainer);
+ }
+
+ /**
+ * Removes the container from bookkeeping records.
+ */
+ void removeContainer(@NonNull TaskFragmentContainer container) {
+ // Remove all split containers that included this one
+ mContainers.remove(container);
+ List<SplitContainer> containersToRemove = new ArrayList<>();
+ for (SplitContainer splitContainer : mSplitContainers) {
+ if (container.equals(splitContainer.getSecondaryContainer())
+ || container.equals(splitContainer.getPrimaryContainer())) {
+ containersToRemove.add(splitContainer);
+ }
+ }
+ mSplitContainers.removeAll(containersToRemove);
+ }
+
+ /**
+ * Removes a secondary container for the given primary container if an existing split is
+ * already registered.
+ */
+ void removeExistingSecondaryContainers(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer primaryContainer) {
+ // If the primary container was already in a split - remove the secondary container that
+ // is now covered by the new one that replaced it.
+ final SplitContainer existingSplitContainer = getActiveSplitForContainer(
+ primaryContainer);
+ if (existingSplitContainer == null
+ || primaryContainer == existingSplitContainer.getSecondaryContainer()) {
+ return;
+ }
+
+ existingSplitContainer.getSecondaryContainer().finish(
+ false /* shouldFinishDependent */, mPresenter, wct, this);
+ }
+
+ /**
+ * Returns the topmost not finished container.
+ */
+ @Nullable
+ TaskFragmentContainer getTopActiveContainer() {
+ for (int i = mContainers.size() - 1; i >= 0; i--) {
+ TaskFragmentContainer container = mContainers.get(i);
+ if (!container.isFinished()) {
+ return container;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Updates the presentation of the container. If the container is part of the split or should
+ * have a placeholder, it will also update the other part of the split.
+ */
+ void updateContainer(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container) {
+ if (launchPlaceholderIfNecessary(container)) {
+ // Placeholder was launched, the positions will be updated when the activity is added
+ // to the secondary container.
+ return;
+ }
+ if (shouldContainerBeExpanded(container)) {
+ if (container.getInfo() != null) {
+ mPresenter.expandTaskFragment(wct, container.getTaskFragmentToken());
+ }
+ // If the info is not available yet the task fragment will be expanded when it's ready
+ return;
+ }
+ SplitContainer splitContainer = getActiveSplitForContainer(container);
+ if (splitContainer == null) {
+ return;
+ }
+ if (splitContainer != mSplitContainers.get(mSplitContainers.size() - 1)) {
+ // Skip position update - it isn't the topmost split.
+ return;
+ }
+ if (splitContainer.getPrimaryContainer().isEmpty()
+ || splitContainer.getSecondaryContainer().isEmpty()) {
+ // Skip position update - one or both containers are empty.
+ return;
+ }
+ if (dismissPlaceholderIfNecessary(splitContainer)) {
+ // Placeholder was finished, the positions will be updated when its container is emptied
+ return;
+ }
+ mPresenter.updateSplitContainer(splitContainer, container, wct);
+ }
+
+ /**
+ * Returns the top active split container that has the provided container, if available.
+ */
+ @Nullable
+ private SplitContainer getActiveSplitForContainer(@NonNull TaskFragmentContainer container) {
+ for (int i = mSplitContainers.size() - 1; i >= 0; i--) {
+ SplitContainer splitContainer = mSplitContainers.get(i);
+ if (container.equals(splitContainer.getSecondaryContainer())
+ || container.equals(splitContainer.getPrimaryContainer())) {
+ return splitContainer;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the active split that has the provided containers as primary and secondary or as
+ * secondary and primary, if available.
+ */
+ @Nullable
+ private SplitContainer getActiveSplitForContainers(
+ @NonNull TaskFragmentContainer firstContainer,
+ @NonNull TaskFragmentContainer secondContainer) {
+ for (int i = mSplitContainers.size() - 1; i >= 0; i--) {
+ SplitContainer splitContainer = mSplitContainers.get(i);
+ final TaskFragmentContainer primary = splitContainer.getPrimaryContainer();
+ final TaskFragmentContainer secondary = splitContainer.getSecondaryContainer();
+ if ((firstContainer == secondary && secondContainer == primary)
+ || (firstContainer == primary && secondContainer == secondary)) {
+ return splitContainer;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Checks if the container requires a placeholder and launches it if necessary.
+ */
+ private boolean launchPlaceholderIfNecessary(@NonNull TaskFragmentContainer container) {
+ final Activity topActivity = container.getTopNonFinishingActivity();
+ if (topActivity == null) {
+ return false;
+ }
+
+ return launchPlaceholderIfNecessary(topActivity);
+ }
+
+ boolean launchPlaceholderIfNecessary(@NonNull Activity activity) {
+ final TaskFragmentContainer container = getContainerWithActivity(
+ activity.getActivityToken());
+
+ SplitContainer splitContainer = container != null ? getActiveSplitForContainer(container)
+ : null;
+ if (splitContainer != null && container.equals(splitContainer.getPrimaryContainer())) {
+ // Don't launch placeholder in primary split container
+ return false;
+ }
+
+ // Check if there is enough space for launch
+ final SplitPlaceholderRule placeholderRule = getPlaceholderRule(activity);
+ if (placeholderRule == null || !mPresenter.shouldShowSideBySide(
+ mPresenter.getParentContainerBounds(activity), placeholderRule)) {
+ return false;
+ }
+
+ // TODO(b/190433398): Handle failed request
+ startActivityToSide(activity, placeholderRule.getPlaceholderIntent(), null,
+ placeholderRule, null);
+ return true;
+ }
+
+ private boolean dismissPlaceholderIfNecessary(@NonNull SplitContainer splitContainer) {
+ if (!splitContainer.isPlaceholderContainer()) {
+ return false;
+ }
+
+ if (mPresenter.shouldShowSideBySide(splitContainer)) {
+ return false;
+ }
+
+ mPresenter.cleanupContainer(splitContainer.getSecondaryContainer(),
+ false /* shouldFinishDependent */);
+ return true;
+ }
+
+ /**
+ * Returns the rule to launch a placeholder for the activity with the provided component name
+ * if it is configured in the split config.
+ */
+ private SplitPlaceholderRule getPlaceholderRule(@NonNull Activity activity) {
+ for (EmbeddingRule rule : mSplitRules) {
+ if (!(rule instanceof SplitPlaceholderRule)) {
+ continue;
+ }
+ SplitPlaceholderRule placeholderRule = (SplitPlaceholderRule) rule;
+ if (placeholderRule.matchesActivity(activity)) {
+ return placeholderRule;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Notifies listeners about changes to split states if necessary.
+ */
+ private void updateCallbackIfNecessary() {
+ if (mEmbeddingCallback == null) {
+ return;
+ }
+ if (!allActivitiesCreated()) {
+ return;
+ }
+ List<SplitInfo> currentSplitStates = getActiveSplitStates();
+ if (mLastReportedSplitStates.equals(currentSplitStates)) {
+ return;
+ }
+ mLastReportedSplitStates.clear();
+ mLastReportedSplitStates.addAll(currentSplitStates);
+ mEmbeddingCallback.accept(currentSplitStates);
+ }
+
+ /**
+ * Returns a list of descriptors for currently active split states.
+ */
+ private List<SplitInfo> getActiveSplitStates() {
+ List<SplitInfo> splitStates = new ArrayList<>();
+ for (SplitContainer container : mSplitContainers) {
+ if (container.getPrimaryContainer().isEmpty()
+ || container.getSecondaryContainer().isEmpty()) {
+ // Skipping containers that do not have any activities to report.
+ continue;
+ }
+ ActivityStack primaryContainer = container.getPrimaryContainer().toActivityStack();
+ ActivityStack secondaryContainer = container.getSecondaryContainer().toActivityStack();
+ SplitInfo splitState = new SplitInfo(primaryContainer,
+ secondaryContainer,
+ // Splits that are not showing side-by-side are reported as having 0 split
+ // ratio, since by definition in the API the primary container occupies no
+ // width of the split when covered by the secondary.
+ mPresenter.shouldShowSideBySide(container)
+ ? container.getSplitRule().getSplitRatio()
+ : 0.0f);
+ splitStates.add(splitState);
+ }
+ return splitStates;
+ }
+
+ /**
+ * Checks if all activities that are registered with the containers have already appeared in
+ * the client.
+ */
+ private boolean allActivitiesCreated() {
+ for (TaskFragmentContainer container : mContainers) {
+ if (container.getInfo() == null
+ || container.getInfo().getActivities().size()
+ != container.collectActivities().size()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns {@code true} if the container is expanded to occupy full task size.
+ * Returns {@code false} if the container is included in an active split.
+ */
+ boolean shouldContainerBeExpanded(@Nullable TaskFragmentContainer container) {
+ if (container == null) {
+ return false;
+ }
+ for (SplitContainer splitContainer : mSplitContainers) {
+ if (container.equals(splitContainer.getPrimaryContainer())
+ || container.equals(splitContainer.getSecondaryContainer())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns a split rule for the provided pair of primary activity and secondary activity intent
+ * if available.
+ */
+ @Nullable
+ private static SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
+ @NonNull Intent secondaryActivityIntent, @NonNull List<EmbeddingRule> splitRules) {
+ for (EmbeddingRule rule : splitRules) {
+ if (!(rule instanceof SplitPairRule)) {
+ continue;
+ }
+ SplitPairRule pairRule = (SplitPairRule) rule;
+ if (pairRule.matchesActivityIntentPair(primaryActivity, secondaryActivityIntent)) {
+ return pairRule;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a split rule for the provided pair of primary and secondary activities if available.
+ */
+ @Nullable
+ private static SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
+ @NonNull Activity secondaryActivity, @NonNull List<EmbeddingRule> splitRules) {
+ for (EmbeddingRule rule : splitRules) {
+ if (!(rule instanceof SplitPairRule)) {
+ continue;
+ }
+ SplitPairRule pairRule = (SplitPairRule) rule;
+ final Intent intent = secondaryActivity.getIntent();
+ if (pairRule.matchesActivityPair(primaryActivity, secondaryActivity)
+ && (intent == null
+ || pairRule.matchesActivityIntentPair(primaryActivity, intent))) {
+ return pairRule;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
+ for (TaskFragmentContainer container : mContainers) {
+ if (container.getTaskFragmentToken().equals(fragmentToken)) {
+ return container;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns {@code true} if an Activity with the provided component name should always be
+ * expanded to occupy full task bounds. Such activity must not be put in a split.
+ */
+ private static boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent,
+ List<EmbeddingRule> splitRules) {
+ if (splitRules == null) {
+ return false;
+ }
+ for (EmbeddingRule rule : splitRules) {
+ if (!(rule instanceof ActivityRule)) {
+ continue;
+ }
+ ActivityRule activityRule = (ActivityRule) rule;
+ if (!activityRule.shouldAlwaysExpand()) {
+ continue;
+ }
+ if (activity != null && activityRule.matchesActivity(activity)) {
+ return true;
+ } else if (intent != null && activityRule.matchesIntent(intent)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private final class LifecycleCallbacks implements ActivityLifecycleCallbacks {
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+ }
+
+ @Override
+ public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
+ // Calling after Activity#onCreate is complete to allow the app launch something
+ // first. In case of a configured placeholder activity we want to make sure
+ // that we don't launch it if an activity itself already requested something to be
+ // launched to side.
+ SplitController.this.onActivityCreated(activity);
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) {
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+ }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+ }
+
+ @Override
+ public void onActivityConfigurationChanged(Activity activity) {
+ SplitController.this.onActivityConfigurationChanged(activity);
+ }
+ }
+
+ /** Executor that posts on the main application thread. */
+ private static class MainThreadExecutor implements Executor {
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ @Override
+ public void execute(Runnable r) {
+ mHandler.post(r);
+ }
+ }
+
+ /**
+ * A monitor that intercepts all activity start requests originating in the client process and
+ * can amend them to target a specific task fragment to form a split.
+ */
+ private class ActivityStartMonitor extends Instrumentation.ActivityMonitor {
+
+ @Override
+ public Instrumentation.ActivityResult onStartActivity(@NonNull Context who,
+ @NonNull Intent intent, @NonNull Bundle options) {
+ // TODO(b/190433398): Check if the activity is configured to always be expanded.
+
+ // Check if activity should be put in a split with the activity that launched it.
+ if (!(who instanceof Activity)) {
+ return super.onStartActivity(who, intent, options);
+ }
+ final Activity launchingActivity = (Activity) who;
+
+ if (shouldExpand(null, intent, getSplitRules())) {
+ setLaunchingInExpandedContainer(launchingActivity, options);
+ } else if (!setLaunchingToSideContainer(launchingActivity, intent, options)) {
+ setLaunchingInSameContainer(launchingActivity, intent, options);
+ }
+
+ return super.onStartActivity(who, intent, options);
+ }
+
+ private void setLaunchingInExpandedContainer(Activity launchingActivity, Bundle options) {
+ TaskFragmentContainer newContainer = mPresenter.createNewExpandedContainer(
+ launchingActivity);
+
+ // Amend the request to let the WM know that the activity should be placed in the
+ // dedicated container.
+ options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
+ newContainer.getTaskFragmentToken());
+ }
+
+ /**
+ * Returns {@code true} if the activity that is going to be started via the
+ * {@code intent} should be paired with the {@code launchingActivity} and is set to be
+ * launched in an empty side container.
+ */
+ private boolean setLaunchingToSideContainer(Activity launchingActivity, Intent intent,
+ Bundle options) {
+ final SplitPairRule splitPairRule = getSplitRule(launchingActivity, intent,
+ getSplitRules());
+ if (splitPairRule == null) {
+ return false;
+ }
+
+ // Create a new split with an empty side container
+ final TaskFragmentContainer secondaryContainer = mPresenter
+ .createNewSplitWithEmptySideContainer(launchingActivity, splitPairRule);
+
+ // Amend the request to let the WM know that the activity should be placed in the
+ // dedicated container.
+ options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
+ secondaryContainer.getTaskFragmentToken());
+ return true;
+ }
+
+ /**
+ * Checks if the activity that is going to be started via the {@code intent} should be
+ * paired with the existing top activity which is currently paired with the
+ * {@code launchingActivity}. If so, set the activity to be launched in the same
+ * container of the {@code launchingActivity}.
+ */
+ private void setLaunchingInSameContainer(Activity launchingActivity, Intent intent,
+ Bundle options) {
+ final TaskFragmentContainer launchingContainer = getContainerWithActivity(
+ launchingActivity.getActivityToken());
+ if (launchingContainer == null) {
+ return;
+ }
+
+ final SplitContainer splitContainer = getActiveSplitForContainer(launchingContainer);
+ if (splitContainer == null) {
+ return;
+ }
+
+ if (splitContainer.getSecondaryContainer() != launchingContainer) {
+ return;
+ }
+
+ // The launching activity is on the secondary container. Retrieve the primary
+ // activity from the other container.
+ Activity primaryActivity =
+ splitContainer.getPrimaryContainer().getTopNonFinishingActivity();
+ if (primaryActivity == null) {
+ return;
+ }
+
+ final SplitPairRule splitPairRule = getSplitRule(primaryActivity, intent,
+ getSplitRules());
+ if (splitPairRule == null) {
+ return;
+ }
+
+ // Amend the request to let the WM know that the activity should be placed in the
+ // dedicated container. This is necessary for the case that the activity is started
+ // into a new Task, or new Task will be escaped from the current host Task and be
+ // displayed in fullscreen.
+ options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
+ launchingContainer.getTaskFragmentToken());
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
new file mode 100644
index 000000000000..81be21cbd7aa
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -0,0 +1,412 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.LayoutDirection;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowMetrics;
+import android.window.TaskFragmentCreationParams;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Controls the visual presentation of the splits according to the containers formed by
+ * {@link SplitController}.
+ */
+class SplitPresenter extends JetpackTaskFragmentOrganizer {
+ private static final int POSITION_START = 0;
+ private static final int POSITION_END = 1;
+ private static final int POSITION_FILL = 2;
+
+ @IntDef(value = {
+ POSITION_START,
+ POSITION_END,
+ POSITION_FILL,
+ })
+ private @interface Position {}
+
+ private final SplitController mController;
+
+ SplitPresenter(@NonNull Executor executor, SplitController controller) {
+ super(executor, controller);
+ mController = controller;
+ registerOrganizer();
+ }
+
+ /**
+ * Updates the presentation of the provided container.
+ */
+ void updateContainer(TaskFragmentContainer container) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mController.updateContainer(wct, container);
+ applyTransaction(wct);
+ }
+
+ /**
+ * Deletes the specified container and all other associated and dependent containers in the same
+ * transaction.
+ */
+ void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ container.finish(shouldFinishDependent, this, wct, mController);
+
+ final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer();
+ if (newTopContainer != null) {
+ mController.updateContainer(wct, newTopContainer);
+ }
+
+ applyTransaction(wct);
+ }
+
+ /**
+ * Creates a new split with the primary activity and an empty secondary container.
+ * @return The newly created secondary container.
+ */
+ TaskFragmentContainer createNewSplitWithEmptySideContainer(@NonNull Activity primaryActivity,
+ @NonNull SplitPairRule rule) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ final Rect parentBounds = getParentContainerBounds(primaryActivity);
+ final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
+ isLtr(primaryActivity, rule));
+ final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
+ primaryActivity, primaryRectBounds, null);
+
+ // Create new empty task fragment
+ final TaskFragmentContainer secondaryContainer = mController.newContainer(null);
+ final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
+ rule, isLtr(primaryActivity, rule));
+ createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
+ primaryActivity.getActivityToken(), secondaryRectBounds,
+ WINDOWING_MODE_MULTI_WINDOW);
+ secondaryContainer.setLastRequestedBounds(secondaryRectBounds);
+
+ // Set adjacent to each other so that the containers below will be invisible.
+ setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
+ secondaryContainer.getTaskFragmentToken(), rule);
+
+ mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
+
+ applyTransaction(wct);
+
+ return secondaryContainer;
+ }
+
+ /**
+ * Creates a new split container with the two provided activities.
+ * @param primaryActivity An activity that should be in the primary container. If it is not
+ * currently in an existing container, a new one will be created and the
+ * activity will be re-parented to it.
+ * @param secondaryActivity An activity that should be in the secondary container. If it is not
+ * currently in an existing container, or if it is currently in the
+ * same container as the primary activity, a new container will be
+ * created and the activity will be re-parented to it.
+ * @param rule The split rule to be applied to the container.
+ */
+ void createNewSplitContainer(@NonNull Activity primaryActivity,
+ @NonNull Activity secondaryActivity, @NonNull SplitPairRule rule) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ final Rect parentBounds = getParentContainerBounds(primaryActivity);
+ final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
+ isLtr(primaryActivity, rule));
+ final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
+ primaryActivity, primaryRectBounds, null);
+
+ final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
+ isLtr(primaryActivity, rule));
+ final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct,
+ secondaryActivity, secondaryRectBounds, primaryContainer);
+
+ // Set adjacent to each other so that the containers below will be invisible.
+ setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
+ secondaryContainer.getTaskFragmentToken(), rule);
+
+ mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
+
+ applyTransaction(wct);
+ }
+
+ /**
+ * Creates a new expanded container.
+ */
+ TaskFragmentContainer createNewExpandedContainer(@NonNull Activity launchingActivity) {
+ final TaskFragmentContainer newContainer = mController.newContainer(null);
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ createTaskFragment(wct, newContainer.getTaskFragmentToken(),
+ launchingActivity.getActivityToken(), new Rect(), WINDOWING_MODE_MULTI_WINDOW);
+
+ applyTransaction(wct);
+ return newContainer;
+ }
+
+ /**
+ * Creates a new container or resizes an existing container for activity to the provided bounds.
+ * @param activity The activity to be re-parented to the container if necessary.
+ * @param containerToAvoid Re-parent from this container if an activity is already in it.
+ */
+ private TaskFragmentContainer prepareContainerForActivity(
+ @NonNull WindowContainerTransaction wct, @NonNull Activity activity,
+ @NonNull Rect bounds, @Nullable TaskFragmentContainer containerToAvoid) {
+ TaskFragmentContainer container = mController.getContainerWithActivity(
+ activity.getActivityToken());
+ if (container == null || container == containerToAvoid) {
+ container = mController.newContainer(activity);
+
+ final TaskFragmentCreationParams fragmentOptions =
+ createFragmentOptions(
+ container.getTaskFragmentToken(),
+ activity.getActivityToken(),
+ bounds,
+ WINDOWING_MODE_MULTI_WINDOW);
+ wct.createTaskFragment(fragmentOptions);
+
+ wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(),
+ activity.getActivityToken());
+
+ container.setLastRequestedBounds(bounds);
+ } else {
+ resizeTaskFragmentIfRegistered(wct, container, bounds);
+ }
+
+ return container;
+ }
+
+ /**
+ * Starts a new activity to the side, creating a new split container. A new container will be
+ * created for the activity that will be started.
+ * @param launchingActivity An activity that should be in the primary container. If it is not
+ * currently in an existing container, a new one will be created and
+ * the activity will be re-parented to it.
+ * @param activityIntent The intent to start the new activity.
+ * @param activityOptions The options to apply to new activity start.
+ * @param rule The split rule to be applied to the container.
+ */
+ void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent activityIntent,
+ @Nullable Bundle activityOptions, @NonNull SplitRule rule) {
+ final Rect parentBounds = getParentContainerBounds(launchingActivity);
+ final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
+ isLtr(launchingActivity, rule));
+ final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
+ isLtr(launchingActivity, rule));
+
+ TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
+ launchingActivity.getActivityToken());
+ if (primaryContainer == null) {
+ primaryContainer = mController.newContainer(launchingActivity);
+ }
+
+ TaskFragmentContainer secondaryContainer = mController.newContainer(null);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
+ rule);
+ startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
+ launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds,
+ activityIntent, activityOptions, rule);
+ applyTransaction(wct);
+
+ primaryContainer.setLastRequestedBounds(primaryRectBounds);
+ secondaryContainer.setLastRequestedBounds(secondaryRectBounds);
+ }
+
+ /**
+ * Updates the positions of containers in an existing split.
+ * @param splitContainer The split container to be updated.
+ * @param updatedContainer The task fragment that was updated and caused this split update.
+ * @param wct WindowContainerTransaction that this update should be performed with.
+ */
+ void updateSplitContainer(@NonNull SplitContainer splitContainer,
+ @NonNull TaskFragmentContainer updatedContainer,
+ @NonNull WindowContainerTransaction wct) {
+ // Getting the parent bounds using the updated container - it will have the recent value.
+ final Rect parentBounds = getParentContainerBounds(updatedContainer);
+ final SplitRule rule = splitContainer.getSplitRule();
+ final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer();
+ final Activity activity = primaryContainer.getTopNonFinishingActivity();
+ if (activity == null) {
+ return;
+ }
+ final boolean isLtr = isLtr(activity, rule);
+ final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
+ isLtr);
+ final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
+ isLtr);
+
+ // If the task fragments are not registered yet, the positions will be updated after they
+ // are created again.
+ resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds);
+ final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
+ resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
+
+ setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
+ secondaryContainer.getTaskFragmentToken(), rule);
+ }
+
+ /**
+ * Resizes the task fragment if it was already registered. Skips the operation if the container
+ * creation has not been reported from the server yet.
+ */
+ // TODO(b/190433398): Handle resize if the fragment hasn't appeared yet.
+ void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container,
+ @Nullable Rect bounds) {
+ if (container.getInfo() == null) {
+ return;
+ }
+ resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds);
+ }
+
+ @Override
+ void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
+ @Nullable Rect bounds) {
+ TaskFragmentContainer container = mController.getContainer(fragmentToken);
+ if (container == null) {
+ throw new IllegalStateException(
+ "Resizing a task fragment that is not registered with controller.");
+ }
+
+ if (container.areLastRequestedBoundsEqual(bounds)) {
+ // Return early if the provided bounds were already requested
+ return;
+ }
+
+ container.setLastRequestedBounds(bounds);
+ super.resizeTaskFragment(wct, fragmentToken, bounds);
+ }
+
+ boolean shouldShowSideBySide(@NonNull SplitContainer splitContainer) {
+ final Rect parentBounds = getParentContainerBounds(splitContainer.getPrimaryContainer());
+ return shouldShowSideBySide(parentBounds, splitContainer.getSplitRule());
+ }
+
+ boolean shouldShowSideBySide(@Nullable Rect parentBounds, @NonNull SplitRule rule) {
+ // TODO(b/190433398): Supply correct insets.
+ final WindowMetrics parentMetrics = new WindowMetrics(parentBounds,
+ new WindowInsets(new Rect()));
+ return rule.checkParentMetrics(parentMetrics);
+ }
+
+ @NonNull
+ private Rect getBoundsForPosition(@Position int position, @NonNull Rect parentBounds,
+ @NonNull SplitRule rule, boolean isLtr) {
+ if (!shouldShowSideBySide(parentBounds, rule)) {
+ return new Rect();
+ }
+
+ final float splitRatio = rule.getSplitRatio();
+ final float rtlSplitRatio = 1 - splitRatio;
+ switch (position) {
+ case POSITION_START:
+ return isLtr ? getLeftContainerBounds(parentBounds, splitRatio)
+ : getRightContainerBounds(parentBounds, rtlSplitRatio);
+ case POSITION_END:
+ return isLtr ? getRightContainerBounds(parentBounds, splitRatio)
+ : getLeftContainerBounds(parentBounds, rtlSplitRatio);
+ case POSITION_FILL:
+ return parentBounds;
+ }
+ return parentBounds;
+ }
+
+ private Rect getLeftContainerBounds(@NonNull Rect parentBounds, float splitRatio) {
+ return new Rect(
+ parentBounds.left,
+ parentBounds.top,
+ (int) (parentBounds.left + parentBounds.width() * splitRatio),
+ parentBounds.bottom);
+ }
+
+ private Rect getRightContainerBounds(@NonNull Rect parentBounds, float splitRatio) {
+ return new Rect(
+ (int) (parentBounds.left + parentBounds.width() * splitRatio),
+ parentBounds.top,
+ parentBounds.right,
+ parentBounds.bottom);
+ }
+
+ /**
+ * Checks if a split with the provided rule should be displays in left-to-right layout
+ * direction, either always or with the current configuration.
+ */
+ private boolean isLtr(@NonNull Context context, @NonNull SplitRule rule) {
+ switch (rule.getLayoutDirection()) {
+ case LayoutDirection.LOCALE:
+ return context.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_LTR;
+ case LayoutDirection.RTL:
+ return false;
+ case LayoutDirection.LTR:
+ default:
+ return true;
+ }
+ }
+
+ @NonNull
+ Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) {
+ final Configuration parentConfig = mFragmentParentConfigs.get(
+ container.getTaskFragmentToken());
+ if (parentConfig != null) {
+ return parentConfig.windowConfiguration.getBounds();
+ }
+
+ // If there is no parent yet - then assuming that activities are running in full task bounds
+ final Activity topActivity = container.getTopNonFinishingActivity();
+ final Rect bounds = topActivity != null ? getParentContainerBounds(topActivity) : null;
+
+ if (bounds == null) {
+ throw new IllegalStateException("Unknown parent bounds");
+ }
+ return bounds;
+ }
+
+ @NonNull
+ Rect getParentContainerBounds(@NonNull Activity activity) {
+ final TaskFragmentContainer container = mController.getContainerWithActivity(
+ activity.getActivityToken());
+ if (container != null) {
+ final Configuration parentConfig = mFragmentParentConfigs.get(
+ container.getTaskFragmentToken());
+ if (parentConfig != null) {
+ return parentConfig.windowConfiguration.getBounds();
+ }
+ }
+
+ // TODO(b/190433398): Check if the client-side available info about parent bounds is enough.
+ if (!activity.isInMultiWindowMode()) {
+ // In fullscreen mode the max bounds should correspond to the task bounds.
+ return activity.getResources().getConfiguration().windowConfiguration.getMaxBounds();
+ }
+ return activity.getResources().getConfiguration().windowConfiguration.getBounds();
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
new file mode 100644
index 000000000000..194b6330d92c
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
@@ -0,0 +1,189 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.graphics.Matrix.MSCALE_X;
+
+import android.graphics.Rect;
+import android.view.Choreographer;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Wrapper to handle the TaskFragment animation update in one {@link SurfaceControl.Transaction}.
+ *
+ * The base adapter can be used for {@link RemoteAnimationTarget} that is simple open/close.
+ */
+class TaskFragmentAnimationAdapter {
+ final Animation mAnimation;
+ final RemoteAnimationTarget mTarget;
+ final SurfaceControl mLeash;
+
+ final Transformation mTransformation = new Transformation();
+ final float[] mMatrix = new float[9];
+ private boolean mIsFirstFrame = true;
+
+ TaskFragmentAnimationAdapter(@NonNull Animation animation,
+ @NonNull RemoteAnimationTarget target) {
+ this(animation, target, target.leash);
+ }
+
+ /**
+ * @param leash the surface to animate.
+ */
+ TaskFragmentAnimationAdapter(@NonNull Animation animation,
+ @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash) {
+ mAnimation = animation;
+ mTarget = target;
+ mLeash = leash;
+ }
+
+ /** Called on frame update. */
+ final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
+ if (mIsFirstFrame) {
+ t.show(mLeash);
+ mIsFirstFrame = false;
+ }
+
+ // Extract the transformation to the current time.
+ mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()),
+ mTransformation);
+ t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+ onAnimationUpdateInner(t);
+ }
+
+ /** To be overridden by subclasses to adjust the animation surface change. */
+ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ mTransformation.getMatrix().postTranslate(
+ mTarget.localBounds.left, mTarget.localBounds.top);
+ t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+ t.setAlpha(mLeash, mTransformation.getAlpha());
+ }
+
+ /** Called after animation finished. */
+ final void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
+ onAnimationUpdate(t, mAnimation.getDuration());
+ }
+
+ final long getDurationHint() {
+ return mAnimation.computeDurationHint();
+ }
+
+ /**
+ * Should be used when the {@link RemoteAnimationTarget} is in split with others, and want to
+ * animate together as one. This adapter will offset the animation leash to make the animate of
+ * two windows look like a single window.
+ */
+ static class SplitAdapter extends TaskFragmentAnimationAdapter {
+ private final boolean mIsLeftHalf;
+ private final int mWholeAnimationWidth;
+
+ /**
+ * @param isLeftHalf whether this is the left half of the animation.
+ * @param wholeAnimationWidth the whole animation windows width.
+ */
+ SplitAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target,
+ boolean isLeftHalf, int wholeAnimationWidth) {
+ super(animation, target);
+ mIsLeftHalf = isLeftHalf;
+ mWholeAnimationWidth = wholeAnimationWidth;
+ if (wholeAnimationWidth == 0) {
+ throw new IllegalArgumentException("SplitAdapter must provide wholeAnimationWidth");
+ }
+ }
+
+ @Override
+ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ float posX = mTarget.localBounds.left;
+ final float posY = mTarget.localBounds.top;
+ // This window is half of the whole animation window. Offset left/right to make it
+ // look as one with the other half.
+ mTransformation.getMatrix().getValues(mMatrix);
+ final int targetWidth = mTarget.localBounds.width();
+ final float scaleX = mMatrix[MSCALE_X];
+ final float totalOffset = mWholeAnimationWidth * (1 - scaleX) / 2;
+ final float curOffset = targetWidth * (1 - scaleX) / 2;
+ final float offsetDiff = totalOffset - curOffset;
+ if (mIsLeftHalf) {
+ posX += offsetDiff;
+ } else {
+ posX -= offsetDiff;
+ }
+ mTransformation.getMatrix().postTranslate(posX, posY);
+ t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+ t.setAlpha(mLeash, mTransformation.getAlpha());
+ }
+ }
+
+ /**
+ * Should be used for the animation of the snapshot of a {@link RemoteAnimationTarget} that has
+ * size change.
+ */
+ static class SnapshotAdapter extends TaskFragmentAnimationAdapter {
+
+ SnapshotAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
+ // Start leash is the snapshot of the starting surface.
+ super(animation, target, target.startLeash);
+ }
+
+ @Override
+ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ // Snapshot should always be placed at the top left of the animation leash.
+ mTransformation.getMatrix().postTranslate(0, 0);
+ t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+ t.setAlpha(mLeash, mTransformation.getAlpha());
+ }
+ }
+
+ /**
+ * Should be used for the animation of the {@link RemoteAnimationTarget} that has size change.
+ */
+ static class BoundsChangeAdapter extends TaskFragmentAnimationAdapter {
+ private final float[] mVecs = new float[4];
+ private final Rect mRect = new Rect();
+
+ BoundsChangeAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
+ super(animation, target);
+ }
+
+ @Override
+ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+ mTransformation.getMatrix().postTranslate(
+ mTarget.localBounds.left, mTarget.localBounds.top);
+ t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+ t.setAlpha(mLeash, mTransformation.getAlpha());
+
+ // The following applies an inverse scale to the clip-rect so that it crops "after" the
+ // scale instead of before.
+ mVecs[1] = mVecs[2] = 0;
+ mVecs[0] = mVecs[3] = 1;
+ mTransformation.getMatrix().mapVectors(mVecs);
+ mVecs[0] = 1.f / mVecs[0];
+ mVecs[3] = 1.f / mVecs[3];
+ final Rect clipRect = mTransformation.getClipRect();
+ mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f);
+ mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f);
+ mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f);
+ mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f);
+ t.setWindowCrop(mLeash, mRect);
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
new file mode 100644
index 000000000000..535dac1a5101
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -0,0 +1,60 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+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 android.util.Log;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationDefinition;
+import android.window.TaskFragmentOrganizer;
+
+/** Controls the TaskFragment remote animations. */
+class TaskFragmentAnimationController {
+
+ private static final String TAG = "TaskFragAnimationCtrl";
+ static final boolean DEBUG = false;
+
+ private final TaskFragmentOrganizer mOrganizer;
+ private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner();
+
+ TaskFragmentAnimationController(TaskFragmentOrganizer organizer) {
+ mOrganizer = organizer;
+ }
+
+ void registerRemoteAnimations() {
+ if (DEBUG) {
+ Log.v(TAG, "registerRemoteAnimations");
+ }
+ final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
+ final RemoteAnimationAdapter animationAdapter =
+ new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */);
+ definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
+ mOrganizer.registerRemoteAnimations(definition);
+ }
+
+ void unregisterRemoteAnimations() {
+ if (DEBUG) {
+ Log.v(TAG, "unregisterRemoteAnimations");
+ }
+ mOrganizer.unregisterRemoteAnimations();
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
new file mode 100644
index 000000000000..412559e34070
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -0,0 +1,264 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+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 android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+
+/** To run the TaskFragment animations. */
+class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
+
+ private static final String TAG = "TaskFragAnimationRunner";
+ private final Handler mHandler = new Handler(Looper.myLooper());
+ private final TaskFragmentAnimationSpec mAnimationSpec;
+
+ TaskFragmentAnimationRunner() {
+ mAnimationSpec = new TaskFragmentAnimationSpec(mHandler);
+ }
+
+ @Nullable
+ private Animator mAnimator;
+
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ @NonNull RemoteAnimationTarget[] apps,
+ @NonNull RemoteAnimationTarget[] wallpapers,
+ @NonNull RemoteAnimationTarget[] nonApps,
+ @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
+ if (wallpapers.length != 0 || nonApps.length != 0) {
+ throw new IllegalArgumentException("TaskFragment shouldn't handle animation with"
+ + "wallpaper or non-app windows.");
+ }
+ if (TaskFragmentAnimationController.DEBUG) {
+ Log.v(TAG, "onAnimationStart transit=" + transit);
+ }
+ mHandler.post(() -> startAnimation(transit, apps, finishedCallback));
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ if (TaskFragmentAnimationController.DEBUG) {
+ Log.v(TAG, "onAnimationCancelled");
+ }
+ mHandler.post(this::cancelAnimation);
+ }
+
+ /** Creates and starts animation. */
+ private void startAnimation(@WindowManager.TransitionOldType int transit,
+ @NonNull RemoteAnimationTarget[] targets,
+ @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
+ if (mAnimator != null) {
+ Log.w(TAG, "start new animation when the previous one is not finished yet.");
+ mAnimator.cancel();
+ }
+ mAnimator = createAnimator(transit, targets, finishedCallback);
+ mAnimator.start();
+ }
+
+ /** Cancels animation. */
+ private void cancelAnimation() {
+ if (mAnimator == null) {
+ return;
+ }
+ mAnimator.cancel();
+ mAnimator = null;
+ }
+
+ /** Creates the animator given the transition type and windows. */
+ private Animator createAnimator(@WindowManager.TransitionOldType int transit,
+ @NonNull RemoteAnimationTarget[] targets,
+ @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
+ final List<TaskFragmentAnimationAdapter> adapters =
+ createAnimationAdapters(transit, targets);
+ long duration = 0;
+ for (TaskFragmentAnimationAdapter adapter : adapters) {
+ duration = Math.max(duration, adapter.getDurationHint());
+ }
+ final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+ animator.setDuration(duration);
+ animator.addUpdateListener((anim) -> {
+ // Update all adapters in the same transaction.
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (TaskFragmentAnimationAdapter adapter : adapters) {
+ adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
+ }
+ t.apply();
+ });
+ animator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {}
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (TaskFragmentAnimationAdapter adapter : adapters) {
+ adapter.onAnimationEnd(t);
+ }
+ t.apply();
+
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ mAnimator = null;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ });
+ return animator;
+ }
+
+ /** List of {@link TaskFragmentAnimationAdapter} to handle animations on all window targets. */
+ private List<TaskFragmentAnimationAdapter> createAnimationAdapters(
+ @WindowManager.TransitionOldType int transit,
+ @NonNull RemoteAnimationTarget[] targets) {
+ switch (transit) {
+ case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
+ return createOpenAnimationAdapters(targets);
+ case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
+ return createCloseAnimationAdapters(targets);
+ case TRANSIT_OLD_TASK_FRAGMENT_CHANGE:
+ return createChangeAnimationAdapters(targets);
+ default:
+ throw new IllegalArgumentException("Unhandled transit type=" + transit);
+ }
+ }
+
+ private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
+ @NonNull RemoteAnimationTarget[] targets) {
+ return createOpenCloseAnimationAdapters(targets,
+ mAnimationSpec::loadOpenAnimation);
+ }
+
+ private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
+ @NonNull RemoteAnimationTarget[] targets) {
+ return createOpenCloseAnimationAdapters(targets,
+ mAnimationSpec::loadCloseAnimation);
+ }
+
+ private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters(
+ @NonNull RemoteAnimationTarget[] targets,
+ @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider) {
+ // We need to know if the target window is only a partial of the whole animation screen.
+ // If so, we will need to adjust it to make the whole animation screen looks like one.
+ final List<RemoteAnimationTarget> openingTargets = new ArrayList<>();
+ final List<RemoteAnimationTarget> closingTargets = new ArrayList<>();
+ final Rect openingWholeScreenBounds = new Rect();
+ final Rect closingWholeScreenBounds = new Rect();
+ for (RemoteAnimationTarget target : targets) {
+ if (target.mode != MODE_CLOSING) {
+ openingTargets.add(target);
+ openingWholeScreenBounds.union(target.localBounds);
+ } else {
+ closingTargets.add(target);
+ closingWholeScreenBounds.union(target.localBounds);
+ }
+ }
+
+ final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
+ for (RemoteAnimationTarget target : openingTargets) {
+ adapters.add(createOpenCloseAnimationAdapter(target, animationProvider,
+ openingWholeScreenBounds));
+ }
+ for (RemoteAnimationTarget target : closingTargets) {
+ adapters.add(createOpenCloseAnimationAdapter(target, animationProvider,
+ closingWholeScreenBounds));
+ }
+ return adapters;
+ }
+
+ private TaskFragmentAnimationAdapter createOpenCloseAnimationAdapter(
+ @NonNull RemoteAnimationTarget target,
+ @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider,
+ @NonNull Rect wholeAnimationBounds) {
+ final Animation animation = animationProvider.apply(target, wholeAnimationBounds);
+ final Rect targetBounds = target.localBounds;
+ if (targetBounds.left == wholeAnimationBounds.left
+ && targetBounds.right != wholeAnimationBounds.right) {
+ // This is the left split of the whole animation window.
+ return new TaskFragmentAnimationAdapter.SplitAdapter(animation, target,
+ true /* isLeftHalf */, wholeAnimationBounds.width());
+ } else if (targetBounds.left != wholeAnimationBounds.left
+ && targetBounds.right == wholeAnimationBounds.right) {
+ // This is the right split of the whole animation window.
+ return new TaskFragmentAnimationAdapter.SplitAdapter(animation, target,
+ false /* isLeftHalf */, wholeAnimationBounds.width());
+ }
+ // Open/close window that fills the whole animation.
+ return new TaskFragmentAnimationAdapter(animation, target);
+ }
+
+ private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
+ @NonNull RemoteAnimationTarget[] targets) {
+ final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
+ for (RemoteAnimationTarget target : targets) {
+ if (target.startBounds != null) {
+ // This is the target with bounds change.
+ final Animation[] animations =
+ mAnimationSpec.createChangeBoundsChangeAnimations(target);
+ // Adapter for the starting snapshot leash.
+ adapters.add(new TaskFragmentAnimationAdapter.SnapshotAdapter(
+ animations[0], target));
+ // Adapter for the ending bounds changed leash.
+ adapters.add(new TaskFragmentAnimationAdapter.BoundsChangeAdapter(
+ animations[1], target));
+ continue;
+ }
+
+ // These are the other targets that don't have bounds change in the same transition.
+ final Animation animation;
+ if (target.hasAnimatingParent) {
+ // No-op if it will be covered by the changing parent window.
+ animation = TaskFragmentAnimationSpec.createNoopAnimation(target);
+ } else if (target.mode == MODE_CLOSING) {
+ animation = mAnimationSpec.createChangeBoundsCloseAnimation(target);
+ } else {
+ animation = mAnimationSpec.createChangeBoundsOpenAnimation(target);
+ }
+ adapters.add(new TaskFragmentAnimationAdapter(animation, target));
+ }
+ return adapters;
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
new file mode 100644
index 000000000000..c0908a548501
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -0,0 +1,215 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+
+import android.app.ActivityThread;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.provider.Settings;
+import android.view.RemoteAnimationTarget;
+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.LinearInterpolator;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.TranslateAnimation;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.internal.policy.AttributeCache;
+import com.android.internal.policy.TransitionAnimation;
+
+/** Animation spec for TaskFragment transition. */
+class TaskFragmentAnimationSpec {
+
+ private static final String TAG = "TaskFragAnimationSpec";
+ private static final int CHANGE_ANIMATION_DURATION = 517;
+ private static final int CHANGE_ANIMATION_FADE_DURATION = 80;
+ private static final int CHANGE_ANIMATION_FADE_OFFSET = 30;
+
+ private final Context mContext;
+ private final TransitionAnimation mTransitionAnimation;
+ private final Interpolator mFastOutExtraSlowInInterpolator;
+ private final LinearInterpolator mLinearInterpolator;
+ private float mTransitionAnimationScaleSetting;
+
+ TaskFragmentAnimationSpec(@NonNull Handler handler) {
+ mContext = ActivityThread.currentActivityThread().getApplication();
+ mTransitionAnimation = new TransitionAnimation(mContext, false /* debug */, TAG);
+ // Initialize the AttributeCache for the TransitionAnimation.
+ AttributeCache.init(mContext);
+ mFastOutExtraSlowInInterpolator = AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_extra_slow_in);
+ mLinearInterpolator = new LinearInterpolator();
+
+ // The transition animation should be adjusted based on the developer option.
+ final ContentResolver resolver = mContext.getContentResolver();
+ mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
+ Settings.Global.TRANSITION_ANIMATION_SCALE,
+ mContext.getResources().getFloat(
+ R.dimen.config_appTransitionAnimationDurationScaleDefault));
+ resolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
+ new SettingsObserver(handler));
+ }
+
+ /** For target that doesn't need to be animated. */
+ static Animation createNoopAnimation(@NonNull RemoteAnimationTarget target) {
+ // Noop but just keep the target showing/hiding.
+ final float alpha = target.mode == MODE_CLOSING ? 0f : 1f;
+ return new AlphaAnimation(alpha, alpha);
+ }
+
+ /** Animation for target that is opening in a change transition. */
+ Animation createChangeBoundsOpenAnimation(@NonNull RemoteAnimationTarget target) {
+ final Rect bounds = target.localBounds;
+ // The target will be animated in from left or right depends on its position.
+ final int startLeft = bounds.left == 0 ? -bounds.width() : bounds.width();
+
+ // The position should be 0-based as we will post translate in
+ // TaskFragmentAnimationAdapter#onAnimationUpdate
+ final Animation animation = new TranslateAnimation(startLeft, 0, 0, 0);
+ animation.setInterpolator(mFastOutExtraSlowInInterpolator);
+ animation.setDuration(CHANGE_ANIMATION_DURATION);
+ animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
+ animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ return animation;
+ }
+
+ /** Animation for target that is closing in a change transition. */
+ Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
+ final Rect bounds = target.localBounds;
+ // The target will be animated out to left or right depends on its position.
+ final int endLeft = bounds.left == 0 ? -bounds.width() : bounds.width();
+
+ // The position should be 0-based as we will post translate in
+ // TaskFragmentAnimationAdapter#onAnimationUpdate
+ final Animation animation = new TranslateAnimation(0, endLeft, 0, 0);
+ animation.setInterpolator(mFastOutExtraSlowInInterpolator);
+ animation.setDuration(CHANGE_ANIMATION_DURATION);
+ animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
+ animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ return animation;
+ }
+
+ /**
+ * Animation for target that is changing (bounds change) in a change transition.
+ * @return the return array always has two elements. The first one is for the start leash, and
+ * the second one is for the end leash.
+ */
+ Animation[] createChangeBoundsChangeAnimations(@NonNull RemoteAnimationTarget target) {
+ // Both start bounds and end bounds are in screen coordinates. We will post translate
+ // to the local coordinates in TaskFragmentAnimationAdapter#onAnimationUpdate
+ final Rect startBounds = target.startBounds;
+ final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
+ final Rect endBounds = target.screenSpaceBounds;
+ float scaleX = ((float) startBounds.width()) / endBounds.width();
+ float scaleY = ((float) startBounds.height()) / endBounds.height();
+ // Start leash is a child of the end leash. Reverse the scale so that the start leash won't
+ // be scaled up with its parent.
+ float startScaleX = 1.f / scaleX;
+ float startScaleY = 1.f / scaleY;
+
+ // The start leash will be fade out.
+ final AnimationSet startSet = new AnimationSet(false /* shareInterpolator */);
+ final Animation startAlpha = new AlphaAnimation(1f, 0f);
+ startAlpha.setInterpolator(mLinearInterpolator);
+ startAlpha.setDuration(CHANGE_ANIMATION_FADE_DURATION);
+ startAlpha.setStartOffset(CHANGE_ANIMATION_FADE_OFFSET);
+ startSet.addAnimation(startAlpha);
+ final Animation startScale = new ScaleAnimation(startScaleX, startScaleX, startScaleY,
+ startScaleY);
+ startScale.setInterpolator(mFastOutExtraSlowInInterpolator);
+ startScale.setDuration(CHANGE_ANIMATION_DURATION);
+ startSet.addAnimation(startScale);
+ startSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(),
+ endBounds.height());
+ startSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+
+ // The end leash will be moved into the end position while scaling.
+ final AnimationSet endSet = new AnimationSet(true /* shareInterpolator */);
+ endSet.setInterpolator(mFastOutExtraSlowInInterpolator);
+ final Animation endScale = new ScaleAnimation(scaleX, 1, scaleY, 1);
+ endScale.setDuration(CHANGE_ANIMATION_DURATION);
+ endSet.addAnimation(endScale);
+ // The position should be 0-based as we will post translate in
+ // TaskFragmentAnimationAdapter#onAnimationUpdate
+ final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
+ 0, 0);
+ endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
+ endSet.addAnimation(endTranslate);
+ // The end leash is resizing, we should update the window crop based on the clip rect.
+ final Rect startClip = new Rect(startBounds);
+ final Rect endClip = new Rect(endBounds);
+ startClip.offsetTo(0, 0);
+ endClip.offsetTo(0, 0);
+ final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
+ clipAnim.setDuration(CHANGE_ANIMATION_DURATION);
+ endSet.addAnimation(clipAnim);
+ endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(),
+ parentBounds.height());
+ endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+
+ return new Animation[]{startSet, endSet};
+ }
+
+ Animation loadOpenAnimation(@NonNull RemoteAnimationTarget target,
+ @NonNull Rect wholeAnimationBounds) {
+ final boolean isEnter = target.mode != MODE_CLOSING;
+ final Animation animation = mTransitionAnimation.loadDefaultAnimationAttr(isEnter
+ ? R.styleable.WindowAnimation_activityOpenEnterAnimation
+ : R.styleable.WindowAnimation_activityOpenExitAnimation);
+ animation.initialize(target.localBounds.width(), target.localBounds.height(),
+ wholeAnimationBounds.width(), wholeAnimationBounds.height());
+ animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ return animation;
+ }
+
+ Animation loadCloseAnimation(@NonNull RemoteAnimationTarget target,
+ @NonNull Rect wholeAnimationBounds) {
+ final boolean isEnter = target.mode != MODE_CLOSING;
+ final Animation animation = mTransitionAnimation.loadDefaultAnimationAttr(isEnter
+ ? R.styleable.WindowAnimation_activityCloseEnterAnimation
+ : R.styleable.WindowAnimation_activityCloseExitAnimation);
+ animation.initialize(target.localBounds.width(), target.localBounds.height(),
+ wholeAnimationBounds.width(), wholeAnimationBounds.height());
+ animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ return animation;
+ }
+
+ private class SettingsObserver extends ContentObserver {
+ SettingsObserver(@NonNull Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mTransitionAnimationScaleSetting = Settings.Global.getFloat(
+ mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE,
+ mTransitionAnimationScaleSetting);
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
new file mode 100644
index 000000000000..80d9c2c1719c
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -0,0 +1,270 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityThread;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.IBinder;
+import android.window.TaskFragmentInfo;
+import android.window.WindowContainerTransaction;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Client-side container for a stack of activities. Corresponds to an instance of TaskFragment
+ * on the server side.
+ */
+class TaskFragmentContainer {
+ /**
+ * Client-created token that uniquely identifies the task fragment container instance.
+ */
+ @NonNull
+ private final IBinder mToken;
+
+ /**
+ * Server-provided task fragment information.
+ */
+ private TaskFragmentInfo mInfo;
+
+ /**
+ * Activities that are being reparented or being started to this container, but haven't been
+ * added to {@link #mInfo} yet.
+ */
+ private final ArrayList<Activity> mPendingAppearedActivities = new ArrayList<>();
+
+ /** Containers that are dependent on this one and should be completely destroyed on exit. */
+ private final List<TaskFragmentContainer> mContainersToFinishOnExit =
+ new ArrayList<>();
+
+ /** Individual associated activities in different containers that should be finished on exit. */
+ private final List<Activity> mActivitiesToFinishOnExit = new ArrayList<>();
+
+ /** Indicates whether the container was cleaned up after the last activity was removed. */
+ private boolean mIsFinished;
+
+ /**
+ * Bounds that were requested last via {@link android.window.WindowContainerTransaction}.
+ */
+ private final Rect mLastRequestedBounds = new Rect();
+
+ /**
+ * Creates a container with an existing activity that will be re-parented to it in a window
+ * container transaction.
+ */
+ TaskFragmentContainer(@Nullable Activity activity) {
+ mToken = new Binder("TaskFragmentContainer");
+ if (activity != null) {
+ addPendingAppearedActivity(activity);
+ }
+ }
+
+ /**
+ * Returns the client-created token that uniquely identifies this container.
+ */
+ @NonNull
+ IBinder getTaskFragmentToken() {
+ return mToken;
+ }
+
+ /** List of activities that belong to this container and live in this process. */
+ @NonNull
+ List<Activity> collectActivities() {
+ // Add the re-parenting activity, in case the server has not yet reported the task
+ // fragment info update with it placed in this container. We still want to apply rules
+ // in this intermediate state.
+ List<Activity> allActivities = new ArrayList<>();
+ if (!mPendingAppearedActivities.isEmpty()) {
+ allActivities.addAll(mPendingAppearedActivities);
+ }
+ // Add activities reported from the server.
+ if (mInfo == null) {
+ return allActivities;
+ }
+ ActivityThread activityThread = ActivityThread.currentActivityThread();
+ for (IBinder token : mInfo.getActivities()) {
+ Activity activity = activityThread.getActivity(token);
+ if (activity != null && !allActivities.contains(activity)) {
+ allActivities.add(activity);
+ }
+ }
+ return allActivities;
+ }
+
+ ActivityStack toActivityStack() {
+ return new ActivityStack(collectActivities(), mInfo.getRunningActivityCount() == 0);
+ }
+
+ void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
+ mPendingAppearedActivities.add(pendingAppearedActivity);
+ }
+
+ boolean hasActivity(@NonNull IBinder token) {
+ if (mInfo != null && mInfo.getActivities().contains(token)) {
+ return true;
+ }
+ for (Activity activity : mPendingAppearedActivities) {
+ if (activity.getActivityToken().equals(token)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ int getRunningActivityCount() {
+ int count = mPendingAppearedActivities.size();
+ if (mInfo != null) {
+ count += mInfo.getRunningActivityCount();
+ }
+ return count;
+ }
+
+ @Nullable
+ TaskFragmentInfo getInfo() {
+ return mInfo;
+ }
+
+ void setInfo(@NonNull TaskFragmentInfo info) {
+ mInfo = info;
+ if (mInfo == null || mPendingAppearedActivities.isEmpty()) {
+ return;
+ }
+ // Cleanup activities that were being re-parented
+ List<IBinder> infoActivities = mInfo.getActivities();
+ for (int i = mPendingAppearedActivities.size() - 1; i >= 0; --i) {
+ final Activity activity = mPendingAppearedActivities.get(i);
+ if (infoActivities.contains(activity.getActivityToken())) {
+ mPendingAppearedActivities.remove(i);
+ }
+ }
+ }
+
+ @Nullable
+ Activity getTopNonFinishingActivity() {
+ List<Activity> activities = collectActivities();
+ if (activities.isEmpty()) {
+ return null;
+ }
+ int i = activities.size() - 1;
+ while (i >= 0 && activities.get(i).isFinishing()) {
+ i--;
+ }
+ return i >= 0 ? activities.get(i) : null;
+ }
+
+ boolean isEmpty() {
+ return mPendingAppearedActivities.isEmpty() && (mInfo == null || mInfo.isEmpty());
+ }
+
+ /**
+ * Adds a container that should be finished when this container is finished.
+ */
+ void addContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToFinish) {
+ mContainersToFinishOnExit.add(containerToFinish);
+ }
+
+ /**
+ * Adds an activity that should be finished when this container is finished.
+ */
+ void addActivityToFinishOnExit(@NonNull Activity activityToFinish) {
+ mActivitiesToFinishOnExit.add(activityToFinish);
+ }
+
+ /**
+ * Removes all activities that belong to this process and finishes other containers/activities
+ * configured to finish together.
+ */
+ void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
+ @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
+ if (!mIsFinished) {
+ mIsFinished = true;
+ finishActivities(shouldFinishDependent, presenter, wct, controller);
+ }
+
+ if (mInfo == null) {
+ // Defer removal the container and wait until TaskFragment appeared.
+ return;
+ }
+
+ // Cleanup the visuals
+ presenter.deleteTaskFragment(wct, getTaskFragmentToken());
+ // Cleanup the records
+ controller.removeContainer(this);
+ // Clean up task fragment information
+ mInfo = null;
+ }
+
+ private void finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
+ @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
+ // Finish own activities
+ for (Activity activity : collectActivities()) {
+ if (!activity.isFinishing()) {
+ activity.finish();
+ }
+ }
+
+ if (!shouldFinishDependent) {
+ return;
+ }
+
+ // Finish dependent containers
+ for (TaskFragmentContainer container : mContainersToFinishOnExit) {
+ container.finish(true /* shouldFinishDependent */, presenter,
+ wct, controller);
+ }
+ mContainersToFinishOnExit.clear();
+
+ // Finish associated activities
+ for (Activity activity : mActivitiesToFinishOnExit) {
+ activity.finish();
+ }
+ mActivitiesToFinishOnExit.clear();
+
+ // Finish activities that were being re-parented to this container.
+ for (Activity activity : mPendingAppearedActivities) {
+ activity.finish();
+ }
+ mPendingAppearedActivities.clear();
+ }
+
+ boolean isFinished() {
+ return mIsFinished;
+ }
+
+ /**
+ * Checks if last requested bounds are equal to the provided value.
+ */
+ boolean areLastRequestedBoundsEqual(@Nullable Rect bounds) {
+ return (bounds == null && mLastRequestedBounds.isEmpty())
+ || mLastRequestedBounds.equals(bounds);
+ }
+
+ /**
+ * Updates the last requested bounds.
+ */
+ void setLastRequestedBounds(@Nullable Rect bounds) {
+ if (bounds == null) {
+ mLastRequestedBounds.setEmpty();
+ } else {
+ mLastRequestedBounds.set(bounds);
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index a0d5b004ff1c..383d91da6af8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.extensions;
+package androidx.window.extensions.layout;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -36,19 +36,27 @@ import androidx.window.util.DataProducer;
import androidx.window.util.PriorityDataProducer;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
/**
- * Reference implementation of androidx.window.extensions OEM interface for use with
+ * Reference implementation of androidx.window.extensions.layout OEM interface for use with
* WindowManager Jetpack.
*
* NOTE: This version is a work in progress and under active development. It MUST NOT be used in
* production builds since the interface can still change before reaching stable version.
* Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead.
*/
-class SampleExtensionImpl extends StubExtension {
+public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private static final String TAG = "SampleExtension";
+ private static WindowLayoutComponent sInstance;
+
+ private final Map<Activity, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
+ new HashMap<>();
private final SettingsDevicePostureProducer mSettingsDevicePostureProducer;
private final DataProducer<Integer> mDevicePostureProducer;
@@ -56,7 +64,7 @@ class SampleExtensionImpl extends StubExtension {
private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer;
private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer;
- SampleExtensionImpl(Context context) {
+ public WindowLayoutComponentImpl(Context context) {
mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context);
mDevicePostureProducer = new PriorityDataProducer<>(List.of(
mSettingsDevicePostureProducer,
@@ -73,28 +81,68 @@ class SampleExtensionImpl extends StubExtension {
mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
}
+ /**
+ * Adds a listener interested in receiving updates to {@link WindowLayoutInfo}
+ * @param activity hosting a {@link android.view.Window}
+ * @param consumer interested in receiving updates to {@link WindowLayoutInfo}
+ */
+ public void addWindowLayoutInfoListener(@NonNull Activity activity,
+ @NonNull Consumer<WindowLayoutInfo> consumer) {
+ mWindowLayoutChangeListeners.put(activity, consumer);
+ updateRegistrations();
+ }
+
+ /**
+ * Removes a listener no longer interested in receiving updates.
+ * @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo}
+ */
+ public void removeWindowLayoutInfoListener(
+ @NonNull Consumer<WindowLayoutInfo> consumer) {
+ mWindowLayoutChangeListeners.values().remove(consumer);
+ updateRegistrations();
+ }
+
+ void updateWindowLayout(@NonNull Activity activity,
+ @NonNull WindowLayoutInfo newLayout) {
+ Consumer<WindowLayoutInfo> consumer = mWindowLayoutChangeListeners.get(activity);
+ if (consumer != null) {
+ consumer.accept(newLayout);
+ }
+ }
+
+ @NonNull
+ Set<Activity> getActivitiesListeningForLayoutChanges() {
+ return mWindowLayoutChangeListeners.keySet();
+ }
+
+ protected boolean hasListeners() {
+ return !mWindowLayoutChangeListeners.isEmpty();
+ }
+
private int getFeatureState(DisplayFeature feature) {
Integer featureState = feature.getState();
Optional<Integer> posture = mDevicePostureProducer.getData();
- int fallbackPosture = posture.orElse(ExtensionFoldingFeature.STATE_FLAT);
+ int fallbackPosture = posture.orElse(FoldingFeature.STATE_FLAT);
return featureState == null ? fallbackPosture : featureState;
}
private void onDisplayFeaturesChanged() {
for (Activity activity : getActivitiesListeningForLayoutChanges()) {
- ExtensionWindowLayoutInfo newLayout = getWindowLayoutInfo(activity);
+ WindowLayoutInfo newLayout = getWindowLayoutInfo(activity);
updateWindowLayout(activity, newLayout);
}
}
@NonNull
- private ExtensionWindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) {
- List<ExtensionDisplayFeature> displayFeatures = getDisplayFeatures(activity);
- return new ExtensionWindowLayoutInfo(displayFeatures);
+ private WindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) {
+ List<androidx.window.extensions.layout.DisplayFeature> displayFeatures =
+ getDisplayFeatures(activity);
+ return new WindowLayoutInfo(displayFeatures);
}
- private List<ExtensionDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
- List<ExtensionDisplayFeature> features = new ArrayList<>();
+ private List<androidx.window.extensions.layout.DisplayFeature> getDisplayFeatures(
+ @NonNull Activity activity) {
+ List<androidx.window.extensions.layout.DisplayFeature> features = new ArrayList<>();
int displayId = activity.getDisplay().getDisplayId();
if (displayId != DEFAULT_DISPLAY) {
Log.w(TAG, "This sample doesn't support display features on secondary displays");
@@ -115,15 +163,14 @@ class SampleExtensionImpl extends StubExtension {
rotateRectToDisplayRotation(displayId, featureRect);
transformToWindowSpaceRect(activity, featureRect);
- features.add(new ExtensionFoldingFeature(featureRect, baseFeature.getType(),
+ features.add(new FoldingFeature(featureRect, baseFeature.getType(),
getFeatureState(baseFeature)));
}
}
return features;
}
- @Override
- protected void onListenersChanged() {
+ private void updateRegistrations() {
if (hasListeners()) {
mSettingsDevicePostureProducer.registerObserversIfNeeded();
mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
index e6f8388b031f..62959b7b95e9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
@@ -28,7 +28,7 @@ public class SidecarProvider {
* an OEM by overriding this method.
*/
public static SidecarInterface getSidecarImpl(Context context) {
- return new SampleSidecarImpl(context);
+ return new SampleSidecarImpl(context.getApplicationContext());
}
/**
@@ -36,6 +36,6 @@ public class SidecarProvider {
* @return API version string in MAJOR.MINOR.PATCH-description format.
*/
public static String getApiVersion() {
- return "0.1.0-settings_sample";
+ return "1.0.0-reference";
}
}
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index be6652d43fb2..830d13dd6dc5 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 9aaef3b1f655..3ba1a34bd432 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -39,6 +39,14 @@ filegroup {
}
filegroup {
+ name: "wm_shell_util-sources",
+ srcs: [
+ "src/com/android/wm/shell/util/**/*.java",
+ ],
+ path: "src",
+}
+
+filegroup {
name: "wm_shell-aidls",
srcs: [
"src/**/*.aidl",
diff --git a/packages/overlays/NoCutoutOverlay/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/color/split_divider_background.xml
index 6ae5ffd32bf8..329e5b9b31a0 100644
--- a/packages/overlays/NoCutoutOverlay/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/color/split_divider_background.xml
@@ -1,5 +1,5 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?>
+<!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,9 +13,7 @@
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Amaga"</string>
-</resources>
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral1_500" android:lStar="35" />
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml b/libs/WindowManager/Shell/res/color/unfold_transition_background.xml
index 14672ef3dcfe..63289a3f75d9 100644
--- a/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml
+++ b/libs/WindowManager/Shell/res/color/unfold_transition_background.xml
@@ -1,22 +1,19 @@
<?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.
-->
-<inset xmlns:android="http://schemas.android.com/apk/res/android">
- <shape android:shape="rectangle">
- <corners
- android:topLeftRadius="@dimen/internet_dialog_corner_radius"
- android:topRightRadius="@dimen/internet_dialog_corner_radius"
- android:bottomLeftRadius="@dimen/internet_dialog_corner_radius"
- android:bottomRightRadius="@dimen/internet_dialog_corner_radius"/>
- <solid android:color="?android:attr/colorBackground" />
- </shape>
-</inset>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Matches taskbar color -->
+ <item android:color="@android:color/system_neutral2_500" android:lStar="35" />
+</selector>
diff --git a/libs/WindowManager/Shell/res/drawable/bubble_manage_btn_bg.xml b/libs/WindowManager/Shell/res/drawable/bubble_manage_btn_bg.xml
index 8710fb8ac69b..96d2d7c954d8 100644
--- a/libs/WindowManager/Shell/res/drawable/bubble_manage_btn_bg.xml
+++ b/libs/WindowManager/Shell/res/drawable/bubble_manage_btn_bg.xml
@@ -18,7 +18,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
- android:color="@android:color/system_neutral1_900"
+ android:color="@android:color/system_neutral1_800"
/>
<corners android:radius="20dp" />
diff --git a/packages/overlays/NoCutoutOverlay/res/values-et/strings.xml b/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml
index 4e5428dc59b8..94165a11eccb 100644
--- a/packages/overlays/NoCutoutOverlay/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml
@@ -1,5 +1,5 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?>
+<!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,9 +13,9 @@
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Peida"</string>
-</resources>
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/size_compat_hint_bubble"/>
+ <corners android:radius="@dimen/size_compat_hint_corner_radius"/>
+</shape> \ No newline at end of file
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-as/strings.xml b/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml
index db2b15a142c3..a8f0f76ef27f 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml
@@ -1,5 +1,5 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?>
+<!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,9 +13,13 @@
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"এপ্‌সমূহ কাটআউট অঞ্চলৰ তলত দেখুৱাওক"</string>
-</resources>
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/size_compat_hint_point_width"
+ android:height="8dp"
+ android:viewportWidth="10"
+ android:viewportHeight="8">
+ <path
+ android:fillColor="@color/size_compat_hint_bubble"
+ android:pathData="M10,0 l-4.1875,6.6875 a1,1 0 0,1 -1.625,0 l-4.1875,-6.6875z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
index 73a48d31a814..3e486df71f91 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
@@ -15,14 +15,21 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
<path
- android:fillColor="#aa000000"
- android:pathData="M0,12 a12,12 0 1,0 24,0 a12,12 0 1,0 -24,0" />
- <path
- android:fillColor="@android:color/white"
- android:pathData="M17.65,6.35c-1.63,-1.63 -3.94,-2.57 -6.48,-2.31c-3.67,0.37 -6.69,3.35 -7.1,7.02C3.52,15.91 7.27,20 12,20c3.19,0 5.93,-1.87 7.21,-4.57c0.31,-0.66 -0.16,-1.43 -0.89,-1.43h-0.01c-0.37,0 -0.72,0.2 -0.88,0.53c-1.13,2.43 -3.84,3.97 -6.81,3.32c-2.22,-0.49 -4.01,-2.3 -4.49,-4.52C5.31,9.44 8.26,6 12,6c1.66,0 3.14,0.69 4.22,1.78l-2.37,2.37C13.54,10.46 13.76,11 14.21,11H19c0.55,0 1,-0.45 1,-1V5.21c0,-0.45 -0.54,-0.67 -0.85,-0.35L17.65,6.35z"/>
+ android:fillColor="#53534D"
+ android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0" />
+ <group
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:fillColor="#E4E3DA"
+ android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z"/>
+ <path
+ android:fillColor="#E4E3DA"
+ android:pathData="M20,13c0,-4.42 -3.58,-8 -8,-8c-0.06,0 -0.12,0.01 -0.18,0.01v0l1.09,-1.09L11.5,2.5L8,6l3.5,3.5l1.41,-1.41l-1.08,-1.08C11.89,7.01 11.95,7 12,7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02C16.95,20.44 20,17.08 20,13z"/>
+ </group>
</vector>
diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_button.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_button.xml
index c09ae53746da..0cf6d73162d2 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_manage_button.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_manage_button.xml
@@ -17,13 +17,13 @@
<com.android.wm.shell.common.AlphaOptimizedButton
xmlns:android="http://schemas.android.com/apk/res/android"
style="@android:style/Widget.DeviceDefault.Button.Borderless"
- android:id="@+id/settings_button"
+ android:id="@+id/manage_button"
android:layout_gravity="start"
android:layout_width="wrap_content"
- android:layout_height="40dp"
- android:layout_marginTop="8dp"
- android:layout_marginLeft="16dp"
- android:layout_marginBottom="8dp"
+ android:layout_height="@dimen/bubble_manage_button_height"
+ android:layout_marginStart="@dimen/bubble_manage_button_margin"
+ android:layout_marginTop="@dimen/bubble_manage_button_margin"
+ android:layout_marginBottom="@dimen/bubble_manage_button_margin"
android:focusable="true"
android:text="@string/manage_bubbles_text"
android:textSize="@*android:dimen/text_size_body_2_material"
diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
index f4b3aca33dd7..298ad3025b00 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
@@ -25,15 +25,15 @@
android:id="@+id/bubble_manage_menu_dismiss_container"
android:background="@drawable/bubble_manage_menu_row"
android:layout_width="match_parent"
- android:layout_height="48dp"
+ android:layout_height="@dimen/bubble_menu_item_height"
android:gravity="center_vertical"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
+ android:paddingStart="@dimen/bubble_menu_padding"
+ android:paddingEnd="@dimen/bubble_menu_padding"
android:orientation="horizontal">
<ImageView
- android:layout_width="24dp"
- android:layout_height="24dp"
+ android:layout_width="@dimen/bubble_menu_icon_size"
+ android:layout_height="@dimen/bubble_menu_icon_size"
android:src="@drawable/ic_remove_no_shadow"
android:tint="@color/bubbles_icon_tint"/>
@@ -50,15 +50,15 @@
android:id="@+id/bubble_manage_menu_dont_bubble_container"
android:background="@drawable/bubble_manage_menu_row"
android:layout_width="match_parent"
- android:layout_height="48dp"
+ android:layout_height="@dimen/bubble_menu_item_height"
android:gravity="center_vertical"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
+ android:paddingStart="@dimen/bubble_menu_padding"
+ android:paddingEnd="@dimen/bubble_menu_padding"
android:orientation="horizontal">
<ImageView
- android:layout_width="24dp"
- android:layout_height="24dp"
+ android:layout_width="@dimen/bubble_menu_icon_size"
+ android:layout_height="@dimen/bubble_menu_icon_size"
android:src="@drawable/bubble_ic_stop_bubble"
android:tint="@color/bubbles_icon_tint"/>
@@ -75,16 +75,16 @@
android:id="@+id/bubble_manage_menu_settings_container"
android:background="@drawable/bubble_manage_menu_row"
android:layout_width="match_parent"
- android:layout_height="48dp"
+ android:layout_height="@dimen/bubble_menu_item_height"
android:gravity="center_vertical"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
+ android:paddingStart="@dimen/bubble_menu_padding"
+ android:paddingEnd="@dimen/bubble_menu_padding"
android:orientation="horizontal">
<ImageView
android:id="@+id/bubble_manage_menu_settings_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
+ android:layout_width="@dimen/bubble_menu_icon_size"
+ android:layout_height="@dimen/bubble_menu_icon_size"
android:src="@drawable/ic_remove_no_shadow"/>
<TextView
diff --git a/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml b/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
index fd4c3ba87026..87deb8b5a1fd 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
@@ -21,7 +21,6 @@
android:layout_width="wrap_content"
android:paddingTop="48dp"
android:paddingBottom="48dp"
- android:paddingStart="@dimen/bubble_stack_user_education_side_inset"
android:paddingEnd="16dp"
android:layout_marginEnd="24dp"
android:orientation="vertical"
diff --git a/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml b/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
index c5c42fca323d..fafe40e924f5 100644
--- a/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
@@ -23,7 +23,6 @@
android:clickable="true"
android:paddingTop="28dp"
android:paddingBottom="16dp"
- android:paddingStart="@dimen/bubble_expanded_view_padding"
android:paddingEnd="48dp"
android:layout_marginEnd="24dp"
android:orientation="vertical"
@@ -66,27 +65,21 @@
android:id="@+id/button_layout"
android:orientation="horizontal" >
- <com.android.wm.shell.common.AlphaOptimizedButton
- style="@android:style/Widget.Material.Button.Borderless"
- android:id="@+id/manage"
- android:layout_gravity="start"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:focusable="true"
- android:clickable="false"
- android:text="@string/manage_bubbles_text"
- android:textColor="@android:color/system_neutral1_900"
+ <include
+ layout="@layout/bubble_manage_button"
/>
<com.android.wm.shell.common.AlphaOptimizedButton
- style="@android:style/Widget.Material.Button.Borderless"
+ style="@android:style/Widget.DeviceDefault.Button.Borderless"
android:id="@+id/got_it"
android:layout_gravity="start"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/bubble_manage_button_height"
android:focusable="true"
android:text="@string/bubbles_user_education_got_it"
+ android:textSize="@*android:dimen/text_size_body_2_material"
android:textColor="@android:color/system_neutral1_900"
+ android:background="@drawable/bubble_manage_btn_bg"
/>
</LinearLayout>
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/layout/docked_stack_divider.xml b/libs/WindowManager/Shell/res/layout/docked_stack_divider.xml
index ed5d2e1b49f5..d732b01ce106 100644
--- a/libs/WindowManager/Shell/res/layout/docked_stack_divider.xml
+++ b/libs/WindowManager/Shell/res/layout/docked_stack_divider.xml
@@ -22,7 +22,7 @@
<View
style="@style/DockedDividerBackground"
android:id="@+id/docked_divider_background"
- android:background="@color/docked_divider_background"/>
+ android:background="@color/split_divider_background"/>
<com.android.wm.shell.legacysplitscreen.MinimizedDockShadow
style="@style/DockedDividerMinimizedShadow"
diff --git a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
index 0dea87c6b7fc..17347f627049 100644
--- a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
+++ b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
@@ -22,41 +22,34 @@
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center"
android:clipToPadding="false"
- android:padding="@dimen/bubble_elevation">
+ android:paddingBottom="5dp">
<LinearLayout
+ android:id="@+id/size_compat_hint_popup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="@android:color/background_light"
- android:elevation="@dimen/bubble_elevation"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:clickable="true">
<TextView
- android:layout_width="180dp"
+ android:layout_width="188dp"
android:layout_height="wrap_content"
- android:paddingLeft="10dp"
- android:paddingRight="10dp"
- android:paddingTop="10dp"
+ android:lineSpacingExtra="4sp"
+ android:background="@drawable/size_compat_hint_bubble"
+ android:padding="16dp"
android:text="@string/restart_button_description"
android:textAlignment="viewStart"
- android:textColor="@android:color/primary_text_light"
- android:textSize="16sp"/>
+ android:textColor="#E4E3DA"
+ android:textSize="14sp"/>
- <Button
- android:id="@+id/got_it"
+ <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:includeFontPadding="false"
android:layout_gravity="end"
- android:minHeight="36dp"
- android:background="?android:attr/selectableItemBackground"
- android:text="@string/got_it"
- android:textAllCaps="true"
- android:textColor="#3c78d8"
- android:textSize="16sp"
- android:textStyle="bold"/>
+ android:src="@drawable/size_compat_hint_point"
+ android:paddingHorizontal="@dimen/size_compat_hint_corner_radius"
+ android:contentDescription="@null"/>
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml
index cd3153145be3..47e76f061877 100644
--- a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml
+++ b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml
@@ -19,12 +19,21 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content">
- <ImageButton
- android:id="@+id/size_compat_restart_button"
- android:layout_width="@dimen/size_compat_button_size"
- android:layout_height="@dimen/size_compat_button_size"
- android:layout_gravity="center"
- android:src="@drawable/size_compat_restart_button"
- android:contentDescription="@string/restart_button_description"/>
+ <FrameLayout
+ android:layout_width="@dimen/size_compat_button_width"
+ android:layout_height="@dimen/size_compat_button_height"
+ android:clipToPadding="false"
+ android:paddingBottom="16dp">
+
+ <ImageButton
+ android:id="@+id/size_compat_restart_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:src="@drawable/size_compat_restart_button"
+ android:background="@android:color/transparent"
+ android:contentDescription="@string/restart_button_description"/>
+
+ </FrameLayout>
</com.android.wm.shell.sizecompatui.SizeCompatRestartButton>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/layout/split_decor.xml
index 2d3b506d8cad..9ffa5e8aa179 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/layout/split_decor.xml
@@ -1,5 +1,4 @@
-<?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");
@@ -13,9 +12,19 @@
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"عرض التطبيقات أسفل منطقة الصورة المقطوعة للشاشة"</string>
-</resources>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent">
+
+ <ImageView android:id="@+id/split_resizing_icon"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center"
+ android:padding="0dp"
+ android:visibility="gone"
+ android:background="@null"/>
+
+</FrameLayout>
diff --git a/libs/WindowManager/Shell/res/layout/split_divider.xml b/libs/WindowManager/Shell/res/layout/split_divider.xml
index 7f583f3e6bac..e3be700469a7 100644
--- a/libs/WindowManager/Shell/res/layout/split_divider.xml
+++ b/libs/WindowManager/Shell/res/layout/split_divider.xml
@@ -19,15 +19,25 @@
android:layout_height="match_parent"
android:layout_width="match_parent">
- <View
- style="@style/DockedDividerBackground"
- android:id="@+id/docked_divider_background"
- android:background="@color/docked_divider_background"/>
+ <FrameLayout
+ android:id="@+id/divider_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
- <com.android.wm.shell.common.split.DividerHandleView
- style="@style/DockedDividerHandle"
- android:id="@+id/docked_divider_handle"
- android:contentDescription="@string/accessibility_divider"
- android:background="@null"/>
+ <View
+ style="@style/DockedDividerBackground"
+ android:id="@+id/docked_divider_background"/>
+
+ <com.android.wm.shell.common.split.DividerHandleView
+ style="@style/DockedDividerHandle"
+ android:id="@+id/docked_divider_handle"
+ android:contentDescription="@string/accessibility_divider"
+ android:background="@null"/>
+
+ <com.android.wm.shell.common.split.DividerRoundedCorner
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+ </FrameLayout>
</com.android.wm.shell.common.split.DividerView>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-az/strings.xml b/libs/WindowManager/Shell/res/layout/split_outline.xml
index a6b7c4346e7e..6cb9ebb72790 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/layout/split_outline.xml
@@ -1,5 +1,5 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?>
+<!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,9 +13,15 @@
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
- -->
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Tətbiqləri kəsilmə sahəsinin aşağısında göstərin"</string>
-</resources>
+ <com.android.wm.shell.splitscreen.OutlineView
+ android:id="@+id/split_outline"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent" />
+
+</FrameLayout>
diff --git a/libs/WindowManager/Shell/res/values-land/dimens.xml b/libs/WindowManager/Shell/res/values-land/dimens.xml
index aafba58cef59..a95323fd4801 100644
--- a/libs/WindowManager/Shell/res/values-land/dimens.xml
+++ b/libs/WindowManager/Shell/res/values-land/dimens.xml
@@ -16,8 +16,12 @@
*/
-->
<resources>
+ <!-- Divider handle size for legacy split screen -->
<dimen name="docked_divider_handle_width">2dp</dimen>
<dimen name="docked_divider_handle_height">16dp</dimen>
+ <!-- Divider handle size for split screen -->
+ <dimen name="split_divider_handle_width">3dp</dimen>
+ <dimen name="split_divider_handle_height">72dp</dimen>
<!-- Padding between status bar and bubbles when displayed in expanded state, smaller
value in landscape since we have limited vertical space-->
diff --git a/libs/WindowManager/Shell/res/values-land/styles.xml b/libs/WindowManager/Shell/res/values-land/styles.xml
index 863bb69d4034..0ed9368aa067 100644
--- a/libs/WindowManager/Shell/res/values-land/styles.xml
+++ b/libs/WindowManager/Shell/res/values-land/styles.xml
@@ -19,10 +19,11 @@
<item name="android:layout_width">10dp</item>
<item name="android:layout_height">match_parent</item>
<item name="android:layout_gravity">center_horizontal</item>
+ <item name="android:background">@color/split_divider_background</item>
</style>
<style name="DockedDividerHandle">
- <item name="android:layout_gravity">center_vertical</item>
+ <item name="android:layout_gravity">center</item>
<item name="android:layout_width">48dp</item>
<item name="android:layout_height">96dp</item>
</style>
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 350beafae961..b25a2189cd4d 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -17,7 +17,6 @@
*/
-->
<resources>
- <color name="docked_divider_background">#ff000000</color>
<color name="docked_divider_handle">#ffffff</color>
<drawable name="forced_resizable_background">#59000000</drawable>
<color name="minimize_dock_shadow_start">#60000000</color>
@@ -30,6 +29,7 @@
<color name="bubbles_light">#FFFFFF</color>
<color name="bubbles_dark">@color/GM2_grey_800</color>
<color name="bubbles_icon_tint">@color/GM2_grey_700</color>
+ <color name="size_compat_hint_bubble">#30312B</color>
<!-- GM2 colors -->
<color name="GM2_grey_200">#E8EAED</color>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index f28ee820eb35..11bb4e92bfdb 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -76,8 +76,15 @@
<!-- How high we lift the divider when touching -->
<dimen name="docked_stack_divider_lift_elevation">4dp</dimen>
+ <!-- Divider handle size for legacy split screen -->
<dimen name="docked_divider_handle_width">16dp</dimen>
<dimen name="docked_divider_handle_height">2dp</dimen>
+ <!-- Divider handle size for split screen -->
+ <dimen name="split_divider_handle_width">72dp</dimen>
+ <dimen name="split_divider_handle_height">3dp</dimen>
+
+ <dimen name="split_divider_bar_width">10dp</dimen>
+ <dimen name="split_divider_corner_size">42dp</dimen>
<!-- One-Handed Mode -->
<!-- Threshold for dragging distance to enable one-handed mode -->
@@ -100,6 +107,8 @@
<dimen name="bubble_flyout_space_from_bubble">8dp</dimen>
<!-- How much space to leave between the flyout text and the avatar displayed in the flyout. -->
<dimen name="bubble_flyout_avatar_message_space">6dp</dimen>
+ <!-- If the screen percentage is smaller than this, we'll use this value instead. -->
+ <dimen name="bubbles_flyout_min_width_large_screen">200dp</dimen>
<!-- Padding between status bar and bubbles when displayed in expanded state -->
<dimen name="bubble_padding_top">16dp</dimen>
<!-- Space between bubbles when expanded. -->
@@ -122,7 +131,7 @@
should also be updated. -->
<dimen name="bubble_expanded_default_height">180dp</dimen>
<!-- On large screens the width of the expanded view is restricted to this size. -->
- <dimen name="bubble_expanded_view_tablet_width">412dp</dimen>
+ <dimen name="bubble_expanded_view_phone_landscape_overflow_width">412dp</dimen>
<!-- Inset to apply to the icon in the overflow button. -->
<dimen name="bubble_overflow_icon_inset">30dp</dimen>
<!-- Default (and minimum) height of bubble overflow -->
@@ -149,9 +158,17 @@
<!-- Extra padding around the dismiss target for bubbles -->
<dimen name="bubble_dismiss_slop">16dp</dimen>
<!-- Height of button allowing users to adjust settings for bubbles. -->
- <dimen name="bubble_manage_button_height">56dp</dimen>
+ <dimen name="bubble_manage_button_height">36dp</dimen>
+ <!-- Height of manage button including margins. -->
+ <dimen name="bubble_manage_button_total_height">68dp</dimen>
+ <!-- The margin around the outside of the manage button. -->
+ <dimen name="bubble_manage_button_margin">16dp</dimen>
<!-- Height of an item in the bubble manage menu. -->
<dimen name="bubble_menu_item_height">60dp</dimen>
+ <!-- Padding applied to the bubble manage menu. -->
+ <dimen name="bubble_menu_padding">16dp</dimen>
+ <!-- Size of the icons in the manage menu. -->
+ <dimen name="bubble_menu_icon_size">24dp</dimen>
<!-- Max width of the message bubble-->
<dimen name="bubble_message_max_width">144dp</dimen>
<!-- Min width of the message bubble -->
@@ -174,17 +191,20 @@
<dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
<dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
<dimen name="bubble_manage_menu_elevation">4dp</dimen>
+ <!-- Size of user education views on large screens (phone is just match parent). -->
+ <dimen name="bubbles_user_education_width_large_screen">400dp</dimen>
+
+ <!-- The width of the size compat restart button including padding. -->
+ <dimen name="size_compat_button_width">80dp</dimen>
+
+ <!-- The height of the size compat restart button including padding. -->
+ <dimen name="size_compat_button_height">64dp</dimen>
- <!-- Bubbles user education views -->
- <dimen name="bubbles_manage_education_width">160dp</dimen>
- <!-- The inset from the top bound of the manage button to place the user education. -->
- <dimen name="bubbles_manage_education_top_inset">65dp</dimen>
- <!-- Size of padding for the user education cling, this should at minimum be larger than
- individual_bubble_size + some padding. -->
- <dimen name="bubble_stack_user_education_side_inset">72dp</dimen>
+ <!-- The radius of the corners of the size compat hint bubble. -->
+ <dimen name="size_compat_hint_corner_radius">28dp</dimen>
- <!-- The width/height of the size compat restart button. -->
- <dimen name="size_compat_button_size">48dp</dimen>
+ <!-- The width of the size compat hint point. -->
+ <dimen name="size_compat_hint_point_width">10dp</dimen>
<!-- The width of the brand image on staring surface. -->
<dimen name="starting_surface_brand_image_width">200dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index e512698ab66c..764854af3b3f 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -155,7 +155,4 @@
<!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
<string name="restart_button_description">Tap to restart this app and go full screen.</string>
-
- <!-- Generic "got it" acceptance of dialog or cling [CHAR LIMIT=NONE] -->
- <string name="got_it">Got it</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index fffcd33f7992..7733201d2465 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -32,8 +32,9 @@
<style name="DockedDividerBackground">
<item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">10dp</item>
+ <item name="android:layout_height">@dimen/split_divider_bar_width</item>
<item name="android:layout_gravity">center_vertical</item>
+ <item name="android:background">@color/split_divider_background</item>
</style>
<style name="DockedDividerMinimizedShadow">
@@ -42,7 +43,7 @@
</style>
<style name="DockedDividerHandle">
- <item name="android:layout_gravity">center_horizontal</item>
+ <item name="android:layout_gravity">center</item>
<item name="android:layout_width">96dp</item>
<item name="android:layout_height">48dp</item>
</style>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
new file mode 100644
index 000000000000..14ba9df93f24
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
@@ -0,0 +1,114 @@
+/*
+ * 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.wm.shell;
+
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+import android.window.DisplayAreaAppearedInfo;
+import android.window.DisplayAreaInfo;
+import android.window.DisplayAreaOrganizer;
+
+import androidx.annotation.NonNull;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/** Display area organizer for the root display areas */
+public class RootDisplayAreaOrganizer extends DisplayAreaOrganizer {
+
+ private static final String TAG = RootDisplayAreaOrganizer.class.getSimpleName();
+
+ /** {@link DisplayAreaInfo} list, which is mapped by display IDs. */
+ private final SparseArray<DisplayAreaInfo> mDisplayAreasInfo = new SparseArray<>();
+ /** Display area leashes, which is mapped by display IDs. */
+ private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>();
+
+ public RootDisplayAreaOrganizer(Executor executor) {
+ super(executor);
+ List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT);
+ for (int i = infos.size() - 1; i >= 0; --i) {
+ onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash());
+ }
+ }
+
+ public void attachToDisplayArea(int displayId, SurfaceControl.Builder b) {
+ final SurfaceControl sc = mLeashes.get(displayId);
+ if (sc != null) {
+ b.setParent(sc);
+ }
+ }
+
+ @Override
+ public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
+ @NonNull SurfaceControl leash) {
+ if (displayAreaInfo.featureId != FEATURE_ROOT) {
+ throw new IllegalArgumentException(
+ "Unknown feature: " + displayAreaInfo.featureId
+ + "displayAreaInfo:" + displayAreaInfo);
+ }
+
+ final int displayId = displayAreaInfo.displayId;
+ if (mDisplayAreasInfo.get(displayId) != null) {
+ throw new IllegalArgumentException(
+ "Duplicate DA for displayId: " + displayId
+ + " displayAreaInfo:" + displayAreaInfo
+ + " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId));
+ }
+
+ mDisplayAreasInfo.put(displayId, displayAreaInfo);
+ mLeashes.put(displayId, leash);
+ }
+
+ @Override
+ public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) {
+ final int displayId = displayAreaInfo.displayId;
+ if (mDisplayAreasInfo.get(displayId) == null) {
+ throw new IllegalArgumentException(
+ "onDisplayAreaVanished() Unknown DA displayId: " + displayId
+ + " displayAreaInfo:" + displayAreaInfo
+ + " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId));
+ }
+
+ mDisplayAreasInfo.remove(displayId);
+ }
+
+ @Override
+ public void onDisplayAreaInfoChanged(@NonNull DisplayAreaInfo displayAreaInfo) {
+ final int displayId = displayAreaInfo.displayId;
+ if (mDisplayAreasInfo.get(displayId) == null) {
+ throw new IllegalArgumentException(
+ "onDisplayAreaInfoChanged() Unknown DA displayId: " + displayId
+ + " displayAreaInfo:" + displayAreaInfo
+ + " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId));
+ }
+
+ mDisplayAreasInfo.put(displayId, displayAreaInfo);
+ }
+
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ final String childPrefix = innerPrefix + " ";
+ pw.println(prefix + this);
+ }
+
+ @Override
+ public String toString() {
+ return TAG + "#" + mDisplayAreasInfo.size();
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 34c66a4f4b82..bf074b0337ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -97,6 +97,14 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
b.setParent(sc);
}
+ public void setPosition(@NonNull SurfaceControl.Transaction tx, int displayId, int x, int y) {
+ final SurfaceControl sc = mLeashes.get(displayId);
+ if (sc == null) {
+ throw new IllegalArgumentException("can't find display" + displayId);
+ }
+ tx.setPosition(sc, x, y);
+ }
+
@Override
public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
@NonNull SurfaceControl leash) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
index 0b941b59b3db..e87b150a0709 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -175,7 +175,7 @@ public final class ShellCommandHandlerImpl {
private boolean runSetSideStageVisibility(String[] args, PrintWriter pw) {
if (args.length < 3) {
// First arguments are "WMShell" and command name.
- pw.println("Error: side stage position should be provided as arguments");
+ pw.println("Error: side stage visibility should be provided as arguments");
return false;
}
final Boolean visible = new Boolean(args[2]);
@@ -197,6 +197,8 @@ public final class ShellCommandHandlerImpl {
pw.println(" Move a task with given id in split-screen mode.");
pw.println(" removeFromSideStage <taskId>");
pw.println(" Remove a task with given id in split-screen mode.");
+ pw.println(" setSideStageOutline <true/false>");
+ pw.println(" Enable/Disable outline on the side-stage.");
pw.println(" setSideStagePosition <SideStagePosition>");
pw.println(" Sets the position of the side-stage.");
pw.println(" setSideStageVisibility <true/false>");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index d1fbf31e2b99..fa58fcda3d3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -20,10 +20,15 @@ import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCR
import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.freeform.FreeformTaskListener;
+import com.android.wm.shell.fullscreen.FullscreenTaskListener;
+import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -38,7 +43,9 @@ import java.util.Optional;
public class ShellInitImpl {
private static final String TAG = ShellInitImpl.class.getSimpleName();
+ private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
private final DragAndDropController mDragAndDropController;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final Optional<BubbleController> mBubblesOptional;
@@ -47,13 +54,18 @@ public class ShellInitImpl {
private final Optional<AppPairsController> mAppPairsOptional;
private final Optional<PipTouchHandler> mPipTouchHandlerOptional;
private final FullscreenTaskListener mFullscreenTaskListener;
+ private final Optional<FullscreenUnfoldController> mFullscreenUnfoldController;
+ private final Optional<FreeformTaskListener> mFreeformTaskListenerOptional;
private final ShellExecutor mMainExecutor;
private final Transitions mTransitions;
private final StartingWindowController mStartingWindow;
private final InitImpl mImpl = new InitImpl();
- public ShellInitImpl(DisplayImeController displayImeController,
+ public ShellInitImpl(
+ DisplayController displayController,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
Optional<BubbleController> bubblesOptional,
@@ -62,10 +74,14 @@ public class ShellInitImpl {
Optional<AppPairsController> appPairsOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
+ Optional<FullscreenUnfoldController> fullscreenUnfoldTransitionController,
+ Optional<Optional<FreeformTaskListener>> freeformTaskListenerOptional,
Transitions transitions,
StartingWindowController startingWindow,
ShellExecutor mainExecutor) {
+ mDisplayController = displayController;
mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
mDragAndDropController = dragAndDropController;
mShellTaskOrganizer = shellTaskOrganizer;
mBubblesOptional = bubblesOptional;
@@ -74,6 +90,8 @@ public class ShellInitImpl {
mAppPairsOptional = appPairsOptional;
mFullscreenTaskListener = fullscreenTaskListener;
mPipTouchHandlerOptional = pipTouchHandlerOptional;
+ mFullscreenUnfoldController = fullscreenUnfoldTransitionController;
+ mFreeformTaskListenerOptional = freeformTaskListenerOptional.flatMap(f -> f);
mTransitions = transitions;
mMainExecutor = mainExecutor;
mStartingWindow = startingWindow;
@@ -84,7 +102,9 @@ public class ShellInitImpl {
}
private void init() {
- // Start listening for display changes
+ // Start listening for display and insets changes
+ mDisplayController.initialize();
+ mDisplayInsetsController.initialize();
mDisplayImeController.startMonitorDisplays();
// Setup the shell organizer
@@ -108,6 +128,13 @@ public class ShellInitImpl {
// controller instead of the feature interface, can just initialize the touch handler if
// needed
mPipTouchHandlerOptional.ifPresent((handler) -> handler.init());
+
+ // Initialize optional freeform
+ mFreeformTaskListenerOptional.ifPresent(f ->
+ mShellTaskOrganizer.addListenerForType(
+ f, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM));
+
+ mFullscreenUnfoldController.ifPresent(FullscreenUnfoldController::init);
}
@ExternalThread
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 656bdff0c782..020ecb7186ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -31,6 +31,7 @@ import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
import android.content.Context;
import android.content.LocusId;
+import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
@@ -41,11 +42,13 @@ import android.util.SparseArray;
import android.view.SurfaceControl;
import android.window.ITaskOrganizerController;
import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.sizecompatui.SizeCompatUIController;
@@ -71,12 +74,14 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
public static final int TASK_LISTENER_TYPE_FULLSCREEN = -2;
public static final int TASK_LISTENER_TYPE_MULTI_WINDOW = -3;
public static final int TASK_LISTENER_TYPE_PIP = -4;
+ public static final int TASK_LISTENER_TYPE_FREEFORM = -5;
@IntDef(prefix = {"TASK_LISTENER_TYPE_"}, value = {
TASK_LISTENER_TYPE_UNDEFINED,
TASK_LISTENER_TYPE_FULLSCREEN,
TASK_LISTENER_TYPE_MULTI_WINDOW,
TASK_LISTENER_TYPE_PIP,
+ TASK_LISTENER_TYPE_FREEFORM,
})
public @interface TaskListenerType {}
@@ -229,14 +234,14 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
+ " already exists");
}
mTaskListeners.put(listenerType, listener);
+ }
- // Notify the listener of all existing tasks with the given type.
- for (int i = mTasks.size() - 1; i >= 0; --i) {
- final TaskAppearedInfo data = mTasks.valueAt(i);
- final TaskListener taskListener = getTaskListener(data.getTaskInfo());
- if (taskListener != listener) continue;
- listener.onTaskAppeared(data.getTaskInfo(), data.getLeash());
- }
+ // Notify the listener of all existing tasks with the given type.
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final TaskAppearedInfo data = mTasks.valueAt(i);
+ final TaskListener taskListener = getTaskListener(data.getTaskInfo());
+ if (taskListener != listener) continue;
+ listener.onTaskAppeared(data.getTaskInfo(), data.getLeash());
}
}
}
@@ -262,8 +267,12 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
tasks.add(data);
}
- // Remove listener
- mTaskListeners.removeAt(index);
+ // Remove listener, there can be the multiple occurrences, so search the whole list.
+ for (int i = mTaskListeners.size() - 1; i >= 0; --i) {
+ if (mTaskListeners.valueAt(i) == listener) {
+ mTaskListeners.removeAt(i);
+ }
+ }
// Associate tasks with new listeners if needed.
for (int i = tasks.size() - 1; i >= 0; --i) {
@@ -314,10 +323,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
@Override
- public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
- boolean playRevealAnimation) {
+ public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
if (mStartingWindow != null) {
- mStartingWindow.removeStartingWindow(taskId, leash, frame, playRevealAnimation);
+ mStartingWindow.removeStartingWindow(removalInfo);
}
}
@@ -493,14 +501,40 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
@Override
+ public void onSizeCompatRestartButtonAppeared(int taskId) {
+ final TaskAppearedInfo info;
+ synchronized (mLock) {
+ info = mTasks.get(taskId);
+ }
+ if (info == null) {
+ return;
+ }
+ logSizeCompatRestartButtonEventReported(info,
+ FrameworkStatsLog.SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED__EVENT__APPEARED);
+ }
+
+ @Override
public void onSizeCompatRestartButtonClicked(int taskId) {
final TaskAppearedInfo info;
synchronized (mLock) {
info = mTasks.get(taskId);
}
- if (info != null) {
- restartTaskTopActivityProcessIfVisible(info.getTaskInfo().token);
+ if (info == null) {
+ return;
+ }
+ logSizeCompatRestartButtonEventReported(info,
+ FrameworkStatsLog.SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED__EVENT__CLICKED);
+ restartTaskTopActivityProcessIfVisible(info.getTaskInfo().token);
+ }
+
+ private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
+ int event) {
+ ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
+ if (topActivityInfo == null) {
+ return;
}
+ FrameworkStatsLog.write(FrameworkStatsLog.SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED,
+ topActivityInfo.applicationInfo.uid, event);
}
/**
@@ -579,6 +613,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
case WINDOWING_MODE_PINNED:
return TASK_LISTENER_TYPE_PIP;
case WINDOWING_MODE_FREEFORM:
+ return TASK_LISTENER_TYPE_FREEFORM;
case WINDOWING_MODE_UNDEFINED:
default:
return TASK_LISTENER_TYPE_UNDEFINED;
@@ -593,6 +628,8 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
return "TASK_LISTENER_TYPE_MULTI_WINDOW";
case TASK_LISTENER_TYPE_PIP:
return "TASK_LISTENER_TYPE_PIP";
+ case TASK_LISTENER_TYPE_FREEFORM:
+ return "TASK_LISTENER_TYPE_FREEFORM";
case TASK_LISTENER_TYPE_UNDEFINED:
return "TASK_LISTENER_TYPE_UNDEFINED";
default:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index 1861e48482b8..2f3214d1d1ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -40,6 +40,8 @@ import android.view.ViewTreeObserver;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
import java.io.PrintWriter;
import java.util.concurrent.Executor;
@@ -74,6 +76,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
private final ShellTaskOrganizer mTaskOrganizer;
private final Executor mShellExecutor;
+ private final SyncTransactionQueue mSyncQueue;
private ActivityManager.RunningTaskInfo mTaskInfo;
private WindowContainerToken mTaskToken;
@@ -89,11 +92,12 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
private final Rect mTmpRootRect = new Rect();
private final int[] mTmpLocation = new int[2];
- public TaskView(Context context, ShellTaskOrganizer organizer) {
+ public TaskView(Context context, ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue) {
super(context, null, 0, 0, true /* disableBackgroundLayer */);
mTaskOrganizer = organizer;
mShellExecutor = organizer.getExecutor();
+ mSyncQueue = syncQueue;
setUseAlpha();
getHolder().addCallback(this);
mGuard.open("release");
@@ -189,8 +193,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mTaskToken, mTmpRect);
- // TODO(b/151449487): Enable synchronization
- mTaskOrganizer.applyTransaction(wct);
+ mSyncQueue.queue(wct);
}
/**
@@ -236,14 +239,16 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
private void updateTaskVisibility() {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
- mTaskOrganizer.applyTransaction(wct);
- // TODO(b/151449487): Only call callback once we enable synchronization
- if (mListener != null) {
- final int taskId = mTaskInfo.taskId;
+ mSyncQueue.queue(wct);
+ if (mListener == null) {
+ return;
+ }
+ int taskId = mTaskInfo.taskId;
+ mSyncQueue.runInSync((t) -> {
mListenerExecutor.execute(() -> {
mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated);
});
- }
+ });
}
@Override
@@ -264,10 +269,12 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
updateTaskVisibility();
}
mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true);
- // TODO: Synchronize show with the resize
onLocationChanged();
if (taskInfo.taskDescription != null) {
- setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+ int backgroundColor = taskInfo.taskDescription.getBackgroundColor();
+ mSyncQueue.runInSync((t) -> {
+ setResizeBackgroundColor(t, backgroundColor);
+ });
}
if (mListener != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
index 58ca1fbaba24..8286d102791e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
@@ -20,8 +20,8 @@ import android.annotation.UiContext;
import android.content.Context;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -30,12 +30,14 @@ import java.util.function.Consumer;
public class TaskViewFactoryController {
private final ShellTaskOrganizer mTaskOrganizer;
private final ShellExecutor mShellExecutor;
+ private final SyncTransactionQueue mSyncQueue;
private final TaskViewFactory mImpl = new TaskViewFactoryImpl();
public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
- ShellExecutor shellExecutor) {
+ ShellExecutor shellExecutor, SyncTransactionQueue syncQueue) {
mTaskOrganizer = taskOrganizer;
mShellExecutor = shellExecutor;
+ mSyncQueue = syncQueue;
}
public TaskViewFactory asTaskViewFactory() {
@@ -44,7 +46,7 @@ public class TaskViewFactoryController {
/** Creates an {@link TaskView} */
public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
- TaskView taskView = new TaskView(context, mTaskOrganizer);
+ TaskView taskView = new TaskView(context, mTaskOrganizer, mSyncQueue);
executor.execute(() -> {
onCreate.accept(taskView);
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
index 8aca01d2467b..2aead9392e59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
@@ -62,4 +62,10 @@ public class Interpolators {
*/
public static final Interpolator PANEL_CLOSE_ACCELERATED =
new PathInterpolator(0.3f, 0, 0.5f, 1);
+
+ public static final PathInterpolator SLOWDOWN_INTERPOLATOR =
+ new PathInterpolator(0.5f, 1f, 0.5f, 1f);
+
+ public static final PathInterpolator DIM_INTERPOLATOR =
+ new PathInterpolator(.23f, .87f, .52f, -0.11f);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
index e6d088e6537d..c8449a362988 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.apppairs;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
@@ -26,7 +27,6 @@ import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEF
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import android.app.ActivityManager;
-import android.graphics.Rect;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
@@ -39,9 +39,11 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.SurfaceUtils;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitWindowManager;
import java.io.PrintWriter;
@@ -67,13 +69,33 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou
private final SyncTransactionQueue mSyncQueue;
private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
private SplitLayout mSplitLayout;
+ private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
+ new SplitWindowManager.ParentContainerCallbacks() {
+ @Override
+ public void attachToParentSurface(SurfaceControl.Builder b) {
+ b.setParent(mRootTaskLeash);
+ }
+
+ @Override
+ public void onLeashReady(SurfaceControl leash) {
+ mSyncQueue.runInSync(t -> t
+ .show(leash)
+ .setLayer(leash, SPLIT_DIVIDER_LAYER)
+ .setPosition(leash,
+ mSplitLayout.getDividerBounds().left,
+ mSplitLayout.getDividerBounds().top));
+ }
+ };
+
AppPair(AppPairsController controller) {
mController = controller;
mSyncQueue = controller.getSyncTransactionQueue();
mDisplayController = controller.getDisplayController();
mDisplayImeController = controller.getDisplayImeController();
+ mDisplayInsetsController = controller.getDisplayInsetsController();
}
int getRootTaskId() {
@@ -109,8 +131,9 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou
mSplitLayout = new SplitLayout(TAG + "SplitDivider",
mDisplayController.getDisplayContext(mRootTaskInfo.displayId),
mRootTaskInfo.configuration, this /* layoutChangeListener */,
- b -> b.setParent(mRootTaskLeash), mDisplayImeController,
- mController.getTaskOrganizer());
+ mParentContainerCallbacks, mDisplayImeController, mController.getTaskOrganizer(),
+ true /* applyDismissingParallax */);
+ mDisplayInsetsController.addInsetsChangedListener(mRootTaskInfo.displayId, mSplitLayout);
final WindowContainerToken token1 = task1.token;
final WindowContainerToken token2 = task2.token;
@@ -176,21 +199,17 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou
if (mTaskLeash1 == null || mTaskLeash2 == null) return;
mSplitLayout.init();
- final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
- final Rect dividerBounds = mSplitLayout.getDividerBounds();
-
- // TODO: Is there more we need to do here?
- mSyncQueue.runInSync(t -> {
- t.setLayer(dividerLeash, Integer.MAX_VALUE)
- .setPosition(mTaskLeash1, mTaskInfo1.positionInParent.x,
- mTaskInfo1.positionInParent.y)
- .setPosition(mTaskLeash2, mTaskInfo2.positionInParent.x,
- mTaskInfo2.positionInParent.y)
- .setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
- .show(mRootTaskLeash)
- .show(mTaskLeash1)
- .show(mTaskLeash2);
- });
+
+ mSyncQueue.runInSync(t -> t
+ .show(mRootTaskLeash)
+ .show(mTaskLeash1)
+ .show(mTaskLeash2)
+ .setPosition(mTaskLeash1,
+ mTaskInfo1.positionInParent.x,
+ mTaskInfo1.positionInParent.y)
+ .setPosition(mTaskLeash2,
+ mTaskInfo2.positionInParent.x,
+ mTaskInfo2.positionInParent.y));
}
@Override
@@ -214,7 +233,7 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou
if (mSplitLayout != null
&& mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)) {
- onBoundsChanged(mSplitLayout);
+ onLayoutSizeChanged(mSplitLayout);
}
} else if (taskInfo.taskId == getTaskId1()) {
mTaskInfo1 = taskInfo;
@@ -295,17 +314,30 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou
}
@Override
- public void onBoundsChanging(SplitLayout layout) {
+ public void onLayoutPositionChanging(SplitLayout layout) {
+ mSyncQueue.runInSync(t ->
+ layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2));
+ }
+
+ @Override
+ public void onLayoutSizeChanging(SplitLayout layout) {
mSyncQueue.runInSync(t ->
layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2));
}
@Override
- public void onBoundsChanged(SplitLayout layout) {
+ public void onLayoutSizeChanged(SplitLayout layout) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
layout.applyTaskChanges(wct, mTaskInfo1, mTaskInfo2);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t ->
layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2));
}
+
+ @Override
+ public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, mTaskInfo1, mTaskInfo2);
+ mController.getTaskOrganizer().applyTransaction(wct);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
index b159333e9a0e..53234ab971d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
@@ -29,6 +29,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -50,14 +51,17 @@ public class AppPairsController {
private final SparseArray<AppPair> mActiveAppPairs = new SparseArray<>();
private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
public AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
DisplayController displayController, ShellExecutor mainExecutor,
- DisplayImeController displayImeController) {
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController) {
mTaskOrganizer = organizer;
mSyncQueue = syncQueue;
mDisplayController = displayController;
mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
mMainExecutor = mainExecutor;
}
@@ -148,6 +152,10 @@ public class AppPairsController {
return mDisplayImeController;
}
+ DisplayInsetsController getDisplayInsetsController() {
+ return mDisplayInsetsController;
+ }
+
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 9d65d28b21b4..8d43f1375a8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -121,7 +121,7 @@ public class Bubble implements BubbleViewProvider {
@Nullable
private Icon mIcon;
private boolean mIsBubble;
- private boolean mIsVisuallyInterruptive;
+ private boolean mIsTextChanged;
private boolean mIsClearable;
private boolean mShouldSuppressNotificationDot;
private boolean mShouldSuppressNotificationList;
@@ -342,12 +342,12 @@ public class Bubble implements BubbleViewProvider {
}
/**
- * Sets whether this bubble is considered visually interruptive. This method is purely for
+ * Sets whether this bubble is considered text changed. This method is purely for
* testing.
*/
@VisibleForTesting
- void setVisuallyInterruptiveForTest(boolean visuallyInterruptive) {
- mIsVisuallyInterruptive = visuallyInterruptive;
+ void setTextChangedForTest(boolean textChanged) {
+ mIsTextChanged = textChanged;
}
/**
@@ -422,14 +422,6 @@ public class Bubble implements BubbleViewProvider {
}
}
- @Override
- public void setExpandedContentAlpha(float alpha) {
- if (mExpandedView != null) {
- mExpandedView.setAlpha(alpha);
- mExpandedView.setTaskViewAlpha(alpha);
- }
- }
-
/**
* Set visibility of bubble in the expanded state.
*
@@ -462,7 +454,7 @@ public class Bubble implements BubbleViewProvider {
mFlyoutMessage = extractFlyoutMessage(entry);
if (entry.getRanking() != null) {
mShortcutInfo = entry.getRanking().getConversationShortcutInfo();
- mIsVisuallyInterruptive = entry.getRanking().visuallyInterruptive();
+ mIsTextChanged = entry.getRanking().isTextChanged();
if (entry.getRanking().getChannel() != null) {
mIsImportantConversation =
entry.getRanking().getChannel().isImportantConversation();
@@ -503,8 +495,8 @@ public class Bubble implements BubbleViewProvider {
return mIcon;
}
- boolean isVisuallyInterruptive() {
- return mIsVisuallyInterruptive;
+ boolean isTextChanged() {
+ return mIsTextChanged;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 09fcb86e56de..f38001b15a7c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -56,7 +56,6 @@ import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -70,6 +69,7 @@ import android.util.SparseArray;
import android.util.SparseSetArray;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.window.WindowContainerTransaction;
@@ -85,6 +85,7 @@ import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
@@ -97,7 +98,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
-import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -137,6 +137,7 @@ public class BubbleController {
private final TaskStackListenerImpl mTaskStackListener;
private final ShellTaskOrganizer mTaskOrganizer;
private final DisplayController mDisplayController;
+ private final SyncTransactionQueue mSyncQueue;
// Used to post to main UI thread
private final ShellExecutor mMainExecutor;
@@ -144,7 +145,6 @@ public class BubbleController {
private BubbleLogger mLogger;
private BubbleData mBubbleData;
- private View mBubbleScrim;
@Nullable private BubbleStackView mStackView;
private BubbleIconFactory mBubbleIconFactory;
private BubblePositioner mBubblePositioner;
@@ -189,6 +189,9 @@ public class BubbleController {
/** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */
private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED;
+ /** Saved insets, used to detect WindowInset changes. */
+ private WindowInsets mWindowInsets;
+
private boolean mInflateSynchronously;
/** True when user is in status bar unlock shade. */
@@ -209,7 +212,8 @@ public class BubbleController {
ShellTaskOrganizer organizer,
DisplayController displayController,
ShellExecutor mainExecutor,
- Handler mainHandler) {
+ Handler mainHandler,
+ SyncTransactionQueue syncQueue) {
BubbleLogger logger = new BubbleLogger(uiEventLogger);
BubblePositioner positioner = new BubblePositioner(context, windowManager);
BubbleData data = new BubbleData(context, logger, positioner, mainExecutor);
@@ -217,7 +221,7 @@ public class BubbleController {
new BubbleDataRepository(context, launcherApps, mainExecutor),
statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
logger, taskStackListener, organizer, positioner, displayController, mainExecutor,
- mainHandler);
+ mainHandler, syncQueue);
}
/**
@@ -239,7 +243,8 @@ public class BubbleController {
BubblePositioner positioner,
DisplayController displayController,
ShellExecutor mainExecutor,
- Handler mainHandler) {
+ Handler mainHandler,
+ SyncTransactionQueue syncQueue) {
mContext = context;
mLauncherApps = launcherApps;
mBarService = statusBarService == null
@@ -262,6 +267,7 @@ public class BubbleController {
mSavedBubbleKeysPerUser = new SparseSetArray<>();
mBubbleIconFactory = new BubbleIconFactory(context);
mDisplayController = displayController;
+ mSyncQueue = syncQueue;
}
public void initialize() {
@@ -561,6 +567,10 @@ public class BubbleController {
return mTaskOrganizer;
}
+ SyncTransactionQueue getSyncTransactionQueue() {
+ return mSyncQueue;
+ }
+
/** Contains information to help position things on the screen. */
BubblePositioner getPositioner() {
return mBubblePositioner;
@@ -572,7 +582,7 @@ public class BubbleController {
/**
* BubbleStackView is lazily created by this method the first time a Bubble is added. This
- * method initializes the stack view and adds it to the StatusBar just above the scrim.
+ * method initializes the stack view and adds it to window manager.
*/
private void ensureStackViewCreated() {
if (mStackView == null) {
@@ -620,20 +630,31 @@ public class BubbleController {
try {
mAddedToWindowManager = true;
mBubbleData.getOverflow().initialize(this);
- mStackView.addView(mBubbleScrim);
mWindowManager.addView(mStackView, mWmLayoutParams);
- // Position info is dependent on us being attached to a window
- mBubblePositioner.update();
+ mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
+ if (!windowInsets.equals(mWindowInsets)) {
+ mWindowInsets = windowInsets;
+ mBubblePositioner.update();
+ mStackView.onDisplaySizeChanged();
+ }
+ return windowInsets;
+ });
} catch (IllegalStateException e) {
// This means the stack has already been added. This shouldn't happen...
e.printStackTrace();
}
}
- /** For the overflow to be focusable & receive key events the flags must be update. **/
- void updateWindowFlagsForOverflow(boolean showingOverflow) {
+ /**
+ * In some situations bubble's should be able to receive key events for back:
+ * - when the bubble overflow is showing
+ * - when the user education for the stack is showing.
+ *
+ * @param interceptBack whether back should be intercepted or not.
+ */
+ void updateWindowFlagsForBackpress(boolean interceptBack) {
if (mStackView != null && mAddedToWindowManager) {
- mWmLayoutParams.flags = showingOverflow
+ mWmLayoutParams.flags = interceptBack
? 0
: WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
@@ -652,7 +673,6 @@ public class BubbleController {
mAddedToWindowManager = false;
if (mStackView != null) {
mWindowManager.removeView(mStackView);
- mStackView.removeView(mBubbleScrim);
mBubbleData.getOverflow().cleanUpExpandedState();
} else {
Log.w(TAG, "StackView added to WindowManager, but was null when removing!");
@@ -754,13 +774,6 @@ public class BubbleController {
}
}
- private void setBubbleScrim(View view, BiConsumer<Executor, Looper> callback) {
- mBubbleScrim = view;
- callback.accept(mMainExecutor, mMainExecutor.executeBlockingForResult(() -> {
- return Looper.myLooper();
- }, Looper.class));
- }
-
private void setSysuiProxy(Bubbles.SysuiProxy proxy) {
mSysuiProxy = proxy;
}
@@ -897,8 +910,7 @@ public class BubbleController {
* Fills the overflow bubbles by loading them from disk.
*/
void loadOverflowBubblesFromDisk() {
- if (!mBubbleData.getOverflowBubbles().isEmpty() && !mOverflowDataLoadNeeded) {
- // we don't need to load overflow bubbles from disk if it is already in memory
+ if (!mOverflowDataLoadNeeded) {
return;
}
mOverflowDataLoadNeeded = false;
@@ -927,7 +939,7 @@ public class BubbleController {
public void updateBubble(BubbleEntry notif, boolean suppressFlyout, boolean showInShade) {
// If this is an interruptive notif, mark that it's interrupted
mSysuiProxy.setNotificationInterruption(notif.getKey());
- if (!notif.getRanking().visuallyInterruptive()
+ if (!notif.getRanking().isTextChanged()
&& (notif.getBubbleMetadata() != null
&& !notif.getBubbleMetadata().getAutoExpandBubble())
&& mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) {
@@ -1016,13 +1028,16 @@ public class BubbleController {
// If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason.
// This means that the app or channel's ability to bubble has been revoked.
mBubbleData.dismissBubbleWithKey(key, DISMISS_BLOCKED);
- } else if (isActiveBubble && !shouldBubbleUp) {
- // If this entry is allowed to bubble, but cannot currently bubble up, dismiss it.
- // This happens when DND is enabled and configured to hide bubbles. Dismissing with
- // the reason DISMISS_NO_BUBBLE_UP will retain the underlying notification, so that
- // the bubble will be re-created if shouldBubbleUp returns true.
+ } else if (isActiveBubble && (!shouldBubbleUp || entry.getRanking().isSuspended())) {
+ // If this entry is allowed to bubble, but cannot currently bubble up or is
+ // suspended, dismiss it. This happens when DND is enabled and configured to hide
+ // bubbles, or focus mode is enabled and the app is designated as distracting.
+ // Dismissing with the reason DISMISS_NO_BUBBLE_UP will retain the underlying
+ // notification, so that the bubble will be re-created if shouldBubbleUp returns
+ // true.
mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP);
- } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) {
+ } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble
+ && !entry.getRanking().isSuspended()) {
entry.setFlagBubble(true);
onEntryUpdated(entry, true /* shouldBubbleUp */);
}
@@ -1366,8 +1381,9 @@ public class BubbleController {
private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedTaskListener {
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+ mBubblePositioner.setImeVisible(imeVisible, imeHeight);
if (mStackView != null) {
- mStackView.onImeVisibilityChanged(imeVisible, imeHeight);
+ mStackView.animateForIme(imeVisible);
}
}
}
@@ -1566,13 +1582,6 @@ public class BubbleController {
}
@Override
- public void setBubbleScrim(View view, BiConsumer<Executor, Looper> callback) {
- mMainExecutor.execute(() -> {
- BubbleController.this.setBubbleScrim(view, callback);
- });
- }
-
- @Override
public void setExpandListener(BubbleExpandListener listener) {
mMainExecutor.execute(() -> {
BubbleController.this.setExpandListener(listener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index d73ce6951e6d..519a856538c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -323,7 +323,7 @@ public class BubbleData {
}
mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here
Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey());
- suppressFlyout |= !bubble.isVisuallyInterruptive();
+ suppressFlyout |= !bubble.isTextChanged();
if (prevBubble == null) {
// Create a new bubble
@@ -558,6 +558,8 @@ public class BubbleData {
}
Bubble bubbleToRemove = mBubbles.get(indexToRemove);
bubbleToRemove.stopInflation();
+ overflowBubble(reason, bubbleToRemove);
+
if (mBubbles.size() == 1) {
if (hasOverflowBubbles() && (mPositioner.showingInTaskbar() || isExpanded())) {
// No more active bubbles but we have stuff in the overflow -- select that view
@@ -581,8 +583,6 @@ public class BubbleData {
mStateChange.orderChanged |= repackAll();
}
- overflowBubble(reason, bubbleToRemove);
-
// Note: If mBubbles.isEmpty(), then mSelectedBubble is now null.
if (Objects.equals(mSelectedBubble, bubbleToRemove)) {
// Move selection to the new bubble at the same position.
@@ -699,10 +699,9 @@ public class BubbleData {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
}
- if (!mShowingOverflow && Objects.equals(bubble, mSelectedBubble)) {
+ if (Objects.equals(bubble, mSelectedBubble)) {
return;
}
- // Otherwise, if we are showing the overflow menu, return to the previously selected bubble.
boolean isOverflow = bubble != null && BubbleOverflow.KEY.equals(bubble.getKey());
if (bubble != null
&& !mBubbles.contains(bubble)
@@ -771,6 +770,10 @@ public class BubbleData {
Log.e(TAG, "Attempt to expand stack without selected bubble!");
return;
}
+ if (mSelectedBubble.getKey().equals(mOverflow.getKey()) && !mBubbles.isEmpty()) {
+ // Show previously selected bubble instead of overflow menu when expanding.
+ setSelectedBubbleInternal(mBubbles.get(0));
+ }
if (mSelectedBubble instanceof Bubble) {
((Bubble) mSelectedBubble).markAsAccessedAt(mTimeSource.currentTimeMillis());
}
@@ -779,16 +782,6 @@ public class BubbleData {
// Apply ordering and grouping rules from expanded -> collapsed, then save
// the result.
mStateChange.orderChanged |= repackAll();
- // Save the state which should be returned to when expanded (with no other changes)
-
- if (mShowingOverflow) {
- // Show previously selected bubble instead of overflow menu on next expansion.
- if (!mSelectedBubble.getKey().equals(mOverflow.getKey())) {
- setSelectedBubbleInternal(mSelectedBubble);
- } else {
- setSelectedBubbleInternal(mBubbles.get(0));
- }
- }
if (mBubbles.indexOf(mSelectedBubble) > 0) {
// Move the selected bubble to the top while collapsed.
int index = mBubbles.indexOf(mSelectedBubble);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 9687ec6a8168..a87aad4261a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -25,6 +25,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -60,7 +61,6 @@ import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R;
import com.android.wm.shell.TaskView;
import com.android.wm.shell.common.AlphaOptimizedButton;
@@ -77,7 +77,6 @@ public class BubbleExpandedView extends LinearLayout {
// The triangle pointing to the expanded view
private View mPointerView;
- private int mPointerMargin;
@Nullable private int[] mExpandedViewContainerLocation;
private AlphaOptimizedButton mManageButton;
@@ -102,9 +101,6 @@ public class BubbleExpandedView extends LinearLayout {
*/
private boolean mIsAlphaAnimating = false;
- private int mMinHeight;
- private int mOverflowHeight;
- private int mManageButtonHeight;
private int mPointerWidth;
private int mPointerHeight;
private float mPointerRadius;
@@ -232,7 +228,7 @@ public class BubbleExpandedView extends LinearLayout {
@Override
public void onBackPressedOnTaskRoot(int taskId) {
if (mTaskId == taskId && mStackView.isExpanded()) {
- mController.collapseStack();
+ mStackView.onBackPressed();
}
}
};
@@ -338,7 +334,8 @@ public class BubbleExpandedView extends LinearLayout {
bringChildToFront(mOverflowView);
mManageButton.setVisibility(GONE);
} else {
- mTaskView = new TaskView(mContext, mController.getTaskOrganizer());
+ mTaskView = new TaskView(mContext, mController.getTaskOrganizer(),
+ mController.getSyncTransactionQueue());
mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener);
mExpandedViewContainer.addView(mTaskView);
bringChildToFront(mTaskView);
@@ -347,12 +344,8 @@ public class BubbleExpandedView extends LinearLayout {
void updateDimensions() {
Resources res = getResources();
- mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
- mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
-
updateFontSize();
- mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
mPointerRadius = getResources().getDimensionPixelSize(R.dimen.bubble_pointer_radius);
@@ -368,7 +361,6 @@ public class BubbleExpandedView extends LinearLayout {
updatePointerView();
}
- mManageButtonHeight = res.getDimensionPixelSize(R.dimen.bubble_manage_button_height);
if (mManageButton != null) {
int visibility = mManageButton.getVisibility();
removeView(mManageButton);
@@ -406,6 +398,7 @@ public class BubbleExpandedView extends LinearLayout {
updatePointerView();
}
+ /** Updates the size and visuals of the pointer. **/
private void updatePointerView() {
LayoutParams lp = (LayoutParams) mPointerView.getLayoutParams();
if (mCurrentPointer == mLeftPointer || mCurrentPointer == mRightPointer) {
@@ -532,9 +525,8 @@ public class BubbleExpandedView extends LinearLayout {
if (mTaskView != null) {
mTaskView.setAlpha(alpha);
}
- if (mManageButton != null && mManageButton.getVisibility() == View.VISIBLE) {
- mManageButton.setAlpha(alpha);
- }
+ mPointerView.setAlpha(alpha);
+ setAlpha(alpha);
}
/**
@@ -553,6 +545,7 @@ public class BubbleExpandedView extends LinearLayout {
mIsContentVisible = visibility;
if (mTaskView != null && !mIsAlphaAnimating) {
mTaskView.setAlpha(visibility ? 1f : 0f);
+ mPointerView.setAlpha(visibility ? 1f : 0f);
}
}
@@ -632,12 +625,11 @@ public class BubbleExpandedView extends LinearLayout {
}
if ((mBubble != null && mTaskView != null) || mIsOverflow) {
- float desiredHeight = mIsOverflow
- ? mPositioner.isLargeScreen() ? getMaxExpandedHeight() : mOverflowHeight
- : mBubble.getDesiredHeight(mContext);
- desiredHeight = Math.max(desiredHeight, mMinHeight);
- float height = Math.min(desiredHeight, getMaxExpandedHeight());
- height = Math.max(height, mMinHeight);
+ float desiredHeight = mPositioner.getExpandedViewHeight(mBubble);
+ int maxHeight = mPositioner.getMaxExpandedViewHeight(mIsOverflow);
+ float height = desiredHeight == MAX_HEIGHT
+ ? maxHeight
+ : Math.min(desiredHeight, maxHeight);
FrameLayout.LayoutParams lp = mIsOverflow
? (FrameLayout.LayoutParams) mOverflowView.getLayoutParams()
: (FrameLayout.LayoutParams) mTaskView.getLayoutParams();
@@ -661,23 +653,6 @@ public class BubbleExpandedView extends LinearLayout {
}
}
- private int getMaxExpandedHeight() {
- int expandedContainerY = mExpandedViewContainerLocation != null
- // Remove top insets back here because availableRect.height would account for that
- ? mExpandedViewContainerLocation[1] - mPositioner.getInsets().top
- : 0;
- int settingsHeight = mIsOverflow ? 0 : mManageButtonHeight;
- int pointerHeight = mPositioner.showBubblesVertically()
- ? mPointerWidth
- : (int) (mPointerHeight - mPointerOverlap + mPointerMargin);
- return mPositioner.getAvailableRect().height()
- - expandedContainerY
- - getPaddingTop()
- - getPaddingBottom()
- - settingsHeight
- - pointerHeight;
- }
-
/**
* Update appearance of the expanded view being displayed.
*
@@ -715,28 +690,29 @@ public class BubbleExpandedView extends LinearLayout {
* the bubble if showing vertically.
* @param onLeft whether the stack was on the left side of the screen when expanded.
*/
- public void setPointerPosition(float bubblePosition, boolean onLeft) {
+ public void setPointerPosition(float bubblePosition, boolean onLeft, boolean animate) {
// Pointer gets drawn in the padding
final boolean showVertically = mPositioner.showBubblesVertically();
final float paddingLeft = (showVertically && onLeft)
? mPointerHeight - mPointerOverlap
: 0;
final float paddingRight = (showVertically && !onLeft)
- ? mPointerHeight - mPointerOverlap : 0;
- final float paddingTop = showVertically ? 0
+ ? mPointerHeight - mPointerOverlap
+ : 0;
+ final float paddingTop = showVertically
+ ? 0
: mPointerHeight - mPointerOverlap;
setPadding((int) paddingLeft, (int) paddingTop, (int) paddingRight, 0);
- final float expandedViewY = mPositioner.getExpandedViewY();
- // TODO: I don't understand why it works but it does - why normalized in portrait
- // & not in landscape? Am I missing ~2dp in the portrait expandedViewY calculation?
- final float normalizedSize = IconNormalizer.getNormalizedCircleSize(
- mPositioner.getBubbleSize());
- final float bubbleCenter = showVertically
- ? bubblePosition + (mPositioner.getBubbleSize() / 2f) - expandedViewY
- : bubblePosition + (normalizedSize / 2f) - mPointerWidth;
+ // Subtract the expandedViewY here because the pointer is placed within the expandedView.
+ float pointerPosition = mPositioner.getPointerPosition(bubblePosition);
+ final float bubbleCenter = mPositioner.showBubblesVertically()
+ ? pointerPosition - mPositioner.getExpandedViewY(mBubble, bubblePosition)
+ : pointerPosition;
// Post because we need the width of the view
post(() -> {
+ mCurrentPointer = showVertically ? onLeft ? mLeftPointer : mRightPointer : mTopPointer;
+ updatePointerView();
float pointerY;
float pointerX;
if (showVertically) {
@@ -748,11 +724,13 @@ public class BubbleExpandedView extends LinearLayout {
pointerY = mPointerOverlap;
pointerX = bubbleCenter - (mPointerWidth / 2f);
}
- mPointerView.setTranslationY(pointerY);
- mPointerView.setTranslationX(pointerX);
- mCurrentPointer = showVertically ? onLeft ? mLeftPointer : mRightPointer : mTopPointer;
- updatePointerView();
- mPointerView.setVisibility(VISIBLE);
+ if (animate) {
+ mPointerView.animate().translationX(pointerX).translationY(pointerY).start();
+ } else {
+ mPointerView.setTranslationY(pointerY);
+ mPointerView.setTranslationX(pointerX);
+ mPointerView.setVisibility(VISIBLE);
+ }
});
}
@@ -764,6 +742,10 @@ public class BubbleExpandedView extends LinearLayout {
mManageButton.getBoundsOnScreen(rect);
}
+ public int getManageButtonMargin() {
+ return ((LinearLayout.LayoutParams) mManageButton.getLayoutParams()).getMarginStart();
+ }
+
/**
* Cleans up anything related to the task and {@code TaskView}. If this view should be reused
* after this method is called, then
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
index 35a4f33ecf72..9374da4c4fab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
@@ -56,9 +56,6 @@ import com.android.wm.shell.common.TriangleShape;
* transform into the 'new' dot, which is used during flyout dismiss animations/gestures.
*/
public class BubbleFlyoutView extends FrameLayout {
- /** Max width of the flyout, in terms of percent of the screen width. */
- private static final float FLYOUT_MAX_WIDTH_PERCENT = .6f;
-
/** Translation Y of fade animation. */
private static final float FLYOUT_FADE_Y = 40f;
@@ -68,6 +65,8 @@ public class BubbleFlyoutView extends FrameLayout {
// Whether the flyout view should show a pointer to the bubble.
private static final boolean SHOW_POINTER = false;
+ private BubblePositioner mPositioner;
+
private final int mFlyoutPadding;
private final int mFlyoutSpaceFromBubble;
private final int mPointerSize;
@@ -156,10 +155,11 @@ public class BubbleFlyoutView extends FrameLayout {
/** Callback to run when the flyout is hidden. */
@Nullable private Runnable mOnHide;
- public BubbleFlyoutView(Context context) {
+ public BubbleFlyoutView(Context context, BubblePositioner positioner) {
super(context);
- LayoutInflater.from(context).inflate(R.layout.bubble_flyout, this, true);
+ mPositioner = positioner;
+ LayoutInflater.from(context).inflate(R.layout.bubble_flyout, this, true);
mFlyoutTextContainer = findViewById(R.id.bubble_flyout_text_container);
mSenderText = findViewById(R.id.bubble_flyout_name);
mSenderAvatar = findViewById(R.id.bubble_flyout_avatar);
@@ -230,11 +230,11 @@ public class BubbleFlyoutView extends FrameLayout {
/*
* Fade animation for consecutive flyouts.
*/
- void animateUpdate(Bubble.FlyoutMessage flyoutMessage, float parentWidth, PointF stackPos,
+ void animateUpdate(Bubble.FlyoutMessage flyoutMessage, PointF stackPos,
boolean hideDot, Runnable onHide) {
mOnHide = onHide;
final Runnable afterFadeOut = () -> {
- updateFlyoutMessage(flyoutMessage, parentWidth);
+ updateFlyoutMessage(flyoutMessage);
// Wait for TextViews to layout with updated height.
post(() -> {
fade(true /* in */, stackPos, hideDot, () -> {} /* after */);
@@ -266,7 +266,7 @@ public class BubbleFlyoutView extends FrameLayout {
.withEndAction(afterFade);
}
- private void updateFlyoutMessage(Bubble.FlyoutMessage flyoutMessage, float parentWidth) {
+ private void updateFlyoutMessage(Bubble.FlyoutMessage flyoutMessage) {
final Drawable senderAvatar = flyoutMessage.senderAvatar;
if (senderAvatar != null && flyoutMessage.isGroupChat) {
mSenderAvatar.setVisibility(VISIBLE);
@@ -278,8 +278,7 @@ public class BubbleFlyoutView extends FrameLayout {
mSenderText.setTranslationX(0);
}
- final int maxTextViewWidth =
- (int) (parentWidth * FLYOUT_MAX_WIDTH_PERCENT) - mFlyoutPadding * 2;
+ final int maxTextViewWidth = (int) mPositioner.getMaxFlyoutSize() - mFlyoutPadding * 2;
// Name visibility
if (!TextUtils.isEmpty(flyoutMessage.senderName)) {
@@ -328,22 +327,20 @@ public class BubbleFlyoutView extends FrameLayout {
void setupFlyoutStartingAsDot(
Bubble.FlyoutMessage flyoutMessage,
PointF stackPos,
- float parentWidth,
boolean arrowPointingLeft,
int dotColor,
@Nullable Runnable onLayoutComplete,
@Nullable Runnable onHide,
float[] dotCenter,
- boolean hideDot,
- BubblePositioner positioner) {
+ boolean hideDot) {
- mBubbleSize = positioner.getBubbleSize();
+ mBubbleSize = mPositioner.getBubbleSize();
mOriginalDotSize = SIZE_PERCENTAGE * mBubbleSize;
mNewDotRadius = (DOT_SCALE * mOriginalDotSize) / 2f;
mNewDotSize = mNewDotRadius * 2f;
- updateFlyoutMessage(flyoutMessage, parentWidth);
+ updateFlyoutMessage(flyoutMessage);
mArrowPointingLeft = arrowPointingLeft;
mDotColor = dotColor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 705a12a5e65b..0c3a6b2dbd84 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -154,10 +154,6 @@ class BubbleOverflow(
return dotPath
}
- override fun setExpandedContentAlpha(alpha: Float) {
- expandedView?.alpha = alpha
- }
-
override fun setTaskViewVisibility(visible: Boolean) {
// Overflow does not have a TaskView.
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
index ede42285d9cd..5e9d97f23c57 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
@@ -142,7 +142,7 @@ public class BubbleOverflowContainerView extends LinearLayout {
super.onAttachedToWindow();
if (mController != null) {
// For the overflow to get key events (e.g. back press) we need to adjust the flags
- mController.updateWindowFlagsForOverflow(true);
+ mController.updateWindowFlagsForBackpress(true);
}
setOnKeyListener(mKeyListener);
}
@@ -151,7 +151,7 @@ public class BubbleOverflowContainerView extends LinearLayout {
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mController != null) {
- mController.updateWindowFlagsForOverflow(false);
+ mController.updateWindowFlagsForBackpress(false);
}
setOnKeyListener(null);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index c600f56ba0c5..127d5a8a9966 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -34,6 +34,7 @@ import android.view.WindowMetrics;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R;
import java.lang.annotation.Retention;
@@ -58,29 +59,48 @@ public class BubblePositioner {
/** When the bubbles are collapsed in a stack only some of them are shown, this is how many. **/
public static final int NUM_VISIBLE_WHEN_RESTING = 2;
+ /** Indicates a bubble's height should be the maximum available space. **/
+ public static final int MAX_HEIGHT = -1;
+ /** The max percent of screen width to use for the flyout on large screens. */
+ public static final float FLYOUT_MAX_WIDTH_PERCENT_LARGE_SCREEN = 0.3f;
+ /** The max percent of screen width to use for the flyout on phone. */
+ public static final float FLYOUT_MAX_WIDTH_PERCENT = 0.6f;
+ /** The percent of screen width that should be used for the expanded view on a large screen. **/
+ public static final float EXPANDED_VIEW_LARGE_SCREEN_WIDTH_PERCENT = 0.72f;
private Context mContext;
private WindowManager mWindowManager;
- private Rect mPositionRect;
+ private Rect mScreenRect;
private @Surface.Rotation int mRotation = Surface.ROTATION_0;
private Insets mInsets;
+ private boolean mImeVisible;
+ private int mImeHeight;
+ private boolean mIsLargeScreen;
+
+ private Rect mPositionRect;
private int mDefaultMaxBubbles;
private int mMaxBubbles;
-
private int mBubbleSize;
- private int mBubbleBadgeSize;
private int mSpacingBetweenBubbles;
+
+ private int mExpandedViewMinHeight;
private int mExpandedViewLargeScreenWidth;
+ private int mExpandedViewLargeScreenInset;
+
+ private int mOverflowWidth;
private int mExpandedViewPadding;
private int mPointerMargin;
- private float mPointerWidth;
- private float mPointerHeight;
+ private int mPointerWidth;
+ private int mPointerHeight;
+ private int mPointerOverlap;
+ private int mManageButtonHeight;
+ private int mOverflowHeight;
+ private int mMinimumFlyoutWidthLargeScreen;
private PointF mPinLocation;
private PointF mRestingStackPosition;
private int[] mPaddings = new int[4];
- private boolean mIsLargeScreen;
private boolean mShowingInTaskbar;
private @TaskbarPosition int mTaskbarPosition = TASKBAR_POSITION_NONE;
private int mTaskbarIconSize;
@@ -143,6 +163,7 @@ public class BubblePositioner {
mRotation = rotation;
mInsets = insets;
+ mScreenRect = new Rect(bounds);
mPositionRect = new Rect(bounds);
mPositionRect.left += mInsets.left;
mPositionRect.top += mInsets.top;
@@ -151,16 +172,27 @@ public class BubblePositioner {
Resources res = mContext.getResources();
mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
- mBubbleBadgeSize = res.getDimensionPixelSize(R.dimen.bubble_badge_size);
mSpacingBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
mDefaultMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
-
- mExpandedViewLargeScreenWidth = res.getDimensionPixelSize(
- R.dimen.bubble_expanded_view_tablet_width);
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
+ mExpandedViewLargeScreenWidth = (int) (bounds.width()
+ * EXPANDED_VIEW_LARGE_SCREEN_WIDTH_PERCENT);
+ mExpandedViewLargeScreenInset = mIsLargeScreen
+ ? (bounds.width() - mExpandedViewLargeScreenWidth) / 2
+ : mExpandedViewPadding;
+ mOverflowWidth = mIsLargeScreen
+ ? mExpandedViewLargeScreenWidth
+ : res.getDimensionPixelSize(
+ R.dimen.bubble_expanded_view_phone_landscape_overflow_width);
mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
+ mPointerOverlap = res.getDimensionPixelSize(R.dimen.bubble_pointer_overlap);
+ mManageButtonHeight = res.getDimensionPixelSize(R.dimen.bubble_manage_button_total_height);
+ mExpandedViewMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
+ mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
+ mMinimumFlyoutWidthLargeScreen = res.getDimensionPixelSize(
+ R.dimen.bubbles_flyout_min_width_large_screen);
mMaxBubbles = calculateMaxBubbles();
@@ -225,6 +257,13 @@ public class BubblePositioner {
}
/**
+ * @return a rect of the screen size.
+ */
+ public Rect getScreenRect() {
+ return mScreenRect;
+ }
+
+ /**
* @return the relevant insets (status bar, nav bar, cutouts). If taskbar is showing, its
* inset is not included here.
*/
@@ -265,48 +304,287 @@ public class BubblePositioner {
return mMaxBubbles;
}
+ /** The height for the IME if it's visible. **/
+ public int getImeHeight() {
+ return mImeVisible ? mImeHeight : 0;
+ }
+
+ /** Sets whether the IME is visible. **/
+ public void setImeVisible(boolean visible, int height) {
+ mImeVisible = visible;
+ mImeHeight = height;
+ }
+
/**
- * Calculates the left & right padding for the bubble expanded view.
+ * Calculates the padding for the bubble expanded view.
*
- * On larger screens the width of the expanded view is restricted via this padding.
- * On landscape the bubble overflow expanded view is also restricted via this padding.
+ * Some specifics:
+ * On large screens the width of the expanded view is restricted via this padding.
+ * On phone landscape the bubble overflow expanded view is also restricted via this padding.
+ * On large screens & landscape no top padding is set, the top position is set via translation.
+ * On phone portrait top padding is set as the space between the tip of the pointer and the
+ * bubble.
+ * When the overflow is shown it doesn't have the manage button to pad out the bottom so
+ * padding is added.
*/
- public int[] getExpandedViewPadding(boolean onLeft, boolean isOverflow) {
- int leftPadding = mInsets.left + mExpandedViewPadding;
- int rightPadding = mInsets.right + mExpandedViewPadding;
- final boolean isLargeOrOverflow = mIsLargeScreen || isOverflow;
- if (showBubblesVertically()) {
- if (!onLeft) {
- rightPadding += mBubbleSize - mPointerHeight;
- leftPadding += isLargeOrOverflow
- ? (mPositionRect.width() - rightPadding - mExpandedViewLargeScreenWidth)
- : 0;
- } else {
- leftPadding += mBubbleSize - mPointerHeight;
- rightPadding += isLargeOrOverflow
- ? (mPositionRect.width() - leftPadding - mExpandedViewLargeScreenWidth)
- : 0;
+ public int[] getExpandedViewContainerPadding(boolean onLeft, boolean isOverflow) {
+ final int pointerTotalHeight = mPointerHeight - mPointerOverlap;
+ if (mIsLargeScreen) {
+ // [left, top, right, bottom]
+ mPaddings[0] = onLeft
+ ? mExpandedViewLargeScreenInset - pointerTotalHeight
+ : mExpandedViewLargeScreenInset;
+ mPaddings[1] = 0;
+ mPaddings[2] = onLeft
+ ? mExpandedViewLargeScreenInset
+ : mExpandedViewLargeScreenInset - pointerTotalHeight;
+ // Overflow doesn't show manage button / get padding from it so add padding here for it
+ mPaddings[3] = isOverflow ? mExpandedViewPadding : 0;
+ return mPaddings;
+ } else {
+ int leftPadding = mInsets.left + mExpandedViewPadding;
+ int rightPadding = mInsets.right + mExpandedViewPadding;
+ final float expandedViewWidth = isOverflow
+ ? mOverflowWidth
+ : mExpandedViewLargeScreenWidth;
+ if (showBubblesVertically()) {
+ if (!onLeft) {
+ rightPadding += mBubbleSize - pointerTotalHeight;
+ leftPadding += isOverflow
+ ? (mPositionRect.width() - rightPadding - expandedViewWidth)
+ : 0;
+ } else {
+ leftPadding += mBubbleSize - pointerTotalHeight;
+ rightPadding += isOverflow
+ ? (mPositionRect.width() - leftPadding - expandedViewWidth)
+ : 0;
+ }
}
+ // [left, top, right, bottom]
+ mPaddings[0] = leftPadding;
+ mPaddings[1] = showBubblesVertically() ? 0 : mPointerMargin;
+ mPaddings[2] = rightPadding;
+ mPaddings[3] = 0;
+ return mPaddings;
}
- // [left, top, right, bottom]
- mPaddings[0] = leftPadding;
- mPaddings[1] = showBubblesVertically() ? 0 : mPointerMargin;
- mPaddings[2] = rightPadding;
- mPaddings[3] = 0;
- return mPaddings;
}
- /** Calculates the y position of the expanded view when it is expanded. */
- public float getExpandedViewY() {
+ /** Gets the y position of the expanded view if it was top-aligned. */
+ public float getExpandedViewYTopAligned() {
final int top = getAvailableRect().top;
if (showBubblesVertically()) {
- return top - mPointerWidth;
+ return top - mPointerWidth + mExpandedViewPadding;
} else {
return top + mBubbleSize + mPointerMargin;
}
}
/**
+ * Calculate the maximum height the expanded view can be depending on where it's placed on
+ * the screen and the size of the elements around it (e.g. padding, pointer, manage button).
+ */
+ public int getMaxExpandedViewHeight(boolean isOverflow) {
+ // Subtract top insets because availableRect.height would account for that
+ int expandedContainerY = (int) getExpandedViewYTopAligned() - getInsets().top;
+ int paddingTop = showBubblesVertically()
+ ? 0
+ : mPointerHeight;
+ // Subtract pointer size because it's laid out in LinearLayout with the expanded view.
+ int pointerSize = showBubblesVertically()
+ ? mPointerWidth
+ : (mPointerHeight + mPointerMargin);
+ int bottomPadding = isOverflow ? mExpandedViewPadding : mManageButtonHeight;
+ return getAvailableRect().height()
+ - expandedContainerY
+ - paddingTop
+ - pointerSize
+ - bottomPadding;
+ }
+
+ /**
+ * Determines the height for the bubble, ensuring a minimum height. If the height should be as
+ * big as available, returns {@link #MAX_HEIGHT}.
+ */
+ public float getExpandedViewHeight(BubbleViewProvider bubble) {
+ boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey());
+ if (isOverflow && showBubblesVertically() && !mIsLargeScreen) {
+ // overflow in landscape on phone is max
+ return MAX_HEIGHT;
+ }
+ float desiredHeight = isOverflow
+ ? mOverflowHeight
+ : ((Bubble) bubble).getDesiredHeight(mContext);
+ desiredHeight = Math.max(desiredHeight, mExpandedViewMinHeight);
+ if (desiredHeight > getMaxExpandedViewHeight(isOverflow)) {
+ return MAX_HEIGHT;
+ }
+ return desiredHeight;
+ }
+
+ /**
+ * Gets the y position for the expanded view. This is the position on screen of the top
+ * horizontal line of the expanded view.
+ *
+ * @param bubble the bubble being positioned.
+ * @param bubblePosition the x position of the bubble if showing on top, the y position of the
+ * bubble if showing vertically.
+ * @return the y position for the expanded view.
+ */
+ public float getExpandedViewY(BubbleViewProvider bubble, float bubblePosition) {
+ boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey());
+ float expandedViewHeight = getExpandedViewHeight(bubble);
+ float topAlignment = getExpandedViewYTopAligned();
+ if (!showBubblesVertically() || expandedViewHeight == MAX_HEIGHT) {
+ // Top-align when bubbles are shown at the top or are max size.
+ return topAlignment;
+ }
+ // If we're here, we're showing vertically & developer has made height less than maximum.
+ int manageButtonHeight = isOverflow ? mExpandedViewPadding : mManageButtonHeight;
+ float pointerPosition = getPointerPosition(bubblePosition);
+ float bottomIfCentered = pointerPosition + (expandedViewHeight / 2) + manageButtonHeight;
+ float topIfCentered = pointerPosition - (expandedViewHeight / 2);
+ if (topIfCentered > mPositionRect.top && mPositionRect.bottom > bottomIfCentered) {
+ // Center it
+ return pointerPosition - mPointerWidth - (expandedViewHeight / 2f);
+ } else if (topIfCentered <= mPositionRect.top) {
+ // Top align
+ return topAlignment;
+ } else {
+ // Bottom align
+ return mPositionRect.bottom - manageButtonHeight - expandedViewHeight - mPointerWidth;
+ }
+ }
+
+ /**
+ * The position the pointer points to, the center of the bubble.
+ *
+ * @param bubblePosition the x position of the bubble if showing on top, the y position of the
+ * bubble if showing vertically.
+ * @return the position the tip of the pointer points to. The x position if showing on top, the
+ * y position if showing vertically.
+ */
+ public float getPointerPosition(float bubblePosition) {
+ // TODO: I don't understand why it works but it does - why normalized in portrait
+ // & not in landscape? Am I missing ~2dp in the portrait expandedViewY calculation?
+ final float normalizedSize = IconNormalizer.getNormalizedCircleSize(
+ getBubbleSize());
+ return showBubblesVertically()
+ ? bubblePosition + (getBubbleSize() / 2f)
+ : bubblePosition + (normalizedSize / 2f) - mPointerWidth;
+ }
+
+ private int getExpandedStackSize(int numberOfBubbles) {
+ return (numberOfBubbles * mBubbleSize)
+ + ((numberOfBubbles - 1) * mSpacingBetweenBubbles);
+ }
+
+ /**
+ * Returns the position of the bubble on-screen when the stack is expanded.
+ *
+ * @param index the index of the bubble in the stack.
+ * @param state state information about the stack to help with calculations.
+ * @return the position of the bubble on-screen when the stack is expanded.
+ */
+ public PointF getExpandedBubbleXY(int index, BubbleStackView.StackViewState state) {
+ final float positionInRow = index * (mBubbleSize + mSpacingBetweenBubbles);
+ final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles);
+ final float centerPosition = showBubblesVertically()
+ ? mPositionRect.centerY()
+ : mPositionRect.centerX();
+ // alignment - centered on the edge
+ final float rowStart = centerPosition - (expandedStackSize / 2f);
+ float x;
+ float y;
+ if (showBubblesVertically()) {
+ y = rowStart + positionInRow;
+ int left = mIsLargeScreen
+ ? mExpandedViewLargeScreenInset - mExpandedViewPadding - mBubbleSize
+ : mPositionRect.left;
+ int right = mIsLargeScreen
+ ? mPositionRect.right - mExpandedViewLargeScreenInset + mExpandedViewPadding
+ : mPositionRect.right - mBubbleSize;
+ x = state.onLeft
+ ? left
+ : right;
+ } else {
+ y = mPositionRect.top + mExpandedViewPadding;
+ x = rowStart + positionInRow;
+ }
+
+ if (showBubblesVertically() && mImeVisible) {
+ return new PointF(x, getExpandedBubbleYForIme(index, state.numberOfBubbles));
+ }
+ return new PointF(x, y);
+ }
+
+ /**
+ * Returns the position of the bubble on-screen when the stack is expanded and the IME
+ * is showing.
+ *
+ * @param index the index of the bubble in the stack.
+ * @param numberOfBubbles the total number of bubbles in the stack.
+ * @return y position of the bubble on-screen when the stack is expanded.
+ */
+ private float getExpandedBubbleYForIme(int index, int numberOfBubbles) {
+ final float top = getAvailableRect().top + mExpandedViewPadding;
+ if (!showBubblesVertically()) {
+ // Showing horizontally: align to top
+ return top;
+ }
+
+ // Showing vertically: might need to translate the bubbles above the IME.
+ // Subtract spacing here to provide a margin between top of IME and bottom of bubble row.
+ final float bottomInset = getImeHeight() + mInsets.bottom - (mSpacingBetweenBubbles * 2);
+ final float expandedStackSize = getExpandedStackSize(numberOfBubbles);
+ final float centerPosition = showBubblesVertically()
+ ? mPositionRect.centerY()
+ : mPositionRect.centerX();
+ final float rowBottom = centerPosition + (expandedStackSize / 2f);
+ final float rowTop = centerPosition - (expandedStackSize / 2f);
+ float rowTopForIme = rowTop;
+ if (rowBottom > bottomInset) {
+ // We overlap with IME, must shift the bubbles
+ float translationY = rowBottom - bottomInset;
+ rowTopForIme = Math.max(rowTop - translationY, top);
+ if (rowTop - translationY < top) {
+ // Even if we shift the bubbles, they will still overlap with the IME.
+ // Hide the overflow for a lil more space:
+ final float expandedStackSizeNoO = getExpandedStackSize(numberOfBubbles - 1);
+ final float centerPositionNoO = showBubblesVertically()
+ ? mPositionRect.centerY()
+ : mPositionRect.centerX();
+ final float rowBottomNoO = centerPositionNoO + (expandedStackSizeNoO / 2f);
+ final float rowTopNoO = centerPositionNoO - (expandedStackSizeNoO / 2f);
+ translationY = rowBottomNoO - bottomInset;
+ rowTopForIme = rowTopNoO - translationY;
+ }
+ }
+ return rowTopForIme + (index * (mBubbleSize + mSpacingBetweenBubbles));
+ }
+
+ /**
+ * @return the width of the bubble flyout (message originating from the bubble).
+ */
+ public float getMaxFlyoutSize() {
+ if (isLargeScreen()) {
+ return Math.max(mScreenRect.width() * FLYOUT_MAX_WIDTH_PERCENT_LARGE_SCREEN,
+ mMinimumFlyoutWidthLargeScreen);
+ }
+ return mScreenRect.width() * FLYOUT_MAX_WIDTH_PERCENT;
+ }
+
+ /**
+ * @return whether the stack is considered on the left side of the screen.
+ */
+ public boolean isStackOnLeft(PointF currentStackPosition) {
+ if (currentStackPosition == null) {
+ currentStackPosition = getRestingPosition();
+ }
+ final int stackCenter = (int) currentStackPosition.x + mBubbleSize / 2;
+ return stackCenter < mScreenRect.width() / 2;
+ }
+
+ /**
* Sets the stack's most recent position along the edge of the screen. This is saved when the
* last bubble is removed, so that the stack can be restored in its previous position.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index ac97c8f80617..300319a2f78f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -19,6 +19,8 @@ package com.android.wm.shell.bubbles;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
+import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -26,6 +28,8 @@ import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RES
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
@@ -33,11 +37,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
@@ -106,14 +110,8 @@ public class BubbleStackView extends FrameLayout
*/
private static final float FLYOUT_OVERSCROLL_ATTENUATION_FACTOR = 8f;
- /** Duration of the flyout alpha animations. */
- private static final int FLYOUT_ALPHA_ANIMATION_DURATION = 100;
-
private static final int FADE_IN_DURATION = 320;
- /** Percent to darken the bubbles when they're in the dismiss target. */
- private static final float DARKEN_PERCENT = 0.3f;
-
/** How long to wait, in milliseconds, before hiding the flyout. */
@VisibleForTesting
static final int FLYOUT_HIDE_AFTER = 5000;
@@ -122,6 +120,8 @@ public class BubbleStackView extends FrameLayout
private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
+ private static final float SCRIM_ALPHA = 0.6f;
+
/**
* How long to wait to animate the stack temporarily invisible after a drag/flyout hide
* animation ends, if we are in fact temporarily invisible.
@@ -188,6 +188,7 @@ public class BubbleStackView extends FrameLayout
};
private final BubbleController mBubbleController;
private final BubbleData mBubbleData;
+ private StackViewState mStackViewState = new StackViewState();
private final ValueAnimator mDismissBubbleAnimator;
@@ -195,7 +196,8 @@ public class BubbleStackView extends FrameLayout
private StackAnimationController mStackAnimationController;
private ExpandedAnimationController mExpandedAnimationController;
- private View mTaskbarScrim;
+ private View mScrim;
+ private View mManageMenuScrim;
private FrameLayout mExpandedViewContainer;
/** Matrix used to scale the expanded view container with a given pivot point. */
@@ -245,7 +247,6 @@ public class BubbleStackView extends FrameLayout
private int mBubbleTouchPadding;
private int mExpandedViewPadding;
private int mCornerRadius;
- private int mImeOffset;
@Nullable private BubbleViewProvider mExpandedBubble;
private boolean mIsExpanded;
@@ -555,7 +556,7 @@ public class BubbleStackView extends FrameLayout
if (mBubbleData.isExpanded()) {
if (mManageEduView != null) {
- mManageEduView.hide(false /* show */);
+ mManageEduView.hide();
}
// If we're expanded, tell the animation controller to prepare to drag this bubble,
@@ -756,7 +757,6 @@ public class BubbleStackView extends FrameLayout
mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
- mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
@@ -777,8 +777,8 @@ public class BubbleStackView extends FrameLayout
floatingContentCoordinator, this::getBubbleCount, onBubbleAnimatedOut,
this::animateShadows /* onStackAnimationFinished */, mPositioner);
- mExpandedAnimationController = new ExpandedAnimationController(
- mPositioner, mExpandedViewPadding, onBubbleAnimatedOut);
+ mExpandedAnimationController = new ExpandedAnimationController(mPositioner,
+ onBubbleAnimatedOut, this);
mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
// Force LTR by default since most of the Bubbles UI is positioned manually by the user, or
@@ -793,8 +793,6 @@ public class BubbleStackView extends FrameLayout
mBubbleContainer.setClipChildren(false);
addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
- updateUserEdu();
-
mExpandedViewContainer = new FrameLayout(context);
mExpandedViewContainer.setElevation(elevation);
mExpandedViewContainer.setClipChildren(false);
@@ -858,11 +856,20 @@ public class BubbleStackView extends FrameLayout
mBubbleData.setExpanded(true);
});
- mTaskbarScrim = new View(getContext());
- mTaskbarScrim.setBackgroundColor(Color.BLACK);
- addView(mTaskbarScrim);
- mTaskbarScrim.setAlpha(0f);
- mTaskbarScrim.setVisibility(GONE);
+ mScrim = new View(getContext());
+ mScrim.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ mScrim.setBackgroundDrawable(new ColorDrawable(
+ getResources().getColor(android.R.color.system_neutral1_1000)));
+ addView(mScrim);
+ mScrim.setAlpha(0f);
+
+ mManageMenuScrim = new View(getContext());
+ mManageMenuScrim.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ mManageMenuScrim.setBackgroundDrawable(new ColorDrawable(
+ getResources().getColor(android.R.color.system_neutral1_1000)));
+ addView(mManageMenuScrim, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ mManageMenuScrim.setAlpha(0f);
+ mManageMenuScrim.setVisibility(INVISIBLE);
mOrientationChangedListener =
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
@@ -882,12 +889,15 @@ public class BubbleStackView extends FrameLayout
// Re-draw bubble row and pointer for new orientation.
beforeExpandedViewAnimation();
updateOverflowVisibility();
- updatePointerPosition();
+ updatePointerPosition(false /* forIme */);
mExpandedAnimationController.expandFromStack(() -> {
afterExpandedViewAnimation();
+ showManageMenu(mShowingManage);
} /* after */);
+ final float translationY = mPositioner.getExpandedViewY(mExpandedBubble,
+ getBubbleIndex(mExpandedBubble));
mExpandedViewContainer.setTranslationX(0f);
- mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY());
+ mExpandedViewContainer.setTranslationY(translationY);
mExpandedViewContainer.setAlpha(1f);
}
removeOnLayoutChangeListener(mOrientationChangedListener);
@@ -917,8 +927,10 @@ public class BubbleStackView extends FrameLayout
setOnClickListener(view -> {
if (mShowingManage) {
showManageMenu(false /* show */);
+ } else if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
+ mManageEduView.hide();
} else if (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) {
- mStackEduView.hide(false);
+ mStackEduView.hide(false /* isExpanding */);
} else if (mBubbleData.isExpanded()) {
mBubbleData.setExpanded(false);
}
@@ -955,8 +967,9 @@ public class BubbleStackView extends FrameLayout
}
});
mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
- if (mExpandedBubble != null) {
- mExpandedBubble.setExpandedContentAlpha((float) valueAnimator.getAnimatedValue());
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ mExpandedBubble.getExpandedView().setTaskViewAlpha(
+ (float) valueAnimator.getAnimatedValue());
}
});
@@ -1117,10 +1130,10 @@ public class BubbleStackView extends FrameLayout
return;
}
if (mManageEduView == null) {
- mManageEduView = new ManageEducationView(mContext);
+ mManageEduView = new ManageEducationView(mContext, mPositioner);
addView(mManageEduView);
}
- mManageEduView.show(mExpandedBubble.getExpandedView(), mTempRect);
+ mManageEduView.show(mExpandedBubble.getExpandedView());
}
/**
@@ -1148,21 +1161,27 @@ public class BubbleStackView extends FrameLayout
return false;
}
if (mStackEduView == null) {
- mStackEduView = new StackEducationView(mContext);
+ mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
addView(mStackEduView);
}
mBubbleContainer.bringToFront();
return mStackEduView.show(mPositioner.getDefaultStartPosition());
}
+ // Recreates & shows the education views. Call when a theme/config change happens.
private void updateUserEdu() {
- maybeShowStackEdu();
- if (mManageEduView != null) {
- mManageEduView.invalidate();
+ if (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) {
+ removeView(mStackEduView);
+ mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
+ addView(mStackEduView);
+ mBubbleContainer.bringToFront(); // Stack appears on top of the stack education
+ mStackEduView.show(mPositioner.getDefaultStartPosition());
}
- maybeShowManageEdu();
- if (mStackEduView != null) {
- mStackEduView.invalidate();
+ if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
+ removeView(mManageEduView);
+ mManageEduView = new ManageEducationView(mContext, mPositioner);
+ addView(mManageEduView);
+ mManageEduView.show(mExpandedBubble.getExpandedView());
}
}
@@ -1171,7 +1190,7 @@ public class BubbleStackView extends FrameLayout
if (mFlyout != null) {
removeView(mFlyout);
}
- mFlyout = new BubbleFlyoutView(getContext());
+ mFlyout = new BubbleFlyoutView(getContext(), mPositioner);
mFlyout.setVisibility(GONE);
mFlyout.setOnClickListener(mFlyoutClickListener);
mFlyout.setOnTouchListener(mFlyoutTouchListener);
@@ -1218,6 +1237,10 @@ public class BubbleStackView extends FrameLayout
updateOverflow();
updateUserEdu();
updateExpandedViewTheme();
+ mScrim.setBackgroundDrawable(new ColorDrawable(
+ getResources().getColor(android.R.color.system_neutral1_1000)));
+ mManageMenuScrim.setBackgroundDrawable(new ColorDrawable(
+ getResources().getColor(android.R.color.system_neutral1_1000)));
}
/**
@@ -1229,9 +1252,6 @@ public class BubbleStackView extends FrameLayout
mRelativeStackPositionBeforeRotation = new RelativeStackPosition(
mPositioner.getRestingPosition(),
mStackAnimationController.getAllowableStackPositionRegion());
- mManageMenu.setVisibility(View.INVISIBLE);
- mShowingManage = false;
-
addOnLayoutChangeListener(mOrientationChangedListener);
hideFlyoutImmediate();
}
@@ -1255,6 +1275,7 @@ public class BubbleStackView extends FrameLayout
setUpManageMenu();
setUpFlyout();
setUpDismissView();
+ updateUserEdu();
mBubbleSize = mPositioner.getBubbleSize();
for (Bubble b : mBubbleData.getBubbles()) {
if (b.getIconView() == null) {
@@ -1292,6 +1313,7 @@ public class BubbleStackView extends FrameLayout
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ mPositioner.update();
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
getViewTreeObserver().addOnDrawListener(mSystemGestureExcludeUpdater);
}
@@ -1534,7 +1556,8 @@ public class BubbleStackView extends FrameLayout
} else {
bubble.cleanupViews();
}
- updatePointerPosition();
+ updatePointerPosition(false /* forIme */);
+ updateExpandedView();
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
return;
}
@@ -1574,7 +1597,7 @@ public class BubbleStackView extends FrameLayout
.map(b -> b.getIconView()).collect(Collectors.toList());
mStackAnimationController.animateReorder(bubbleViews, reorder);
}
- updatePointerPosition();
+ updatePointerPosition(false /* forIme */);
}
/**
@@ -1645,7 +1668,6 @@ public class BubbleStackView extends FrameLayout
private void showNewlySelectedBubble(BubbleViewProvider bubbleToSelect) {
final BubbleViewProvider previouslySelected = mExpandedBubble;
mExpandedBubble = bubbleToSelect;
- updatePointerPosition();
if (mIsExpanded) {
hideCurrentInputMethod();
@@ -1710,6 +1732,21 @@ public class BubbleStackView extends FrameLayout
notifyExpansionChanged(mExpandedBubble, mIsExpanded);
}
+ /**
+ * Called when back press occurs while bubbles are expanded.
+ */
+ public void onBackPressed() {
+ if (mIsExpanded) {
+ if (mShowingManage) {
+ showManageMenu(false);
+ } else if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
+ mManageEduView.hide();
+ } else {
+ setExpanded(false);
+ }
+ }
+ }
+
void setBubbleVisibility(Bubble b, boolean visible) {
if (b.getIconView() != null) {
b.getIconView().setVisibility(visible ? VISIBLE : GONE);
@@ -1722,6 +1759,7 @@ public class BubbleStackView extends FrameLayout
* not.
*/
void hideCurrentInputMethod() {
+ mPositioner.setImeVisible(false, 0);
mBubbleController.hideCurrentInputMethod();
}
@@ -1796,6 +1834,20 @@ public class BubbleStackView extends FrameLayout
mExpandedViewAlphaAnimator.start();
}
+ private void showScrim(boolean show) {
+ if (show) {
+ mScrim.animate()
+ .setInterpolator(ALPHA_IN)
+ .alpha(SCRIM_ALPHA)
+ .start();
+ } else {
+ mScrim.animate()
+ .alpha(0f)
+ .setInterpolator(ALPHA_OUT)
+ .start();
+ }
+ }
+
private void animateExpansion() {
cancelDelayedExpandCollapseSwitchAnimations();
final boolean showVertically = mPositioner.showBubblesVertically();
@@ -1805,47 +1857,38 @@ public class BubbleStackView extends FrameLayout
}
beforeExpandedViewAnimation();
+ showScrim(true);
updateZOrder();
updateBadges(false /* setBadgeForCollapsedStack */);
mBubbleContainer.setActiveController(mExpandedAnimationController);
updateOverflowVisibility();
- updatePointerPosition();
+ updatePointerPosition(false /* forIme */);
mExpandedAnimationController.expandFromStack(() -> {
if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
maybeShowManageEdu();
}
} /* after */);
-
- if (mPositioner.showingInTaskbar()
- // Don't need the scrim when the bar is at the bottom
- && mPositioner.getTaskbarPosition() != BubblePositioner.TASKBAR_POSITION_BOTTOM) {
- mTaskbarScrim.getLayoutParams().width = mPositioner.getTaskbarSize();
- mTaskbarScrim.setTranslationX(mStackOnLeftOrWillBe
- ? 0f
- : mPositioner.getAvailableRect().right - mPositioner.getTaskbarSize());
- mTaskbarScrim.setVisibility(VISIBLE);
- mTaskbarScrim.animate().alpha(1f).start();
- }
-
- mExpandedViewContainer.setTranslationX(0f);
- mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY());
- mExpandedViewContainer.setAlpha(1f);
-
int index;
if (mExpandedBubble != null && BubbleOverflow.KEY.equals(mExpandedBubble.getKey())) {
index = mBubbleData.getBubbles().size();
} else {
index = getBubbleIndex(mExpandedBubble);
}
- // Position of the bubble we're expanding, once it's settled in its row.
- final float bubbleWillBeAt =
- mExpandedAnimationController.getBubbleXOrYForOrientation(index);
+ PointF p = mPositioner.getExpandedBubbleXY(index, getState());
+ final float translationY = mPositioner.getExpandedViewY(mExpandedBubble,
+ mPositioner.showBubblesVertically() ? p.y : p.x);
+ mExpandedViewContainer.setTranslationX(0f);
+ mExpandedViewContainer.setTranslationY(translationY);
+ mExpandedViewContainer.setAlpha(1f);
// How far horizontally the bubble will be animating. We'll wait a bit longer for bubbles
// that are animating farther, so that the expanded view doesn't move as much.
final float relevantStackPosition = showVertically
? mStackAnimationController.getStackPosition().y
: mStackAnimationController.getStackPosition().x;
+ final float bubbleWillBeAt = showVertically
+ ? p.y
+ : p.x;
final float distanceAnimated = Math.abs(bubbleWillBeAt - relevantStackPosition);
// Wait for the path animation target to reach its end, and add a small amount of extra time
@@ -1862,27 +1905,27 @@ public class BubbleStackView extends FrameLayout
// Set the pivot point for the scale, so the expanded view animates out from the bubble.
if (showVertically) {
float pivotX;
- float pivotY = bubbleWillBeAt + mBubbleSize / 2f;
if (mStackOnLeftOrWillBe) {
- pivotX = mPositioner.getAvailableRect().left + mBubbleSize + mExpandedViewPadding;
+ pivotX = p.x + mBubbleSize + mExpandedViewPadding;
} else {
- pivotX = mPositioner.getAvailableRect().right - mBubbleSize - mExpandedViewPadding;
+ pivotX = p.x - mExpandedViewPadding;
}
mExpandedViewContainerMatrix.setScale(
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
- pivotX, pivotY);
+ pivotX,
+ p.y + mBubbleSize / 2f);
} else {
mExpandedViewContainerMatrix.setScale(
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
- bubbleWillBeAt + mBubbleSize / 2f,
- mPositioner.getExpandedViewY());
+ p.x + mBubbleSize / 2f,
+ p.y + mBubbleSize + mExpandedViewPadding);
}
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
if (mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.setExpandedContentAlpha(0f);
+ mExpandedBubble.getExpandedView().setTaskViewAlpha(0f);
// We'll be starting the alpha animation after a slight delay, so set this flag early
// here.
@@ -1914,6 +1957,7 @@ public class BubbleStackView extends FrameLayout
mExpandedViewContainerMatrix);
})
.withEndActions(() -> {
+ mExpandedViewContainer.setAnimationMatrix(null);
afterExpandedViewAnimation();
if (mExpandedBubble != null
&& mExpandedBubble.getExpandedView() != null) {
@@ -1929,12 +1973,17 @@ public class BubbleStackView extends FrameLayout
private void animateCollapse() {
cancelDelayedExpandCollapseSwitchAnimations();
+ if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
+ mManageEduView.hide();
+ }
// Hide the menu if it's visible.
showManageMenu(false);
mIsExpanded = false;
mIsExpansionAnimating = true;
+ showScrim(false);
+
mBubbleContainer.cancelAllAnimations();
// If we were in the middle of swapping, the animating-out surface would have been scaling
@@ -1952,10 +2001,6 @@ public class BubbleStackView extends FrameLayout
/* collapseTo */,
() -> mBubbleContainer.setActiveController(mStackAnimationController));
- if (mTaskbarScrim.getVisibility() == VISIBLE) {
- mTaskbarScrim.animate().alpha(0f).start();
- }
-
int index;
if (mExpandedBubble != null && BubbleOverflow.KEY.equals(mExpandedBubble.getKey())) {
index = mBubbleData.getBubbles().size();
@@ -1963,12 +2008,10 @@ public class BubbleStackView extends FrameLayout
index = mBubbleData.getBubbles().indexOf(mExpandedBubble);
}
// Value the bubble is animating from (back into the stack).
- final float expandingFromBubbleAt =
- mExpandedAnimationController.getBubbleXOrYForOrientation(index);
- final boolean showVertically = mPositioner.showBubblesVertically();
+ final PointF p = mPositioner.getExpandedBubbleXY(index, getState());
if (mPositioner.showBubblesVertically()) {
float pivotX;
- float pivotY = expandingFromBubbleAt + mBubbleSize / 2f;
+ float pivotY = p.y + mBubbleSize / 2f;
if (mStackOnLeftOrWillBe) {
pivotX = mPositioner.getAvailableRect().left + mBubbleSize + mExpandedViewPadding;
} else {
@@ -1980,8 +2023,8 @@ public class BubbleStackView extends FrameLayout
} else {
mExpandedViewContainerMatrix.setScale(
1f, 1f,
- expandingFromBubbleAt + mBubbleSize / 2f,
- mPositioner.getExpandedViewY());
+ p.x + mBubbleSize / 2f,
+ p.y + mBubbleSize + mExpandedViewPadding);
}
mExpandedViewAlphaAnimator.reverse();
@@ -2008,7 +2051,7 @@ public class BubbleStackView extends FrameLayout
final BubbleViewProvider previouslySelected = mExpandedBubble;
beforeExpandedViewAnimation();
if (mManageEduView != null) {
- mManageEduView.hide(false /* fromExpansion */);
+ mManageEduView.hide();
}
if (DEBUG_BUBBLE_STACK_VIEW) {
@@ -2023,10 +2066,6 @@ public class BubbleStackView extends FrameLayout
if (previouslySelected != null) {
previouslySelected.setTaskViewVisibility(false);
}
-
- if (mPositioner.showingInTaskbar()) {
- mTaskbarScrim.setVisibility(GONE);
- }
})
.start();
}
@@ -2063,32 +2102,31 @@ public class BubbleStackView extends FrameLayout
boolean isOverflow = mExpandedBubble != null
&& mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
- float expandingFromBubbleDestination =
- mExpandedAnimationController.getBubbleXOrYForOrientation(isOverflow
- ? getBubbleCount()
- : mBubbleData.getBubbles().indexOf(mExpandedBubble));
-
+ PointF p = mPositioner.getExpandedBubbleXY(isOverflow
+ ? mBubbleContainer.getChildCount() - 1
+ : mBubbleData.getBubbles().indexOf(mExpandedBubble),
+ getState());
mExpandedViewContainer.setAlpha(1f);
mExpandedViewContainer.setVisibility(View.VISIBLE);
if (mPositioner.showBubblesVertically()) {
float pivotX;
- float pivotY = expandingFromBubbleDestination + mBubbleSize / 2f;
+ float pivotY = p.y + mBubbleSize / 2f;
if (mStackOnLeftOrWillBe) {
- pivotX = mPositioner.getAvailableRect().left + mBubbleSize + mExpandedViewPadding;
+ pivotX = p.x + mBubbleSize + mExpandedViewPadding;
} else {
- pivotX = mPositioner.getAvailableRect().right - mBubbleSize - mExpandedViewPadding;
-
+ pivotX = p.x - mExpandedViewPadding;
}
mExpandedViewContainerMatrix.setScale(
- 0f, 0f,
+ 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
+ 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
pivotX, pivotY);
} else {
mExpandedViewContainerMatrix.setScale(
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
- expandingFromBubbleDestination + mBubbleSize / 2f,
- mPositioner.getExpandedViewY());
+ p.x + mBubbleSize / 2f,
+ p.y + mBubbleSize + mExpandedViewPadding);
}
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
@@ -2113,6 +2151,7 @@ public class BubbleStackView extends FrameLayout
.withEndActions(() -> {
mExpandedViewTemporarilyHidden = false;
mIsBubbleSwitchAnimating = false;
+ mExpandedViewContainer.setAnimationMatrix(null);
})
.start();
}, 25);
@@ -2144,9 +2183,20 @@ public class BubbleStackView extends FrameLayout
}
}
- /** Moves the bubbles out of the way if they're going to be over the keyboard. */
- public void onImeVisibilityChanged(boolean visible, int height) {
- mStackAnimationController.setImeHeight(visible ? height + mImeOffset : 0);
+ /**
+ * Updates the stack based for IME changes. When collapsed it'll move the stack if it
+ * overlaps where they IME would be. When expanded it'll shift the expanded bubbles
+ * if they might overlap with the IME (this only happens for large screens).
+ */
+ public void animateForIme(boolean visible) {
+ if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
+ // This will update the animation so the bubbles move to position for the IME
+ mExpandedAnimationController.expandFromStack(() -> {
+ updatePointerPosition(false /* forIme */);
+ afterExpandedViewAnimation();
+ } /* after */);
+ return;
+ }
if (!mIsExpanded && getBubbleCount() > 0) {
final float stackDestinationY =
@@ -2165,9 +2215,20 @@ public class BubbleStackView extends FrameLayout
FLYOUT_IME_ANIMATION_SPRING_CONFIG)
.start();
}
- } else if (mIsExpanded && mExpandedBubble != null
- && mExpandedBubble.getExpandedView() != null) {
+ } else if (mPositioner.showBubblesVertically() && mIsExpanded
+ && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
mExpandedBubble.getExpandedView().setImeVisible(visible);
+ List<Animator> animList = new ArrayList();
+ for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
+ View child = mBubbleContainer.getChildAt(i);
+ float transY = mPositioner.getExpandedBubbleXY(i, getState()).y;
+ ObjectAnimator anim = ObjectAnimator.ofFloat(child, TRANSLATION_Y, transY);
+ animList.add(anim);
+ }
+ updatePointerPosition(true /* forIme */);
+ AnimatorSet set = new AnimatorSet();
+ set.playTogether(animList);
+ set.start();
}
}
@@ -2403,20 +2464,19 @@ public class BubbleStackView extends FrameLayout
if (mFlyout.getVisibility() == View.VISIBLE) {
- mFlyout.animateUpdate(bubble.getFlyoutMessage(), getWidth(),
+ mFlyout.animateUpdate(bubble.getFlyoutMessage(),
mStackAnimationController.getStackPosition(), !bubble.showDot(),
mAfterFlyoutHidden /* onHide */);
} else {
mFlyout.setVisibility(INVISIBLE);
mFlyout.setupFlyoutStartingAsDot(bubble.getFlyoutMessage(),
- mStackAnimationController.getStackPosition(), getWidth(),
+ mStackAnimationController.getStackPosition(),
mStackAnimationController.isStackOnLeftSide(),
bubble.getIconView().getDotColor() /* dotColor */,
expandFlyoutAfterDelay /* onLayoutComplete */,
mAfterFlyoutHidden /* onHide */,
bubble.getIconView().getDotCenter(),
- !bubble.showDot(),
- mPositioner);
+ !bubble.showDot());
}
mFlyout.bringToFront();
});
@@ -2472,7 +2532,7 @@ public class BubbleStackView extends FrameLayout
// Account for the IME in the touchable region so that the touchable region of the
// Bubble window doesn't obscure the IME. The touchable region affects which areas
// of the screen can be excluded by lower windows (IME is just above the embedded task)
- outRect.bottom -= (int) mStackAnimationController.getImeHeight();
+ outRect.bottom -= mPositioner.getImeHeight();
}
if (mFlyout.getVisibility() == View.VISIBLE) {
@@ -2491,15 +2551,36 @@ public class BubbleStackView extends FrameLayout
invalidate();
}
- private void showManageMenu(boolean show) {
+ /** Hide or show the manage menu for the currently expanded bubble. */
+ @VisibleForTesting
+ public void showManageMenu(boolean show) {
mShowingManage = show;
// This should not happen, since the manage menu is only visible when there's an expanded
// bubble. If we end up in this state, just hide the menu immediately.
if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
mManageMenu.setVisibility(View.INVISIBLE);
+ mManageMenuScrim.setVisibility(INVISIBLE);
+ mBubbleController.getSysuiProxy().onManageMenuExpandChanged(false /* show */);
return;
}
+ if (show) {
+ mManageMenuScrim.setVisibility(VISIBLE);
+ mManageMenuScrim.setTranslationZ(mManageMenu.getElevation() - 1f);
+ }
+ Runnable endAction = () -> {
+ if (!show) {
+ mManageMenuScrim.setVisibility(INVISIBLE);
+ mManageMenuScrim.setTranslationZ(0f);
+ }
+ };
+
+ mBubbleController.getSysuiProxy().onManageMenuExpandChanged(show);
+ mManageMenuScrim.animate()
+ .setInterpolator(show ? ALPHA_IN : ALPHA_OUT)
+ .alpha(show ? SCRIM_ALPHA : 0f)
+ .withEndAction(endAction)
+ .start();
// If available, update the manage menu's settings option with the expanded bubble's app
// name and icon.
@@ -2510,7 +2591,6 @@ public class BubbleStackView extends FrameLayout
R.string.bubbles_app_settings, bubble.getAppName()));
}
- mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect);
if (mExpandedBubble.getExpandedView().getTaskView() != null) {
mExpandedBubble.getExpandedView().getTaskView().setObscuredTouchRect(mShowingManage
? new Rect(0, 0, getWidth(), getHeight())
@@ -2522,7 +2602,11 @@ public class BubbleStackView extends FrameLayout
// When the menu is open, it should be at these coordinates. The menu pops out to the right
// in LTR and to the left in RTL.
- final float targetX = isLtr ? mTempRect.left : mTempRect.right - mManageMenu.getWidth();
+ mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect);
+ final float margin = mExpandedBubble.getExpandedView().getManageButtonMargin();
+ final float targetX = isLtr
+ ? mTempRect.left - margin
+ : mTempRect.right + margin - mManageMenu.getWidth();
final float targetY = mTempRect.bottom - mManageMenu.getHeight();
final float xOffsetForAnimation = (isLtr ? 1 : -1) * mManageMenu.getWidth() / 4f;
@@ -2702,18 +2786,21 @@ public class BubbleStackView extends FrameLayout
}
boolean isOverflowExpanded = mExpandedBubble != null
&& BubbleOverflow.KEY.equals(mExpandedBubble.getKey());
- int[] paddings = mPositioner.getExpandedViewPadding(
+ int[] paddings = mPositioner.getExpandedViewContainerPadding(
mStackAnimationController.isStackOnLeftSide(), isOverflowExpanded);
mExpandedViewContainer.setPadding(paddings[0], paddings[1], paddings[2], paddings[3]);
if (mIsExpansionAnimating) {
mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
}
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY());
+ PointF p = mPositioner.getExpandedBubbleXY(getBubbleIndex(mExpandedBubble),
+ getState());
+ mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY(mExpandedBubble,
+ mPositioner.showBubblesVertically() ? p.y : p.x));
mExpandedViewContainer.setTranslationX(0f);
mExpandedBubble.getExpandedView().updateView(
mExpandedViewContainer.getLocationOnScreen());
- updatePointerPosition();
+ updatePointerPosition(false /* forIme */);
}
mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
@@ -2784,7 +2871,13 @@ public class BubbleStackView extends FrameLayout
}
}
- private void updatePointerPosition() {
+ /**
+ * Updates the position of the pointer based on the expanded bubble.
+ *
+ * @param forIme whether the position is being updated due to the ime appearing, in this case
+ * the pointer is animated to the location.
+ */
+ private void updatePointerPosition(boolean forIme) {
if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
return;
}
@@ -2792,8 +2885,12 @@ public class BubbleStackView extends FrameLayout
if (index == -1) {
return;
}
- float bubblePosition = mExpandedAnimationController.getBubbleXOrYForOrientation(index);
- mExpandedBubble.getExpandedView().setPointerPosition(bubblePosition, mStackOnLeftOrWillBe);
+ PointF position = mPositioner.getExpandedBubbleXY(index, getState());
+ float bubblePosition = mPositioner.showBubblesVertically()
+ ? position.y
+ : position.x;
+ mExpandedBubble.getExpandedView().setPointerPosition(bubblePosition,
+ mStackOnLeftOrWillBe, forIme /* animate */);
}
/**
@@ -2876,6 +2973,26 @@ public class BubbleStackView extends FrameLayout
return bubbles;
}
+ /** @return the current stack state. */
+ public StackViewState getState() {
+ mStackViewState.numberOfBubbles = mBubbleContainer.getChildCount();
+ mStackViewState.selectedIndex = getBubbleIndex(mExpandedBubble);
+ mStackViewState.onLeft = mStackOnLeftOrWillBe;
+ return mStackViewState;
+ }
+
+ /**
+ * Holds some commonly queried information about the stack.
+ */
+ public static class StackViewState {
+ // Number of bubbles (including the overflow itself) in the stack.
+ public int numberOfBubbles;
+ // The selected index if the stack is expanded.
+ public int selectedIndex;
+ // Whether the stack is resting on the left or right side of the screen when collapsed.
+ public boolean onLeft;
+ }
+
/**
* Representation of stack position that uses relative properties rather than absolute
* coordinates. This is used to maintain similar stack positions across configuration changes.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
index 38b3ba9dfda0..7e552826e94a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
@@ -29,12 +29,6 @@ public interface BubbleViewProvider {
@Nullable BubbleExpandedView getExpandedView();
/**
- * Sets the alpha of the expanded view content. This will be applied to both the expanded view
- * container itself (the manage button, etc.) as well as the TaskView within it.
- */
- void setExpandedContentAlpha(float alpha);
-
- /**
* Sets whether the contents of the bubble's TaskView should be visible.
*/
void setTaskViewVisibility(boolean visible);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index c73b5eebc5c2..c82249b8a369 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -24,12 +24,10 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.os.Bundle;
-import android.os.Looper;
import android.service.notification.NotificationListenerService.RankingMap;
import android.util.ArraySet;
import android.util.Pair;
import android.util.SparseArray;
-import android.view.View;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
@@ -43,7 +41,6 @@ import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Executor;
-import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -160,14 +157,6 @@ public interface Bubbles {
/** Set the proxy to commnuicate with SysUi side components. */
void setSysuiProxy(SysuiProxy proxy);
- /**
- * Set the scrim view for bubbles.
- *
- * @param callback The callback made with the executor and the executor's looper that the view
- * will be running on.
- **/
- void setBubbleScrim(View view, BiConsumer<Executor, Looper> callback);
-
/** Set a listener to be notified of bubble expand events. */
void setExpandListener(BubbleExpandListener listener);
@@ -295,6 +284,8 @@ public interface Bubbles {
void onStackExpandChanged(boolean shouldExpand);
+ void onManageMenuExpandChanged(boolean menuExpanded);
+
void onUnbubbleConversation(String key);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
index 0a1cd2246339..74672a336161 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
@@ -28,37 +28,40 @@ import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
import com.android.wm.shell.R
import com.android.wm.shell.animation.PhysicsAnimator
import com.android.wm.shell.common.DismissCircleView
+import android.view.WindowInsets
+import android.view.WindowManager
/*
* View that handles interactions between DismissCircleView and BubbleStackView.
*/
class DismissView(context: Context) : FrameLayout(context) {
- var circle = DismissCircleView(context).apply {
- val targetSize: Int = context.resources.getDimensionPixelSize(R.dimen.dismiss_circle_size)
- val newParams = LayoutParams(targetSize, targetSize)
- newParams.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
- setLayoutParams(newParams)
- setTranslationY(
- resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height).toFloat())
- }
-
+ var circle = DismissCircleView(context)
var isShowing = false
+
private val animator = PhysicsAnimator.getInstance(circle)
private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
private val DISMISS_SCRIM_FADE_MS = 200
+ private var wm: WindowManager =
+ context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
init {
setLayoutParams(LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height),
Gravity.BOTTOM))
- setPadding(0, 0, 0, resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin))
+ updatePadding()
setClipToPadding(false)
setClipChildren(false)
setVisibility(View.INVISIBLE)
setBackgroundResource(
R.drawable.floating_dismiss_gradient_transition)
- addView(circle)
+
+ val targetSize: Int = resources.getDimensionPixelSize(R.dimen.dismiss_circle_size)
+ addView(circle, LayoutParams(targetSize, targetSize,
+ Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL))
+ // start with circle offscreen so it's animated up
+ circle.setTranslationY(resources.getDimensionPixelSize(
+ R.dimen.floating_dismiss_gradient_height).toFloat())
}
/**
@@ -91,9 +94,21 @@ class DismissView(context: Context) : FrameLayout(context) {
}
fun updateResources() {
- val targetSize: Int = context.resources.getDimensionPixelSize(R.dimen.dismiss_circle_size)
+ updatePadding()
+ layoutParams.height = resources.getDimensionPixelSize(
+ R.dimen.floating_dismiss_gradient_height)
+
+ val targetSize: Int = resources.getDimensionPixelSize(R.dimen.dismiss_circle_size)
circle.layoutParams.width = targetSize
circle.layoutParams.height = targetSize
circle.requestLayout()
}
+
+ private fun updatePadding() {
+ val insets: WindowInsets = wm.getCurrentWindowMetrics().getWindowInsets()
+ val navInset = insets.getInsetsIgnoringVisibility(
+ WindowInsets.Type.navigationBars())
+ setPadding(0, 0, 0, navInset.bottom +
+ resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin))
+ }
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index 4cc67025fff4..eb4737ac6c63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -18,12 +18,13 @@ package com.android.wm.shell.bubbles
import android.content.Context
import android.graphics.Color
import android.graphics.Rect
+import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater
import android.view.View
+import android.view.ViewGroup
import android.widget.Button
import android.widget.LinearLayout
-import android.widget.TextView
-import com.android.internal.util.ContrastColorUtil
+import com.android.internal.R.color.system_neutral1_900
import com.android.wm.shell.R
import com.android.wm.shell.animation.Interpolators
@@ -31,21 +32,22 @@ import com.android.wm.shell.animation.Interpolators
* User education view to highlight the manage button that allows a user to configure the settings
* for the bubble. Shown only the first time a user expands a bubble.
*/
-class ManageEducationView constructor(context: Context) : LinearLayout(context) {
+class ManageEducationView constructor(context: Context, positioner: BubblePositioner)
+ : LinearLayout(context) {
- private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleManageEducationView"
+ private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "ManageEducationView"
else BubbleDebugConfig.TAG_BUBBLES
private val ANIMATE_DURATION: Long = 200
- private val ANIMATE_DURATION_SHORT: Long = 40
- private val manageView by lazy { findViewById<View>(R.id.manage_education_view) }
- private val manageButton by lazy { findViewById<Button>(R.id.manage) }
+ private val positioner: BubblePositioner = positioner
+ private val manageView by lazy { findViewById<ViewGroup>(R.id.manage_education_view) }
+ private val manageButton by lazy { findViewById<Button>(R.id.manage_button) }
private val gotItButton by lazy { findViewById<Button>(R.id.got_it) }
- private val titleTextView by lazy { findViewById<TextView>(R.id.user_education_title) }
- private val descTextView by lazy { findViewById<TextView>(R.id.user_education_description) }
private var isHiding = false
+ private var realManageButtonRect = Rect()
+ private var bubbleExpandedView: BubbleExpandedView? = null
init {
LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this)
@@ -66,18 +68,17 @@ class ManageEducationView constructor(context: Context) : LinearLayout(context)
override fun onFinishInflate() {
super.onFinishInflate()
layoutDirection = resources.configuration.layoutDirection
- setTextColor()
}
- private fun setTextColor() {
- val typedArray = mContext.obtainStyledAttributes(intArrayOf(android.R.attr.colorAccent,
- android.R.attr.textColorPrimaryInverse))
- val bgColor = typedArray.getColor(0 /* index */, Color.BLACK)
- var textColor = typedArray.getColor(1 /* index */, Color.WHITE)
+ private fun setButtonColor() {
+ val typedArray = mContext.obtainStyledAttributes(intArrayOf(
+ com.android.internal.R.attr.colorAccentPrimary))
+ val buttonColor = typedArray.getColor(0 /* index */, Color.TRANSPARENT)
typedArray.recycle()
- textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true)
- titleTextView.setTextColor(textColor)
- descTextView.setTextColor(textColor)
+
+ manageButton.setTextColor(mContext.getColor(system_neutral1_900))
+ manageButton.setBackgroundDrawable(ColorDrawable(buttonColor))
+ gotItButton.setBackgroundDrawable(ColorDrawable(buttonColor))
}
private fun setDrawableDirection() {
@@ -91,30 +92,39 @@ class ManageEducationView constructor(context: Context) : LinearLayout(context)
* If necessary, toggles the user education view for the manage button. This is shown when the
* bubble stack is expanded for the first time.
*
- * @param show whether the user education view should show or not.
+ * @param expandedView the expandedView the user education is shown on top of.
*/
- fun show(expandedView: BubbleExpandedView, rect: Rect) {
+ fun show(expandedView: BubbleExpandedView) {
+ setButtonColor()
if (visibility == VISIBLE) return
+ bubbleExpandedView = expandedView
+ expandedView.taskView?.setObscuredTouchRect(Rect(positioner.screenRect))
+
+ layoutParams.width = if (positioner.isLargeScreen)
+ context.resources.getDimensionPixelSize(
+ R.dimen.bubbles_user_education_width_large_screen)
+ else ViewGroup.LayoutParams.MATCH_PARENT
+
alpha = 0f
visibility = View.VISIBLE
+ expandedView.getManageButtonBoundsOnScreen(realManageButtonRect)
+ manageView.setPadding(realManageButtonRect.left - expandedView.manageButtonMargin,
+ manageView.paddingTop, manageView.paddingRight, manageView.paddingBottom)
post {
- expandedView.getManageButtonBoundsOnScreen(rect)
-
manageButton
.setOnClickListener {
- expandedView.findViewById<View>(R.id.settings_button).performClick()
- hide(true /* isStackExpanding */)
+ hide()
+ expandedView.findViewById<View>(R.id.manage_button).performClick()
}
- gotItButton.setOnClickListener { hide(true /* isStackExpanding */) }
- setOnClickListener { hide(true /* isStackExpanding */) }
-
- with(manageView) {
- translationX = 0f
- val inset = resources.getDimensionPixelSize(
- R.dimen.bubbles_manage_education_top_inset)
- translationY = (rect.top - manageView.height + inset).toFloat()
- }
+ gotItButton.setOnClickListener { hide() }
+ setOnClickListener { hide() }
+
+ val offsetViewBounds = Rect()
+ manageButton.getDrawingRect(offsetViewBounds)
+ manageView.offsetDescendantRectToMyCoords(manageButton, offsetViewBounds)
+ translationX = 0f
+ translationY = (realManageButtonRect.top - offsetViewBounds.top).toFloat()
bringToFront()
animate()
.setDuration(ANIMATE_DURATION)
@@ -124,13 +134,14 @@ class ManageEducationView constructor(context: Context) : LinearLayout(context)
setShouldShow(false)
}
- fun hide(isStackExpanding: Boolean) {
+ fun hide() {
+ bubbleExpandedView?.taskView?.setObscuredTouchRect(null)
if (visibility != VISIBLE || isHiding) return
animate()
.withStartAction { isHiding = true }
.alpha(0f)
- .setDuration(if (isStackExpanding) ANIMATE_DURATION_SHORT else ANIMATE_DURATION)
+ .setDuration(ANIMATE_DURATION)
.withEndAction {
isHiding = false
visibility = GONE
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 0a2cfc4089ed..f6a90b7a76cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -18,8 +18,11 @@ package com.android.wm.shell.bubbles
import android.content.Context
import android.graphics.Color
import android.graphics.PointF
+import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
+import android.view.View.OnKeyListener
+import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import com.android.internal.util.ContrastColorUtil
@@ -30,7 +33,12 @@ import com.android.wm.shell.animation.Interpolators
* User education view to highlight the collapsed stack of bubbles.
* Shown only the first time a user taps the stack.
*/
-class StackEducationView constructor(context: Context) : LinearLayout(context) {
+class StackEducationView constructor(
+ context: Context,
+ positioner: BubblePositioner,
+ controller: BubbleController
+)
+ : LinearLayout(context) {
private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
else BubbleDebugConfig.TAG_BUBBLES
@@ -38,6 +46,9 @@ class StackEducationView constructor(context: Context) : LinearLayout(context) {
private val ANIMATE_DURATION: Long = 200
private val ANIMATE_DURATION_SHORT: Long = 40
+ private val positioner: BubblePositioner = positioner
+ private val controller: BubbleController = controller
+
private val view by lazy { findViewById<View>(R.id.stack_education_layout) }
private val titleTextView by lazy { findViewById<TextView>(R.id.stack_education_title) }
private val descTextView by lazy { findViewById<TextView>(R.id.stack_education_description) }
@@ -67,6 +78,28 @@ class StackEducationView constructor(context: Context) : LinearLayout(context) {
setTextColor()
}
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ setFocusableInTouchMode(true)
+ setOnKeyListener(object : OnKeyListener {
+ override fun onKey(v: View?, keyCode: Int, event: KeyEvent): Boolean {
+ // if the event is a key down event on the enter button
+ if (event.action == KeyEvent.ACTION_UP &&
+ keyCode == KeyEvent.KEYCODE_BACK && !isHiding) {
+ hide(false)
+ return true
+ }
+ return false
+ }
+ })
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ setOnKeyListener(null)
+ controller.updateWindowFlagsForBackpress(false /* interceptBack */)
+ }
+
private fun setTextColor() {
val ta = mContext.obtainStyledAttributes(intArrayOf(android.R.attr.colorAccent,
android.R.attr.textColorPrimaryInverse))
@@ -94,13 +127,25 @@ class StackEducationView constructor(context: Context) : LinearLayout(context) {
fun show(stackPosition: PointF): Boolean {
if (visibility == VISIBLE) return false
+ controller.updateWindowFlagsForBackpress(true /* interceptBack */)
+ layoutParams.width = if (positioner.isLargeScreen)
+ context.resources.getDimensionPixelSize(
+ R.dimen.bubbles_user_education_width_large_screen)
+ else ViewGroup.LayoutParams.MATCH_PARENT
+
setAlpha(0f)
setVisibility(View.VISIBLE)
post {
+ requestFocus()
with(view) {
- val bubbleSize = context.resources.getDimensionPixelSize(
- R.dimen.bubble_size)
- translationY = stackPosition.y + bubbleSize / 2 - getHeight() / 2
+ if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
+ setPadding(positioner.bubbleSize + paddingRight, paddingTop, paddingRight,
+ paddingBottom)
+ } else {
+ setPadding(paddingLeft, paddingTop, positioner.bubbleSize + paddingLeft,
+ paddingBottom)
+ }
+ translationY = stackPosition.y + positioner.bubbleSize / 2 - getHeight() / 2
}
animate()
.setDuration(ANIMATE_DURATION)
@@ -114,15 +159,16 @@ class StackEducationView constructor(context: Context) : LinearLayout(context) {
/**
* If necessary, hides the stack education view.
*
- * @param fromExpansion if true this indicates the hide is happening due to the bubble being
+ * @param isExpanding if true this indicates the hide is happening due to the bubble being
* expanded, false if due to a touch outside of the bubble stack.
*/
- fun hide(fromExpansion: Boolean) {
+ fun hide(isExpanding: Boolean) {
if (visibility != VISIBLE || isHiding) return
+ controller.updateWindowFlagsForBackpress(false /* interceptBack */)
animate()
.alpha(0f)
- .setDuration(if (fromExpansion) ANIMATE_DURATION_SHORT else ANIMATE_DURATION)
+ .setDuration(if (isExpanding) ANIMATE_DURATION_SHORT else ANIMATE_DURATION)
.withEndAction { visibility = GONE }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index df2b440c19df..f0f78748e343 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -21,7 +21,6 @@ import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RES
import android.content.res.Resources;
import android.graphics.Path;
import android.graphics.PointF;
-import android.graphics.Rect;
import android.view.View;
import androidx.annotation.NonNull;
@@ -33,6 +32,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.google.android.collect.Sets;
@@ -64,9 +64,6 @@ public class ExpandedAnimationController
/** Stiffness for the expand/collapse path-following animation. */
private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000;
- /** What percentage of the screen to use when centering the bubbles in landscape. */
- private static final float CENTER_BUBBLES_LANDSCAPE_PERCENT = 0.66f;
-
/**
* Velocity required to dismiss an individual bubble without dragging it into the dismiss
* target.
@@ -79,16 +76,8 @@ public class ExpandedAnimationController
/** Horizontal offset between bubbles, which we need to know to re-stack them. */
private float mStackOffsetPx;
- /** Space between status bar and bubbles in the expanded state. */
- private float mBubblePaddingTop;
/** Size of each bubble. */
private float mBubbleSizePx;
- /** Max number of bubbles shown in row above expanded view. */
- private int mBubblesMaxRendered;
- /** Max amount of space to have between bubbles when expanded. */
- private int mBubblesMaxSpace;
- /** Amount of space between the bubbles when expanded. */
- private float mSpaceBetweenBubbles;
/** Whether the expand / collapse animation is running. */
private boolean mAnimatingExpand = false;
@@ -127,8 +116,6 @@ public class ExpandedAnimationController
/** The bubble currently being dragged out of the row (to potentially be dismissed). */
private MagnetizedObject<View> mMagnetizedBubbleDraggingOut;
- private int mExpandedViewPadding;
-
/**
* Callback to run whenever any bubble is animated out. The BubbleStackView will check if the
* end of this animation means we have no bubbles left, and notify the BubbleController.
@@ -137,13 +124,15 @@ public class ExpandedAnimationController
private BubblePositioner mPositioner;
- public ExpandedAnimationController(BubblePositioner positioner, int expandedViewPadding,
- Runnable onBubbleAnimatedOutAction) {
+ private BubbleStackView mBubbleStackView;
+
+ public ExpandedAnimationController(BubblePositioner positioner,
+ Runnable onBubbleAnimatedOutAction, BubbleStackView stackView) {
mPositioner = positioner;
updateResources();
- mExpandedViewPadding = expandedViewPadding;
mOnBubbleAnimatedOutAction = onBubbleAnimatedOutAction;
mCollapsePoint = mPositioner.getDefaultStartPosition();
+ mBubbleStackView = stackView;
}
/**
@@ -208,11 +197,8 @@ public class ExpandedAnimationController
return;
}
Resources res = mLayout.getContext().getResources();
- mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
mBubbleSizePx = mPositioner.getBubbleSize();
- mBubblesMaxRendered = mPositioner.getMaxBubbles();
- mSpaceBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
}
/**
@@ -256,31 +242,19 @@ public class ExpandedAnimationController
final Path path = new Path();
path.moveTo(bubble.getTranslationX(), bubble.getTranslationY());
- final float expandedY = mPositioner.showBubblesVertically()
- ? getBubbleXOrYForOrientation(index)
- : getExpandedY();
+ final PointF p = mPositioner.getExpandedBubbleXY(index, mBubbleStackView.getState());
if (expanding) {
- // If we're expanding, first draw a line from the bubble's current position to the
- // top of the screen.
- path.lineTo(bubble.getTranslationX(), expandedY);
+ // If we're expanding, first draw a line from the bubble's current position to where
+ // it'll end up
+ path.lineTo(bubble.getTranslationX(), p.y);
// Then, draw a line across the screen to the bubble's resting position.
- if (mPositioner.showBubblesVertically()) {
- Rect availableRect = mPositioner.getAvailableRect();
- boolean onLeft = mCollapsePoint != null
- && mCollapsePoint.x < (availableRect.width() / 2f);
- float translationX = onLeft
- ? availableRect.left
- : availableRect.right - mBubbleSizePx;
- path.lineTo(translationX, getBubbleXOrYForOrientation(index));
- } else {
- path.lineTo(getBubbleXOrYForOrientation(index), expandedY);
- }
+ path.lineTo(p.x, p.y);
} else {
final float stackedX = mCollapsePoint.x;
// If we're collapsing, draw a line from the bubble's current position to the side
// of the screen where the bubble will be stacked.
- path.lineTo(stackedX, expandedY);
+ path.lineTo(stackedX, p.y);
// Then, draw a line down to the stack position.
path.lineTo(stackedX, mCollapsePoint.y
@@ -390,8 +364,9 @@ public class ExpandedAnimationController
bubbleView.setTranslationY(y);
}
+ final float expandedY = mPositioner.getExpandedViewYTopAligned();
final boolean draggedOutEnough =
- y > getExpandedY() + mBubbleSizePx || y < getExpandedY() - mBubbleSizePx;
+ y > expandedY + mBubbleSizePx || y < expandedY - mBubbleSizePx;
if (draggedOutEnough != mBubbleDraggedOutEnough) {
updateBubblePositions();
mBubbleDraggedOutEnough = draggedOutEnough;
@@ -435,9 +410,9 @@ public class ExpandedAnimationController
return;
}
final int index = mLayout.indexOfChild(bubbleView);
-
+ final PointF p = mPositioner.getExpandedBubbleXY(index, mBubbleStackView.getState());
animationForChildAtIndex(index)
- .position(getBubbleXOrYForOrientation(index), getExpandedY())
+ .position(p.x, p.y)
.withPositionStartVelocities(velX, velY)
.start(() -> bubbleView.setTranslationZ(0f) /* after */);
@@ -453,20 +428,6 @@ public class ExpandedAnimationController
updateBubblePositions();
}
- /**
- * Animates the bubbles to {@link #getExpandedY()} position. Used in response to IME showing.
- */
- public void updateYPosition(Runnable after) {
- if (mLayout == null) return;
- animationsForChildrenFromIndex(
- 0, (i, anim) -> anim.translationY(getExpandedY())).startAll(after);
- }
-
- /** The Y value of the row of expanded bubbles. */
- public float getExpandedY() {
- return mPositioner.getAvailableRect().top + mBubblePaddingTop;
- }
-
/** Description of current animation controller state. */
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("ExpandedAnimationController state:");
@@ -522,37 +483,35 @@ public class ExpandedAnimationController
startOrUpdatePathAnimation(true /* expanding */);
} else if (mAnimatingCollapse) {
startOrUpdatePathAnimation(false /* expanding */);
- } else if (mPositioner.showBubblesVertically()) {
- child.setTranslationY(getBubbleXOrYForOrientation(index));
- if (!mPreparingToCollapse) {
- // Only animate if we're not collapsing as that animation will handle placing the
+ } else {
+ boolean onLeft = mPositioner.isStackOnLeft(mCollapsePoint);
+ final PointF p = mPositioner.getExpandedBubbleXY(index, mBubbleStackView.getState());
+ if (mPositioner.showBubblesVertically()) {
+ child.setTranslationY(p.y);
+ } else {
+ child.setTranslationX(p.x);
+ }
+
+ if (mPreparingToCollapse) {
+ // Don't animate if we're collapsing, as that animation will handle placing the
// new bubble in the stacked position.
- Rect availableRect = mPositioner.getAvailableRect();
- boolean onLeft = mCollapsePoint != null
- && mCollapsePoint.x < (availableRect.width() / 2f);
+ return;
+ }
+
+ if (mPositioner.showBubblesVertically()) {
float fromX = onLeft
- ? -mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
- : availableRect.right + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
- float toX = onLeft
- ? availableRect.left + mExpandedViewPadding
- : availableRect.right - mBubbleSizePx - mExpandedViewPadding;
+ ? p.x - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
+ : p.x + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
animationForChild(child)
- .translationX(fromX, toX)
+ .translationX(fromX, p.y)
.start();
- updateBubblePositions();
- }
- } else {
- child.setTranslationX(getBubbleXOrYForOrientation(index));
- if (!mPreparingToCollapse) {
- // Only animate if we're not collapsing as that animation will handle placing the
- // new bubble in the stacked position.
- float toY = getExpandedY();
- float fromY = getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
+ } else {
+ float fromY = p.y - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
animationForChild(child)
- .translationY(fromY, toY)
+ .translationY(fromY, p.y)
.start();
- updateBubblePositions();
}
+ updateBubblePositions();
}
}
@@ -599,7 +558,6 @@ public class ExpandedAnimationController
if (mAnimatingExpand || mAnimatingCollapse) {
return;
}
-
for (int i = 0; i < mLayout.getChildCount(); i++) {
final View bubble = mLayout.getChildAt(i);
@@ -609,49 +567,11 @@ public class ExpandedAnimationController
return;
}
- if (mPositioner.showBubblesVertically()) {
- Rect availableRect = mPositioner.getAvailableRect();
- boolean onLeft = mCollapsePoint != null
- && mCollapsePoint.x < (availableRect.width() / 2f);
- animationForChild(bubble)
- .translationX(onLeft
- ? availableRect.left
- : availableRect.right - mBubbleSizePx)
- .translationY(getBubbleXOrYForOrientation(i))
- .start();
- } else {
- animationForChild(bubble)
- .translationX(getBubbleXOrYForOrientation(i))
- .translationY(getExpandedY())
- .start();
- }
- }
- }
-
- // TODO - could move to method on bubblePositioner if mSpaceBetweenBubbles gets moved
- /**
- * When bubbles are expanded in portrait, they display at the top of the screen in a horizontal
- * row. When in landscape or on a large screen, they show at the left or right side in a
- * vertical row. This method accounts for screen orientation and will return an x or y value
- * for the position of the bubble in the row.
- *
- * @param index Bubble index in row.
- * @return the y position of the bubble if showing vertically and the x position if showing
- * horizontally.
- */
- public float getBubbleXOrYForOrientation(int index) {
- if (mLayout == null) {
- return 0;
+ final PointF p = mPositioner.getExpandedBubbleXY(i, mBubbleStackView.getState());
+ animationForChild(bubble)
+ .translationX(p.x)
+ .translationY(p.y)
+ .start();
}
- final float positionInBar = index * (mBubbleSizePx + mSpaceBetweenBubbles);
- Rect availableRect = mPositioner.getAvailableRect();
- final boolean isLandscape = mPositioner.showBubblesVertically();
- final float expandedStackSize = (mLayout.getChildCount() * mBubbleSizePx)
- + ((mLayout.getChildCount() - 1) * mSpaceBetweenBubbles);
- final float centerPosition = isLandscape
- ? availableRect.centerY()
- : availableRect.centerX();
- final float rowStart = centerPosition - (expandedStackSize / 2f);
- return rowStart + positionInBar;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 636e1452aa9b..60b64333114e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -127,9 +127,6 @@ public class StackAnimationController extends
/** Whether or not the stack's start position has been set. */
private boolean mStackMovedToStartPosition = false;
- /** The height of the most recently visible IME. */
- private float mImeHeight = 0f;
-
/**
* The Y position of the stack before the IME became visible, or {@link Float#MIN_VALUE} if the
* IME is not visible or the user moved the stack since the IME became visible.
@@ -173,7 +170,7 @@ public class StackAnimationController extends
*/
private boolean mSpringToTouchOnNextMotionEvent = false;
- /** Horizontal offset of bubbles in the stack. */
+ /** Offset of bubbles in the stack (i.e. how much they overlap). */
private float mStackOffset;
/** Offset between stack y and animation y for bubble swap. */
private float mSwapAnimationOffset;
@@ -305,10 +302,7 @@ public class StackAnimationController extends
if (mLayout == null || !isStackPositionSet()) {
return true; // Default to left, which is where it starts by default.
}
-
- float stackCenter = mStackPosition.x + mBubbleSize / 2;
- float screenCenter = mLayout.getWidth() / 2;
- return stackCenter < screenCenter;
+ return mPositioner.isStackOnLeft(mStackPosition);
}
/**
@@ -524,16 +518,6 @@ public class StackAnimationController extends
removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
}
- /** Save the current IME height so that we know where the stack bounds should be. */
- public void setImeHeight(int imeHeight) {
- mImeHeight = imeHeight;
- }
-
- /** Returns the current IME height that the stack is offset by. */
- public float getImeHeight() {
- return mImeHeight;
- }
-
/**
* Animates the stack either away from the newly visible IME, or back to its original position
* due to the IME going away.
@@ -592,11 +576,14 @@ public class StackAnimationController extends
*/
public RectF getAllowableStackPositionRegion() {
final RectF allowableRegion = new RectF(mPositioner.getAvailableRect());
+ final int imeHeight = mPositioner.getImeHeight();
+ final float bottomPadding = getBubbleCount() > 1
+ ? mBubblePaddingTop + mStackOffset
+ : mBubblePaddingTop;
allowableRegion.left -= mBubbleOffscreen;
allowableRegion.top += mBubblePaddingTop;
allowableRegion.right += mBubbleOffscreen - mBubbleSize;
- allowableRegion.bottom -= mBubblePaddingTop + mBubbleSize
- + (mImeHeight != UNSET ? mImeHeight + mBubblePaddingTop : 0f);
+ allowableRegion.bottom -= imeHeight + bottomPadding + mBubbleSize;
return allowableRegion;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index 3a7b534f3c17..ffda1f92ec90 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.common;
import android.os.RemoteException;
+import android.util.Slog;
import android.view.IDisplayWindowRotationCallback;
import android.view.IDisplayWindowRotationController;
import android.view.IWindowManager;
@@ -27,6 +28,7 @@ import androidx.annotation.BinderThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import java.util.ArrayList;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* This module deals with display rotations coming from WM. When WM starts a rotation: after it has
@@ -35,14 +37,14 @@ import java.util.ArrayList;
* rotation.
*/
public class DisplayChangeController {
+ private static final String TAG = DisplayChangeController.class.getSimpleName();
private final ShellExecutor mMainExecutor;
private final IWindowManager mWmService;
private final IDisplayWindowRotationController mControllerImpl;
- private final ArrayList<OnDisplayChangingListener> mRotationListener =
- new ArrayList<>();
- private final ArrayList<OnDisplayChangingListener> mTmpListeners = new ArrayList<>();
+ private final CopyOnWriteArrayList<OnDisplayChangingListener> mRotationListener =
+ new CopyOnWriteArrayList<>();
public DisplayChangeController(IWindowManager wmService, ShellExecutor mainExecutor) {
mMainExecutor = mainExecutor;
@@ -59,34 +61,26 @@ public class DisplayChangeController {
* Adds a display rotation controller.
*/
public void addRotationListener(OnDisplayChangingListener listener) {
- synchronized (mRotationListener) {
- mRotationListener.add(listener);
- }
+ mRotationListener.add(listener);
}
/**
* Removes a display rotation controller.
*/
public void removeRotationListener(OnDisplayChangingListener listener) {
- synchronized (mRotationListener) {
- mRotationListener.remove(listener);
- }
+ mRotationListener.remove(listener);
}
private void onRotateDisplay(int displayId, final int fromRotation, final int toRotation,
IDisplayWindowRotationCallback callback) {
WindowContainerTransaction t = new WindowContainerTransaction();
- synchronized (mRotationListener) {
- mTmpListeners.clear();
- // Make a local copy in case the handlers add/remove themselves.
- mTmpListeners.addAll(mRotationListener);
- }
- for (OnDisplayChangingListener c : mTmpListeners) {
+ for (OnDisplayChangingListener c : mRotationListener) {
c.onRotateDisplay(displayId, fromRotation, toRotation, t);
}
try {
callback.continueRotateDisplay(toRotation, t);
} catch (RemoteException e) {
+ Slog.e(TAG, "Failed to continue rotation", e);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index ba9ba5e5883a..a1fb658ccb9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -26,6 +26,7 @@ import android.util.SparseArray;
import android.view.Display;
import android.view.IDisplayWindowListener;
import android.view.IWindowManager;
+import android.view.InsetsState;
import androidx.annotation.BinderThread;
@@ -52,14 +53,6 @@ public class DisplayController {
private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>();
private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>();
- /**
- * Gets a display by id from DisplayManager.
- */
- public Display getDisplay(int displayId) {
- final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
- return displayManager.getDisplay(displayId);
- }
-
public DisplayController(Context context, IWindowManager wmService,
ShellExecutor mainExecutor) {
mMainExecutor = mainExecutor;
@@ -67,14 +60,31 @@ public class DisplayController {
mWmService = wmService;
mChangeController = new DisplayChangeController(mWmService, mainExecutor);
mDisplayContainerListener = new DisplayWindowListenerImpl();
+ }
+
+ /**
+ * Initializes the window listener.
+ */
+ public void initialize() {
try {
- mWmService.registerDisplayWindowListener(mDisplayContainerListener);
+ int[] displayIds = mWmService.registerDisplayWindowListener(mDisplayContainerListener);
+ for (int i = 0; i < displayIds.length; i++) {
+ onDisplayAdded(displayIds[i]);
+ }
} catch (RemoteException e) {
- throw new RuntimeException("Unable to register hierarchy listener");
+ throw new RuntimeException("Unable to register display controller");
}
}
/**
+ * Gets a display by id from DisplayManager.
+ */
+ public Display getDisplay(int displayId) {
+ final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ return displayManager.getDisplay(displayId);
+ }
+
+ /**
* Gets the DisplayLayout associated with a display.
*/
public @Nullable DisplayLayout getDisplayLayout(int displayId) {
@@ -91,6 +101,16 @@ public class DisplayController {
}
/**
+ * Updates the insets for a given display.
+ */
+ public void updateDisplayInsets(int displayId, InsetsState state) {
+ final DisplayRecord r = mDisplays.get(displayId);
+ if (r != null) {
+ r.setInsets(state);
+ }
+ }
+
+ /**
* Add a display window-container listener. It will get notified whenever a display's
* configuration changes or when displays are added/removed from the WM hierarchy.
*/
@@ -134,17 +154,18 @@ public class DisplayController {
if (mDisplays.get(displayId) != null) {
return;
}
- Display display = getDisplay(displayId);
+ final Display display = getDisplay(displayId);
if (display == null) {
// It's likely that the display is private to some app and thus not
// accessible by system-ui.
return;
}
- DisplayRecord record = new DisplayRecord();
- record.mDisplayId = displayId;
- record.mContext = (displayId == Display.DEFAULT_DISPLAY) ? mContext
+
+ final Context context = (displayId == Display.DEFAULT_DISPLAY)
+ ? mContext
: mContext.createDisplayContext(display);
- record.mDisplayLayout = new DisplayLayout(record.mContext, display);
+ final DisplayRecord record = new DisplayRecord(displayId);
+ record.setDisplayLayout(context, new DisplayLayout(context, display));
mDisplays.put(displayId, record);
for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
mDisplayChangedListeners.get(i).onDisplayAdded(displayId);
@@ -154,24 +175,23 @@ public class DisplayController {
private void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
synchronized (mDisplays) {
- DisplayRecord dr = mDisplays.get(displayId);
+ final DisplayRecord dr = mDisplays.get(displayId);
if (dr == null) {
Slog.w(TAG, "Skipping Display Configuration change on non-added"
+ " display.");
return;
}
- Display display = getDisplay(displayId);
+ final Display display = getDisplay(displayId);
if (display == null) {
Slog.w(TAG, "Skipping Display Configuration change on invalid"
+ " display. It may have been removed.");
return;
}
- Context perDisplayContext = mContext;
- if (displayId != Display.DEFAULT_DISPLAY) {
- perDisplayContext = mContext.createDisplayContext(display);
- }
- dr.mContext = perDisplayContext.createConfigurationContext(newConfig);
- dr.mDisplayLayout = new DisplayLayout(dr.mContext, display);
+ final Context perDisplayContext = (displayId == Display.DEFAULT_DISPLAY)
+ ? mContext
+ : mContext.createDisplayContext(display);
+ final Context context = perDisplayContext.createConfigurationContext(newConfig);
+ dr.setDisplayLayout(context, new DisplayLayout(context, display));
for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
mDisplayChangedListeners.get(i).onDisplayConfigurationChanged(
displayId, newConfig);
@@ -219,9 +239,25 @@ public class DisplayController {
}
private static class DisplayRecord {
- int mDisplayId;
- Context mContext;
- DisplayLayout mDisplayLayout;
+ private int mDisplayId;
+ private Context mContext;
+ private DisplayLayout mDisplayLayout;
+ private InsetsState mInsetsState = new InsetsState();
+
+ private DisplayRecord(int displayId) {
+ mDisplayId = displayId;
+ }
+
+ private void setDisplayLayout(Context context, DisplayLayout displayLayout) {
+ mContext = context;
+ mDisplayLayout = displayLayout;
+ mDisplayLayout.setInsets(mContext.getResources(), mInsetsState);
+ }
+
+ private void setInsets(InsetsState state) {
+ mInsetsState = state;
+ mDisplayLayout.setInsets(mContext.getResources(), state);
+ }
}
@BinderThread
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index a7996f056785..a7052bc49699 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -33,6 +33,7 @@ import android.view.IWindowManager;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowInsets;
@@ -68,14 +69,17 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
protected final Executor mMainExecutor;
private final TransactionPool mTransactionPool;
private final DisplayController mDisplayController;
+ private final DisplayInsetsController mDisplayInsetsController;
private final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>();
private final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>();
public DisplayImeController(IWindowManager wmService, DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
Executor mainExecutor, TransactionPool transactionPool) {
mWmService = wmService;
mDisplayController = displayController;
+ mDisplayInsetsController = displayInsetsController;
mMainExecutor = mainExecutor;
mTransactionPool = transactionPool;
}
@@ -109,11 +113,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
@Override
public void onDisplayRemoved(int displayId) {
- try {
- mWmService.setDisplayWindowInsetsController(displayId, null);
- } catch (RemoteException e) {
- Slog.w(TAG, "Unable to remove insets controller on display " + displayId);
+ PerDisplay pd = mImePerDisplay.get(displayId);
+ if (pd == null) {
+ return;
}
+ pd.unregister();
mImePerDisplay.remove(displayId);
}
@@ -195,11 +199,10 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
/** An implementation of {@link IDisplayWindowInsetsController} for a given display id. */
- public class PerDisplay {
+ public class PerDisplay implements DisplayInsetsController.OnInsetsChangedListener {
final int mDisplayId;
final InsetsState mInsetsState = new InsetsState();
- protected final DisplayWindowInsetsControllerImpl mInsetsControllerImpl =
- new DisplayWindowInsetsControllerImpl();
+ final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
InsetsSourceControl mImeSourceControl = null;
int mAnimationDirection = DIRECTION_NONE;
ValueAnimator mAnimation = null;
@@ -214,14 +217,15 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
public void register() {
- try {
- mWmService.setDisplayWindowInsetsController(mDisplayId, mInsetsControllerImpl);
- } catch (RemoteException e) {
- Slog.w(TAG, "Unable to set insets controller on display " + mDisplayId);
- }
+ mDisplayInsetsController.addInsetsChangedListener(mDisplayId, this);
}
- protected void insetsChanged(InsetsState insetsState) {
+ public void unregister() {
+ mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, this);
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
if (mInsetsState.equals(insetsState)) {
return;
}
@@ -239,8 +243,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
}
+ @Override
@VisibleForTesting
- protected void insetsControlChanged(InsetsState insetsState,
+ public void insetsControlChanged(InsetsState insetsState,
InsetsSourceControl[] activeControls) {
insetsChanged(insetsState);
InsetsSourceControl imeSourceControl = null;
@@ -279,9 +284,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
if (!mImeShowing) {
removeImeSurface();
}
- }
- if (mImeSourceControl != null) {
- mImeSourceControl.release(SurfaceControl::release);
+ if (mImeSourceControl != null) {
+ mImeSourceControl.release(SurfaceControl::release);
+ }
}
mImeSourceControl = imeSourceControl;
}
@@ -301,7 +306,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
}
- protected void showInsets(int types, boolean fromIme) {
+ @Override
+ public void showInsets(int types, boolean fromIme) {
if ((types & WindowInsets.Type.ime()) == 0) {
return;
}
@@ -309,8 +315,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
startAnimation(true /* show */, false /* forceRestart */);
}
-
- protected void hideInsets(int types, boolean fromIme) {
+ @Override
+ public void hideInsets(int types, boolean fromIme) {
if ((types & WindowInsets.Type.ime()) == 0) {
return;
}
@@ -318,6 +324,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
startAnimation(false /* show */, false /* forceRestart */);
}
+ @Override
public void topFocusedWindowChanged(String packageName) {
// Do nothing
}
@@ -327,8 +334,10 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
*/
private void setVisibleDirectly(boolean visible) {
mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible);
+ mRequestedVisibilities.setVisibility(InsetsState.ITYPE_IME, visible);
try {
- mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
+ mWmService.updateDisplayWindowRequestedVisibilities(mDisplayId,
+ mRequestedVisibilities);
} catch (RemoteException e) {
}
}
@@ -489,47 +498,6 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
dispatchVisibilityChanged(mDisplayId, isShowing);
}
}
-
- @VisibleForTesting
- @BinderThread
- public class DisplayWindowInsetsControllerImpl
- extends IDisplayWindowInsetsController.Stub {
- @Override
- public void topFocusedWindowChanged(String packageName) throws RemoteException {
- mMainExecutor.execute(() -> {
- PerDisplay.this.topFocusedWindowChanged(packageName);
- });
- }
-
- @Override
- public void insetsChanged(InsetsState insetsState) throws RemoteException {
- mMainExecutor.execute(() -> {
- PerDisplay.this.insetsChanged(insetsState);
- });
- }
-
- @Override
- public void insetsControlChanged(InsetsState insetsState,
- InsetsSourceControl[] activeControls) throws RemoteException {
- mMainExecutor.execute(() -> {
- PerDisplay.this.insetsControlChanged(insetsState, activeControls);
- });
- }
-
- @Override
- public void showInsets(int types, boolean fromIme) throws RemoteException {
- mMainExecutor.execute(() -> {
- PerDisplay.this.showInsets(types, fromIme);
- });
- }
-
- @Override
- public void hideInsets(int types, boolean fromIme) throws RemoteException {
- mMainExecutor.execute(() -> {
- PerDisplay.this.hideInsets(types, fromIme);
- });
- }
- }
}
void removeImeSurface() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
new file mode 100644
index 000000000000..565f1481233c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -0,0 +1,265 @@
+/*
+ * 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.wm.shell.common;
+
+import android.os.RemoteException;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.IDisplayWindowInsetsController;
+import android.view.IWindowManager;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+
+import androidx.annotation.BinderThread;
+
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Manages insets from the core.
+ */
+public class DisplayInsetsController implements DisplayController.OnDisplaysChangedListener {
+ private static final String TAG = "DisplayInsetsController";
+
+ private final IWindowManager mWmService;
+ private final ShellExecutor mMainExecutor;
+ private final DisplayController mDisplayController;
+ private final SparseArray<PerDisplay> mInsetsPerDisplay = new SparseArray<>();
+ private final SparseArray<CopyOnWriteArrayList<OnInsetsChangedListener>> mListeners =
+ new SparseArray<>();
+
+ public DisplayInsetsController(IWindowManager wmService, DisplayController displayController,
+ ShellExecutor mainExecutor) {
+ mWmService = wmService;
+ mDisplayController = displayController;
+ mMainExecutor = mainExecutor;
+ }
+
+ /**
+ * Starts listening for insets for each display.
+ **/
+ public void initialize() {
+ mDisplayController.addDisplayWindowListener(this);
+ }
+
+ /**
+ * Adds a callback to listen for insets changes for a particular display. Note that the
+ * listener will not be updated with the existing state of the insets on that display.
+ */
+ public void addInsetsChangedListener(int displayId, OnInsetsChangedListener listener) {
+ CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(displayId);
+ if (listeners == null) {
+ listeners = new CopyOnWriteArrayList<>();
+ mListeners.put(displayId, listeners);
+ }
+ if (!listeners.contains(listener)) {
+ listeners.add(listener);
+ }
+ }
+
+ /**
+ * Removes a callback listening for insets changes from a particular display.
+ */
+ public void removeInsetsChangedListener(int displayId, OnInsetsChangedListener listener) {
+ CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(displayId);
+ if (listeners == null) {
+ return;
+ }
+ listeners.remove(listener);
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ PerDisplay pd = new PerDisplay(displayId);
+ pd.register();
+ mInsetsPerDisplay.put(displayId, pd);
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ PerDisplay pd = mInsetsPerDisplay.get(displayId);
+ if (pd == null) {
+ return;
+ }
+ pd.unregister();
+ mInsetsPerDisplay.remove(displayId);
+ }
+
+ /**
+ * An implementation of {@link IDisplayWindowInsetsController} for a given display id.
+ **/
+ public class PerDisplay {
+ private final int mDisplayId;
+ private final DisplayWindowInsetsControllerImpl mInsetsControllerImpl =
+ new DisplayWindowInsetsControllerImpl();
+
+ public PerDisplay(int displayId) {
+ mDisplayId = displayId;
+ }
+
+ public void register() {
+ try {
+ mWmService.setDisplayWindowInsetsController(mDisplayId, mInsetsControllerImpl);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to set insets controller on display " + mDisplayId);
+ }
+ }
+
+ public void unregister() {
+ try {
+ mWmService.setDisplayWindowInsetsController(mDisplayId, null);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to remove insets controller on display " + mDisplayId);
+ }
+ }
+
+ private void insetsChanged(InsetsState insetsState) {
+ CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
+ if (listeners == null) {
+ return;
+ }
+ mDisplayController.updateDisplayInsets(mDisplayId, insetsState);
+ for (OnInsetsChangedListener listener : listeners) {
+ listener.insetsChanged(insetsState);
+ }
+ }
+
+ private void insetsControlChanged(InsetsState insetsState,
+ InsetsSourceControl[] activeControls) {
+ CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
+ if (listeners == null) {
+ return;
+ }
+ for (OnInsetsChangedListener listener : listeners) {
+ listener.insetsControlChanged(insetsState, activeControls);
+ }
+ }
+
+ private void showInsets(int types, boolean fromIme) {
+ CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
+ if (listeners == null) {
+ return;
+ }
+ for (OnInsetsChangedListener listener : listeners) {
+ listener.showInsets(types, fromIme);
+ }
+ }
+
+ private void hideInsets(int types, boolean fromIme) {
+ CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
+ if (listeners == null) {
+ return;
+ }
+ for (OnInsetsChangedListener listener : listeners) {
+ listener.hideInsets(types, fromIme);
+ }
+ }
+
+ private void topFocusedWindowChanged(String packageName) {
+ CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
+ if (listeners == null) {
+ return;
+ }
+ for (OnInsetsChangedListener listener : listeners) {
+ listener.topFocusedWindowChanged(packageName);
+ }
+ }
+
+ @BinderThread
+ private class DisplayWindowInsetsControllerImpl
+ extends IDisplayWindowInsetsController.Stub {
+ @Override
+ public void topFocusedWindowChanged(String packageName) throws RemoteException {
+ mMainExecutor.execute(() -> {
+ PerDisplay.this.topFocusedWindowChanged(packageName);
+ });
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) throws RemoteException {
+ mMainExecutor.execute(() -> {
+ PerDisplay.this.insetsChanged(insetsState);
+ });
+ }
+
+ @Override
+ public void insetsControlChanged(InsetsState insetsState,
+ InsetsSourceControl[] activeControls) throws RemoteException {
+ mMainExecutor.execute(() -> {
+ PerDisplay.this.insetsControlChanged(insetsState, activeControls);
+ });
+ }
+
+ @Override
+ public void showInsets(int types, boolean fromIme) throws RemoteException {
+ mMainExecutor.execute(() -> {
+ PerDisplay.this.showInsets(types, fromIme);
+ });
+ }
+
+ @Override
+ public void hideInsets(int types, boolean fromIme) throws RemoteException {
+ mMainExecutor.execute(() -> {
+ PerDisplay.this.hideInsets(types, fromIme);
+ });
+ }
+ }
+ }
+
+ /**
+ * Gets notified whenever the insets change.
+ *
+ * @see IDisplayWindowInsetsController
+ */
+ @ShellMainThread
+ public interface OnInsetsChangedListener {
+ /**
+ * Called when top focused window changes to determine whether or not to take over insets
+ * control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false.
+ * @param packageName: Passes the top package name
+ */
+ default void topFocusedWindowChanged(String packageName) {}
+
+ /**
+ * Called when the window insets configuration has changed.
+ */
+ default void insetsChanged(InsetsState insetsState) {}
+
+ /**
+ * Called when this window retrieved control over a specified set of insets sources.
+ */
+ default void insetsControlChanged(InsetsState insetsState,
+ InsetsSourceControl[] activeControls) {}
+
+ /**
+ * Called when a set of insets source window should be shown by policy.
+ *
+ * @param types internal insets types (WindowInsets.Type.InsetsType) to show
+ * @param fromIme true if this request originated from IME (InputMethodService).
+ */
+ default void showInsets(int types, boolean fromIme) {}
+
+ /**
+ * Called when a set of insets source window should be hidden by policy.
+ *
+ * @param types internal insets types (WindowInsets.Type.InsetsType) to hide
+ * @param fromIme true if this request originated from IME (InputMethodService).
+ */
+ default void hideInsets(int types, boolean fromIme) {}
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index b7235a31af03..7784665b3031 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -25,6 +25,7 @@ import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON
import static android.util.RotationUtils.rotateBounds;
import static android.util.RotationUtils.rotateInsets;
import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -44,9 +45,14 @@ import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
+import android.view.InsetsSource;
+import android.view.InsetsState;
import android.view.Surface;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.R;
+import com.android.internal.policy.SystemBarUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -82,6 +88,10 @@ public class DisplayLayout {
private boolean mHasNavigationBar = false;
private boolean mHasStatusBar = false;
private int mNavBarFrameHeight = 0;
+ private boolean mAllowSeamlessRotationDespiteNavBarMoving = false;
+ private boolean mNavigationBarCanMove = false;
+ private boolean mReverseDefaultRotation = false;
+ private InsetsState mInsetsState = new InsetsState();
@Override
public boolean equals(Object o) {
@@ -98,14 +108,20 @@ public class DisplayLayout {
&& Objects.equals(mStableInsets, other.mStableInsets)
&& mHasNavigationBar == other.mHasNavigationBar
&& mHasStatusBar == other.mHasStatusBar
- && mNavBarFrameHeight == other.mNavBarFrameHeight;
+ && mAllowSeamlessRotationDespiteNavBarMoving
+ == other.mAllowSeamlessRotationDespiteNavBarMoving
+ && mNavigationBarCanMove == other.mNavigationBarCanMove
+ && mReverseDefaultRotation == other.mReverseDefaultRotation
+ && mNavBarFrameHeight == other.mNavBarFrameHeight
+ && Objects.equals(mInsetsState, other.mInsetsState);
}
@Override
public int hashCode() {
return Objects.hash(mUiMode, mWidth, mHeight, mCutout, mRotation, mDensityDpi,
mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar,
- mNavBarFrameHeight);
+ mNavBarFrameHeight, mAllowSeamlessRotationDespiteNavBarMoving,
+ mNavigationBarCanMove, mReverseDefaultRotation, mInsetsState);
}
/**
@@ -150,9 +166,13 @@ public class DisplayLayout {
mDensityDpi = dl.mDensityDpi;
mHasNavigationBar = dl.mHasNavigationBar;
mHasStatusBar = dl.mHasStatusBar;
+ mAllowSeamlessRotationDespiteNavBarMoving = dl.mAllowSeamlessRotationDespiteNavBarMoving;
+ mNavigationBarCanMove = dl.mNavigationBarCanMove;
+ mReverseDefaultRotation = dl.mReverseDefaultRotation;
mNavBarFrameHeight = dl.mNavBarFrameHeight;
mNonDecorInsets.set(dl.mNonDecorInsets);
mStableInsets.set(dl.mStableInsets);
+ mInsetsState.set(dl.mInsetsState, true /* copySources */);
}
private void init(DisplayInfo info, Resources res, boolean hasNavigationBar,
@@ -165,15 +185,28 @@ public class DisplayLayout {
mDensityDpi = info.logicalDensityDpi;
mHasNavigationBar = hasNavigationBar;
mHasStatusBar = hasStatusBar;
+ mAllowSeamlessRotationDespiteNavBarMoving = res.getBoolean(
+ R.bool.config_allowSeamlessRotationDespiteNavBarMoving);
+ mNavigationBarCanMove = res.getBoolean(R.bool.config_navBarCanMove);
+ mReverseDefaultRotation = res.getBoolean(R.bool.config_reverseDefaultRotation);
recalcInsets(res);
}
- private void recalcInsets(Resources res) {
- computeNonDecorInsets(res, mRotation, mWidth, mHeight, mCutout, mUiMode, mNonDecorInsets,
- mHasNavigationBar);
+ /**
+ * Updates the current insets.
+ */
+ public void setInsets(Resources res, InsetsState state) {
+ mInsetsState = state;
+ recalcInsets(res);
+ }
+
+ @VisibleForTesting
+ void recalcInsets(Resources res) {
+ computeNonDecorInsets(res, mRotation, mWidth, mHeight, mCutout, mInsetsState, mUiMode,
+ mNonDecorInsets, mHasNavigationBar);
mStableInsets.set(mNonDecorInsets);
if (mHasStatusBar) {
- convertNonDecorInsetsToStableInsets(res, mStableInsets, mWidth, mHeight, mHasStatusBar);
+ convertNonDecorInsetsToStableInsets(res, mStableInsets, mCutout, mHasStatusBar);
}
mNavBarFrameHeight = getNavigationBarFrameHeight(res, mWidth > mHeight);
}
@@ -244,11 +277,33 @@ public class DisplayLayout {
return mWidth > mHeight;
}
- /** Get the navbar frame height (used by ime). */
+ /** Get the navbar frame (or window) height (used by ime). */
public int navBarFrameHeight() {
return mNavBarFrameHeight;
}
+ /** @return whether we can seamlessly rotate even if nav-bar can change sides. */
+ public boolean allowSeamlessRotationDespiteNavBarMoving() {
+ return mAllowSeamlessRotationDespiteNavBarMoving;
+ }
+
+ /** @return whether the navigation bar will change sides during rotation. */
+ public boolean navigationBarCanMove() {
+ return mNavigationBarCanMove;
+ }
+
+ /** @return the rotation that would make the physical display "upside down". */
+ public int getUpsideDownRotation() {
+ boolean displayHardwareIsLandscape = mWidth > mHeight;
+ if ((mRotation % 2) != 0) {
+ displayHardwareIsLandscape = !displayHardwareIsLandscape;
+ }
+ if (displayHardwareIsLandscape) {
+ return mReverseDefaultRotation ? Surface.ROTATION_270 : Surface.ROTATION_90;
+ }
+ return Surface.ROTATION_180;
+ }
+
/** Gets the orientation of this layout */
public int getOrientation() {
return (mWidth > mHeight) ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
@@ -271,12 +326,12 @@ public class DisplayLayout {
/**
* Calculates the stable insets if we already have the non-decor insets.
*/
- private static void convertNonDecorInsetsToStableInsets(Resources res, Rect inOutInsets,
- int displayWidth, int displayHeight, boolean hasStatusBar) {
+ private void convertNonDecorInsetsToStableInsets(Resources res, Rect inOutInsets,
+ DisplayCutout cutout, boolean hasStatusBar) {
if (!hasStatusBar) {
return;
}
- int statusBarHeight = getStatusBarHeight(displayWidth > displayHeight, res);
+ int statusBarHeight = SystemBarUtils.getStatusBarHeight(res, cutout);
inOutInsets.top = Math.max(inOutInsets.top, statusBarHeight);
}
@@ -291,21 +346,29 @@ public class DisplayLayout {
* @param outInsets the insets to return
*/
static void computeNonDecorInsets(Resources res, int displayRotation, int displayWidth,
- int displayHeight, DisplayCutout displayCutout, int uiMode, Rect outInsets,
- boolean hasNavigationBar) {
+ int displayHeight, DisplayCutout displayCutout, InsetsState insetsState, int uiMode,
+ Rect outInsets, boolean hasNavigationBar) {
outInsets.setEmpty();
// Only navigation bar
if (hasNavigationBar) {
+ final InsetsSource extraNavBar = insetsState.getSource(ITYPE_EXTRA_NAVIGATION_BAR);
+ final boolean hasExtraNav = extraNavBar != null && extraNavBar.isVisible();
int position = navigationBarPosition(res, displayWidth, displayHeight, displayRotation);
int navBarSize =
getNavigationBarSize(res, position, displayWidth > displayHeight, uiMode);
if (position == NAV_BAR_BOTTOM) {
- outInsets.bottom = navBarSize;
+ outInsets.bottom = hasExtraNav
+ ? Math.max(navBarSize, extraNavBar.getFrame().height())
+ : navBarSize;
} else if (position == NAV_BAR_RIGHT) {
- outInsets.right = navBarSize;
+ outInsets.right = hasExtraNav
+ ? Math.max(navBarSize, extraNavBar.getFrame().width())
+ : navBarSize;
} else if (position == NAV_BAR_LEFT) {
- outInsets.left = navBarSize;
+ outInsets.left = hasExtraNav
+ ? Math.max(navBarSize, extraNavBar.getFrame().width())
+ : navBarSize;
}
}
@@ -317,35 +380,6 @@ public class DisplayLayout {
}
}
- /**
- * Calculates the stable insets without running a layout.
- *
- * @param displayRotation the current display rotation
- * @param displayWidth the current display width
- * @param displayHeight the current display height
- * @param displayCutout the current display cutout
- * @param outInsets the insets to return
- */
- static void computeStableInsets(Resources res, int displayRotation, int displayWidth,
- int displayHeight, DisplayCutout displayCutout, int uiMode, Rect outInsets,
- boolean hasNavigationBar, boolean hasStatusBar) {
- outInsets.setEmpty();
-
- // Navigation bar and status bar.
- computeNonDecorInsets(res, displayRotation, displayWidth, displayHeight, displayCutout,
- uiMode, outInsets, hasNavigationBar);
- convertNonDecorInsetsToStableInsets(res, outInsets, displayWidth, displayHeight,
- hasStatusBar);
- }
-
- /** Retrieve the statusbar height from resources. */
- static int getStatusBarHeight(boolean landscape, Resources res) {
- return landscape ? res.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height_landscape)
- : res.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height_portrait);
- }
-
/** Calculate the DisplayCutout for a particular display size/rotation. */
public static DisplayCutout calculateDisplayCutoutForRotation(
DisplayCutout cutout, int rotation, int displayWidth, int displayHeight) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SurfaceUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SurfaceUtils.java
index 55c5125f0a00..4b138e43bc3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SurfaceUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SurfaceUtils.java
@@ -23,16 +23,22 @@ import android.view.SurfaceSession;
* Helpers for handling surface.
*/
public class SurfaceUtils {
- /** Creates a dim layer above indicated host surface. */
+ /** Creates a dim layer above host surface. */
public static SurfaceControl makeDimLayer(SurfaceControl.Transaction t, SurfaceControl host,
String name, SurfaceSession surfaceSession) {
- SurfaceControl dimLayer = new SurfaceControl.Builder(surfaceSession)
+ final SurfaceControl dimLayer = makeColorLayer(host, name, surfaceSession);
+ t.setLayer(dimLayer, Integer.MAX_VALUE).setColor(dimLayer, new float[]{0f, 0f, 0f});
+ return dimLayer;
+ }
+
+ /** Creates a color layer for host surface. */
+ public static SurfaceControl makeColorLayer(SurfaceControl host, String name,
+ SurfaceSession surfaceSession) {
+ return new SurfaceControl.Builder(surfaceSession)
.setParent(host)
.setColorLayer()
.setName(name)
- .setCallsite("SurfaceUtils.makeDimLayer")
+ .setCallsite("SurfaceUtils.makeColorLayer")
.build();
- t.setLayer(dimLayer, Integer.MAX_VALUE).setColor(dimLayer, new float[]{0f, 0f, 0f});
- return dimLayer;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
index 33beab5ee3f1..4c0281dcc517 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
@@ -18,13 +18,15 @@ package com.android.wm.shell.common;
import android.annotation.BinderThread;
import android.annotation.NonNull;
+import android.os.RemoteException;
import android.util.Slog;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;
import android.window.WindowOrganizer;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.transition.LegacyTransitions;
import java.util.ArrayList;
@@ -66,6 +68,10 @@ public final class SyncTransactionQueue {
* Queues a sync transaction to be sent serially to WM.
*/
public void queue(WindowContainerTransaction wct) {
+ if (wct.isEmpty()) {
+ if (DEBUG) Slog.d(TAG, "Skip queue due to transaction change is empty");
+ return;
+ }
SyncCallback cb = new SyncCallback(wct);
synchronized (mQueue) {
if (DEBUG) Slog.d(TAG, "Queueing up " + wct);
@@ -77,11 +83,34 @@ public final class SyncTransactionQueue {
}
/**
+ * Queues a legacy transition to be sent serially to WM
+ */
+ public void queue(LegacyTransitions.ILegacyTransition transition,
+ @WindowManager.TransitionType int type, WindowContainerTransaction wct) {
+ if (wct.isEmpty()) {
+ if (DEBUG) Slog.d(TAG, "Skip queue due to transaction change is empty");
+ return;
+ }
+ SyncCallback cb = new SyncCallback(transition, type, wct);
+ synchronized (mQueue) {
+ if (DEBUG) Slog.d(TAG, "Queueing up legacy transition " + wct);
+ mQueue.add(cb);
+ if (mQueue.size() == 1) {
+ cb.send();
+ }
+ }
+ }
+
+ /**
* Queues a sync transaction only if there are already sync transaction(s) queued or in flight.
* Otherwise just returns without queueing.
* @return {@code true} if queued, {@code false} if not.
*/
public boolean queueIfWaiting(WindowContainerTransaction wct) {
+ if (wct.isEmpty()) {
+ if (DEBUG) Slog.d(TAG, "Skip queueIfWaiting due to transaction change is empty");
+ return false;
+ }
synchronized (mQueue) {
if (mQueue.isEmpty()) {
if (DEBUG) Slog.d(TAG, "Nothing in queue, so skip queueing up " + wct);
@@ -118,12 +147,12 @@ public final class SyncTransactionQueue {
// Synchronized on mQueue
private void onTransactionReceived(@NonNull SurfaceControl.Transaction t) {
if (DEBUG) Slog.d(TAG, " Running " + mRunnables.size() + " sync runnables");
- for (int i = 0, n = mRunnables.size(); i < n; ++i) {
+ final int n = mRunnables.size();
+ for (int i = 0; i < n; ++i) {
mRunnables.get(i).runWithTransaction(t);
}
- mRunnables.clear();
- t.apply();
- t.close();
+ // More runnables may have been added, so only remove the ones that ran.
+ mRunnables.subList(0, n).clear();
}
/** Task to run with transaction. */
@@ -135,20 +164,38 @@ public final class SyncTransactionQueue {
private class SyncCallback extends WindowContainerTransactionCallback {
int mId = -1;
final WindowContainerTransaction mWCT;
+ final LegacyTransitions.LegacyTransition mLegacyTransition;
SyncCallback(WindowContainerTransaction wct) {
mWCT = wct;
+ mLegacyTransition = null;
+ }
+
+ SyncCallback(LegacyTransitions.ILegacyTransition legacyTransition,
+ @WindowManager.TransitionType int type, WindowContainerTransaction wct) {
+ mWCT = wct;
+ mLegacyTransition = new LegacyTransitions.LegacyTransition(type, legacyTransition);
}
// Must be sychronized on mQueue
void send() {
+ if (mInFlight == this) {
+ // This was probably queued up and sent during a sync runnable of the last callback.
+ // Don't queue it again.
+ return;
+ }
if (mInFlight != null) {
throw new IllegalStateException("Sync Transactions must be serialized. In Flight: "
+ mInFlight.mId + " - " + mInFlight.mWCT);
}
mInFlight = this;
if (DEBUG) Slog.d(TAG, "Sending sync transaction: " + mWCT);
- mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
+ if (mLegacyTransition != null) {
+ mId = new WindowOrganizer().startLegacyTransition(mLegacyTransition.getType(),
+ mLegacyTransition.getAdapter(), this, mWCT);
+ } else {
+ mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
+ }
if (DEBUG) Slog.d(TAG, " Sent sync transaction. Got id=" + mId);
mMainExecutor.executeDelayed(mOnReplyTimeout, REPLY_TIMEOUT);
}
@@ -169,6 +216,16 @@ public final class SyncTransactionQueue {
if (DEBUG) Slog.d(TAG, "onTransactionReady id=" + mId);
mQueue.remove(this);
onTransactionReceived(t);
+ if (mLegacyTransition != null) {
+ try {
+ mLegacyTransition.getSyncCallback().onTransactionReady(mId, t);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending callback to legacy transition: " + mId, e);
+ }
+ } else {
+ t.apply();
+ t.close();
+ }
if (!mQueue.isEmpty()) {
mQueue.get(0).send();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
index 218bf47e24aa..c76937de6669 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
@@ -70,7 +70,8 @@ public class DividerHandleView extends View {
private final Paint mPaint = new Paint();
private final int mWidth;
private final int mHeight;
- private final int mCircleDiameter;
+ private final int mTouchingWidth;
+ private final int mTouchingHeight;
private int mCurrentWidth;
private int mCurrentHeight;
private AnimatorSet mAnimator;
@@ -80,11 +81,12 @@ public class DividerHandleView extends View {
super(context, attrs);
mPaint.setColor(getResources().getColor(R.color.docked_divider_handle, null));
mPaint.setAntiAlias(true);
- mWidth = getResources().getDimensionPixelSize(R.dimen.docked_divider_handle_width);
- mHeight = getResources().getDimensionPixelSize(R.dimen.docked_divider_handle_height);
+ mWidth = getResources().getDimensionPixelSize(R.dimen.split_divider_handle_width);
+ mHeight = getResources().getDimensionPixelSize(R.dimen.split_divider_handle_height);
mCurrentWidth = mWidth;
mCurrentHeight = mHeight;
- mCircleDiameter = (mWidth + mHeight) / 3;
+ mTouchingWidth = mWidth > mHeight ? mWidth / 2 : mWidth;
+ mTouchingHeight = mHeight > mWidth ? mHeight / 2 : mHeight;
}
/** Sets touching state for this handle view. */
@@ -98,16 +100,16 @@ public class DividerHandleView extends View {
}
if (!animate) {
if (touching) {
- mCurrentWidth = mCircleDiameter;
- mCurrentHeight = mCircleDiameter;
+ mCurrentWidth = mTouchingWidth;
+ mCurrentHeight = mTouchingHeight;
} else {
mCurrentWidth = mWidth;
mCurrentHeight = mHeight;
}
invalidate();
} else {
- animateToTarget(touching ? mCircleDiameter : mWidth,
- touching ? mCircleDiameter : mHeight, touching);
+ animateToTarget(touching ? mTouchingWidth : mWidth,
+ touching ? mTouchingHeight : mHeight, touching);
}
mTouching = touching;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
new file mode 100644
index 000000000000..364bb651d55d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
@@ -0,0 +1,160 @@
+/*
+ * 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.wm.shell.common.split;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
+import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.view.RoundedCorner;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+
+/**
+ * Draws inverted rounded corners beside divider bar to keep splitting tasks cropped with proper
+ * rounded corners.
+ */
+public class DividerRoundedCorner extends View {
+ private final int mDividerWidth;
+ private final Paint mDividerBarBackground;
+ private final Point mStartPos = new Point();
+ private InvertedRoundedCornerDrawInfo mTopLeftCorner;
+ private InvertedRoundedCornerDrawInfo mTopRightCorner;
+ private InvertedRoundedCornerDrawInfo mBottomLeftCorner;
+ private InvertedRoundedCornerDrawInfo mBottomRightCorner;
+
+ public DividerRoundedCorner(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ mDividerWidth = getResources().getDimensionPixelSize(R.dimen.split_divider_bar_width);
+ mDividerBarBackground = new Paint();
+ mDividerBarBackground.setColor(
+ getResources().getColor(R.color.split_divider_background, null));
+ mDividerBarBackground.setFlags(Paint.ANTI_ALIAS_FLAG);
+ mDividerBarBackground.setStyle(Paint.Style.FILL);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mTopLeftCorner = new InvertedRoundedCornerDrawInfo(POSITION_TOP_LEFT);
+ mTopRightCorner = new InvertedRoundedCornerDrawInfo(POSITION_TOP_RIGHT);
+ mBottomLeftCorner = new InvertedRoundedCornerDrawInfo(POSITION_BOTTOM_LEFT);
+ mBottomRightCorner = new InvertedRoundedCornerDrawInfo(POSITION_BOTTOM_RIGHT);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.save();
+
+ mTopLeftCorner.calculateStartPos(mStartPos);
+ canvas.translate(mStartPos.x, mStartPos.y);
+ canvas.drawPath(mTopLeftCorner.mPath, mDividerBarBackground);
+
+ canvas.translate(-mStartPos.x, -mStartPos.y);
+ mTopRightCorner.calculateStartPos(mStartPos);
+ canvas.translate(mStartPos.x, mStartPos.y);
+ canvas.drawPath(mTopRightCorner.mPath, mDividerBarBackground);
+
+ canvas.translate(-mStartPos.x, -mStartPos.y);
+ mBottomLeftCorner.calculateStartPos(mStartPos);
+ canvas.translate(mStartPos.x, mStartPos.y);
+ canvas.drawPath(mBottomLeftCorner.mPath, mDividerBarBackground);
+
+ canvas.translate(-mStartPos.x, -mStartPos.y);
+ mBottomRightCorner.calculateStartPos(mStartPos);
+ canvas.translate(mStartPos.x, mStartPos.y);
+ canvas.drawPath(mBottomRightCorner.mPath, mDividerBarBackground);
+
+ canvas.restore();
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ private boolean isLandscape() {
+ return getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+ }
+
+ /**
+ * Holds draw information of the inverted rounded corner at a specific position.
+ *
+ * @see {@link com.android.launcher3.taskbar.TaskbarDragLayer}
+ */
+ private class InvertedRoundedCornerDrawInfo {
+ @RoundedCorner.Position
+ private final int mCornerPosition;
+
+ private final int mRadius;
+
+ private final Path mPath = new Path();
+
+ InvertedRoundedCornerDrawInfo(@RoundedCorner.Position int cornerPosition) {
+ mCornerPosition = cornerPosition;
+
+ final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(cornerPosition);
+ mRadius = roundedCorner == null ? 0 : roundedCorner.getRadius();
+
+ // Starts with a filled square, and then subtracting out a circle from the appropriate
+ // corner.
+ final Path square = new Path();
+ square.addRect(0, 0, mRadius, mRadius, Path.Direction.CW);
+ final Path circle = new Path();
+ circle.addCircle(
+ isLeftCorner() ? mRadius : 0 /* x */,
+ isTopCorner() ? mRadius : 0 /* y */,
+ mRadius, Path.Direction.CW);
+ mPath.op(square, circle, Path.Op.DIFFERENCE);
+ }
+
+ private void calculateStartPos(Point outPos) {
+ if (isLandscape()) {
+ // Place left corner at the right side of the divider bar.
+ outPos.x = isLeftCorner()
+ ? getWidth() / 2 + mDividerWidth / 2
+ : getWidth() / 2 - mDividerWidth / 2 - mRadius;
+ outPos.y = isTopCorner() ? 0 : getHeight() - mRadius;
+ } else {
+ outPos.x = isLeftCorner() ? 0 : getWidth() - mRadius;
+ // Place top corner at the bottom of the divider bar.
+ outPos.y = isTopCorner()
+ ? getHeight() / 2 + mDividerWidth / 2
+ : getHeight() / 2 - mDividerWidth / 2 - mRadius;
+ }
+ }
+
+ private boolean isLeftCorner() {
+ return mCornerPosition == POSITION_TOP_LEFT || mCornerPosition == POSITION_BOTTOM_LEFT;
+ }
+
+ private boolean isTopCorner() {
+ return mCornerPosition == POSITION_TOP_LEFT || mCornerPosition == POSITION_TOP_RIGHT;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index cba019a11b28..4b125b118ceb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -19,14 +19,23 @@ package com.android.wm.shell.common.split;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
import android.content.Context;
+import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.Property;
import android.view.GestureDetector;
+import android.view.InsetsController;
+import android.view.InsetsSource;
+import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.SurfaceControlViewHost;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
@@ -44,9 +53,13 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
public static final long TOUCH_ANIMATION_DURATION = 150;
public static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
+ /** The task bar expanded height. Used to determine whether to insets divider bounds or not. */
+ private float mExpandedTaskBarHeight;
+
private final int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
private SplitLayout mSplitLayout;
+ private SplitWindowManager mSplitWindowManager;
private SurfaceControlViewHost mViewHost;
private DividerHandleView mHandle;
private View mBackground;
@@ -57,6 +70,44 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
private int mStartPos;
private GestureDetector mDoubleTapDetector;
private boolean mInteractive;
+ private boolean mSetTouchRegion = true;
+
+ /**
+ * Tracks divider bar visible bounds in screen-based coordination. Used to calculate with
+ * insets.
+ */
+ private final Rect mDividerBounds = new Rect();
+ private final Rect mTempRect = new Rect();
+ private FrameLayout mDividerBar;
+
+
+ static final Property<DividerView, Integer> DIVIDER_HEIGHT_PROPERTY =
+ new Property<DividerView, Integer>(Integer.class, "height") {
+ @Override
+ public Integer get(DividerView object) {
+ return object.mDividerBar.getLayoutParams().height;
+ }
+
+ @Override
+ public void set(DividerView object, Integer value) {
+ ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams)
+ object.mDividerBar.getLayoutParams();
+ lp.height = value;
+ object.mDividerBar.setLayoutParams(lp);
+ }
+ };
+
+ private AnimatorListenerAdapter mAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mSetTouchRegion = true;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mSetTouchRegion = true;
+ }
+ };
public DividerView(@NonNull Context context) {
super(context);
@@ -79,16 +130,50 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
/** Sets up essential dependencies of the divider bar. */
public void setup(
SplitLayout layout,
- SurfaceControlViewHost viewHost) {
+ SplitWindowManager splitWindowManager,
+ SurfaceControlViewHost viewHost,
+ InsetsState insetsState) {
mSplitLayout = layout;
+ mSplitWindowManager = splitWindowManager;
mViewHost = viewHost;
+ mDividerBounds.set(layout.getDividerBounds());
+ onInsetsChanged(insetsState, false /* animate */);
+ }
+
+ void onInsetsChanged(InsetsState insetsState, boolean animate) {
+ mTempRect.set(mSplitLayout.getDividerBounds());
+ final InsetsSource taskBarInsetsSource =
+ insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ // Only insets the divider bar with task bar when it's expanded so that the rounded corners
+ // will be drawn against task bar.
+ if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ mTempRect.inset(taskBarInsetsSource.calculateVisibleInsets(mTempRect));
+ }
+
+ if (!mTempRect.equals(mDividerBounds)) {
+ if (animate) {
+ ObjectAnimator animator = ObjectAnimator.ofInt(this,
+ DIVIDER_HEIGHT_PROPERTY, mDividerBounds.height(), mTempRect.height());
+ animator.setInterpolator(InsetsController.RESIZE_INTERPOLATOR);
+ animator.setDuration(InsetsController.ANIMATION_DURATION_RESIZE);
+ animator.addListener(mAnimatorListener);
+ animator.start();
+ } else {
+ DIVIDER_HEIGHT_PROPERTY.set(this, mTempRect.height());
+ mSetTouchRegion = true;
+ }
+ mDividerBounds.set(mTempRect);
+ }
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ mDividerBar = findViewById(R.id.divider_bar);
mHandle = findViewById(R.id.docked_divider_handle);
mBackground = findViewById(R.id.docked_divider_background);
+ mExpandedTaskBarHeight = getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.taskbar_frame_height);
mTouchElevation = getResources().getDimensionPixelSize(
R.dimen.docked_stack_divider_lift_elevation);
mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener());
@@ -97,6 +182,17 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
}
@Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (mSetTouchRegion) {
+ mTempRect.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
+ mHandle.getBottom());
+ mSplitWindowManager.setTouchRegion(mTempRect);
+ mSetTouchRegion = false;
+ }
+ }
+
+ @Override
public boolean onTouch(View v, MotionEvent event) {
if (mSplitLayout == null || !mInteractive) {
return false;
@@ -106,10 +202,12 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
return true;
}
+ // Convert to use screen-based coordinates to prevent lost track of motion events while
+ // moving divider bar and calculating dragging velocity.
+ event.setLocation(event.getRawX(), event.getRawY());
final int action = event.getAction() & MotionEvent.ACTION_MASK;
final boolean isLandscape = isLandscape();
- // Using raw xy to prevent lost track of motion events while moving divider bar.
- final int touchPos = isLandscape ? (int) event.getRawX() : (int) event.getRawY();
+ final int touchPos = (int) (isLandscape ? event.getX() : event.getY());
switch (action) {
case MotionEvent.ACTION_DOWN:
mVelocityTracker = VelocityTracker.obtain();
@@ -153,16 +251,6 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
private void setTouching() {
setSlippery(false);
mHandle.setTouching(true, true);
- if (isLandscape()) {
- mBackground.animate().scaleX(1.4f);
- } else {
- mBackground.animate().scaleY(1.4f);
- }
- mBackground.animate()
- .setInterpolator(Interpolators.TOUCH_RESPONSE)
- .setDuration(TOUCH_ANIMATION_DURATION)
- .translationZ(mTouchElevation)
- .start();
// Lift handle as well so it doesn't get behind the background, even though it doesn't
// cast shadow.
mHandle.animate()
@@ -175,13 +263,6 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
private void releaseTouching() {
setSlippery(true);
mHandle.setTouching(false, true);
- mBackground.animate()
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
- .translationZ(0)
- .scaleX(1f)
- .scaleY(1f)
- .start();
mHandle.animate()
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
new file mode 100644
index 000000000000..ad9ebb2ef6ae
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -0,0 +1,184 @@
+/*
+ * 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.wm.shell.common.split;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Binder;
+import android.view.IWindow;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceSession;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.SurfaceUtils;
+
+/**
+ * Handles split decor like showing resizing hint for a specific split.
+ */
+public class SplitDecorManager extends WindowlessWindowManager {
+ private static final String TAG = SplitDecorManager.class.getSimpleName();
+ private static final String RESIZING_BACKGROUND_SURFACE_NAME = "ResizingBackground";
+
+ private final IconProvider mIconProvider;
+ private final SurfaceSession mSurfaceSession;
+
+ private Drawable mIcon;
+ private ImageView mResizingIconView;
+ private SurfaceControlViewHost mViewHost;
+ private SurfaceControl mHostLeash;
+ private SurfaceControl mIconLeash;
+ private SurfaceControl mBackgroundLeash;
+
+ public SplitDecorManager(Configuration configuration, IconProvider iconProvider,
+ SurfaceSession surfaceSession) {
+ super(configuration, null /* rootSurface */, null /* hostInputToken */);
+ mIconProvider = iconProvider;
+ mSurfaceSession = surfaceSession;
+ }
+
+ @Override
+ protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+ // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+ .setContainerLayer()
+ .setName(TAG)
+ .setHidden(true)
+ .setParent(mHostLeash)
+ .setCallsite("SplitDecorManager#attachToParentSurface");
+ mIconLeash = builder.build();
+ b.setParent(mIconLeash);
+ }
+
+ /** Inflates split decor surface on the root surface. */
+ public void inflate(Context context, SurfaceControl rootLeash, Rect rootBounds) {
+ if (mIconLeash != null && mViewHost != null) {
+ return;
+ }
+
+ context = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
+ null /* options */);
+ mHostLeash = rootLeash;
+ mViewHost = new SurfaceControlViewHost(context, context.getDisplay(), this);
+
+ final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(context)
+ .inflate(R.layout.split_decor, null);
+ mResizingIconView = rootLayout.findViewById(R.id.split_resizing_icon);
+
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
+ FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
+ lp.width = rootBounds.width();
+ lp.height = rootBounds.height();
+ lp.token = new Binder();
+ lp.setTitle(TAG);
+ lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+ // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
+ // TRUSTED_OVERLAY for windowless window without input channel.
+ mViewHost.setView(rootLayout, lp);
+ }
+
+ /** Releases the surfaces for split decor. */
+ public void release(SurfaceControl.Transaction t) {
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+ if (mIconLeash != null) {
+ t.remove(mIconLeash);
+ mIconLeash = null;
+ }
+ if (mBackgroundLeash != null) {
+ t.remove(mBackgroundLeash);
+ mBackgroundLeash = null;
+ }
+ mHostLeash = null;
+ mIcon = null;
+ mResizingIconView = null;
+ }
+
+ /** Showing resizing hint. */
+ public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
+ SurfaceControl.Transaction t) {
+ if (mResizingIconView == null) {
+ return;
+ }
+
+ if (mIcon == null) {
+ // TODO: add fade-in animation.
+ mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
+ RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession);
+ t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask))
+ .setLayer(mBackgroundLeash, SPLIT_DIVIDER_LAYER - 1)
+ .show(mBackgroundLeash);
+
+ mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
+ mResizingIconView.setImageDrawable(mIcon);
+ mResizingIconView.setVisibility(View.VISIBLE);
+
+ WindowManager.LayoutParams lp =
+ (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
+ lp.width = mIcon.getIntrinsicWidth();
+ lp.height = mIcon.getIntrinsicHeight();
+ mViewHost.relayout(lp);
+ t.show(mIconLeash).setLayer(mIconLeash, SPLIT_DIVIDER_LAYER);
+ }
+
+ t.setPosition(mIconLeash,
+ newBounds.width() / 2 - mIcon.getIntrinsicWidth() / 2,
+ newBounds.height() / 2 - mIcon.getIntrinsicWidth() / 2);
+ }
+
+ /** Stops showing resizing hint. */
+ public void onResized(Rect newBounds, SurfaceControl.Transaction t) {
+ if (mResizingIconView == null) {
+ return;
+ }
+
+ if (mIcon != null) {
+ mResizingIconView.setVisibility(View.GONE);
+ mResizingIconView.setImageDrawable(null);
+ t.remove(mBackgroundLeash).hide(mIconLeash);
+ mIcon = null;
+ mBackgroundLeash = null;
+ }
+ }
+
+ private static float[] getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
+ final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
+ return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).getComponents();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 5b158d2063ba..625bcee673b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -16,11 +16,19 @@
package com.android.wm.shell.common.split;
+import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
+import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
+import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR;
+import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -30,7 +38,12 @@ import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.view.Display;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.RoundedCorner;
import android.view.SurfaceControl;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -39,16 +52,20 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.policy.DockedDividerUtils;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
/**
* Records and handles layout of splits. Helps to calculate proper bounds when configuration or
* divide position changes.
*/
-public final class SplitLayout {
+public final class SplitLayout implements DisplayInsetsController.OnInsetsChangedListener {
/**
* Split position isn't specified normally meaning to use what ever it is currently set to.
*/
@@ -78,45 +95,73 @@ public final class SplitLayout {
private final int mDividerInsets;
private final int mDividerSize;
+ private final Rect mTempRect = new Rect();
private final Rect mRootBounds = new Rect();
private final Rect mDividerBounds = new Rect();
private final Rect mBounds1 = new Rect();
private final Rect mBounds2 = new Rect();
+ private final Rect mWinBounds1 = new Rect();
+ private final Rect mWinBounds2 = new Rect();
private final SplitLayoutHandler mSplitLayoutHandler;
private final SplitWindowManager mSplitWindowManager;
private final DisplayImeController mDisplayImeController;
private final ImePositionProcessor mImePositionProcessor;
+ private final DismissingEffectPolicy mDismissingEffectPolicy;
private final ShellTaskOrganizer mTaskOrganizer;
+ private final InsetsState mInsetsState = new InsetsState();
private Context mContext;
private DividerSnapAlgorithm mDividerSnapAlgorithm;
+ private WindowContainerToken mWinToken1;
+ private WindowContainerToken mWinToken2;
private int mDividePosition;
private boolean mInitialized = false;
+ private int mOrientation;
+ private int mRotation;
public SplitLayout(String windowName, Context context, Configuration configuration,
SplitLayoutHandler splitLayoutHandler,
SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks,
- DisplayImeController displayImeController, ShellTaskOrganizer taskOrganizer) {
+ DisplayImeController displayImeController, ShellTaskOrganizer taskOrganizer,
+ boolean applyDismissingParallax) {
mContext = context.createConfigurationContext(configuration);
+ mOrientation = configuration.orientation;
+ mRotation = configuration.windowConfiguration.getRotation();
mSplitLayoutHandler = splitLayoutHandler;
mDisplayImeController = displayImeController;
- mSplitWindowManager = new SplitWindowManager(
- windowName, mContext, configuration, parentContainerCallbacks);
+ mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration,
+ parentContainerCallbacks);
mTaskOrganizer = taskOrganizer;
mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
+ mDismissingEffectPolicy = new DismissingEffectPolicy(applyDismissingParallax);
final Resources resources = context.getResources();
- mDividerWindowWidth = resources.getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_thickness);
- mDividerInsets = resources.getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_insets);
- mDividerSize = mDividerWindowWidth - mDividerInsets * 2;
+ mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
+ mDividerInsets = getDividerInsets(resources, context.getDisplay());
+ mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
mRootBounds.set(configuration.windowConfiguration.getBounds());
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
resetDividerPosition();
}
+ private int getDividerInsets(Resources resources, Display display) {
+ final int dividerInset = resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_insets);
+
+ int radius = 0;
+ RoundedCorner corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT);
+ radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
+ corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT);
+ radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
+ corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
+ radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
+ corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
+ radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
+
+ return Math.max(dividerInset, radius);
+ }
+
/** Gets bounds of the primary split. */
public Rect getBounds1() {
return new Rect(mBounds1);
@@ -142,35 +187,71 @@ public final class SplitLayout {
return mDividePosition;
}
+ /**
+ * Returns the divider position as a fraction from 0 to 1.
+ */
+ public float getDividerPositionAsFraction() {
+ return Math.min(1f, Math.max(0f, isLandscape()
+ ? (float) ((mBounds1.right + mBounds2.left) / 2f) / mBounds2.right
+ : (float) ((mBounds1.bottom + mBounds2.top) / 2f) / mBounds2.bottom));
+ }
+
/** Applies new configuration, returns {@code false} if there's no effect to the layout. */
public boolean updateConfiguration(Configuration configuration) {
+ boolean affectsLayout = false;
+
+ // Update the split bounds when necessary. Besides root bounds changed, split bounds need to
+ // be updated when the rotation changed to cover the case that users rotated the screen 180
+ // degrees.
+ // Make sure to render the divider bar with proper resources that matching the screen
+ // orientation.
+ final int rotation = configuration.windowConfiguration.getRotation();
final Rect rootBounds = configuration.windowConfiguration.getBounds();
- if (mRootBounds.equals(rootBounds)) {
+ final int orientation = configuration.orientation;
+
+ if (mOrientation == orientation
+ && rotation == mRotation
+ && mRootBounds.equals(rootBounds)) {
return false;
}
mContext = mContext.createConfigurationContext(configuration);
mSplitWindowManager.setConfiguration(configuration);
+ mOrientation = orientation;
+ mTempRect.set(mRootBounds);
mRootBounds.set(rootBounds);
+ mRotation = rotation;
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
- resetDividerPosition();
+ initDividerPosition(mTempRect);
- // Don't inflate divider bar if it is not initialized.
- if (!mInitialized) {
- return false;
+ if (mInitialized) {
+ release();
+ init();
}
- release();
- init();
return true;
}
+ private void initDividerPosition(Rect oldBounds) {
+ final float snapRatio = (float) mDividePosition
+ / (float) (isLandscape(oldBounds) ? oldBounds.width() : oldBounds.height());
+ // Estimate position by previous ratio.
+ final float length =
+ (float) (isLandscape() ? mRootBounds.width() : mRootBounds.height());
+ final int estimatePosition = (int) (length * snapRatio);
+ // Init divider position by estimated position using current bounds snap algorithm.
+ mDividePosition = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
+ estimatePosition).position;
+ updateBounds(mDividePosition);
+ }
+
/** Updates recording bounds of divider window and both of the splits. */
private void updateBounds(int position) {
mDividerBounds.set(mRootBounds);
mBounds1.set(mRootBounds);
mBounds2.set(mRootBounds);
- if (isLandscape(mRootBounds)) {
+ final boolean isLandscape = isLandscape(mRootBounds);
+ if (isLandscape) {
position += mRootBounds.left;
mDividerBounds.left = position - mDividerInsets;
mDividerBounds.right = mDividerBounds.left + mDividerWindowWidth;
@@ -183,13 +264,16 @@ public final class SplitLayout {
mBounds1.bottom = position;
mBounds2.top = mBounds1.bottom + mDividerSize;
}
+ DockedDividerUtils.sanitizeStackBounds(mBounds1, true /** topLeft */);
+ DockedDividerUtils.sanitizeStackBounds(mBounds2, false /** topLeft */);
+ mDismissingEffectPolicy.applyDividerPosition(position, isLandscape);
}
/** Inflates {@link DividerView} on the root surface. */
public void init() {
if (mInitialized) return;
mInitialized = true;
- mSplitWindowManager.init(this);
+ mSplitWindowManager.init(this, mInsetsState);
mDisplayImeController.addPositionProcessor(mImePositionProcessor);
}
@@ -202,27 +286,46 @@ public final class SplitLayout {
mImePositionProcessor.reset();
}
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ mInsetsState.set(insetsState);
+ if (!mInitialized) {
+ return;
+ }
+ mSplitWindowManager.onInsetsChanged(insetsState);
+ }
+
+ @Override
+ public void insetsControlChanged(InsetsState insetsState,
+ InsetsSourceControl[] activeControls) {
+ if (!mInsetsState.equals(insetsState)) {
+ insetsChanged(insetsState);
+ }
+ }
+
/**
* Updates bounds with the passing position. Usually used to update recording bounds while
* performing animation or dragging divider bar to resize the splits.
*/
void updateDivideBounds(int position) {
updateBounds(position);
- mSplitWindowManager.setResizingSplits(true);
- mSplitLayoutHandler.onBoundsChanging(this);
+ mSplitLayoutHandler.onLayoutSizeChanging(this);
}
void setDividePosition(int position) {
mDividePosition = position;
updateBounds(mDividePosition);
- mSplitLayoutHandler.onBoundsChanged(this);
- mSplitWindowManager.setResizingSplits(false);
+ mSplitLayoutHandler.onLayoutSizeChanged(this);
}
/** Resets divider position. */
public void resetDividerPosition() {
mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position;
updateBounds(mDividePosition);
+ mWinToken1 = null;
+ mWinToken2 = null;
+ mWinBounds1.setEmpty();
+ mWinBounds2.setEmpty();
}
/**
@@ -232,15 +335,15 @@ public final class SplitLayout {
public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
switch (snapTarget.flag) {
case FLAG_DISMISS_START:
- mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */);
- mSplitWindowManager.setResizingSplits(false);
+ flingDividePosition(currentPosition, snapTarget.position,
+ () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */));
break;
case FLAG_DISMISS_END:
- mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */);
- mSplitWindowManager.setResizingSplits(false);
+ flingDividePosition(currentPosition, snapTarget.position,
+ () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */));
break;
default:
- flingDividePosition(currentPosition, snapTarget.position);
+ flingDividePosition(currentPosition, snapTarget.position, null);
break;
}
}
@@ -270,8 +373,13 @@ public final class SplitLayout {
isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
}
- private void flingDividePosition(int from, int to) {
- if (from == to) return;
+ @VisibleForTesting
+ void flingDividePosition(int from, int to, @Nullable Runnable flingFinishedCallback) {
+ if (from == to) {
+ // No animation run, still callback to stop resizing.
+ mSplitLayoutHandler.onLayoutSizeChanged(this);
+ return;
+ }
ValueAnimator animator = ValueAnimator
.ofInt(from, to)
.setDuration(250);
@@ -282,6 +390,9 @@ public final class SplitLayout {
@Override
public void onAnimationEnd(Animator animation) {
setDividePosition(to);
+ if (flingFinishedCallback != null) {
+ flingFinishedCallback.run();
+ }
}
@Override
@@ -296,42 +407,99 @@ public final class SplitLayout {
return context.getSystemService(WindowManager.class)
.getMaximumWindowMetrics()
.getWindowInsets()
- .getInsets(WindowInsets.Type.navigationBars()
- | WindowInsets.Type.statusBars()
- | WindowInsets.Type.displayCutout()).toRect();
+ .getInsets(WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout())
+ .toRect();
}
private static boolean isLandscape(Rect bounds) {
return bounds.width() > bounds.height();
}
+ /**
+ * Return if this layout is landscape.
+ */
+ public boolean isLandscape() {
+ return isLandscape(mRootBounds);
+ }
+
/** Apply recorded surface layout to the {@link SurfaceControl.Transaction}. */
public void applySurfaceChanges(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, SurfaceControl dimLayer1, SurfaceControl dimLayer2) {
- final Rect dividerBounds = mImePositionProcessor.adjustForIme(mDividerBounds);
- final Rect bounds1 = mImePositionProcessor.adjustForIme(mBounds1);
- final Rect bounds2 = mImePositionProcessor.adjustForIme(mBounds2);
final SurfaceControl dividerLeash = getDividerLeash();
if (dividerLeash != null) {
- t.setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
- // Resets layer of divider bar to make sure it is always on top.
- .setLayer(dividerLeash, Integer.MAX_VALUE);
+ t.setPosition(dividerLeash, mDividerBounds.left, mDividerBounds.top);
+ // Resets layer of divider bar to make sure it is always on top.
+ t.setLayer(dividerLeash, SPLIT_DIVIDER_LAYER);
+ }
+ t.setPosition(leash1, mBounds1.left, mBounds1.top)
+ .setWindowCrop(leash1, mBounds1.width(), mBounds1.height());
+ t.setPosition(leash2, mBounds2.left, mBounds2.top)
+ .setWindowCrop(leash2, mBounds2.width(), mBounds2.height());
+
+ if (mImePositionProcessor.adjustSurfaceLayoutForIme(
+ t, dividerLeash, leash1, leash2, dimLayer1, dimLayer2)) {
+ return;
}
- t.setPosition(leash1, bounds1.left, bounds1.top)
- .setWindowCrop(leash1, bounds1.width(), bounds1.height());
-
- t.setPosition(leash2, bounds2.left, bounds2.top)
- .setWindowCrop(leash2, bounds2.width(), bounds2.height());
-
- mImePositionProcessor.applySurfaceDimValues(t, dimLayer1, dimLayer2);
+ mDismissingEffectPolicy.adjustDismissingSurface(t, leash1, leash2, dimLayer1, dimLayer2);
}
/** Apply recorded task layout to the {@link WindowContainerTransaction}. */
public void applyTaskChanges(WindowContainerTransaction wct,
ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
- wct.setBounds(task1.token, mImePositionProcessor.adjustForIme(mBounds1))
- .setBounds(task2.token, mImePositionProcessor.adjustForIme(mBounds2));
+ if (mImePositionProcessor.applyTaskLayoutForIme(wct, task1.token, task2.token)) {
+ return;
+ }
+
+ if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
+ wct.setBounds(task1.token, mBounds1);
+ mWinBounds1.set(mBounds1);
+ mWinToken1 = task1.token;
+ }
+ if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
+ wct.setBounds(task2.token, mBounds2);
+ mWinBounds2.set(mBounds2);
+ mWinToken2 = task2.token;
+ }
+ }
+
+ /**
+ * Shift configuration bounds to prevent client apps get configuration changed or relaunch. And
+ * restore shifted configuration bounds if it's no longer shifted.
+ */
+ public void applyLayoutOffsetTarget(WindowContainerTransaction wct, int offsetX, int offsetY,
+ ActivityManager.RunningTaskInfo taskInfo1, ActivityManager.RunningTaskInfo taskInfo2) {
+ if (offsetX == 0 && offsetY == 0) {
+ wct.setBounds(taskInfo1.token, mBounds1);
+ wct.setAppBounds(taskInfo1.token, null);
+ wct.setScreenSizeDp(taskInfo1.token,
+ SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
+
+ wct.setBounds(taskInfo2.token, mBounds2);
+ wct.setAppBounds(taskInfo2.token, null);
+ wct.setScreenSizeDp(taskInfo2.token,
+ SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
+ } else {
+ mTempRect.set(taskInfo1.configuration.windowConfiguration.getBounds());
+ mTempRect.offset(offsetX, offsetY);
+ wct.setBounds(taskInfo1.token, mTempRect);
+ mTempRect.set(taskInfo1.configuration.windowConfiguration.getAppBounds());
+ mTempRect.offset(offsetX, offsetY);
+ wct.setAppBounds(taskInfo1.token, mTempRect);
+ wct.setScreenSizeDp(taskInfo1.token,
+ taskInfo1.configuration.screenWidthDp,
+ taskInfo1.configuration.screenHeightDp);
+
+ mTempRect.set(taskInfo2.configuration.windowConfiguration.getBounds());
+ mTempRect.offset(offsetX, offsetY);
+ wct.setBounds(taskInfo2.token, mTempRect);
+ mTempRect.set(taskInfo2.configuration.windowConfiguration.getAppBounds());
+ mTempRect.offset(offsetX, offsetY);
+ wct.setAppBounds(taskInfo2.token, mTempRect);
+ wct.setScreenSizeDp(taskInfo2.token,
+ taskInfo2.configuration.screenWidthDp,
+ taskInfo2.configuration.screenHeightDp);
+ }
}
/** Handles layout change event. */
@@ -340,11 +508,43 @@ public final class SplitLayout {
/** Calls when dismissing split. */
void onSnappedToDismiss(boolean snappedToEnd);
- /** Calls when the bounds is changing due to animation or dragging divider bar. */
- void onBoundsChanging(SplitLayout layout);
+ /**
+ * Calls when resizing the split bounds.
+ *
+ * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
+ * SurfaceControl, SurfaceControl)
+ */
+ void onLayoutSizeChanging(SplitLayout layout);
+
+ /**
+ * Calls when finish resizing the split bounds.
+ *
+ * @see #applyTaskChanges(WindowContainerTransaction, ActivityManager.RunningTaskInfo,
+ * ActivityManager.RunningTaskInfo)
+ * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
+ * SurfaceControl, SurfaceControl)
+ */
+ void onLayoutSizeChanged(SplitLayout layout);
+
+ /**
+ * Calls when re-positioning the split bounds. Like moving split bounds while showing IME
+ * panel.
+ *
+ * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
+ * SurfaceControl, SurfaceControl)
+ */
+ void onLayoutPositionChanging(SplitLayout layout);
- /** Calls when the target bounds changed. */
- void onBoundsChanged(SplitLayout layout);
+ /**
+ * Notifies the target offset for shifting layout. So layout handler can shift configuration
+ * bounds correspondingly to make sure client apps won't get configuration changed or
+ * relaunched. If the layout is no longer shifted, layout handler should restore shifted
+ * configuration bounds.
+ *
+ * @see #applyLayoutOffsetTarget(WindowContainerTransaction, int, int,
+ * ActivityManager.RunningTaskInfo, ActivityManager.RunningTaskInfo)
+ */
+ void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout);
/** Calls when user double tapped on the divider bar. */
default void onDoubleTappedDivider() {
@@ -355,6 +555,115 @@ public final class SplitLayout {
int getSplitItemPosition(WindowContainerToken token);
}
+ /**
+ * Calculates and applies proper dismissing parallax offset and dimming value to hint users
+ * dismissing gesture.
+ */
+ private class DismissingEffectPolicy {
+ /** Indicates whether to offset splitting bounds to hint dismissing progress or not. */
+ private final boolean mApplyParallax;
+
+ // The current dismissing side.
+ int mDismissingSide = DOCKED_INVALID;
+
+ // The parallax offset to hint the dismissing side and progress.
+ final Point mDismissingParallaxOffset = new Point();
+
+ // The dimming value to hint the dismissing side and progress.
+ float mDismissingDimValue = 0.0f;
+
+ DismissingEffectPolicy(boolean applyDismissingParallax) {
+ mApplyParallax = applyDismissingParallax;
+ }
+
+ /**
+ * Applies a parallax to the task to hint dismissing progress.
+ *
+ * @param position the split position to apply dismissing parallax effect
+ * @param isLandscape indicates whether it's splitting horizontally or vertically
+ */
+ void applyDividerPosition(int position, boolean isLandscape) {
+ mDismissingSide = DOCKED_INVALID;
+ mDismissingParallaxOffset.set(0, 0);
+ mDismissingDimValue = 0;
+
+ int totalDismissingDistance = 0;
+ if (position < mDividerSnapAlgorithm.getFirstSplitTarget().position) {
+ mDismissingSide = isLandscape ? DOCKED_LEFT : DOCKED_TOP;
+ totalDismissingDistance = mDividerSnapAlgorithm.getDismissStartTarget().position
+ - mDividerSnapAlgorithm.getFirstSplitTarget().position;
+ } else if (position > mDividerSnapAlgorithm.getLastSplitTarget().position) {
+ mDismissingSide = isLandscape ? DOCKED_RIGHT : DOCKED_BOTTOM;
+ totalDismissingDistance = mDividerSnapAlgorithm.getLastSplitTarget().position
+ - mDividerSnapAlgorithm.getDismissEndTarget().position;
+ }
+
+ if (mDismissingSide != DOCKED_INVALID) {
+ float fraction = Math.max(0,
+ Math.min(mDividerSnapAlgorithm.calculateDismissingFraction(position), 1f));
+ mDismissingDimValue = DIM_INTERPOLATOR.getInterpolation(fraction);
+ fraction = calculateParallaxDismissingFraction(fraction, mDismissingSide);
+ if (isLandscape) {
+ mDismissingParallaxOffset.x = (int) (fraction * totalDismissingDistance);
+ } else {
+ mDismissingParallaxOffset.y = (int) (fraction * totalDismissingDistance);
+ }
+ }
+ }
+
+ /**
+ * @return for a specified {@code fraction}, this returns an adjusted value that simulates a
+ * slowing down parallax effect
+ */
+ private float calculateParallaxDismissingFraction(float fraction, int dockSide) {
+ float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
+
+ // Less parallax at the top, just because.
+ if (dockSide == WindowManager.DOCKED_TOP) {
+ result /= 2f;
+ }
+ return result;
+ }
+
+ /** Applies parallax offset and dimming value to the root surface at the dismissing side. */
+ boolean adjustDismissingSurface(SurfaceControl.Transaction t,
+ SurfaceControl leash1, SurfaceControl leash2,
+ SurfaceControl dimLayer1, SurfaceControl dimLayer2) {
+ SurfaceControl targetLeash, targetDimLayer;
+ switch (mDismissingSide) {
+ case DOCKED_TOP:
+ case DOCKED_LEFT:
+ targetLeash = leash1;
+ targetDimLayer = dimLayer1;
+ mTempRect.set(mBounds1);
+ break;
+ case DOCKED_BOTTOM:
+ case DOCKED_RIGHT:
+ targetLeash = leash2;
+ targetDimLayer = dimLayer2;
+ mTempRect.set(mBounds2);
+ break;
+ case DOCKED_INVALID:
+ default:
+ t.setAlpha(dimLayer1, 0).hide(dimLayer1);
+ t.setAlpha(dimLayer2, 0).hide(dimLayer2);
+ return false;
+ }
+
+ if (mApplyParallax) {
+ t.setPosition(targetLeash,
+ mTempRect.left + mDismissingParallaxOffset.x,
+ mTempRect.top + mDismissingParallaxOffset.y);
+ // Transform the screen-based split bounds to surface-based crop bounds.
+ mTempRect.offsetTo(-mDismissingParallaxOffset.x, -mDismissingParallaxOffset.y);
+ t.setWindowCrop(targetLeash, mTempRect);
+ }
+ t.setAlpha(targetDimLayer, mDismissingDimValue)
+ .setVisibility(targetDimLayer, mDismissingDimValue > 0.001f);
+ return true;
+ }
+ }
+
/** Records IME top offset changes and updates SplitLayout correspondingly. */
private class ImePositionProcessor implements DisplayImeController.ImePositionProcessor {
/**
@@ -409,6 +718,18 @@ public final class SplitLayout {
&& !isFloating && !isLandscape(mRootBounds) && showing;
mTargetYOffset = needOffset ? getTargetYOffset() : 0;
+ if (mTargetYOffset != mLastYOffset) {
+ // Freeze the configuration size with offset to prevent app get a configuration
+ // changed or relaunch. This is required to make sure client apps will calculate
+ // insets properly after layout shifted.
+ if (mTargetYOffset == 0) {
+ mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
+ } else {
+ mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset - mLastYOffset,
+ SplitLayout.this);
+ }
+ }
+
// Make {@link DividerView} non-interactive while IME showing in split mode. Listen to
// ImePositionProcessor#onImeVisibilityChanged directly in DividerView is not enough
// because DividerView won't receive onImeVisibilityChanged callback after it being
@@ -423,7 +744,7 @@ public final class SplitLayout {
public void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) {
if (displayId != mDisplayId) return;
onProgress(getProgress(imeTop));
- mSplitLayoutHandler.onBoundsChanging(SplitLayout.this);
+ mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
@Override
@@ -431,7 +752,7 @@ public final class SplitLayout {
SurfaceControl.Transaction t) {
if (displayId != mDisplayId || cancel) return;
onProgress(1.0f);
- mSplitLayoutHandler.onBoundsChanging(SplitLayout.this);
+ mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
@Override
@@ -441,7 +762,7 @@ public final class SplitLayout {
if (!controlling && mImeShown) {
reset();
mSplitWindowManager.setInteractive(true);
- mSplitLayoutHandler.onBoundsChanging(SplitLayout.this);
+ mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
}
@@ -473,24 +794,66 @@ public final class SplitLayout {
return start + (end - start) * progress;
}
- private void reset() {
+ void reset() {
mImeShown = false;
mYOffsetForIme = mLastYOffset = mTargetYOffset = 0;
mDimValue1 = mLastDim1 = mTargetDim1 = 0.0f;
mDimValue2 = mLastDim2 = mTargetDim2 = 0.0f;
}
- /* Adjust bounds with IME offset. */
- private Rect adjustForIme(Rect bounds) {
- final Rect temp = new Rect(bounds);
- if (mYOffsetForIme != 0) temp.offset(0, mYOffsetForIme);
- return temp;
+ /**
+ * Applies adjusted task layout for showing IME.
+ *
+ * @return {@code false} if there's no need to adjust, otherwise {@code true}
+ */
+ boolean applyTaskLayoutForIme(WindowContainerTransaction wct,
+ WindowContainerToken token1, WindowContainerToken token2) {
+ if (mYOffsetForIme == 0) return false;
+
+ mTempRect.set(mBounds1);
+ mTempRect.offset(0, mYOffsetForIme);
+ wct.setBounds(token1, mTempRect);
+
+ mTempRect.set(mBounds2);
+ mTempRect.offset(0, mYOffsetForIme);
+ wct.setBounds(token2, mTempRect);
+
+ return true;
}
- private void applySurfaceDimValues(SurfaceControl.Transaction t, SurfaceControl dimLayer1,
- SurfaceControl dimLayer2) {
- t.setAlpha(dimLayer1, mDimValue1).setVisibility(dimLayer1, mDimValue1 > 0.001f);
- t.setAlpha(dimLayer2, mDimValue2).setVisibility(dimLayer2, mDimValue2 > 0.001f);
+ /**
+ * Adjusts surface layout while showing IME.
+ *
+ * @return {@code false} if there's no need to adjust, otherwise {@code true}
+ */
+ boolean adjustSurfaceLayoutForIme(SurfaceControl.Transaction t,
+ SurfaceControl dividerLeash, SurfaceControl leash1, SurfaceControl leash2,
+ SurfaceControl dimLayer1, SurfaceControl dimLayer2) {
+ final boolean showDim = mDimValue1 > 0.001f || mDimValue2 > 0.001f;
+ boolean adjusted = false;
+ if (mYOffsetForIme != 0) {
+ if (dividerLeash != null) {
+ mTempRect.set(mDividerBounds);
+ mTempRect.offset(0, mYOffsetForIme);
+ t.setPosition(dividerLeash, mTempRect.left, mTempRect.top);
+ }
+
+ mTempRect.set(mBounds1);
+ mTempRect.offset(0, mYOffsetForIme);
+ t.setPosition(leash1, mTempRect.left, mTempRect.top);
+
+ mTempRect.set(mBounds2);
+ mTempRect.offset(0, mYOffsetForIme);
+ t.setPosition(leash2, mTempRect.left, mTempRect.top);
+ adjusted = true;
+ }
+
+ if (showDim) {
+ t.setAlpha(dimLayer1, mDimValue1).setVisibility(dimLayer1, mDimValue1 > 0.001f);
+ t.setAlpha(dimLayer2, mDimValue2).setVisibility(dimLayer2, mDimValue2 > 0.001f);
+ adjusted = true;
+ }
+ return adjusted;
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 0cea0efc0057..4903f9d46dc7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -25,17 +25,14 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
import android.view.IWindow;
+import android.view.InsetsState;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
@@ -43,6 +40,7 @@ import android.view.SurfaceSession;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.wm.shell.R;
@@ -58,11 +56,11 @@ public final class SplitWindowManager extends WindowlessWindowManager {
private Context mContext;
private SurfaceControlViewHost mViewHost;
private SurfaceControl mLeash;
- private boolean mResizingSplits;
private DividerView mDividerView;
public interface ParentContainerCallbacks {
void attachToParentSurface(SurfaceControl.Builder b);
+ void onLeashReady(SurfaceControl leash);
}
public SplitWindowManager(String windowName, Context context, Configuration config,
@@ -73,9 +71,10 @@ public final class SplitWindowManager extends WindowlessWindowManager {
mWindowName = windowName;
}
- @Override
- public void setTouchRegion(IBinder window, Region region) {
- super.setTouchRegion(window, region);
+ void setTouchRegion(@NonNull Rect region) {
+ if (mViewHost != null) {
+ setTouchRegion(mViewHost.getWindowToken().asBinder(), new Region(region));
+ }
}
@Override
@@ -95,15 +94,16 @@ public final class SplitWindowManager extends WindowlessWindowManager {
final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
.setContainerLayer()
.setName(TAG)
- .setHidden(false)
+ .setHidden(true)
.setCallsite("SplitWindowManager#attachToParentSurface");
mParentContainerCallbacks.attachToParentSurface(builder);
mLeash = builder.build();
+ mParentContainerCallbacks.onLeashReady(mLeash);
b.setParent(mLeash);
}
/** Inflates {@link DividerView} on to the root surface. */
- void init(SplitLayout splitLayout) {
+ void init(SplitLayout splitLayout, InsetsState insetsState) {
if (mDividerView != null || mViewHost != null) {
throw new UnsupportedOperationException(
"Try to inflate divider view again without release first");
@@ -123,7 +123,7 @@ public final class SplitWindowManager extends WindowlessWindowManager {
lp.setTitle(mWindowName);
lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
mViewHost.setView(mDividerView, lp);
- mDividerView.setup(splitLayout, mViewHost);
+ mDividerView.setup(splitLayout, this, mViewHost, insetsState);
}
/**
@@ -151,16 +151,6 @@ public final class SplitWindowManager extends WindowlessWindowManager {
mDividerView.setInteractive(interactive);
}
- void setResizingSplits(boolean resizing) {
- if (resizing == mResizingSplits) return;
- try {
- ActivityTaskManager.getService().setSplitScreenResizing(resizing);
- mResizingSplits = resizing;
- } catch (RemoteException e) {
- Slog.w(TAG, "Error calling setSplitScreenResizing", e);
- }
- }
-
/**
* Gets {@link SurfaceControl} of the surface holding divider view. @return {@code null} if not
* feasible.
@@ -169,4 +159,10 @@ public final class SplitWindowManager extends WindowlessWindowManager {
SurfaceControl getSurfaceControl() {
return mLeash;
}
+
+ void onInsetsChanged(InsetsState insetsState) {
+ if (mDividerView != null) {
+ mDividerView.onInsetsChanged(insetsState, true /* animate */);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelper.java
new file mode 100644
index 000000000000..defbd5af01d9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelper.java
@@ -0,0 +1,39 @@
+/*
+ * 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.wm.shell.displayareahelper;
+
+import android.view.SurfaceControl;
+
+import java.util.function.Consumer;
+
+/**
+ * Interface that allows to perform various display area related actions
+ */
+public interface DisplayAreaHelper {
+
+ /**
+ * Updates SurfaceControl builder to reparent it to the root display area
+ * @param displayId id of the display to which root display area it should be reparented to
+ * @param builder surface control builder that should be updated
+ * @param onUpdated callback that is invoked after updating the builder, called on
+ * the shell main thread
+ */
+ default void attachToRootDisplayArea(int displayId, SurfaceControl.Builder builder,
+ Consumer<SurfaceControl.Builder> onUpdated) {
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelperController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelperController.java
new file mode 100644
index 000000000000..ef9ad6d10e6b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelperController.java
@@ -0,0 +1,45 @@
+/*
+ * 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.wm.shell.displayareahelper;
+
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.RootDisplayAreaOrganizer;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+public class DisplayAreaHelperController implements DisplayAreaHelper {
+
+ private final Executor mExecutor;
+ private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
+
+ public DisplayAreaHelperController(Executor executor,
+ RootDisplayAreaOrganizer rootDisplayAreaOrganizer) {
+ mExecutor = executor;
+ mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
+ }
+
+ @Override
+ public void attachToRootDisplayArea(int displayId, SurfaceControl.Builder builder,
+ Consumer<SurfaceControl.Builder> onUpdated) {
+ mExecutor.execute(() -> {
+ mRootDisplayAreaOrganizer.attachToDisplayArea(displayId, builder);
+ onUpdated.accept(builder);
+ });
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 58bf22ad29b2..0c12d6c7bca2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -34,6 +34,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.res.Configuration;
@@ -48,6 +49,8 @@ import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
@@ -67,14 +70,17 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
private final Context mContext;
private final DisplayController mDisplayController;
+ private final DragAndDropEventLogger mLogger;
private SplitScreenController mSplitScreen;
private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
- public DragAndDropController(Context context, DisplayController displayController) {
+ public DragAndDropController(Context context, DisplayController displayController,
+ UiEventLogger uiEventLogger) {
mContext = context;
mDisplayController = displayController;
+ mLogger = new DragAndDropEventLogger(uiEventLogger);
}
public void initialize(Optional<SplitScreenController> splitscreen) {
@@ -175,9 +181,10 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
Slog.w(TAG, "Unexpected drag start during an active drag");
return false;
}
+ InstanceId loggerSessionId = mLogger.logStart(event);
pd.activeDragCount++;
pd.dragLayout.prepare(mDisplayController.getDisplayLayout(displayId),
- event.getClipData());
+ event.getClipData(), loggerSessionId);
setDropTargetWindowVisibility(pd, View.VISIBLE);
break;
case ACTION_DRAG_ENTERED:
@@ -198,7 +205,9 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
case ACTION_DRAG_ENDED:
// TODO(b/169894807): Ensure sure it's not possible to get ENDED without DROP
// or EXITED
- if (!pd.dragLayout.hasDropped()) {
+ if (pd.dragLayout.hasDropped()) {
+ mLogger.logDrop();
+ } else {
pd.activeDragCount--;
pd.dragLayout.hide(event, () -> {
if (pd.activeDragCount == 0) {
@@ -208,6 +217,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
}
});
}
+ mLogger.logEnd();
break;
}
return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java
new file mode 100644
index 000000000000..6e4b81563441
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java
@@ -0,0 +1,132 @@
+/*
+ * 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.wm.shell.draganddrop;
+
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.pm.ActivityInfo;
+import android.view.DragEvent;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
+/**
+ * Helper class that to log Drag & Drop UIEvents for a single session, see also go/uievent
+ */
+public class DragAndDropEventLogger {
+
+ private final UiEventLogger mUiEventLogger;
+ // Used to generate instance ids for this drag if one is not provided
+ private final InstanceIdSequence mIdSequence;
+
+ // Tracks the current drag session
+ private ActivityInfo mActivityInfo;
+ private InstanceId mInstanceId;
+
+ public DragAndDropEventLogger(UiEventLogger uiEventLogger) {
+ mUiEventLogger = uiEventLogger;
+ mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE);
+ }
+
+ /**
+ * Logs the start of a drag.
+ */
+ public InstanceId logStart(DragEvent event) {
+ final ClipDescription description = event.getClipDescription();
+ final ClipData data = event.getClipData();
+ final ClipData.Item item = data.getItemAt(0);
+ mInstanceId = item.getIntent().getParcelableExtra(
+ ClipDescription.EXTRA_LOGGING_INSTANCE_ID);
+ if (mInstanceId == null) {
+ mInstanceId = mIdSequence.newInstanceId();
+ }
+ mActivityInfo = item.getActivityInfo();
+ mUiEventLogger.logWithInstanceId(getStartEnum(description),
+ mActivityInfo.applicationInfo.uid,
+ mActivityInfo.applicationInfo.packageName, mInstanceId);
+ return mInstanceId;
+ }
+
+ /**
+ * Logs a successful drop.
+ */
+ public void logDrop() {
+ mUiEventLogger.logWithInstanceId(DragAndDropUiEventEnum.GLOBAL_APP_DRAG_DROPPED,
+ mActivityInfo.applicationInfo.uid,
+ mActivityInfo.applicationInfo.packageName, mInstanceId);
+ }
+
+ /**
+ * Logs the end of a drag.
+ */
+ public void logEnd() {
+ mUiEventLogger.logWithInstanceId(DragAndDropUiEventEnum.GLOBAL_APP_DRAG_END,
+ mActivityInfo.applicationInfo.uid,
+ mActivityInfo.applicationInfo.packageName, mInstanceId);
+ }
+
+ /**
+ * Returns the start logging enum for the given drag description.
+ */
+ private DragAndDropUiEventEnum getStartEnum(ClipDescription description) {
+ if (description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY)) {
+ return DragAndDropUiEventEnum.GLOBAL_APP_DRAG_START_ACTIVITY;
+ } else if (description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT)) {
+ return DragAndDropUiEventEnum.GLOBAL_APP_DRAG_START_SHORTCUT;
+ } else if (description.hasMimeType(MIMETYPE_APPLICATION_TASK)) {
+ return DragAndDropUiEventEnum.GLOBAL_APP_DRAG_START_TASK;
+ }
+ throw new IllegalArgumentException("Not an app drag");
+ }
+
+ /**
+ * Enums for logging Drag & Drop UiEvents
+ */
+ public enum DragAndDropUiEventEnum implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Starting a global drag and drop of an activity")
+ GLOBAL_APP_DRAG_START_ACTIVITY(884),
+
+ @UiEvent(doc = "Starting a global drag and drop of a shortcut")
+ GLOBAL_APP_DRAG_START_SHORTCUT(885),
+
+ @UiEvent(doc = "Starting a global drag and drop of a task")
+ GLOBAL_APP_DRAG_START_TASK(888),
+
+ @UiEvent(doc = "A global app drag was successfully dropped")
+ GLOBAL_APP_DRAG_DROPPED(887),
+
+ @UiEvent(doc = "Ending a global app drag and drop")
+ GLOBAL_APP_DRAG_END(886);
+
+ private final int mId;
+
+ DragAndDropUiEventEnum(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 9bcc3acf7a57..fbf04d6f3fff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -63,6 +63,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
@@ -86,6 +87,7 @@ public class DragAndDropPolicy {
private final SplitScreenController mSplitScreen;
private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>();
+ private InstanceId mLoggerSessionId;
private DragSession mSession;
public DragAndDropPolicy(Context context, SplitScreenController splitScreen) {
@@ -104,7 +106,8 @@ public class DragAndDropPolicy {
/**
* Starts a new drag session with the given initial drag data.
*/
- void start(DisplayLayout displayLayout, ClipData data) {
+ void start(DisplayLayout displayLayout, ClipData data, InstanceId loggerSessionId) {
+ mLoggerSessionId = loggerSessionId;
mSession = new DragSession(mContext, mActivityTaskManager, displayLayout, data);
// TODO(b/169894807): Also update the session data with task stack changes
mSession.update();
@@ -151,10 +154,8 @@ public class DragAndDropPolicy {
final Rect rightHitRegion = new Rect();
final Rect rightDrawRegion = bottomOrRightBounds;
- displayRegion.splitVertically(leftHitRegion, fullscreenHitRegion, rightHitRegion);
+ displayRegion.splitVertically(leftHitRegion, rightHitRegion);
- mTargets.add(
- new Target(TYPE_FULLSCREEN, fullscreenHitRegion, fullscreenDrawRegion));
mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, leftDrawRegion));
mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, rightDrawRegion));
@@ -165,10 +166,8 @@ public class DragAndDropPolicy {
final Rect bottomDrawRegion = bottomOrRightBounds;
displayRegion.splitHorizontally(
- topHitRegion, fullscreenHitRegion, bottomHitRegion);
+ topHitRegion, bottomHitRegion);
- mTargets.add(
- new Target(TYPE_FULLSCREEN, fullscreenHitRegion, fullscreenDrawRegion));
mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topDrawRegion));
mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomDrawRegion));
}
@@ -211,6 +210,8 @@ public class DragAndDropPolicy {
// Launch in the side stage if we are not in split-screen already.
stage = STAGE_TYPE_SIDE;
}
+ // Add some data for logging splitscreen once it is invoked
+ mSplitScreen.logOnDroppedToSplit(position, mLoggerSessionId);
}
final ClipDescription description = data.getDescription();
@@ -269,7 +270,6 @@ public class DragAndDropPolicy {
* Updates the session data based on the current state of the system.
*/
void update() {
-
List<ActivityManager.RunningTaskInfo> tasks =
mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
if (!tasks.isEmpty()) {
@@ -299,7 +299,12 @@ public class DragAndDropPolicy {
@StageType int stage, @SplitPosition int position,
@Nullable Bundle options);
void enterSplitScreen(int taskId, boolean leftOrTop);
- void exitSplitScreen();
+
+ /**
+ * Exits splitscreen, with an associated exit trigger from the SplitscreenUIChanged proto
+ * for logging.
+ */
+ void exitSplitScreen(int toTopTaskId, int exitTrigger);
}
/**
@@ -352,7 +357,7 @@ public class DragAndDropPolicy {
}
@Override
- public void exitSplitScreen() {
+ public void exitSplitScreen(int toTopTaskId, int exitTrigger) {
throw new UnsupportedOperationException("exitSplitScreen not implemented by starter");
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index b3423362347f..efc9ed0f75b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -38,6 +38,7 @@ import android.view.WindowInsets.Type;
import androidx.annotation.NonNull;
+import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
@@ -98,8 +99,9 @@ public class DragLayout extends View {
return mHasDropped;
}
- public void prepare(DisplayLayout displayLayout, ClipData initialData) {
- mPolicy.start(displayLayout, initialData);
+ public void prepare(DisplayLayout displayLayout, ClipData initialData,
+ InstanceId loggerSessionId) {
+ mPolicy.start(displayLayout, initialData, loggerSessionId);
mHasDropped = false;
mCurrentTarget = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java
index 64f7be5be813..73deea54e52f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java
@@ -86,7 +86,7 @@ public class DropOutlineDrawable extends Drawable {
public DropOutlineDrawable(Context context) {
super();
// TODO(b/169894807): Use corner specific radii and maybe lower radius for non-edge corners
- mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context.getResources());
+ mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mColor = context.getColor(R.color.drop_outline_background);
mMaxAlpha = Color.alpha(mColor);
// Initialize as hidden
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
new file mode 100644
index 000000000000..5fb3297aa6d3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -0,0 +1,147 @@
+/*
+ * 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.wm.shell.freeform;
+
+import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
+import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.provider.Settings;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.io.PrintWriter;
+
+/**
+ * {@link ShellTaskOrganizer.TaskListener} for {@link
+ * ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
+ */
+public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
+ private static final String TAG = "FreeformTaskListener";
+
+ private final SyncTransactionQueue mSyncQueue;
+
+ private final SparseArray<State> mTasks = new SparseArray<>();
+
+ private static class State {
+ RunningTaskInfo mTaskInfo;
+ SurfaceControl mLeash;
+ }
+
+ public FreeformTaskListener(SyncTransactionQueue syncQueue) {
+ mSyncQueue = syncQueue;
+ }
+
+ @Override
+ public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (mTasks.get(taskInfo.taskId) != null) {
+ throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId);
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Appeared: #%d",
+ taskInfo.taskId);
+ final State state = new State();
+ state.mTaskInfo = taskInfo;
+ state.mLeash = leash;
+ mTasks.put(taskInfo.taskId, state);
+
+ final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ mSyncQueue.runInSync(t -> {
+ Point taskPosition = taskInfo.positionInParent;
+ t.setPosition(leash, taskPosition.x, taskPosition.y)
+ .setWindowCrop(leash, taskBounds.width(), taskBounds.height())
+ .show(leash);
+ });
+ }
+
+ @Override
+ public void onTaskVanished(RunningTaskInfo taskInfo) {
+ State state = mTasks.get(taskInfo.taskId);
+ if (state == null) {
+ Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
+ return;
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d",
+ taskInfo.taskId);
+ mTasks.remove(taskInfo.taskId);
+ }
+
+ @Override
+ public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+ State state = mTasks.get(taskInfo.taskId);
+ if (state == null) {
+ throw new RuntimeException(
+ "Task info changed before appearing: #" + taskInfo.taskId);
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d",
+ taskInfo.taskId);
+ state.mTaskInfo = taskInfo;
+
+ final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ final SurfaceControl leash = state.mLeash;
+ mSyncQueue.runInSync(t -> {
+ Point taskPosition = taskInfo.positionInParent;
+ t.setPosition(leash, taskPosition.x, taskPosition.y)
+ .setWindowCrop(leash, taskBounds.width(), taskBounds.height())
+ .show(leash);
+ });
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + this);
+ pw.println(innerPrefix + mTasks.size() + " tasks");
+ }
+
+ @Override
+ public String toString() {
+ return TAG;
+ }
+
+ /**
+ * Checks if freeform support is enabled in system.
+ *
+ * @param context context used to check settings and package manager.
+ * @return {@code true} if freeform is enabled, {@code false} if not.
+ */
+ public static boolean isFreeformEnabled(Context context) {
+ return context.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
+ || Settings.Global.getInt(context.getContentResolver(),
+ DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
+ }
+
+ /**
+ * Creates {@link FreeformTaskListener} if freeform is enabled.
+ */
+ public static FreeformTaskListener create(Context context,
+ SyncTransactionQueue syncQueue) {
+ if (!isFreeformEnabled(context)) {
+ return null;
+ }
+
+ return new FreeformTaskListener(syncQueue);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 006730d333eb..3f17f2ba9394 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -14,25 +14,31 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.fullscreen;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.TaskInfo;
import android.graphics.Point;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.view.SurfaceControl;
import androidx.annotation.NonNull;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
+import java.util.Optional;
/**
* Organizes tasks presented in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN}.
@@ -43,13 +49,17 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
private final SyncTransactionQueue mSyncQueue;
private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>();
+ private final AnimatableTasksListener mAnimatableTasksListener = new AnimatableTasksListener();
+ private final FullscreenUnfoldController mFullscreenUnfoldController;
- public FullscreenTaskListener(SyncTransactionQueue syncQueue) {
+ public FullscreenTaskListener(SyncTransactionQueue syncQueue,
+ Optional<FullscreenUnfoldController> unfoldController) {
mSyncQueue = syncQueue;
+ mFullscreenUnfoldController = unfoldController.orElse(null);
}
@Override
- public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
if (mDataByTaskId.get(taskInfo.taskId) != null) {
throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId);
}
@@ -67,11 +77,16 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
t.setMatrix(leash, 1, 0, 0, 1);
t.show(leash);
});
+
+ mAnimatableTasksListener.onTaskAppeared(taskInfo);
}
@Override
- public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
+
+ mAnimatableTasksListener.onTaskInfoChanged(taskInfo);
+
final TaskData data = mDataByTaskId.get(taskInfo.taskId);
final Point positionInParent = taskInfo.positionInParent;
if (!positionInParent.equals(data.positionInParent)) {
@@ -83,12 +98,15 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
}
@Override
- public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ public void onTaskVanished(RunningTaskInfo taskInfo) {
if (mDataByTaskId.get(taskInfo.taskId) == null) {
Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
return;
}
+
+ mAnimatableTasksListener.onTaskVanished(taskInfo);
mDataByTaskId.remove(taskInfo.taskId);
+
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d",
taskInfo.taskId);
}
@@ -125,4 +143,65 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
this.positionInParent = positionInParent;
}
}
+
+ class AnimatableTasksListener {
+ private final SparseBooleanArray mTaskIds = new SparseBooleanArray();
+
+ public void onTaskAppeared(RunningTaskInfo taskInfo) {
+ final boolean isApplicable = isAnimatable(taskInfo);
+ if (isApplicable) {
+ mTaskIds.put(taskInfo.taskId, true);
+
+ if (mFullscreenUnfoldController != null) {
+ SurfaceControl leash = mDataByTaskId.get(taskInfo.taskId).surface;
+ mFullscreenUnfoldController.onTaskAppeared(taskInfo, leash);
+ }
+ }
+ }
+
+ public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+ final boolean isCurrentlyApplicable = mTaskIds.get(taskInfo.taskId);
+ final boolean isApplicable = isAnimatable(taskInfo);
+
+ if (isCurrentlyApplicable) {
+ if (isApplicable) {
+ // Still applicable, send update
+ if (mFullscreenUnfoldController != null) {
+ mFullscreenUnfoldController.onTaskInfoChanged(taskInfo);
+ }
+ } else {
+ // Became inapplicable
+ if (mFullscreenUnfoldController != null) {
+ mFullscreenUnfoldController.onTaskVanished(taskInfo);
+ }
+ mTaskIds.put(taskInfo.taskId, false);
+ }
+ } else {
+ if (isApplicable) {
+ // Became applicable
+ mTaskIds.put(taskInfo.taskId, true);
+
+ if (mFullscreenUnfoldController != null) {
+ SurfaceControl leash = mDataByTaskId.get(taskInfo.taskId).surface;
+ mFullscreenUnfoldController.onTaskAppeared(taskInfo, leash);
+ }
+ }
+ }
+ }
+
+ public void onTaskVanished(RunningTaskInfo taskInfo) {
+ final boolean isCurrentlyApplicable = mTaskIds.get(taskInfo.taskId);
+ if (isCurrentlyApplicable && mFullscreenUnfoldController != null) {
+ mFullscreenUnfoldController.onTaskVanished(taskInfo);
+ }
+ mTaskIds.put(taskInfo.taskId, false);
+ }
+
+ private boolean isAnimatable(TaskInfo taskInfo) {
+ // Filter all visible tasks that are not launcher tasks
+ // We do not animate launcher as it handles the animation by itself
+ return taskInfo != null && taskInfo.isVisible && taskInfo.getConfiguration()
+ .windowConfiguration.getActivityType() != ACTIVITY_TYPE_HOME;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java
new file mode 100644
index 000000000000..aa3868cfca84
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java
@@ -0,0 +1,229 @@
+/*
+ * 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.wm.shell.fullscreen;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.util.MathUtils.lerp;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.animation.RectEvaluator;
+import android.animation.TypeEvaluator;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.unfold.UnfoldBackgroundController;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Controls full screen app unfold transition: animating cropping window and scaling when
+ * folding or unfolding a foldable device.
+ */
+public final class FullscreenUnfoldController implements UnfoldListener,
+ OnInsetsChangedListener {
+
+ private static final float[] FLOAT_9 = new float[9];
+ private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
+
+ private static final float HORIZONTAL_START_MARGIN = 0.08f;
+ private static final float VERTICAL_START_MARGIN = 0.03f;
+ private static final float END_SCALE = 1f;
+ private static final float START_SCALE = END_SCALE - VERTICAL_START_MARGIN * 2;
+
+ private final Executor mExecutor;
+ private final ShellUnfoldProgressProvider mProgressProvider;
+ private final DisplayInsetsController mDisplayInsetsController;
+
+ private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
+ private final UnfoldBackgroundController mBackgroundController;
+
+ private InsetsSource mTaskbarInsetsSource;
+
+ private final float mWindowCornerRadiusPx;
+ private final float mExpandedTaskBarHeight;
+
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+ public FullscreenUnfoldController(
+ @NonNull Context context,
+ @NonNull Executor executor,
+ @NonNull UnfoldBackgroundController backgroundController,
+ @NonNull ShellUnfoldProgressProvider progressProvider,
+ @NonNull DisplayInsetsController displayInsetsController
+ ) {
+ mExecutor = executor;
+ mProgressProvider = progressProvider;
+ mDisplayInsetsController = displayInsetsController;
+ mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.taskbar_frame_height);
+ mBackgroundController = backgroundController;
+ }
+
+ /**
+ * Initializes the controller
+ */
+ public void init() {
+ mProgressProvider.addListener(mExecutor, this);
+ mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
+ }
+
+ @Override
+ public void onStateChangeProgress(float progress) {
+ if (mAnimationContextByTaskId.size() == 0) return;
+
+ mBackgroundController.ensureBackground(mTransaction);
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+
+ context.mCurrentCropRect.set(RECT_EVALUATOR
+ .evaluate(progress, context.mStartCropRect, context.mEndCropRect));
+
+ float scale = lerp(START_SCALE, END_SCALE, progress);
+ context.mMatrix.setScale(scale, scale, context.mCurrentCropRect.exactCenterX(),
+ context.mCurrentCropRect.exactCenterY());
+
+ mTransaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
+ .setMatrix(context.mLeash, context.mMatrix, FLOAT_9)
+ .setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
+ }
+
+ mTransaction.apply();
+ }
+
+ @Override
+ public void onStateChangeFinished() {
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ resetSurface(context);
+ }
+
+ mBackgroundController.removeBackground(mTransaction);
+ mTransaction.apply();
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ context.update(mTaskbarInsetsSource, context.mTaskInfo);
+ }
+ }
+
+ /**
+ * Called when a new matching task appeared
+ */
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource,
+ taskInfo);
+ mAnimationContextByTaskId.put(taskInfo.taskId, animationContext);
+ }
+
+ /**
+ * Called when matching task changed
+ */
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
+ if (animationContext != null) {
+ animationContext.update(mTaskbarInsetsSource, taskInfo);
+ }
+ }
+
+ /**
+ * Called when matching task vanished
+ */
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
+ if (animationContext != null) {
+ // PiP task has its own cleanup path, ignore surface reset to avoid conflict.
+ if (taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED) {
+ resetSurface(animationContext);
+ }
+ mAnimationContextByTaskId.remove(taskInfo.taskId);
+ }
+
+ if (mAnimationContextByTaskId.size() == 0) {
+ mBackgroundController.removeBackground(mTransaction);
+ }
+
+ mTransaction.apply();
+ }
+
+ private void resetSurface(AnimationContext context) {
+ mTransaction
+ .setWindowCrop(context.mLeash, null)
+ .setCornerRadius(context.mLeash, 0.0F)
+ .setMatrix(context.mLeash, 1.0F, 0.0F, 0.0F, 1.0F)
+ .setPosition(context.mLeash,
+ (float) context.mTaskInfo.positionInParent.x,
+ (float) context.mTaskInfo.positionInParent.y);
+ }
+
+ private class AnimationContext {
+ final SurfaceControl mLeash;
+ final Rect mStartCropRect = new Rect();
+ final Rect mEndCropRect = new Rect();
+ final Rect mCurrentCropRect = new Rect();
+ final Matrix mMatrix = new Matrix();
+
+ TaskInfo mTaskInfo;
+
+ private AnimationContext(SurfaceControl leash,
+ InsetsSource taskBarInsetsSource,
+ TaskInfo taskInfo) {
+ this.mLeash = leash;
+ update(taskBarInsetsSource, taskInfo);
+ }
+
+ private void update(InsetsSource taskBarInsetsSource, TaskInfo taskInfo) {
+ mTaskInfo = taskInfo;
+ mStartCropRect.set(mTaskInfo.getConfiguration().windowConfiguration.getBounds());
+
+ if (taskBarInsetsSource != null) {
+ // Only insets the cropping window with task bar when it's expanded
+ if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ mStartCropRect.inset(taskBarInsetsSource
+ .calculateVisibleInsets(mStartCropRect));
+ }
+ }
+
+ mEndCropRect.set(mStartCropRect);
+
+ int horizontalMargin = (int) (mEndCropRect.width() * HORIZONTAL_START_MARGIN);
+ mStartCropRect.left = mEndCropRect.left + horizontalMargin;
+ mStartCropRect.right = mEndCropRect.right - horizontalMargin;
+ int verticalMargin = (int) (mEndCropRect.height() * VERTICAL_START_MARGIN);
+ mStartCropRect.top = mEndCropRect.top + verticalMargin;
+ mStartCropRect.bottom = mEndCropRect.bottom - verticalMargin;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
index 75a1ddeccb22..3f7d78dda037 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
@@ -39,7 +39,7 @@ import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.R;
+import com.android.internal.policy.SystemBarUtils;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
@@ -307,12 +307,9 @@ class HideDisplayCutoutOrganizer extends DisplayAreaOrganizer {
t.apply();
}
- private int getStatusBarHeight() {
- final boolean isLandscape =
- mIsDefaultPortrait ? isDisplaySizeFlipped() : !isDisplaySizeFlipped();
- return mContext.getResources().getDimensionPixelSize(
- isLandscape ? R.dimen.status_bar_height_landscape
- : R.dimen.status_bar_height_portrait);
+ @VisibleForTesting
+ int getStatusBarHeight() {
+ return SystemBarUtils.getStatusBarHeight(mContext);
}
void dump(@NonNull PrintWriter pw) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java
index 362b40f33e89..067f80800ed5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java
@@ -20,6 +20,8 @@ import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
import static android.view.WindowManager.DOCKED_RIGHT;
+import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR;
+import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
import static com.android.wm.shell.common.split.DividerView.TOUCH_ANIMATION_DURATION;
import static com.android.wm.shell.common.split.DividerView.TOUCH_RELEASE_ANIMATION_DURATION;
@@ -100,10 +102,6 @@ public class DividerView extends FrameLayout implements OnTouchListener,
private static final float MINIMIZE_DOCK_SCALE = 0f;
private static final float ADJUSTED_FOR_IME_SCALE = 0.5f;
- private static final PathInterpolator SLOWDOWN_INTERPOLATOR =
- new PathInterpolator(0.5f, 1f, 0.5f, 1f);
- private static final PathInterpolator DIM_INTERPOLATOR =
- new PathInterpolator(.23f, .87f, .52f, -0.11f);
private static final Interpolator IME_ADJUST_INTERPOLATOR =
new PathInterpolator(0.2f, 0f, 0.1f, 1f);
@@ -460,6 +458,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
private void stopDragging() {
mHandle.setTouching(false, true /* animate */);
mWindowManager.setSlippery(true);
+ mWindowManagerProxy.setResizing(false);
releaseBackground();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java
index 40244fbb4503..f201634d3d4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java
@@ -62,6 +62,7 @@ public class LegacySplitDisplayLayout {
Rect mSecondary = null;
Rect mAdjustedPrimary = null;
Rect mAdjustedSecondary = null;
+ final Rect mTmpBounds = new Rect();
public LegacySplitDisplayLayout(Context ctx, DisplayLayout dl,
LegacySplitScreenTaskListener taskTiles) {
@@ -136,31 +137,41 @@ public class LegacySplitDisplayLayout {
return mMinimizedSnapAlgorithm;
}
- void resizeSplits(int position) {
+ /**
+ * Resize primary bounds and secondary bounds by divider position.
+ *
+ * @param position divider position.
+ * @return true if calculated bounds changed.
+ */
+ boolean resizeSplits(int position) {
mPrimary = mPrimary == null ? new Rect() : mPrimary;
mSecondary = mSecondary == null ? new Rect() : mSecondary;
- calcSplitBounds(position, mPrimary, mSecondary);
- }
-
- void resizeSplits(int position, WindowContainerTransaction t) {
- resizeSplits(position);
- t.setBounds(mTiles.mPrimary.token, mPrimary);
- t.setBounds(mTiles.mSecondary.token, mSecondary);
-
- t.setSmallestScreenWidthDp(mTiles.mPrimary.token,
- getSmallestWidthDpForBounds(mContext, mDisplayLayout, mPrimary));
- t.setSmallestScreenWidthDp(mTiles.mSecondary.token,
- getSmallestWidthDpForBounds(mContext, mDisplayLayout, mSecondary));
- }
-
- void calcSplitBounds(int position, @NonNull Rect outPrimary, @NonNull Rect outSecondary) {
int dockSide = getPrimarySplitSide();
- DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outPrimary,
+ boolean boundsChanged;
+
+ mTmpBounds.set(mPrimary);
+ DockedDividerUtils.calculateBoundsForPosition(position, dockSide, mPrimary,
mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize);
+ boundsChanged = !mPrimary.equals(mTmpBounds);
+ mTmpBounds.set(mSecondary);
DockedDividerUtils.calculateBoundsForPosition(position,
- DockedDividerUtils.invertDockSide(dockSide), outSecondary, mDisplayLayout.width(),
+ DockedDividerUtils.invertDockSide(dockSide), mSecondary, mDisplayLayout.width(),
mDisplayLayout.height(), mDividerSize);
+ boundsChanged |= !mSecondary.equals(mTmpBounds);
+ return boundsChanged;
+ }
+
+ void resizeSplits(int position, WindowContainerTransaction t) {
+ if (resizeSplits(position)) {
+ t.setBounds(mTiles.mPrimary.token, mPrimary);
+ t.setBounds(mTiles.mSecondary.token, mSecondary);
+
+ t.setSmallestScreenWidthDp(mTiles.mPrimary.token,
+ getSmallestWidthDpForBounds(mContext, mDisplayLayout, mPrimary));
+ t.setSmallestScreenWidthDp(mTiles.mSecondary.token,
+ getSmallestWidthDpForBounds(mContext, mDisplayLayout, mSecondary));
+ }
}
Rect calcResizableMinimizedHomeStackBounds() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java
index d9409ec2dc17..b1fa2ac25fe7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java
@@ -204,7 +204,8 @@ public class LegacySplitScreenTransitions implements Transitions.TransitionHandl
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (transition != mPendingDismiss && transition != mPendingEnter) {
// If we're not in split-mode, just abort
@@ -239,12 +240,12 @@ public class LegacySplitScreenTransitions implements Transitions.TransitionHandl
if (change.getParent() != null) {
// This is probably reparented, so we want the parent to be immediately visible
final TransitionInfo.Change parentChange = info.getChange(change.getParent());
- t.show(parentChange.getLeash());
- t.setAlpha(parentChange.getLeash(), 1.f);
+ startTransaction.show(parentChange.getLeash());
+ startTransaction.setAlpha(parentChange.getLeash(), 1.f);
// and then animate this layer outside the parent (since, for example, this is
// the home task animating from fullscreen to part-screen).
- t.reparent(leash, info.getRootLeash());
- t.setLayer(leash, info.getChanges().size() - i);
+ startTransaction.reparent(leash, info.getRootLeash());
+ startTransaction.setLayer(leash, info.getChanges().size() - i);
// build the finish reparent/reposition
mFinishTransaction.reparent(leash, parentChange.getLeash());
mFinishTransaction.setPosition(leash,
@@ -271,12 +272,12 @@ public class LegacySplitScreenTransitions implements Transitions.TransitionHandl
if (transition == mPendingEnter
&& mListener.mPrimary.token.equals(change.getContainer())
|| mListener.mSecondary.token.equals(change.getContainer())) {
- t.setWindowCrop(leash, change.getStartAbsBounds().width(),
+ startTransaction.setWindowCrop(leash, change.getStartAbsBounds().width(),
change.getStartAbsBounds().height());
if (mListener.mPrimary.token.equals(change.getContainer())) {
// Move layer to top since we want it above the oversized home task during
// animation even though home task is on top in hierarchy.
- t.setLayer(leash, info.getChanges().size() + 1);
+ startTransaction.setLayer(leash, info.getChanges().size() + 1);
}
}
boolean isOpening = Transitions.isOpeningType(info.getType());
@@ -289,7 +290,7 @@ public class LegacySplitScreenTransitions implements Transitions.TransitionHandl
// Dismissing via snap-to-top/bottom means that the dismissed task is already
// not-visible (usually cropped to oblivion) so immediately set its alpha to 0
// and don't animate it so it doesn't pop-in when reparented.
- t.setAlpha(leash, 0.f);
+ startTransaction.setAlpha(leash, 0.f);
} else {
startExampleAnimation(leash, false /* show */);
}
@@ -311,7 +312,7 @@ public class LegacySplitScreenTransitions implements Transitions.TransitionHandl
}
mSplitScreen.finishEnterSplitTransition(homeIsVisible);
}
- t.apply();
+ startTransaction.apply();
onFinish();
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index e511bffad247..900743712227 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -43,6 +43,7 @@ import android.util.Slog;
import android.view.Surface;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -64,7 +65,8 @@ import java.io.PrintWriter;
/**
* Manages and manipulates the one handed states, transitions, and gesture for phones.
*/
-public class OneHandedController implements RemoteCallable<OneHandedController> {
+public class OneHandedController implements RemoteCallable<OneHandedController>,
+ DisplayChangeController.OnDisplayChangingListener {
private static final String TAG = "OneHandedController";
private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
@@ -106,19 +108,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;
private OneHandedUiEventLogger mOneHandedUiEventLogger;
- /**
- * Handle rotation based on OnDisplayChangingListener callback
- */
- private final DisplayChangeController.OnDisplayChangingListener mRotationController =
- (display, fromRotation, toRotation, wct) -> {
- if (!isInitialized()) {
- return;
- }
- mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation, wct);
- mOneHandedUiEventLogger.writeEvent(
- OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_ROTATION_OUT);
- };
-
private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener =
new DisplayController.OnDisplaysChangedListener() {
@Override
@@ -296,7 +285,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
getObserver(this::onSwipeToNotificationEnabledChanged);
mShortcutEnabledObserver = getObserver(this::onShortcutEnabledChanged);
- mDisplayController.addDisplayChangingController(mRotationController);
+ mDisplayController.addDisplayChangingController(this);
setupCallback();
registerSettingObservers(mUserId);
setupTimeoutListener();
@@ -548,6 +537,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
mContext.getContentResolver(), mUserId);
setSwipeToNotificationEnabled(enabled);
+ notifyShortcutStateChanged(mState.getState());
mOneHandedUiEventLogger.writeEvent(enabled
? OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_SHOW_NOTIFICATION_ENABLED_ON
@@ -699,6 +689,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
pw.println(mUserId);
pw.print(innerPrefix + "isShortcutEnabled=");
pw.println(isShortcutEnabled());
+ pw.print(innerPrefix + "mIsSwipeToNotificationEnabled=");
+ pw.println(mIsSwipeToNotificationEnabled);
if (mBackgroundPanelOrganizer != null) {
mBackgroundPanelOrganizer.dump(pw);
@@ -745,6 +737,27 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
}
/**
+ * Handles rotation based on OnDisplayChangingListener callback
+ */
+ @Override
+ public void onRotateDisplay(int displayId, int fromRotation, int toRotation,
+ WindowContainerTransaction wct) {
+ if (!isInitialized()) {
+ return;
+ }
+
+ if (!mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(mContext.getContentResolver(),
+ mUserId) || mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+ mContext.getContentResolver(), mUserId)) {
+ return;
+ }
+
+ mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation, wct);
+ mOneHandedUiEventLogger.writeEvent(
+ OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_ROTATION_OUT);
+ }
+
+ /**
* The interface for calls from outside the Shell, within the host process.
*/
@ExternalThread
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index c2bbd9e99bac..1b2f4768110b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -16,8 +16,6 @@
package com.android.wm.shell.onehanded;
-import static android.os.UserHandle.myUserId;
-
import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_EXIT;
import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_TRIGGER;
@@ -186,20 +184,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
if (mDisplayLayout.rotation() == toRotation) {
return;
}
-
- if (!mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(context.getContentResolver(),
- myUserId())) {
- return;
- }
-
mDisplayLayout.rotateTo(context.getResources(), toRotation);
updateDisplayBounds();
-
- if (mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
- context.getContentResolver(), myUserId())) {
- // If current settings is swipe notification, skip finishOffset.
- return;
- }
finishOffset(0, TRANSITION_DIRECTION_EXIT);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
index ff333c8c659d..2cb7d1b0fa0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
@@ -244,6 +244,8 @@ public final class OneHandedSettingsUtil {
pw.println(TAG);
pw.print(innerPrefix + "isOneHandedModeEnable=");
pw.println(getSettingsOneHandedModeEnabled(resolver, userId));
+ pw.print(innerPrefix + "isSwipeToNotificationEnabled=");
+ pw.println(getSettingsSwipeToNotificationEnabled(resolver, userId));
pw.print(innerPrefix + "oneHandedTimeOut=");
pw.println(getSettingsOneHandedModeTimeout(resolver, userId));
pw.print(innerPrefix + "tapsAppToExit=");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 200af7415eb1..05111a3d4436 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -38,6 +38,7 @@ import android.view.SurfaceSession;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.transition.Transitions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -617,14 +618,28 @@ public class PipAnimationController {
setCurrentValue(bounds);
final Rect insets = computeInsets(fraction);
final float degree, x, y;
- if (rotationDelta == ROTATION_90) {
- degree = 90 * fraction;
- x = fraction * (end.right - start.left) + start.left;
- y = fraction * (end.top - start.top) + start.top;
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ if (rotationDelta == ROTATION_90) {
+ degree = 90 * (1 - fraction);
+ x = fraction * (end.left - start.left)
+ + start.left + start.right * (1 - fraction);
+ y = fraction * (end.top - start.top) + start.top;
+ } else {
+ degree = -90 * (1 - fraction);
+ x = fraction * (end.left - start.left) + start.left;
+ y = fraction * (end.top - start.top)
+ + start.top + start.bottom * (1 - fraction);
+ }
} else {
- degree = -90 * fraction;
- x = fraction * (end.left - start.left) + start.left;
- y = fraction * (end.bottom - start.top) + start.top;
+ if (rotationDelta == ROTATION_90) {
+ degree = 90 * fraction;
+ x = fraction * (end.right - start.left) + start.left;
+ y = fraction * (end.top - start.top) + start.top;
+ } else {
+ degree = -90 * fraction;
+ x = fraction * (end.left - start.left) + start.left;
+ y = fraction * (end.bottom - start.top) + start.top;
+ }
}
getSurfaceTransactionHelper()
.rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index 728794de0865..180e3fb48c9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -23,6 +23,7 @@ import android.graphics.RectF;
import android.view.SurfaceControl;
import com.android.wm.shell.R;
+import com.android.wm.shell.transition.Transitions;
/**
* Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition.
@@ -137,7 +138,8 @@ public class PipSurfaceTransactionHelper {
// destination are different.
final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH;
final Rect crop = mTmpDestinationRect;
- crop.set(0, 0, destW, destH);
+ crop.set(0, 0, Transitions.ENABLE_SHELL_TRANSITIONS ? destH
+ : destW, Transitions.ENABLE_SHELL_TRANSITIONS ? destW : destH);
// Inverse scale for crop to fit in screen coordinates.
crop.scale(1 / scale);
crop.offset(insets.left, insets.top);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f2bad6caf3e8..b6e5804a64dd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -114,38 +114,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
*/
private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 500;
- // Not a complete set of states but serves what we want right now.
- private enum State {
- UNDEFINED(0),
- TASK_APPEARED(1),
- ENTRY_SCHEDULED(2),
- ENTERING_PIP(3),
- ENTERED_PIP(4),
- EXITING_PIP(5);
-
- private final int mStateValue;
-
- State(int value) {
- mStateValue = value;
- }
-
- private boolean isInPip() {
- return mStateValue >= TASK_APPEARED.mStateValue
- && mStateValue != EXITING_PIP.mStateValue;
- }
-
- /**
- * Resize request can be initiated in other component, ignore if we are no longer in PIP,
- * still waiting for animation or we're exiting from it.
- *
- * @return {@code true} if the resize request should be blocked/ignored.
- */
- private boolean shouldBlockResizeRequest() {
- return mStateValue < ENTERING_PIP.mStateValue
- || mStateValue == EXITING_PIP.mStateValue;
- }
- }
-
private final Context mContext;
private final SyncTransactionQueue mSyncTransactionQueue;
private final PipBoundsState mPipBoundsState;
@@ -169,11 +137,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
public void onPipAnimationStart(TaskInfo taskInfo,
PipAnimationController.PipTransitionAnimator animator) {
final int direction = animator.getTransitionDirection();
- if (direction == TRANSITION_DIRECTION_TO_PIP) {
- // TODO (b//169221267): Add jank listener for transactions without buffer updates.
- //InteractionJankMonitor.getInstance().begin(
- // InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP, 2000);
- }
sendOnPipTransitionStarted(direction);
}
@@ -201,7 +164,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
final boolean isExitPipDirection = isOutPipDirection(direction)
|| isRemovePipDirection(direction);
- if (mState != State.EXITING_PIP || isExitPipDirection) {
+ if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP
+ || isExitPipDirection) {
// Finish resize as long as we're not exiting PIP, or, if we are, only if this is
// the end of an exit PIP animation.
// This is necessary in case there was a resize animation ongoing when exit PIP
@@ -244,7 +208,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private ActivityManager.RunningTaskInfo mDeferredTaskInfo;
private WindowContainerToken mToken;
private SurfaceControl mLeash;
- private State mState = State.UNDEFINED;
+ private PipTransitionState mPipTransitionState;
private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
private long mLastOneShotAlphaAnimationTime;
private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
@@ -274,21 +238,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private @Surface.Rotation int mCurrentRotation;
/**
- * If set to {@code true}, no entering PiP transition would be kicked off and most likely
- * it's due to the fact that Launcher is handling the transition directly when swiping
- * auto PiP-able Activity to home.
- * See also {@link #startSwipePipToHome(ComponentName, ActivityInfo, PictureInPictureParams)}.
- */
- private boolean mInSwipePipToHomeTransition;
-
- /**
* An optional overlay used to mask content changing between an app in/out of PiP, only set if
- * {@link #mInSwipePipToHomeTransition} is true.
+ * {@link PipTransitionState#getInSwipePipToHomeTransition()} is true.
*/
private SurfaceControl mSwipePipToHomeOverlay;
public PipTaskOrganizer(Context context,
@NonNull SyncTransactionQueue syncTransactionQueue,
+ @NonNull PipTransitionState pipTransitionState,
@NonNull PipBoundsState pipBoundsState,
@NonNull PipBoundsAlgorithm boundsHandler,
@NonNull PipMenuController pipMenuController,
@@ -302,6 +259,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@ShellMainThread ShellExecutor mainExecutor) {
mContext = context;
mSyncTransactionQueue = syncTransactionQueue;
+ mPipTransitionState = pipTransitionState;
mPipBoundsState = pipBoundsState;
mPipBoundsAlgorithm = boundsHandler;
mPipMenuController = pipMenuController;
@@ -324,6 +282,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mMainExecutor.execute(() -> {
mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
});
+ mPipTransitionController.setPipOrganizer(this);
displayController.addDisplayWindowListener(this);
}
@@ -337,14 +296,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
public boolean isInPip() {
- return mState.isInPip();
+ return mPipTransitionState.isInPip();
}
/**
* Returns whether the entry animation is waiting to be started.
*/
public boolean isEntryScheduled() {
- return mState == State.ENTRY_SCHEDULED;
+ return mPipTransitionState.getTransitionState() == PipTransitionState.ENTRY_SCHEDULED;
}
/**
@@ -372,7 +331,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
*/
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams) {
- mInSwipePipToHomeTransition = true;
+ mPipTransitionState.setInSwipePipToHomeTransition(true);
sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo);
return mPipBoundsAlgorithm.getEntryDestinationBounds();
@@ -385,12 +344,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds,
SurfaceControl overlay) {
// do nothing if there is no startSwipePipToHome being called before
- if (mInSwipePipToHomeTransition) {
+ if (mPipTransitionState.getInSwipePipToHomeTransition()) {
mPipBoundsState.setBounds(destinationBounds);
mSwipePipToHomeOverlay = overlay;
}
}
+ public ActivityManager.RunningTaskInfo getTaskInfo() {
+ return mTaskInfo;
+ }
+
public SurfaceControl getSurfaceControl() {
return mLeash;
}
@@ -412,9 +375,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
* @param animationDurationMs duration in millisecond for the exiting PiP transition
*/
public void exitPip(int animationDurationMs) {
- if (!mState.isInPip() || mState == State.EXITING_PIP || mToken == null) {
+ if (!mPipTransitionState.isInPip()
+ || mPipTransitionState.getTransitionState() == PipTransitionState.EXITING_PIP
+ || mToken == null) {
Log.wtf(TAG, "Not allowed to exitPip in current state"
- + " mState=" + mState + " mToken=" + mToken);
+ + " mState=" + mPipTransitionState.getTransitionState() + " mToken=" + mToken);
return;
}
@@ -438,7 +403,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
wct.setBoundsChangeTransaction(mToken, tx);
// Set the exiting state first so if there is fixed rotation later, the running animation
// won't be interrupted by alpha animation for existing PiP.
- mState = State.EXITING_PIP;
+ mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP);
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mPipTransitionController.startTransition(destinationBounds, wct);
+ return;
+ }
mSyncTransactionQueue.queue(wct);
mSyncTransactionQueue.runInSync(t -> {
// Make sure to grab the latest source hint rect as it could have been
@@ -476,9 +446,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
* Removes PiP immediately.
*/
public void removePip() {
- if (!mState.isInPip() || mToken == null) {
+ if (!mPipTransitionState.isInPip() || mToken == null) {
Log.wtf(TAG, "Not allowed to removePip in current state"
- + " mState=" + mState + " mToken=" + mToken);
+ + " mState=" + mPipTransitionState.getTransitionState() + " mToken=" + mToken);
return;
}
@@ -492,10 +462,19 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
animator.setDuration(mExitAnimationDuration);
animator.setInterpolator(Interpolators.ALPHA_OUT);
animator.start();
- mState = State.EXITING_PIP;
+ mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP);
}
private void removePipImmediately() {
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(mToken, null);
+ wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+ wct.reorder(mToken, false);
+ mPipTransitionController.startTransition(null, wct);
+ return;
+ }
+
try {
// Reset the task bounds first to ensure the activity configuration is reset as well
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -514,7 +493,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
Objects.requireNonNull(info, "Requires RunningTaskInfo");
mTaskInfo = info;
mToken = mTaskInfo.token;
- mState = State.TASK_APPEARED;
+ mPipTransitionState.setTransitionState(PipTransitionState.TASK_APPEARED);
mLeash = leash;
mPictureInPictureParams = mTaskInfo.pictureInPictureParams;
setBoundsStateForEntry(mTaskInfo.topActivity, mPictureInPictureParams,
@@ -530,7 +509,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mOnDisplayIdChangeCallback.accept(info.displayId);
}
- if (mInSwipePipToHomeTransition) {
+ if (mPipTransitionState.getInSwipePipToHomeTransition()) {
if (!mWaitForFixedRotation) {
onEndOfSwipePipToHomeTransition();
} else {
@@ -557,6 +536,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
mPipMenuController.attach(mLeash);
+ } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ mOneShotAnimationType = ANIM_TYPE_BOUNDS;
}
return;
}
@@ -568,7 +549,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
scheduleAnimateResizePip(currentBounds, destinationBounds, 0 /* startingAngle */,
sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration,
null /* updateBoundsCallback */);
- mState = State.ENTERING_PIP;
+ mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
} else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration);
mOneShotAnimationType = ANIM_TYPE_BOUNDS;
@@ -595,7 +576,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
animateResizePip(currentBounds, destinationBounds, sourceHintRect,
TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration, 0 /* startingAngle */);
- mState = State.ENTERING_PIP;
+ mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
}
/**
@@ -620,7 +601,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSurfaceControlTransactionFactory.getTransaction();
tx.setAlpha(mLeash, 0f);
tx.apply();
- mState = State.ENTRY_SCHEDULED;
+ mPipTransitionState.setTransitionState(PipTransitionState.ENTRY_SCHEDULED);
applyEnterPipSyncTransaction(destinationBounds, () -> {
mPipAnimationController
.getAnimator(mTaskInfo, mLeash, destinationBounds, 0f, 1f)
@@ -631,11 +612,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
.start();
// mState is set right after the animation is kicked off to block any resize
// requests such as offsetPip that may have been called prior to the transition.
- mState = State.ENTERING_PIP;
+ mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
}, null /* boundsChangeTransaction */);
}
private void onEndOfSwipePipToHomeTransition() {
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mSwipePipToHomeOverlay = null;
+ return;
+ }
+
final Rect destinationBounds = mPipBoundsState.getBounds();
final SurfaceControl swipeToHomeOverlay = mSwipePipToHomeOverlay;
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
@@ -655,7 +641,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
null /* callback */, false /* withStartDelay */);
}
}, tx);
- mInSwipePipToHomeTransition = false;
+ mPipTransitionState.setInSwipePipToHomeTransition(false);
mSwipePipToHomeOverlay = null;
}
@@ -679,7 +665,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private void sendOnPipTransitionStarted(
@PipAnimationController.TransitionDirection int direction) {
if (direction == TRANSITION_DIRECTION_TO_PIP) {
- mState = State.ENTERING_PIP;
+ mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
}
mPipTransitionController.sendOnPipTransitionStarted(direction);
}
@@ -688,7 +674,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
void sendOnPipTransitionFinished(
@PipAnimationController.TransitionDirection int direction) {
if (direction == TRANSITION_DIRECTION_TO_PIP) {
- mState = State.ENTERED_PIP;
+ mPipTransitionState.setTransitionState(PipTransitionState.ENTERED_PIP);
}
mPipTransitionController.sendOnPipTransitionFinished(direction);
// Apply the deferred RunningTaskInfo if applicable after all proper callbacks are sent.
@@ -713,7 +699,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
*/
@Override
public void onTaskVanished(ActivityManager.RunningTaskInfo info) {
- if (mState == State.UNDEFINED) {
+ if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
return;
}
final WindowContainerToken token = info.token;
@@ -723,9 +709,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return;
}
clearWaitForFixedRotation();
- mInSwipePipToHomeTransition = false;
+ mPipTransitionState.setInSwipePipToHomeTransition(false);
mPictureInPictureParams = null;
- mState = State.UNDEFINED;
+ mPipTransitionState.setTransitionState(PipTransitionState.UNDEFINED);
// Re-set the PIP bounds to none.
mPipBoundsState.setBounds(new Rect());
mPipUiEventLoggerLogger.setTaskInfo(null);
@@ -735,6 +721,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY);
}
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mPipTransitionController.forceFinishTransition();
+ }
final PipAnimationController.PipTransitionAnimator<?> animator =
mPipAnimationController.getCurrentAnimator();
if (animator != null) {
@@ -750,8 +739,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@Override
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken");
- if (mState != State.ENTERED_PIP && mState != State.EXITING_PIP) {
- Log.d(TAG, "Defer onTaskInfoChange in current state: " + mState);
+ if (mPipTransitionState.getTransitionState() != PipTransitionState.ENTERED_PIP
+ && mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP) {
+ Log.d(TAG, "Defer onTaskInfoChange in current state: "
+ + mPipTransitionState.getTransitionState());
// Defer applying PiP parameters if the task is entering PiP to avoid disturbing
// the animation.
mDeferredTaskInfo = info;
@@ -784,7 +775,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mNextRotation = newRotation;
mWaitForFixedRotation = true;
- if (mState.isInPip()) {
+ if (mPipTransitionState.isInPip()) {
// Fade out the existing PiP to avoid jump cut during seamless rotation.
fadeExistingPip(false /* show */);
}
@@ -795,17 +786,19 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (!mWaitForFixedRotation) {
return;
}
- if (mState == State.TASK_APPEARED) {
- if (mInSwipePipToHomeTransition) {
+ if (mPipTransitionState.getTransitionState() == PipTransitionState.TASK_APPEARED) {
+ if (mPipTransitionState.getInSwipePipToHomeTransition()) {
onEndOfSwipePipToHomeTransition();
} else {
// Schedule a regular animation to ensure all the callbacks are still being sent.
enterPipWithAlphaAnimation(mPipBoundsAlgorithm.getEntryDestinationBounds(),
mEnterAnimationDuration);
}
- } else if (mState == State.ENTERED_PIP && mHasFadeOut) {
+ } else if (mPipTransitionState.getTransitionState() == PipTransitionState.ENTERED_PIP
+ && mHasFadeOut) {
fadeExistingPip(true /* show */);
- } else if (mState == State.ENTERING_PIP && mDeferredAnimEndTransaction != null) {
+ } else if (mPipTransitionState.getTransitionState() == PipTransitionState.ENTERING_PIP
+ && mDeferredAnimEndTransaction != null) {
final PipAnimationController.PipTransitionAnimator<?> animator =
mPipAnimationController.getCurrentAnimator();
final Rect destinationBounds = animator.getDestinationBounds();
@@ -859,13 +852,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// note that this can be called when swipe-to-home or fixed-rotation is happening.
// Skip this entirely if that's the case.
final boolean waitForFixedRotationOnEnteringPip = mWaitForFixedRotation
- && (mState != State.ENTERED_PIP);
- if ((mInSwipePipToHomeTransition || waitForFixedRotationOnEnteringPip) && fromRotation) {
+ && (mPipTransitionState.getTransitionState() != PipTransitionState.ENTERED_PIP);
+ if ((mPipTransitionState.getInSwipePipToHomeTransition()
+ || waitForFixedRotationOnEnteringPip) && fromRotation) {
if (DEBUG) {
Log.d(TAG, "Skip onMovementBoundsChanged on rotation change"
- + " mInSwipePipToHomeTransition=" + mInSwipePipToHomeTransition
+ + " InSwipePipToHomeTransition="
+ + mPipTransitionState.getInSwipePipToHomeTransition()
+ " mWaitForFixedRotation=" + mWaitForFixedRotation
- + " mState=" + mState);
+ + " getTransitionState=" + mPipTransitionState.getTransitionState());
}
return;
}
@@ -873,7 +868,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mPipAnimationController.getCurrentAnimator();
if (animator == null || !animator.isRunning()
|| animator.getTransitionDirection() != TRANSITION_DIRECTION_TO_PIP) {
- final boolean rotatingPip = mState.isInPip() && fromRotation;
+ final boolean rotatingPip = mPipTransitionState.isInPip() && fromRotation;
if (rotatingPip && mWaitForFixedRotation && mHasFadeOut) {
// The position will be used by fade-in animation when the fixed rotation is done.
mPipBoundsState.setBounds(destinationBoundsOut);
@@ -1006,7 +1001,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
Rect currentBounds, Rect destinationBounds, float startingAngle, Rect sourceHintRect,
@PipAnimationController.TransitionDirection int direction, int durationMs,
Consumer<Rect> updateBoundsCallback) {
- if (!mState.isInPip()) {
+ if (!mPipTransitionState.isInPip()) {
// TODO: tend to use shouldBlockResizeRequest here as well but need to consider
// the fact that when in exitPip, scheduleAnimateResizePip is executed in the window
// container transaction callback and we want to set the mState immediately.
@@ -1036,7 +1031,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper
.crop(tx, mLeash, toBounds)
- .round(tx, mLeash, mState.isInPip());
+ .round(tx, mLeash, mPipTransitionState.isInPip());
if (mPipMenuController.isMenuVisible()) {
mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
} else {
@@ -1114,7 +1109,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
public void scheduleFinishResizePip(Rect destinationBounds,
@PipAnimationController.TransitionDirection int direction,
Consumer<Rect> updateBoundsCallback) {
- if (mState.shouldBlockResizeRequest()) {
+ if (mPipTransitionState.shouldBlockResizeRequest()) {
return;
}
@@ -1131,7 +1126,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSurfaceTransactionHelper
.crop(tx, mLeash, destinationBounds)
.resetScale(tx, mLeash, destinationBounds)
- .round(tx, mLeash, mState.isInPip());
+ .round(tx, mLeash, mPipTransitionState.isInPip());
return tx;
}
@@ -1140,7 +1135,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
*/
public void scheduleOffsetPip(Rect originalBounds, int offset, int duration,
Consumer<Rect> updateBoundsCallback) {
- if (mState.shouldBlockResizeRequest()) {
+ if (mPipTransitionState.shouldBlockResizeRequest()) {
return;
}
if (mWaitForFixedRotation) {
@@ -1297,13 +1292,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
Rect baseBounds = direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
? mPipBoundsState.getBounds() : currentBounds;
+ final boolean existingAnimatorRunning = mPipAnimationController.getCurrentAnimator() != null
+ && mPipAnimationController.getCurrentAnimator().isRunning();
final PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseBounds, currentBounds, destinationBounds,
sourceHintRect, direction, startingAngle, rotationDelta);
animator.setTransitionDirection(direction)
- .setPipAnimationCallback(mPipAnimationCallback)
.setPipTransactionHandler(mPipTransactionHandler)
.setDuration(durationMs);
+ if (!existingAnimatorRunning) {
+ animator.setPipAnimationCallback(mPipAnimationCallback);
+ }
if (isInPipDirection(direction)) {
// Similar to auto-enter-pip transition, we use content overlay when there is no
// source rect hint to enter PiP use bounds animation.
@@ -1384,7 +1383,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
final ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0.0f);
animator.setDuration(mCrossFadeAnimationDuration);
animator.addUpdateListener(animation -> {
- if (mState == State.UNDEFINED) {
+ if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
// Could happen if onTaskVanished happens during the animation since we may have
// set a start delay on this animation.
Log.d(TAG, "Task vanished, skip fadeOutAndRemoveOverlay");
@@ -1410,7 +1409,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
private void removeContentOverlay(SurfaceControl surface, Runnable callback) {
- if (mState == State.UNDEFINED) {
+ if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
// Avoid double removal, which is fatal.
return;
}
@@ -1432,7 +1431,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
pw.println(innerPrefix + "mToken=" + mToken
+ " binder=" + (mToken != null ? mToken.asBinder() : null));
pw.println(innerPrefix + "mLeash=" + mLeash);
- pw.println(innerPrefix + "mState=" + mState);
+ pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState());
pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType);
pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 4759550c35c0..b31e6e0750ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -18,6 +18,10 @@ package com.android.wm.shell.pip;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.util.RotationUtils.deltaRotation;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PIP;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
@@ -25,11 +29,16 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.Context;
+import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.IBinder;
+import android.util.Log;
import android.view.Surface;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -49,74 +58,275 @@ import com.android.wm.shell.transition.Transitions;
*/
public class PipTransition extends PipTransitionController {
+ private static final String TAG = PipTransition.class.getSimpleName();
+
+ private final PipTransitionState mPipTransitionState;
private final int mEnterExitAnimationDuration;
private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
private Transitions.TransitionFinishCallback mFinishCallback;
+ private Rect mExitDestinationBounds = new Rect();
+ private IBinder mExitTransition = null;
public PipTransition(Context context,
- PipBoundsState pipBoundsState, PipMenuController pipMenuController,
+ PipBoundsState pipBoundsState,
+ PipTransitionState pipTransitionState,
+ PipMenuController pipMenuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipAnimationController pipAnimationController,
Transitions transitions,
@NonNull ShellTaskOrganizer shellTaskOrganizer) {
super(pipBoundsState, pipMenuController, pipBoundsAlgorithm,
pipAnimationController, transitions, shellTaskOrganizer);
+ mPipTransitionState = pipTransitionState;
mEnterExitAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
}
@Override
+ public void setIsFullAnimation(boolean isFullAnimation) {
+ setOneShotAnimationType(isFullAnimation ? ANIM_TYPE_BOUNDS : ANIM_TYPE_ALPHA);
+ }
+
+ /**
+ * Sets the preferred animation type for one time.
+ * This is typically used to set the animation type to
+ * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
+ */
+ private void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
+ mOneShotAnimationType = animationType;
+ }
+
+ @Override
+ public void startTransition(Rect destinationBounds, WindowContainerTransaction out) {
+ if (destinationBounds != null) {
+ mExitDestinationBounds.set(destinationBounds);
+ mExitTransition = mTransitions.startTransition(TRANSIT_EXIT_PIP, out, this);
+ } else {
+ mTransitions.startTransition(TRANSIT_REMOVE_PIP, out, this);
+ }
+ }
+
+ @Override
public boolean startAnimation(@android.annotation.NonNull IBinder transition,
@android.annotation.NonNull TransitionInfo info,
- @android.annotation.NonNull SurfaceControl.Transaction t,
+ @android.annotation.NonNull SurfaceControl.Transaction startTransaction,
+ @android.annotation.NonNull SurfaceControl.Transaction finishTransaction,
@android.annotation.NonNull Transitions.TransitionFinishCallback finishCallback) {
+
+ if (mExitTransition == transition || info.getType() == TRANSIT_EXIT_PIP) {
+ mExitTransition = null;
+ if (info.getChanges().size() == 1) {
+ if (mFinishCallback != null) {
+ mFinishCallback.onTransitionFinished(null, null);
+ mFinishCallback = null;
+ throw new RuntimeException("Previous callback not called, aborting exit PIP.");
+ }
+
+ final TransitionInfo.Change change = info.getChanges().get(0);
+ mFinishCallback = finishCallback;
+ startTransaction.apply();
+ boolean success = startExpandAnimation(change.getTaskInfo(), change.getLeash(),
+ new Rect(mExitDestinationBounds));
+ mExitDestinationBounds.setEmpty();
+ return success;
+ } else {
+ Log.e(TAG, "Got an exit-pip transition with unexpected change-list");
+ }
+ }
+
+ if (info.getType() == TRANSIT_REMOVE_PIP) {
+ if (mFinishCallback != null) {
+ mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */);
+ mFinishCallback = null;
+ throw new RuntimeException("Previous callback not called, aborting remove PIP.");
+ }
+
+ startTransaction.apply();
+ finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
+ mPipBoundsState.getDisplayBounds());
+ finishCallback.onTransitionFinished(null, null);
+ return true;
+ }
+
+ // We only support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps
+ // that enter PiP instantly on opening, mostly from CTS/Flicker tests)
+ if (info.getType() != TRANSIT_PIP && info.getType() != TRANSIT_OPEN) {
+ return false;
+ }
+
+ // Search for an Enter PiP transition (along with a show wallpaper one)
+ TransitionInfo.Change enterPip = null;
+ TransitionInfo.Change wallpaper = null;
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
if (change.getTaskInfo() != null
&& change.getTaskInfo().configuration.windowConfiguration.getWindowingMode()
== WINDOWING_MODE_PINNED) {
- mFinishCallback = finishCallback;
- return startEnterAnimation(change.getTaskInfo(), change.getLeash(), t);
+ enterPip = change;
+ } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+ wallpaper = change;
}
}
- return false;
+ if (enterPip == null) {
+ return false;
+ }
+
+ if (mFinishCallback != null) {
+ mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */);
+ mFinishCallback = null;
+ throw new RuntimeException("Previous callback not called, aborting entering PIP.");
+ }
+
+ // Show the wallpaper if there is a wallpaper change.
+ if (wallpaper != null) {
+ startTransaction.show(wallpaper.getLeash());
+ startTransaction.setAlpha(wallpaper.getLeash(), 1.f);
+ }
+
+ mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
+ mFinishCallback = finishCallback;
+ return startEnterAnimation(enterPip.getTaskInfo(), enterPip.getLeash(),
+ startTransaction, finishTransaction, enterPip.getStartRotation(),
+ enterPip.getEndRotation());
}
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
- return null;
+ if (request.getType() == TRANSIT_PIP) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ mPipTransitionState.setTransitionState(PipTransitionState.ENTRY_SCHEDULED);
+ if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ wct.setActivityWindowingMode(request.getTriggerTask().token,
+ WINDOWING_MODE_UNDEFINED);
+ final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+ wct.setBounds(request.getTriggerTask().token, destinationBounds);
+ }
+ return wct;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void onTransitionMerged(@NonNull IBinder transition) {
+ if (transition != mExitTransition) {
+ return;
+ }
+ // This means an expand happened before enter-pip finished and we are now "merging" a
+ // no-op transition that happens to match our exit-pip.
+ boolean cancelled = false;
+ if (mPipAnimationController.getCurrentAnimator() != null) {
+ mPipAnimationController.getCurrentAnimator().cancel();
+ cancelled = true;
+ }
+ // Unset exitTransition AFTER cancel so that finishResize knows we are merging.
+ mExitTransition = null;
+ if (!cancelled) return;
+ final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo();
+ if (taskInfo != null) {
+ startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(),
+ new Rect(mExitDestinationBounds));
+ }
+ mExitDestinationBounds.setEmpty();
}
@Override
public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
@PipAnimationController.TransitionDirection int direction,
- SurfaceControl.Transaction tx) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareFinishResizeTransaction(taskInfo, destinationBounds,
- direction, tx, wct);
- mFinishCallback.onTransitionFinished(wct, null);
+ @Nullable SurfaceControl.Transaction tx) {
+
+ if (isInPipDirection(direction)) {
+ mPipTransitionState.setTransitionState(PipTransitionState.ENTERED_PIP);
+ }
+ // If there is an expected exit transition, then the exit will be "merged" into this
+ // transition so don't fire the finish-callback in that case.
+ if (mExitTransition == null && mFinishCallback != null) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareFinishResizeTransaction(taskInfo, destinationBounds,
+ direction, wct);
+ if (tx != null) {
+ wct.setBoundsChangeTransaction(taskInfo.token, tx);
+ }
+ mFinishCallback.onTransitionFinished(wct, null /* callback */);
+ mFinishCallback = null;
+ }
finishResizeForMenu(destinationBounds);
}
+ @Override
+ public void forceFinishTransition() {
+ if (mFinishCallback == null) return;
+ mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */);
+ mFinishCallback = null;
+ }
+
+ private boolean startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
+ final Rect destinationBounds) {
+ PipAnimationController.PipTransitionAnimator animator =
+ mPipAnimationController.getAnimator(taskInfo, leash, mPipBoundsState.getBounds(),
+ mPipBoundsState.getBounds(), destinationBounds, null,
+ TRANSITION_DIRECTION_LEAVE_PIP, 0 /* startingAngle */, Surface.ROTATION_0);
+
+ animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
+ .setPipAnimationCallback(mPipAnimationCallback)
+ .setDuration(mEnterExitAnimationDuration)
+ .start();
+
+ return true;
+ }
+
private boolean startEnterAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
- final SurfaceControl.Transaction t) {
+ final SurfaceControl.Transaction startTransaction,
+ final SurfaceControl.Transaction finishTransaction,
+ final int startRotation, final int endRotation) {
setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
taskInfo.topActivityInfo);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds();
PipAnimationController.PipTransitionAnimator animator;
+ finishTransaction.setPosition(leash, destinationBounds.left, destinationBounds.top);
+ if (taskInfo.pictureInPictureParams != null
+ && taskInfo.pictureInPictureParams.isAutoEnterEnabled()
+ && mPipTransitionState.getInSwipePipToHomeTransition()) {
+ mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+
+ // PiP menu is attached late in the process here to avoid any artifacts on the leash
+ // caused by addShellRoot when in gesture navigation mode.
+ mPipMenuController.attach(leash);
+ SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9])
+ .setPosition(leash, destinationBounds.left, destinationBounds.top)
+ .setWindowCrop(leash, destinationBounds.width(), destinationBounds.height());
+ startTransaction.merge(tx);
+ startTransaction.apply();
+ mPipBoundsState.setBounds(destinationBounds);
+ onFinishResize(taskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, null /* tx */);
+ sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
+ mPipTransitionState.setInSwipePipToHomeTransition(false);
+ return true;
+ }
+
+ int rotationDelta = deltaRotation(endRotation, startRotation);
+ if (rotationDelta != Surface.ROTATION_0) {
+ Matrix tmpTransform = new Matrix();
+ tmpTransform.postRotate(rotationDelta == Surface.ROTATION_90
+ ? Surface.ROTATION_270 : Surface.ROTATION_90);
+ startTransaction.setMatrix(leash, tmpTransform, new float[9]);
+ }
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
final Rect sourceHintRect =
PipBoundsAlgorithm.getValidSourceHintRect(
taskInfo.pictureInPictureParams, currentBounds);
animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
- 0 /* startingAngle */, Surface.ROTATION_0);
+ 0 /* startingAngle */, rotationDelta);
} else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
- t.setAlpha(leash, 0f);
- t.apply();
+ startTransaction.setAlpha(leash, 0f);
+ // PiP menu is attached late in the process here to avoid any artifacts on the leash
+ // caused by addShellRoot when in gesture navigation mode.
+ mPipMenuController.attach(leash);
animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
0f, 1f);
mOneShotAnimationType = ANIM_TYPE_BOUNDS;
@@ -124,10 +334,12 @@ public class PipTransition extends PipTransitionController {
throw new RuntimeException("Unrecognized animation type: "
+ mOneShotAnimationType);
}
+ startTransaction.apply();
animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration)
.start();
+
return true;
}
@@ -138,7 +350,6 @@ public class PipTransition extends PipTransitionController {
private void prepareFinishResizeTransaction(TaskInfo taskInfo, Rect destinationBounds,
@PipAnimationController.TransitionDirection int direction,
- SurfaceControl.Transaction tx,
WindowContainerTransaction wct) {
Rect taskBounds = null;
if (isInPipDirection(direction)) {
@@ -158,6 +369,5 @@ public class PipTransition extends PipTransitionController {
}
wct.setBounds(taskInfo.token, taskBounds);
- wct.setBoundsChangeTransaction(taskInfo.token, tx);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index d801c918973a..376f3298a83c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -19,7 +19,6 @@ package com.android.wm.shell.pip;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
-import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
import android.app.PictureInPictureParams;
import android.app.TaskInfo;
@@ -29,6 +28,7 @@ import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.view.SurfaceControl;
+import android.window.WindowContainerTransaction;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.transition.Transitions;
@@ -46,8 +46,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH
protected final PipBoundsState mPipBoundsState;
protected final ShellTaskOrganizer mShellTaskOrganizer;
protected final PipMenuController mPipMenuController;
+ protected final Transitions mTransitions;
private final Handler mMainHandler;
private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
+ protected PipTaskOrganizer mPipOrganizer;
protected final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
new PipAnimationController.PipAnimationCallback() {
@@ -55,12 +57,6 @@ public abstract class PipTransitionController implements Transitions.TransitionH
public void onPipAnimationStart(TaskInfo taskInfo,
PipAnimationController.PipTransitionAnimator animator) {
final int direction = animator.getTransitionDirection();
- if (direction == TRANSITION_DIRECTION_TO_PIP) {
- // TODO (b//169221267): Add jank listener for transactions without buffer
- // updates.
- //InteractionJankMonitor.getInstance().begin(
- // InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP, 2000);
- }
sendOnPipTransitionStarted(direction);
}
@@ -74,12 +70,6 @@ public abstract class PipTransitionController implements Transitions.TransitionH
}
onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx);
sendOnPipTransitionFinished(direction);
- if (direction == TRANSITION_DIRECTION_TO_PIP) {
- // TODO (b//169221267): Add jank listener for transactions without buffer
- // updates.
- //InteractionJankMonitor.getInstance().end(
- // InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP);
- }
}
@Override
@@ -98,6 +88,29 @@ public abstract class PipTransitionController implements Transitions.TransitionH
SurfaceControl.Transaction tx) {
}
+ /**
+ * Called to inform the transition that the animation should start with the assumption that
+ * PiP is not animating from its original bounds, but rather a continuation of another
+ * animation. For example, gesture navigation would first fade out the PiP activity, and the
+ * transition should be responsible to animate in (such as fade in) the PiP.
+ */
+ public void setIsFullAnimation(boolean isFullAnimation) {
+ }
+
+ /**
+ * Called when the Shell wants to starts a transition/animation.
+ */
+ public void startTransition(Rect destinationBounds, WindowContainerTransaction out) {
+ // Default implementation does nothing.
+ }
+
+ /**
+ * Called when the transition animation can't continue (eg. task is removed during
+ * animation)
+ */
+ public void forceFinishTransition() {
+ }
+
public PipTransitionController(PipBoundsState pipBoundsState,
PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm,
PipAnimationController pipAnimationController, Transitions transitions,
@@ -107,12 +120,17 @@ public abstract class PipTransitionController implements Transitions.TransitionH
mShellTaskOrganizer = shellTaskOrganizer;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipAnimationController = pipAnimationController;
+ mTransitions = transitions;
mMainHandler = new Handler(Looper.getMainLooper());
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.addHandler(this);
}
}
+ void setPipOrganizer(PipTaskOrganizer pto) {
+ mPipOrganizer = pto;
+ }
+
/**
* Registers {@link PipTransitionCallback} to receive transition callbacks.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
new file mode 100644
index 000000000000..85e56b7dd99f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
@@ -0,0 +1,97 @@
+/*
+ * 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.wm.shell.pip;
+
+import android.annotation.IntDef;
+import android.app.PictureInPictureParams;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Used to keep track of PiP leash state as it appears and animates by {@link PipTaskOrganizer} and
+ * {@link PipTransition}.
+ */
+public class PipTransitionState {
+
+ public static final int UNDEFINED = 0;
+ public static final int TASK_APPEARED = 1;
+ public static final int ENTRY_SCHEDULED = 2;
+ public static final int ENTERING_PIP = 3;
+ public static final int ENTERED_PIP = 4;
+ public static final int EXITING_PIP = 5;
+
+ /**
+ * If set to {@code true}, no entering PiP transition would be kicked off and most likely
+ * it's due to the fact that Launcher is handling the transition directly when swiping
+ * auto PiP-able Activity to home.
+ * See also {@link PipTaskOrganizer#startSwipePipToHome(ComponentName, ActivityInfo,
+ * PictureInPictureParams)}.
+ */
+ private boolean mInSwipePipToHomeTransition;
+
+ // Not a complete set of states but serves what we want right now.
+ @IntDef(prefix = { "TRANSITION_STATE_" }, value = {
+ UNDEFINED,
+ TASK_APPEARED,
+ ENTRY_SCHEDULED,
+ ENTERING_PIP,
+ ENTERED_PIP,
+ EXITING_PIP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TransitionState {}
+
+ private @TransitionState int mState;
+
+ public PipTransitionState() {
+ mState = UNDEFINED;
+ }
+
+ public void setTransitionState(@TransitionState int state) {
+ mState = state;
+ }
+
+ public @TransitionState int getTransitionState() {
+ return mState;
+ }
+
+ public boolean isInPip() {
+ return mState >= TASK_APPEARED
+ && mState != EXITING_PIP;
+ }
+
+ public void setInSwipePipToHomeTransition(boolean inSwipePipToHomeTransition) {
+ mInSwipePipToHomeTransition = inSwipePipToHomeTransition;
+ }
+
+ public boolean getInSwipePipToHomeTransition() {
+ return mInSwipePipToHomeTransition;
+ }
+ /**
+ * Resize request can be initiated in other component, ignore if we are no longer in PIP,
+ * still waiting for animation or we're exiting from it.
+ *
+ * @return {@code true} if the resize request should be blocked/ignored.
+ */
+ public boolean shouldBlockResizeRequest() {
+ return mState < ENTERING_PIP
+ || mState == EXITING_PIP;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index a646b07c49dc..ae8c1b6f8c1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -60,8 +60,7 @@ public class PhonePipMenuController implements PipMenuController {
private static final boolean DEBUG = false;
public static final int MENU_STATE_NONE = 0;
- public static final int MENU_STATE_CLOSE = 1;
- public static final int MENU_STATE_FULL = 2;
+ public static final int MENU_STATE_FULL = 1;
/**
* A listener interface to receive notification on changes in PIP.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 63f1985aa86e..8e5c5c52cb3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -21,7 +21,16 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static android.view.WindowManager.INPUT_CONSUMER_PIP;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PIP_TRANSITION;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SAME;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SNAP_AFTER_RESIZE;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
import android.app.ActivityManager;
@@ -52,6 +61,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.R;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayChangeController;
@@ -67,6 +77,7 @@ import com.android.wm.shell.pip.IPip;
import com.android.wm.shell.pip.IPipAnimationListener;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
@@ -74,6 +85,7 @@ import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.Objects;
@@ -445,11 +457,18 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return;
}
Runnable updateDisplayLayout = () -> {
+ final boolean fromRotation = Transitions.ENABLE_SHELL_TRANSITIONS
+ && mPipBoundsState.getDisplayLayout().rotation() != layout.rotation();
mPipBoundsState.setDisplayLayout(layout);
+ final WindowContainerTransaction wct =
+ fromRotation ? new WindowContainerTransaction() : null;
updateMovementBounds(null /* toBounds */,
- false /* fromRotation */, false /* fromImeAdjustment */,
+ fromRotation, false /* fromImeAdjustment */,
false /* fromShelfAdjustment */,
- null /* windowContainerTransaction */);
+ wct /* windowContainerTransaction */);
+ if (wct != null) {
+ mPipTaskOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_SAME);
+ }
};
if (mPipTaskOrganizer.isInPip() && saveRestoreSnapFraction) {
@@ -528,6 +547,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private void setPinnedStackAnimationType(int animationType) {
mPipTaskOrganizer.setOneShotAnimationType(animationType);
+ mPipTransitionController.setIsFullAnimation(
+ animationType == PipAnimationController.ANIM_TYPE_BOUNDS);
}
private void setPinnedStackAnimationListener(IPipAnimationListener callback) {
@@ -564,8 +585,37 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipTaskOrganizer.stopSwipePipToHome(componentName, destinationBounds, overlay);
}
+ private String getTransitionTag(int direction) {
+ switch (direction) {
+ case TRANSITION_DIRECTION_TO_PIP:
+ return "TRANSITION_TO_PIP";
+ case TRANSITION_DIRECTION_LEAVE_PIP:
+ return "TRANSITION_LEAVE_PIP";
+ case TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN:
+ return "TRANSITION_LEAVE_PIP_TO_SPLIT_SCREEN";
+ case TRANSITION_DIRECTION_REMOVE_STACK:
+ return "TRANSITION_REMOVE_STACK";
+ case TRANSITION_DIRECTION_SNAP_AFTER_RESIZE:
+ return "TRANSITION_SNAP_AFTER_RESIZE";
+ case TRANSITION_DIRECTION_USER_RESIZE:
+ return "TRANSITION_USER_RESIZE";
+ case TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND:
+ return "TRANSITION_EXPAND_OR_UNEXPAND";
+ default:
+ return "TRANSITION_LEAVE_UNKNOWN";
+ }
+ }
+
@Override
public void onPipTransitionStarted(int direction, Rect pipBounds) {
+ // Begin InteractionJankMonitor with PIP transition CUJs
+ final InteractionJankMonitor.Configuration.Builder builder =
+ InteractionJankMonitor.Configuration.Builder.withSurface(
+ CUJ_PIP_TRANSITION, mContext, mPipTaskOrganizer.getSurfaceControl())
+ .setTag(getTransitionTag(direction))
+ .setTimeout(2000);
+ InteractionJankMonitor.getInstance().begin(builder);
+
if (isOutPipDirection(direction)) {
// Exiting PIP, save the reentry state to restore to when re-entering.
saveReentryState(pipBounds);
@@ -604,6 +654,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
private void onPipTransitionFinishedOrCanceled(int direction) {
+ // End InteractionJankMonitor with PIP transition by CUJs
+ InteractionJankMonitor.getInstance().end(CUJ_PIP_TRANSITION);
+
// Re-enable touches after the animation completes
mTouchHandler.setTouchEnabled(true);
mTouchHandler.onPinnedStackAnimationEnded(direction);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index 1da9577fe49a..0fbdf90fd9d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -20,6 +20,7 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
@@ -30,6 +31,7 @@ import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.widget.FrameLayout;
@@ -93,6 +95,7 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
private int mTargetSize;
private int mDismissAreaHeight;
private float mMagneticFieldRadiusPercent = 1f;
+ private WindowInsets mWindowInsets;
private SurfaceControl mTaskLeash;
private boolean mHasDismissTargetSurface;
@@ -123,6 +126,13 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
mContext.getDrawable(R.drawable.floating_dismiss_gradient_transition));
mTargetViewContainer.setClipChildren(false);
mTargetViewContainer.addView(mTargetView);
+ mTargetViewContainer.setOnApplyWindowInsetsListener((view, windowInsets) -> {
+ if (!windowInsets.equals(mWindowInsets)) {
+ mWindowInsets = windowInsets;
+ updateMagneticTargetSize();
+ }
+ return windowInsets;
+ });
mMagnetizedPip = mMotionHelper.getMagnetizedPip();
mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
@@ -158,14 +168,16 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- mMainExecutor.executeDelayed(() -> {
- mMotionHelper.notifyDismissalPending();
- mMotionHelper.animateDismiss();
- hideDismissTargetMaybe();
-
- mPipUiEventLogger.log(
- PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
- }, 0);
+ if (mEnableDismissDragToEdge) {
+ mMainExecutor.executeDelayed(() -> {
+ mMotionHelper.notifyDismissalPending();
+ mMotionHelper.animateDismiss();
+ hideDismissTargetMaybe();
+
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
+ }, 0);
+ }
}
});
@@ -199,10 +211,13 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
final Resources res = mContext.getResources();
mTargetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
+ final WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+ final Insets navInset = insets.getInsetsIgnoringVisibility(
+ WindowInsets.Type.navigationBars());
final FrameLayout.LayoutParams newParams =
new FrameLayout.LayoutParams(mTargetSize, mTargetSize);
newParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
- newParams.bottomMargin = mContext.getResources().getDimensionPixelSize(
+ newParams.bottomMargin = navInset.bottom + mContext.getResources().getDimensionPixelSize(
R.dimen.floating_dismiss_bottom_margin);
mTargetView.setLayoutParams(newParams);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 67b1e6dd4cc7..8ef2b6b12030 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -23,7 +23,6 @@ import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTR
import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
-import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_CLOSE;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
@@ -203,7 +202,7 @@ public class PipMenuView extends FrameLayout {
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
- if (action == ACTION_CLICK && mMenuState == MENU_STATE_CLOSE) {
+ if (action == ACTION_CLICK && mMenuState != MENU_STATE_FULL) {
mController.showMenu();
}
return super.performAccessibilityAction(host, action, args);
@@ -271,13 +270,12 @@ public class PipMenuView extends FrameLayout {
mDismissButton.getAlpha(), 1f);
ObjectAnimator resizeAnim = ObjectAnimator.ofFloat(mResizeHandle, View.ALPHA,
mResizeHandle.getAlpha(),
- ENABLE_RESIZE_HANDLE && menuState == MENU_STATE_CLOSE && showResizeHandle
- ? 1f : 0f);
+ ENABLE_RESIZE_HANDLE && showResizeHandle ? 1f : 0f);
if (menuState == MENU_STATE_FULL) {
mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
resizeAnim);
} else {
- mMenuContainerAnimator.playTogether(dismissAnim, resizeAnim);
+ mMenuContainerAnimator.playTogether(resizeAnim);
}
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
mMenuContainerAnimator.setDuration(ANIMATION_HIDE_DURATION_MS);
@@ -429,7 +427,7 @@ public class PipMenuView extends FrameLayout {
FrameLayout.LayoutParams expandedLp =
(FrameLayout.LayoutParams) expandContainer.getLayoutParams();
- if (mActions.isEmpty() || menuState == MENU_STATE_CLOSE || menuState == MENU_STATE_NONE) {
+ if (mActions.isEmpty() || menuState == MENU_STATE_NONE) {
actionsContainer.setVisibility(View.INVISIBLE);
// Update the expand container margin to adjust the center of the expand button to
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 7867f933de4f..9f2f6a575aca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -22,7 +22,6 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI
import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
-import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_CLOSE;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
@@ -81,7 +80,6 @@ public class PipTouchHandler {
private final PhonePipMenuController mMenuController;
private final AccessibilityManager mAccessibilityManager;
- private boolean mShowPipMenuOnAnimationEnd = false;
/**
* Whether PIP stash is enabled or not. When enabled, if the user flings toward the edge of the
@@ -280,7 +278,6 @@ public class PipTouchHandler {
public void onActivityPinned() {
mPipDismissTargetHandler.createOrUpdateDismissTarget();
- mShowPipMenuOnAnimationEnd = true;
mPipResizeGestureHandler.onActivityPinned();
mFloatingContentCoordinator.onContentAdded(mMotionHelper);
}
@@ -304,13 +301,6 @@ public class PipTouchHandler {
// Set the initial bounds as the user resize bounds.
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
}
-
- if (mShowPipMenuOnAnimationEnd) {
- mMenuController.showMenu(MENU_STATE_CLOSE, mPipBoundsState.getBounds(),
- true /* allowMenuTimeout */, false /* willResizeMenu */,
- shouldShowResizeHandle());
- mShowPipMenuOnAnimationEnd = false;
- }
}
public void onConfigurationChanged() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index b7caf72641a3..551476dc9d54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -58,7 +58,8 @@ public class TvPipTransition extends PipTransitionController {
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @android.annotation.NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java
index 78af9df30e6a..ff6f913207f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java
@@ -17,13 +17,10 @@
package com.android.wm.shell.sizecompatui;
import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.Button;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import androidx.annotation.Nullable;
@@ -58,10 +55,8 @@ public class SizeCompatHintPopup extends FrameLayout implements View.OnClickList
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- final Button gotItButton = findViewById(R.id.got_it);
- gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY),
- null /* content */, null /* mask */));
- gotItButton.setOnClickListener(this);
+ final LinearLayout hintPopup = findViewById(R.id.size_compat_hint_popup);
+ hintPopup.setOnClickListener(this);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
index 08a840297df1..d75fe5173c5f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
@@ -17,10 +17,6 @@
package com.android.wm.shell.sizecompatui;
import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
@@ -63,11 +59,6 @@ public class SizeCompatRestartButton extends FrameLayout implements View.OnClick
protected void onFinishInflate() {
super.onFinishInflate();
final ImageButton restartButton = findViewById(R.id.size_compat_restart_button);
- final ColorStateList color = ColorStateList.valueOf(Color.LTGRAY);
- final GradientDrawable mask = new GradientDrawable();
- mask.setShape(GradientDrawable.OVAL);
- mask.setColor(color);
- restartButton.setBackground(new RippleDrawable(color, null /* content */, mask));
restartButton.setOnClickListener(this);
restartButton.setOnLongClickListener(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
index 1fc4d12def1f..ab3cbd655ea1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
@@ -47,6 +47,8 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang
/** Callback for size compat UI interaction. */
public interface SizeCompatUICallback {
+ /** Called when the size compat restart button appears. */
+ void onSizeCompatRestartButtonAppeared(int taskId);
/** Called when the size compat restart button is clicked. */
void onSizeCompatRestartButtonClicked(int taskId);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
index 20021ebea834..bebb6d30c47f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
@@ -25,6 +25,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Binder;
@@ -54,6 +55,10 @@ class SizeCompatUILayout {
private final int mTaskId;
private ShellTaskOrganizer.TaskListener mTaskListener;
private DisplayLayout mDisplayLayout;
+ private final int mButtonWidth;
+ private final int mButtonHeight;
+ private final int mPopupOffsetX;
+ private final int mPopupOffsetY;
@VisibleForTesting
final SizeCompatUIWindowManager mButtonWindowManager;
@@ -66,9 +71,7 @@ class SizeCompatUILayout {
@VisibleForTesting
@Nullable
SizeCompatHintPopup mHint;
- final int mButtonSize;
- final int mPopupOffsetX;
- final int mPopupOffsetY;
+ @VisibleForTesting
boolean mShouldShowHint;
SizeCompatUILayout(SyncTransactionQueue syncQueue,
@@ -86,10 +89,13 @@ class SizeCompatUILayout {
mShouldShowHint = !hasShownHint;
mButtonWindowManager = new SizeCompatUIWindowManager(mContext, taskConfig, this);
- mButtonSize =
- mContext.getResources().getDimensionPixelSize(R.dimen.size_compat_button_size);
- mPopupOffsetX = mButtonSize / 4;
- mPopupOffsetY = mButtonSize;
+ final Resources resources = mContext.getResources();
+ mButtonWidth = resources.getDimensionPixelSize(R.dimen.size_compat_button_width);
+ mButtonHeight = resources.getDimensionPixelSize(R.dimen.size_compat_button_height);
+ mPopupOffsetX = (mButtonWidth / 2) - resources.getDimensionPixelSize(
+ R.dimen.size_compat_hint_corner_radius) - (resources.getDimensionPixelSize(
+ R.dimen.size_compat_hint_point_width) / 2);
+ mPopupOffsetY = mButtonHeight;
}
/** Creates the activity restart button window. */
@@ -106,6 +112,8 @@ class SizeCompatUILayout {
mShouldShowHint = false;
createSizeCompatHint();
}
+
+ mCallback.onSizeCompatRestartButtonAppeared(mTaskId);
}
/** Creates the restart button hint window. */
@@ -220,7 +228,7 @@ class SizeCompatUILayout {
WindowManager.LayoutParams getButtonWindowLayoutParams() {
final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
// Cannot be wrap_content as this determines the actual window size
- mButtonSize, mButtonSize,
+ mButtonWidth, mButtonHeight,
TYPE_APPLICATION_OVERLAY,
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT);
@@ -276,8 +284,8 @@ class SizeCompatUILayout {
// Position of the button in the container coordinate.
final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
? stableBounds.left - taskBounds.left
- : stableBounds.right - taskBounds.left - mButtonSize;
- final int positionY = stableBounds.bottom - taskBounds.top - mButtonSize;
+ : stableBounds.right - taskBounds.left - mButtonWidth;
+ final int positionY = stableBounds.bottom - taskBounds.top - mButtonHeight;
updateSurfacePosition(leash, positionX, positionY);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 8f0892fdcbba..3d3a63057dde 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -20,7 +20,9 @@ import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.os.UserHandle;
-import android.window.IRemoteTransition;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.window.RemoteTransition;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
@@ -50,9 +52,10 @@ interface ISplitScreen {
oneway void removeFromSideStage(int taskId) = 4;
/**
- * Removes the split-screen stages.
+ * Removes the split-screen stages and leaving indicated task to top. Passing INVALID_TASK_ID
+ * to indicate leaving no top task after leaving split-screen.
*/
- oneway void exitSplitScreen() = 5;
+ oneway void exitSplitScreen(int toTopTaskId) = 5;
/**
* @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible.
@@ -77,9 +80,24 @@ interface ISplitScreen {
int position, in Bundle options) = 9;
/**
- * Starts tasks simultaneously in one transition. The first task in the list will be in the
- * main-stage and on the left/top.
+ * Starts tasks simultaneously in one transition.
*/
oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
- in Bundle sideOptions, int sidePosition, in IRemoteTransition remoteTransition) = 10;
+ in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10;
+
+ /**
+ * Version of startTasks using legacy transition system.
+ */
+ oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
+ int sideTaskId, in Bundle sideOptions, int sidePosition,
+ in RemoteAnimationAdapter adapter) = 11;
+
+ /**
+ * Blocking call that notifies and gets additional split-screen targets when entering
+ * recents (for example: the dividerBar).
+ * @param cancel is true if leaving recents back to split (eg. the gesture was cancelled).
+ * @param appTargets apps that will be re-parented to display area
+ */
+ RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
+ in RemoteAnimationTarget[] appTargets) = 12;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index d0998eb57633..082fe9205be8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -16,13 +16,14 @@
package com.android.wm.shell.splitscreen;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-
+import android.annotation.Nullable;
+import android.content.Context;
import android.graphics.Rect;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -36,35 +37,35 @@ class MainStage extends StageTaskListener {
private boolean mIsActive = false;
- MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
+ MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession) {
- super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession);
+ SurfaceSession surfaceSession, IconProvider iconProvider,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, iconProvider,
+ stageTaskUnfoldController);
}
boolean isActive() {
return mIsActive;
}
- void activate(Rect rootBounds, WindowContainerTransaction wct) {
+ void activate(Rect rootBounds, WindowContainerTransaction wct, boolean includingTopTask) {
if (mIsActive) return;
final WindowContainerToken rootToken = mRootTaskInfo.token;
wct.setBounds(rootToken, rootBounds)
- .setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW)
- .setLaunchRoot(
- rootToken,
- CONTROLLED_WINDOWING_MODES,
- CONTROLLED_ACTIVITY_TYPES)
- .reparentTasks(
- null /* currentParent */,
- rootToken,
- CONTROLLED_WINDOWING_MODES,
- CONTROLLED_ACTIVITY_TYPES,
- true /* onTop */)
// Moving the root task to top after the child tasks were re-parented , or the root
// task cannot be visible and focused.
.reorder(rootToken, true /* onTop */);
+ if (includingTopTask) {
+ wct.reparentTasks(
+ null /* currentParent */,
+ rootToken,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES,
+ true /* onTop */,
+ true /* reparentTopOnly */);
+ }
mIsActive = true;
}
@@ -79,11 +80,7 @@ class MainStage extends StageTaskListener {
if (mRootTaskInfo == null) return;
final WindowContainerToken rootToken = mRootTaskInfo.token;
- wct.setLaunchRoot(
- rootToken,
- null,
- null)
- .reparentTasks(
+ wct.reparentTasks(
rootToken,
null /* newParent */,
CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
@@ -93,9 +90,4 @@ class MainStage extends StageTaskListener {
// all its tasks.
.reorder(rootToken, false /* onTop */);
}
-
- void updateConfiguration(int windowingMode, Rect bounds, WindowContainerTransaction wct) {
- wct.setBounds(mRootTaskInfo.token, bounds)
- .setWindowingMode(mRootTaskInfo.token, windowingMode);
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
index 82f95a4f32ea..f8c03044c5c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
@@ -16,37 +16,42 @@
package com.android.wm.shell.splitscreen;
+import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.content.Context;
import android.graphics.Rect;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
/**
* Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
* here. All other task are launch in the {@link MainStage}.
+ *
* @see StageCoordinator
*/
class SideStage extends StageTaskListener {
private static final String TAG = SideStage.class.getSimpleName();
- SideStage(ShellTaskOrganizer taskOrganizer, int displayId,
+ SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession) {
- super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession);
+ SurfaceSession surfaceSession, IconProvider iconProvider,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, iconProvider,
+ stageTaskUnfoldController);
}
- void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,
- WindowContainerTransaction wct) {
+ void moveToTop(Rect rootBounds, WindowContainerTransaction wct) {
final WindowContainerToken rootToken = mRootTaskInfo.token;
- wct.setBounds(rootToken, rootBounds)
- .reparent(task.token, rootToken, true /* onTop*/)
- // Moving the root task to top after the child tasks were repareted , or the root
- // task cannot be visible and focused.
- .reorder(rootToken, true /* onTop */);
+ wct.setBounds(rootToken, rootBounds).reorder(rootToken, true /* onTop */);
+ }
+
+ void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) {
+ wct.reparent(task.token, mRootTaskInfo.token, true /* onTop*/);
}
boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 002bfb6e429f..e86462f666c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -17,10 +17,13 @@
package com.android.wm.shell.splitscreen;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+import java.util.concurrent.Executor;
+
/**
* Interface to engage split-screen feature.
* TODO: Figure out which of these are actually needed outside of the Shell
@@ -53,10 +56,18 @@ public interface SplitScreen {
/** Callback interface for listening to changes in a split-screen stage. */
interface SplitScreenListener {
- void onStagePositionChanged(@StageType int stage, @SplitPosition int position);
- void onTaskStageChanged(int taskId, @StageType int stage, boolean visible);
+ default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {}
+ default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {}
+ default void onSplitVisibilityChanged(boolean visible) {}
}
+ /** Registers listener that gets split screen callback. */
+ void registerSplitScreenListener(@NonNull SplitScreenListener listener,
+ @NonNull Executor executor);
+
+ /** Unregisters listener that gets split screen callback. */
+ void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
+
/**
* Returns a binder that can be passed to an external process to manipulate SplitScreen.
*/
@@ -64,6 +75,18 @@ public interface SplitScreen {
return null;
}
+ /**
+ * Called when the keyguard occluded state changes.
+ * @param occluded Indicates if the keyguard is now occluded.
+ */
+ void onKeyguardOccludedChanged(boolean occluded);
+
+ /**
+ * Called when the visibility of the keyguard changes.
+ * @param showing Indicates if the keyguard is now visible.
+ */
+ void onKeyguardVisibilityChanged(boolean showing);
+
/** Get a string representation of a stage type */
static String stageTypeToString(@StageType int stage) {
switch (stage) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 9a457b5fd88e..36f140614deb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -16,15 +16,14 @@
package com.android.wm.shell.splitscreen;
+import static android.app.ActivityManager.START_SUCCESS;
+import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -38,16 +37,28 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.ArrayMap;
import android.util.Slog;
-import android.window.IRemoteTransition;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.WindowManager;
+import android.window.RemoteTransition;
+import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -55,16 +66,23 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
-import com.android.wm.shell.splitscreen.ISplitScreenListener;
+import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
+import javax.inject.Provider;
/**
* Class manages split-screen multitasking mode and implements the main interface
* {@link SplitScreen}.
+ *
* @see StageCoordinator
*/
+// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
public class SplitScreenController implements DragAndDropPolicy.Starter,
RemoteCallable<SplitScreenController> {
private static final String TAG = SplitScreenController.class.getSimpleName();
@@ -76,8 +94,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final ShellExecutor mMainExecutor;
private final SplitScreenImpl mImpl = new SplitScreenImpl();
private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
private final Transitions mTransitions;
private final TransactionPool mTransactionPool;
+ private final SplitscreenEventLogger mLogger;
+ private final IconProvider mIconProvider;
+ private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
private StageCoordinator mStageCoordinator;
@@ -85,15 +107,21 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
SyncTransactionQueue syncQueue, Context context,
RootTaskDisplayAreaOrganizer rootTDAOrganizer,
ShellExecutor mainExecutor, DisplayImeController displayImeController,
- Transitions transitions, TransactionPool transactionPool) {
+ DisplayInsetsController displayInsetsController,
+ Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
mContext = context;
mRootTDAOrganizer = rootTDAOrganizer;
mMainExecutor = mainExecutor;
mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
mTransitions = transitions;
mTransactionPool = transactionPool;
+ mUnfoldControllerProvider = unfoldControllerProvider;
+ mLogger = new SplitscreenEventLogger();
+ mIconProvider = iconProvider;
}
public SplitScreen asSplitScreen() {
@@ -114,8 +142,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
if (mStageCoordinator == null) {
// TODO: Multi-display
mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
- mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController, mTransitions,
- mTransactionPool);
+ mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
+ mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
+ mIconProvider, mUnfoldControllerProvider);
}
}
@@ -141,7 +170,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
public void setSideStagePosition(@SplitPosition int sideStagePosition) {
- mStageCoordinator.setSideStagePosition(sideStagePosition);
+ mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
}
public void setSideStageVisibility(boolean visible) {
@@ -153,8 +182,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
}
- public void exitSplitScreen() {
- mStageCoordinator.exitSplitScreen();
+ public void exitSplitScreen(int toTopTaskId, int exitReason) {
+ mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
+ }
+
+ public void onKeyguardOccludedChanged(boolean occluded) {
+ mStageCoordinator.onKeyguardOccludedChanged(occluded);
+ }
+
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mStageCoordinator.onKeyguardVisibilityChanged(showing);
}
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
@@ -175,10 +212,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public void startTask(int taskId, @SplitScreen.StageType int stage,
@SplitPosition int position, @Nullable Bundle options) {
- options = resolveStartStage(stage, position, options);
+ options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */);
try {
- ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ mStageCoordinator.prepareEvictChildTasks(position, evictWct);
+ final int result =
+ ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
+ if (result == START_SUCCESS || result == START_TASK_TO_FRONT) {
+ mSyncQueue.queue(evictWct);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Failed to launch task", e);
}
@@ -187,13 +230,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public void startShortcut(String packageName, String shortcutId,
@SplitScreen.StageType int stage, @SplitPosition int position,
@Nullable Bundle options, UserHandle user) {
- options = resolveStartStage(stage, position, options);
+ options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */);
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ mStageCoordinator.prepareEvictChildTasks(position, evictWct);
try {
LauncherApps launcherApps =
mContext.getSystemService(LauncherApps.class);
launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
options, user);
+ mSyncQueue.queue(evictWct);
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "Failed to launch shortcut", e);
}
@@ -202,64 +248,85 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public void startIntent(PendingIntent intent, Intent fillInIntent,
@SplitScreen.StageType int stage, @SplitPosition int position,
@Nullable Bundle options) {
- options = resolveStartStage(stage, position, options);
-
- try {
- intent.send(mContext, 0, fillInIntent, null, null, null, options);
- } catch (PendingIntent.CanceledException e) {
- Slog.e(TAG, "Failed to launch activity", e);
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ startIntentLegacy(intent, fillInIntent, stage, position, options);
+ return;
}
+ mStageCoordinator.startIntent(intent, fillInIntent, stage, position, options,
+ null /* remote */);
}
- private Bundle resolveStartStage(@SplitScreen.StageType int stage,
- @SplitPosition int position, @Nullable Bundle options) {
- switch (stage) {
- case STAGE_TYPE_UNDEFINED: {
- // Use the stage of the specified position is valid.
- if (position != SPLIT_POSITION_UNDEFINED) {
- if (position == mStageCoordinator.getSideStagePosition()) {
- options = resolveStartStage(STAGE_TYPE_SIDE, position, options);
- } else {
- options = resolveStartStage(STAGE_TYPE_MAIN, position, options);
+ private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options) {
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ mStageCoordinator.prepareEvictChildTasks(position, evictWct);
+
+ LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback,
+ SurfaceControl.Transaction t) {
+ mStageCoordinator.updateSurfaceBounds(null /* layout */, t);
+
+ if (apps != null) {
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
}
- } else {
- // Exit split-screen and launch fullscreen since stage wasn't specified.
- mStageCoordinator.exitSplitScreen();
- }
- break;
- }
- case STAGE_TYPE_SIDE: {
- if (position != SPLIT_POSITION_UNDEFINED) {
- mStageCoordinator.setSideStagePosition(position);
- } else {
- position = mStageCoordinator.getSideStagePosition();
- }
- if (options == null) {
- options = new Bundle();
}
- mStageCoordinator.updateActivityOptions(options, position);
- break;
- }
- case STAGE_TYPE_MAIN: {
- if (position != SPLIT_POSITION_UNDEFINED) {
- // Set the side stage opposite of what we want to the main stage.
- final int sideStagePosition = position == SPLIT_POSITION_TOP_OR_LEFT
- ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
- mStageCoordinator.setSideStagePosition(sideStagePosition);
- } else {
- position = mStageCoordinator.getMainStagePosition();
- }
- if (options == null) {
- options = new Bundle();
+
+ t.apply();
+ if (finishedCallback != null) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error finishing legacy transition: ", e);
+ }
}
- mStageCoordinator.updateActivityOptions(options, position);
- break;
+
+ mSyncQueue.queue(evictWct);
}
- default:
- throw new IllegalArgumentException("Unknown stage=" + stage);
+ };
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ options = mStageCoordinator.resolveStartStage(stage, position, options, wct);
+ wct.sendPendingIntent(intent, fillInIntent, options);
+ mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
+ }
+
+ RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) {
+ if (!isSplitScreenVisible()) return null;
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+ .setContainerLayer()
+ .setName("RecentsAnimationSplitTasks")
+ .setHidden(false)
+ .setCallsite("SplitScreenController#onGoingtoRecentsLegacy");
+ mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder);
+ SurfaceControl sc = builder.build();
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+
+ // Ensure that we order these in the parent in the right z-order as their previous order
+ Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex);
+ int layer = 1;
+ for (RemoteAnimationTarget appTarget : apps) {
+ transaction.reparent(appTarget.leash, sc);
+ transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,
+ appTarget.screenSpaceBounds.top);
+ transaction.setLayer(appTarget.leash, layer++);
}
+ transaction.apply();
+ transaction.close();
+ return new RemoteAnimationTarget[]{mStageCoordinator.getDividerBarLegacyTarget()};
+ }
- return options;
+ /**
+ * Sets drag info to be logged when splitscreen is entered.
+ */
+ public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ mStageCoordinator.logOnDroppedToSplit(position, dragSessionId);
}
public void dump(@NonNull PrintWriter pw, String prefix) {
@@ -275,6 +342,38 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@ExternalThread
private class SplitScreenImpl implements SplitScreen {
private ISplitScreenImpl mISplitScreen;
+ private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
+ private final SplitScreen.SplitScreenListener mListener = new SplitScreenListener() {
+ @Override
+ public void onStagePositionChanged(int stage, int position) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onStagePositionChanged(stage, position);
+ });
+ }
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onTaskStageChanged(taskId, stage, visible);
+ });
+ }
+ }
+
+ @Override
+ public void onSplitVisibilityChanged(boolean visible) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onSplitVisibilityChanged(visible);
+ });
+ }
+ }
+ };
@Override
public ISplitScreen createExternalInterface() {
@@ -284,6 +383,48 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
return mISplitScreen;
}
+
+ @Override
+ public void onKeyguardOccludedChanged(boolean occluded) {
+ mMainExecutor.execute(() -> {
+ SplitScreenController.this.onKeyguardOccludedChanged(occluded);
+ });
+ }
+
+ @Override
+ public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
+ if (mExecutors.containsKey(listener)) return;
+
+ mMainExecutor.execute(() -> {
+ if (mExecutors.size() == 0) {
+ SplitScreenController.this.registerSplitScreenListener(mListener);
+ }
+
+ mExecutors.put(listener, executor);
+ });
+
+ executor.execute(() -> {
+ mStageCoordinator.sendStatusToListener(listener);
+ });
+ }
+
+ @Override
+ public void unregisterSplitScreenListener(SplitScreenListener listener) {
+ mMainExecutor.execute(() -> {
+ mExecutors.remove(listener);
+
+ if (mExecutors.size() == 0) {
+ SplitScreenController.this.unregisterSplitScreenListener(mListener);
+ }
+ });
+ }
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mMainExecutor.execute(() -> {
+ SplitScreenController.this.onKeyguardVisibilityChanged(showing);
+ });
+ }
}
/**
@@ -377,10 +518,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
@Override
- public void exitSplitScreen() {
+ public void exitSplitScreen(int toTopTaskId) {
executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
(controller) -> {
- controller.exitSplitScreen();
+ controller.exitSplitScreen(toTopTaskId,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT);
});
}
@@ -417,10 +559,20 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
@Override
+ public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ RemoteAnimationAdapter adapter) {
+ executeRemoteCallWithTaskPermission(mController, "startTasks",
+ (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
+ mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
+ adapter));
+ }
+
+ @Override
public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions,
@SplitPosition int sidePosition,
- @Nullable IRemoteTransition remoteTransition) {
+ @Nullable RemoteTransition remoteTransition) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
sideTaskId, sideOptions, sidePosition, remoteTransition));
@@ -444,5 +596,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
controller.startIntent(intent, fillInIntent, stage, position, options);
});
}
+
+ @Override
+ public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
+ RemoteAnimationTarget[] apps) {
+ final RemoteAnimationTarget[][] out = new RemoteAnimationTarget[][]{null};
+ executeRemoteCallWithTaskPermission(mController, "onGoingToRecentsLegacy",
+ (controller) -> out[0] = controller.onGoingToRecentsLegacy(cancel, apps),
+ true /* blocking */);
+ return out[0];
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index c37789ecbc9d..86e7b0e4cb7f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -35,7 +35,7 @@ import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
import android.view.WindowManager;
-import android.window.IRemoteTransition;
+import android.window.RemoteTransition;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -84,17 +84,19 @@ class SplitScreenTransitions {
}
void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) {
mFinishCallback = finishCallback;
mAnimatingTransition = transition;
if (mRemoteHandler != null) {
- mRemoteHandler.startAnimation(transition, info, t, mRemoteFinishCB);
+ mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction,
+ mRemoteFinishCB);
mRemoteHandler = null;
return;
}
- playInternalAnimation(transition, info, t, mainRoot, sideRoot);
+ playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot);
}
private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@@ -167,7 +169,7 @@ class SplitScreenTransitions {
/** Starts a transition to enter split with a remote transition animator. */
IBinder startEnterTransition(@WindowManager.TransitionType int transitType,
- @NonNull WindowContainerTransaction wct, @Nullable IRemoteTransition remoteTransition,
+ @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition,
@NonNull Transitions.TransitionHandler handler) {
if (remoteTransition != null) {
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
new file mode 100644
index 000000000000..319079baaccf
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -0,0 +1,324 @@
+/*
+ * 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.wm.shell.splitscreen;
+
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+
+/**
+ * Helper class that to log Drag & Drop UIEvents for a single session, see also go/uievent
+ */
+public class SplitscreenEventLogger {
+
+ // Used to generate instance ids for this drag if one is not provided
+ private final InstanceIdSequence mIdSequence;
+
+ // The instance id for the current splitscreen session (from start to end)
+ private InstanceId mLoggerSessionId;
+
+ // Drag info
+ private @SplitPosition int mDragEnterPosition;
+ private InstanceId mDragEnterSessionId;
+
+ // For deduping async events
+ private int mLastMainStagePosition = -1;
+ private int mLastMainStageUid = -1;
+ private int mLastSideStagePosition = -1;
+ private int mLastSideStageUid = -1;
+ private float mLastSplitRatio = -1f;
+
+ public SplitscreenEventLogger() {
+ mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE);
+ }
+
+ /**
+ * Return whether a splitscreen session has started.
+ */
+ public boolean hasStartedSession() {
+ return mLoggerSessionId != null;
+ }
+
+ /**
+ * May be called before logEnter() to indicate that the session was started from a drag.
+ */
+ public void enterRequestedByDrag(@SplitPosition int position, InstanceId dragSessionId) {
+ mDragEnterPosition = position;
+ mDragEnterSessionId = dragSessionId;
+ }
+
+ /**
+ * Logs when the user enters splitscreen.
+ */
+ public void logEnter(float splitRatio,
+ @SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid,
+ boolean isLandscape) {
+ mLoggerSessionId = mIdSequence.newInstanceId();
+ int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED
+ ? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape)
+ : SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
+ updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid);
+ updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid);
+ updateSplitRatioState(splitRatio);
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__ENTER,
+ enterReason,
+ 0 /* exitReason */,
+ splitRatio,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ mDragEnterSessionId != null ? mDragEnterSessionId.getId() : 0,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the user exits splitscreen. Only one of the main or side stages should be
+ * specified to indicate which position was focused as a part of exiting (both can be unset).
+ */
+ public void logExit(int exitReason, @SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if ((mainStagePosition != SPLIT_POSITION_UNDEFINED
+ && sideStagePosition != SPLIT_POSITION_UNDEFINED)
+ || (mainStageUid != 0 && sideStageUid != 0)) {
+ throw new IllegalArgumentException("Only main or side stage should be set");
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__EXIT,
+ 0 /* enterReason */,
+ exitReason,
+ 0f /* splitRatio */,
+ getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid,
+ getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+
+ // Reset states
+ mLoggerSessionId = null;
+ mDragEnterPosition = SPLIT_POSITION_UNDEFINED;
+ mDragEnterSessionId = null;
+ mLastMainStagePosition = -1;
+ mLastMainStageUid = -1;
+ mLastSideStagePosition = -1;
+ mLastSideStageUid = -1;
+ }
+
+ /**
+ * Logs when an app in the main stage changes.
+ */
+ public void logMainStageAppChange(@SplitPosition int mainStagePosition, int mainStageUid,
+ boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (!updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition,
+ isLandscape), mainStageUid)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ 0 /* sideStagePosition */,
+ 0 /* sideStageUid */,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when an app in the side stage changes.
+ */
+ public void logSideStageAppChange(@SplitPosition int sideStagePosition, int sideStageUid,
+ boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (!updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition,
+ isLandscape), sideStageUid)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ 0 /* mainStagePosition */,
+ 0 /* mainStageUid */,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the splitscreen ratio changes.
+ */
+ public void logResize(float splitRatio) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (splitRatio <= 0f || splitRatio >= 1f) {
+ // Don't bother reporting resizes that end up dismissing the split, that will be logged
+ // via the exit event
+ return;
+ }
+ if (!updateSplitRatioState(splitRatio)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__RESIZE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ mLastSplitRatio,
+ 0 /* mainStagePosition */, 0 /* mainStageUid */,
+ 0 /* sideStagePosition */, 0 /* sideStageUid */,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the apps in splitscreen are swapped.
+ */
+ public void logSwap(@SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+
+ updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid);
+ updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid);
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__SWAP,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ private boolean updateMainStageState(int mainStagePosition, int mainStageUid) {
+ boolean changed = (mLastMainStagePosition != mainStagePosition)
+ || (mLastMainStageUid != mainStageUid);
+ if (!changed) {
+ return false;
+ }
+
+ mLastMainStagePosition = mainStagePosition;
+ mLastMainStageUid = mainStageUid;
+ return true;
+ }
+
+ private boolean updateSideStageState(int sideStagePosition, int sideStageUid) {
+ boolean changed = (mLastSideStagePosition != sideStagePosition)
+ || (mLastSideStageUid != sideStageUid);
+ if (!changed) {
+ return false;
+ }
+
+ mLastSideStagePosition = sideStagePosition;
+ mLastSideStageUid = sideStageUid;
+ return true;
+ }
+
+ private boolean updateSplitRatioState(float splitRatio) {
+ boolean changed = Float.compare(mLastSplitRatio, splitRatio) != 0;
+ if (!changed) {
+ return false;
+ }
+
+ mLastSplitRatio = splitRatio;
+ return true;
+ }
+
+ public int getDragEnterReasonFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_BOTTOM;
+ }
+ }
+
+ private int getMainStagePositionFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (position == SPLIT_POSITION_UNDEFINED) {
+ return 0;
+ }
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__BOTTOM;
+ }
+ }
+
+ private int getSideStagePositionFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (position == SPLIT_POSITION_UNDEFINED) {
+ return 0;
+ }
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__BOTTOM;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 0264c5a1c55a..7be199c75816 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -18,13 +18,19 @@ package com.android.wm.shell.splitscreen;
import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
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.transitTypeToString;
-
+import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
+
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
@@ -35,6 +41,7 @@ import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
import static com.android.wm.shell.splitscreen.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
import static com.android.wm.shell.transition.Transitions.isClosingType;
import static com.android.wm.shell.transition.Transitions.isOpeningType;
@@ -42,16 +49,28 @@ import static com.android.wm.shell.transition.Transitions.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.app.WindowConfiguration;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Rect;
+import android.hardware.devicestate.DeviceStateManager;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteException;
import android.util.Log;
+import android.util.Slog;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
import android.window.DisplayAreaInfo;
-import android.window.IRemoteTransition;
+import android.window.RemoteTransition;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;
@@ -59,20 +78,27 @@ import android.window.WindowContainerTransaction;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+import com.android.wm.shell.common.split.SplitWindowManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
+
+import javax.inject.Provider;
/**
* Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -99,8 +125,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private final MainStage mMainStage;
private final StageListenerImpl mMainStageListener = new StageListenerImpl();
+ private final StageTaskUnfoldController mMainUnfoldController;
private final SideStage mSideStage;
private final StageListenerImpl mSideStageListener = new StageListenerImpl();
+ private final StageTaskUnfoldController mSideUnfoldController;
@SplitPosition
private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -114,14 +142,22 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private final Context mContext;
private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
private final SplitScreenTransitions mSplitTransitions;
- private boolean mExitSplitScreenOnHide = true;
+ private final SplitscreenEventLogger mLogger;
+ private boolean mExitSplitScreenOnHide;
+ private boolean mKeyguardOccluded;
// TODO(b/187041611): remove this flag after totally deprecated legacy split
/** Whether the device is supporting legacy split or not. */
private boolean mUseLegacySplit;
- @SplitScreen.StageType int mDismissTop = NO_DISMISS;
+ @SplitScreen.StageType
+ private int mDismissTop = NO_DISMISS;
+
+ /** The target stage to dismiss to when unlock after folded. */
+ @SplitScreen.StageType
+ private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
private final Runnable mOnTransitionAnimationComplete = () -> {
// If still playing, let it finish.
@@ -134,29 +170,60 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDismissTop = NO_DISMISS;
};
+ private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
+ new SplitWindowManager.ParentContainerCallbacks() {
+ @Override
+ public void attachToParentSurface(SurfaceControl.Builder b) {
+ mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b);
+ }
+
+ @Override
+ public void onLeashReady(SurfaceControl leash) {
+ mSyncQueue.runInSync(t -> applyDividerVisibility(t));
+ }
+ };
+
StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
- DisplayImeController displayImeController, Transitions transitions,
- TransactionPool transactionPool) {
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController, Transitions transitions,
+ TransactionPool transactionPool, SplitscreenEventLogger logger,
+ IconProvider iconProvider,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
mRootTDAOrganizer = rootTDAOrganizer;
mTaskOrganizer = taskOrganizer;
+ mLogger = logger;
+ mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
+
mMainStage = new MainStage(
+ mContext,
mTaskOrganizer,
mDisplayId,
mMainStageListener,
mSyncQueue,
- mSurfaceSession);
+ mSurfaceSession,
+ iconProvider,
+ mMainUnfoldController);
mSideStage = new SideStage(
+ mContext,
mTaskOrganizer,
mDisplayId,
mSideStageListener,
mSyncQueue,
- mSurfaceSession);
+ mSurfaceSession,
+ iconProvider,
+ mSideUnfoldController);
mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
mRootTDAOrganizer.registerListener(displayId, this);
+ final DeviceStateManager deviceStateManager =
+ mContext.getSystemService(DeviceStateManager.class);
+ deviceStateManager.registerCallback(taskOrganizer.getExecutor(),
+ new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged));
mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
mOnTransitionAnimationComplete);
transitions.addHandler(this);
@@ -166,7 +233,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController,
- SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool) {
+ DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
+ Transitions transitions, TransactionPool transactionPool,
+ SplitscreenEventLogger logger,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -175,10 +245,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage = mainStage;
mSideStage = sideStage;
mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
mRootTDAOrganizer.registerListener(displayId, this);
mSplitLayout = splitLayout;
mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
mOnTransitionAnimationComplete);
+ mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mLogger = logger;
transitions.addHandler(this);
}
@@ -194,9 +268,13 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
@SplitPosition int sideStagePosition) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- setSideStagePosition(sideStagePosition);
- mMainStage.activate(getMainStageBounds(), wct);
- mSideStage.addTask(task, getSideStageBounds(), wct);
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ setSideStagePosition(sideStagePosition, wct);
+ mSideStage.evictAllChildren(evictWct);
+ mSideStage.addTask(task, wct);
+ if (!evictWct.isEmpty()) {
+ wct.merge(evictWct, true /* transfer */);
+ }
mTaskOrganizer.applyTransaction(wct);
return true;
}
@@ -218,15 +296,15 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Starts 2 tasks in one transition. */
void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
@Nullable Bundle sideOptions, @SplitPosition int sidePosition,
- @Nullable IRemoteTransition remoteTransition) {
+ @Nullable RemoteTransition remoteTransition) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mainOptions = mainOptions != null ? mainOptions : new Bundle();
sideOptions = sideOptions != null ? sideOptions : new Bundle();
- setSideStagePosition(sidePosition);
+ setSideStagePosition(sidePosition, wct);
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
- mMainStage.activate(getMainStageBounds(), wct);
+ mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
mSideStage.setBounds(getSideStageBounds(), wct);
// Make sure the launch options will put tasks in the corresponding split roots
@@ -241,6 +319,150 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this);
}
+ /** Starts 2 tasks in one legacy transition. */
+ void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ RemoteAnimationAdapter adapter) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Need to add another wrapper here in shell so that we can inject the divider bar
+ // and also manage the process elevation via setRunningRemote
+ IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
+ RemoteAnimationTarget[] augmentedNonApps =
+ new RemoteAnimationTarget[nonApps.length + 1];
+ for (int i = 0; i < nonApps.length; ++i) {
+ augmentedNonApps[i] = nonApps[i];
+ }
+ augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget();
+ try {
+ ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
+ adapter.getCallingApplication());
+ adapter.getRunner().onAnimationStart(transit, apps, wallpapers, nonApps,
+ finishedCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ try {
+ adapter.getRunner().onAnimationCancelled();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ }
+ };
+ RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
+ wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
+
+ if (mainOptions == null) {
+ mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
+ } else {
+ ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
+ mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
+ }
+
+ sideOptions = sideOptions != null ? sideOptions : new Bundle();
+ setSideStagePosition(sidePosition, wct);
+
+ // Build a request WCT that will launch both apps such that task 0 is on the main stage
+ // while task 1 is on the side stage.
+ mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
+ mSideStage.setBounds(getSideStageBounds(), wct);
+
+ // Make sure the launch options will put tasks in the corresponding split roots
+ addActivityOptions(mainOptions, mMainStage);
+ addActivityOptions(sideOptions, mSideStage);
+
+ // Add task launch requests
+ wct.startTask(mainTaskId, mainOptions);
+ wct.startTask(sideTaskId, sideOptions);
+
+ // Using legacy transitions, so we can't use blast sync since it conflicts.
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ public void startIntent(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @androidx.annotation.Nullable Bundle options,
+ @Nullable RemoteTransition remoteTransition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ options = resolveStartStage(stage, position, options, wct);
+ wct.sendPendingIntent(intent, fillInIntent, options);
+ mSplitTransitions.startEnterTransition(
+ TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this);
+ }
+
+ /**
+ * Collects all the current child tasks of a specific split and prepares transaction to evict
+ * them to display.
+ */
+ void prepareEvictChildTasks(@SplitPosition int position, WindowContainerTransaction wct) {
+ if (position == mSideStagePosition) {
+ mSideStage.evictAllChildren(wct);
+ } else {
+ mMainStage.evictAllChildren(wct);
+ }
+ }
+
+ Bundle resolveStartStage(@SplitScreen.StageType int stage,
+ @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
+ @androidx.annotation.Nullable WindowContainerTransaction wct) {
+ switch (stage) {
+ case STAGE_TYPE_UNDEFINED: {
+ // Use the stage of the specified position is valid.
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ if (position == getSideStagePosition()) {
+ options = resolveStartStage(STAGE_TYPE_SIDE, position, options, wct);
+ } else {
+ options = resolveStartStage(STAGE_TYPE_MAIN, position, options, wct);
+ }
+ } else {
+ // Exit split-screen and launch fullscreen since stage wasn't specified.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+ }
+ break;
+ }
+ case STAGE_TYPE_SIDE: {
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ setSideStagePosition(position, wct);
+ } else {
+ position = getSideStagePosition();
+ }
+ if (options == null) {
+ options = new Bundle();
+ }
+ updateActivityOptions(options, position);
+ break;
+ }
+ case STAGE_TYPE_MAIN: {
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ // Set the side stage opposite of what we want to the main stage.
+ final int sideStagePosition = position == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
+ setSideStagePosition(sideStagePosition, wct);
+ } else {
+ position = getMainStagePosition();
+ }
+ if (options == null) {
+ options = new Bundle();
+ }
+ updateActivityOptions(options, position);
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unknown stage=" + stage);
+ }
+
+ return options;
+ }
+
@SplitLayout.SplitPosition
int getSideStagePosition() {
return mSideStagePosition;
@@ -252,18 +474,25 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
}
- void setSideStagePosition(@SplitPosition int sideStagePosition) {
- setSideStagePosition(sideStagePosition, true /* updateVisibility */);
+ void setSideStagePosition(@SplitPosition int sideStagePosition,
+ @Nullable WindowContainerTransaction wct) {
+ setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
}
- private void setSideStagePosition(@SplitPosition int sideStagePosition,
- boolean updateVisibility) {
+ private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
+ @Nullable WindowContainerTransaction wct) {
if (mSideStagePosition == sideStagePosition) return;
mSideStagePosition = sideStagePosition;
sendOnStagePositionChanged();
- if (mSideStageListener.mVisible && updateVisibility) {
- onStageVisibilityChanged(mSideStageListener);
+ if (mSideStageListener.mVisible && updateBounds) {
+ if (wct == null) {
+ // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
+ onLayoutSizeChanged(mSplitLayout);
+ } else {
+ updateWindowBounds(mSplitLayout, wct);
+ updateUnfoldBounds();
+ }
}
}
@@ -275,24 +504,69 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mTaskOrganizer.applyTransaction(wct);
}
- void exitSplitScreen() {
- exitSplitScreen(null /* childrenToTop */);
+ void onKeyguardOccludedChanged(boolean occluded) {
+ // Do not exit split directly, because it needs to wait for task info update to determine
+ // which task should remain on top after split dismissed.
+ mKeyguardOccluded = occluded;
+ }
+
+ void onKeyguardVisibilityChanged(boolean showing) {
+ if (!showing && mMainStage.isActive()
+ && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+ exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED);
+ }
}
void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
mExitSplitScreenOnHide = exitSplitScreenOnHide;
}
- private void exitSplitScreen(StageTaskListener childrenToTop) {
+ void exitSplitScreen(int toTopTaskId, int exitReason) {
+ StageTaskListener childrenToTop = null;
+ if (mMainStage.containsTask(toTopTaskId)) {
+ childrenToTop = mMainStage;
+ } else if (mSideStage.containsTask(toTopTaskId)) {
+ childrenToTop = mSideStage;
+ }
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (childrenToTop != null) {
+ childrenToTop.reorderChild(toTopTaskId, true /* onTop */, wct);
+ }
+ applyExitSplitScreen(childrenToTop, wct, exitReason);
+ }
+
+ private void exitSplitScreen(StageTaskListener childrenToTop, int exitReason) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ applyExitSplitScreen(childrenToTop, wct, exitReason);
+ }
+
+ private void applyExitSplitScreen(StageTaskListener childrenToTop,
+ WindowContainerTransaction wct, int exitReason) {
mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
mMainStage.deactivate(wct, childrenToTop == mMainStage);
mTaskOrganizer.applyTransaction(wct);
- // Reset divider position.
+ mSyncQueue.runInSync(t -> t
+ .setWindowCrop(mMainStage.mRootLeash, null)
+ .setWindowCrop(mSideStage.mRootLeash, null));
+ // Hide divider and reset its position.
+ setDividerVisibility(false);
mSplitLayout.resetDividerPosition();
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ if (childrenToTop != null) {
+ logExitToStage(exitReason, childrenToTop == mMainStage);
+ } else {
+ logExit(exitReason);
+ }
}
- private void prepareExitSplitScreen(@SplitScreen.StageType int stageToTop,
+ /**
+ * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
+ * an existing WindowContainerTransaction (rather than applying immediately). This is intended
+ * to be used when exiting split might be bundled with other window operations.
+ */
+ void prepareExitSplitScreen(@SplitScreen.StageType int stageToTop,
@NonNull WindowContainerTransaction wct) {
mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
@@ -309,29 +583,26 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void updateActivityOptions(Bundle opts, @SplitPosition int position) {
addActivityOptions(opts, position == mSideStagePosition ? mSideStage : mMainStage);
-
- if (!mMainStage.isActive()) {
- // Activate the main stage in anticipation of an app launch.
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mMainStage.activate(getMainStageBounds(), wct);
- mSideStage.setBounds(getSideStageBounds(), wct);
- mTaskOrganizer.applyTransaction(wct);
- }
}
void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
if (mListeners.contains(listener)) return;
mListeners.add(listener);
- listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
- listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
- mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
- mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
+ sendStatusToListener(listener);
}
void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
mListeners.remove(listener);
}
+ void sendStatusToListener(SplitScreen.SplitScreenListener listener) {
+ listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+ listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+ listener.onSplitVisibilityChanged(isSplitScreenVisible());
+ mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
+ mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
+ }
+
private void sendOnStagePositionChanged() {
for (int i = mListeners.size() - 1; i >= 0; --i) {
final SplitScreen.SplitScreenListener l = mListeners.get(i);
@@ -340,9 +611,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- private void onStageChildTaskStatusChanged(
- StageListenerImpl stageListener, int taskId, boolean present, boolean visible) {
-
+ private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId,
+ boolean present, boolean visible) {
int stage;
if (present) {
stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
@@ -350,12 +620,31 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// No longer on any stage
stage = STAGE_TYPE_UNDEFINED;
}
+ if (stage == STAGE_TYPE_MAIN) {
+ mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ } else {
+ mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onTaskStageChanged(taskId, stage, visible);
}
}
+ private void sendSplitVisibilityChanged() {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ final SplitScreen.SplitScreenListener l = mListeners.get(i);
+ l.onSplitVisibilityChanged(mDividerVisible);
+ }
+
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+ mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+ }
+ }
+
private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
mUseLegacySplit = mContext.getResources().getBoolean(R.bool.config_useLegacySplit);
@@ -392,83 +681,69 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDividerVisible = visible;
if (visible) {
mSplitLayout.init();
+ updateUnfoldBounds();
} else {
mSplitLayout.release();
}
+ sendSplitVisibilityChanged();
}
private void onStageVisibilityChanged(StageListenerImpl stageListener) {
final boolean sideStageVisible = mSideStageListener.mVisible;
final boolean mainStageVisible = mMainStageListener.mVisible;
- // Divider is only visible if both the main stage and side stages are visible
- setDividerVisibility(isSplitScreenVisible());
-
- if (mExitSplitScreenOnHide && !mainStageVisible && !sideStageVisible) {
- // Exit split-screen if both stage are not visible.
- // TODO: This is only a temporary request from UX and is likely to be removed soon...
- exitSplitScreen();
+ final boolean bothStageVisible = sideStageVisible && mainStageVisible;
+ final boolean bothStageInvisible = !sideStageVisible && !mainStageVisible;
+ final boolean sameVisibility = sideStageVisible == mainStageVisible;
+ // Only add or remove divider when both visible or both invisible to avoid sometimes we only
+ // got one stage visibility changed for a moment and it will cause flicker.
+ if (sameVisibility) {
+ setDividerVisibility(bothStageVisible);
}
- if (mainStageVisible) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (sideStageVisible) {
- // The main stage configuration should to follow split layout when side stage is
- // visible.
- mMainStage.updateConfiguration(
- WINDOWING_MODE_MULTI_WINDOW, getMainStageBounds(), wct);
- } else {
- // We want the main stage configuration to be fullscreen when the side stage isn't
- // visible.
- mMainStage.updateConfiguration(WINDOWING_MODE_FULLSCREEN, null, wct);
+ if (bothStageInvisible) {
+ if (mExitSplitScreenOnHide
+ // Don't dismiss staged split when both stages are not visible due to sleeping
+ // display, like the cases keyguard showing or screen off.
+ || (!mMainStage.mRootTaskInfo.isSleeping
+ && !mSideStage.mRootTaskInfo.isSleeping)) {
+ exitSplitScreen(null /* childrenToTop */,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME);
}
- // TODO: Change to `mSyncQueue.queue(wct)` once BLAST is stable.
- mTaskOrganizer.applyTransaction(wct);
+ } else if (mKeyguardOccluded) {
+ // At least one of the stages is visible while keyguard occluded. Dismiss split because
+ // there's show-when-locked activity showing on top of keyguard. Also make sure the
+ // task contains show-when-locked activity remains on top after split dismissed.
+ final StageTaskListener toTop =
+ mainStageVisible ? mMainStage : (sideStageVisible ? mSideStage : null);
+ exitSplitScreen(toTop, SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP);
}
mSyncQueue.runInSync(t -> {
- final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
- final SurfaceControl sideStageLeash = mSideStage.mRootLeash;
- final SurfaceControl mainStageLeash = mMainStage.mRootLeash;
-
- if (dividerLeash != null) {
- if (mDividerVisible) {
- t.show(dividerLeash)
- .setLayer(dividerLeash, Integer.MAX_VALUE)
- .setPosition(dividerLeash,
- mSplitLayout.getDividerBounds().left,
- mSplitLayout.getDividerBounds().top);
- } else {
- t.hide(dividerLeash);
- }
+ // Same above, we only set root tasks and divider leash visibility when both stage
+ // change to visible or invisible to avoid flicker.
+ if (sameVisibility) {
+ t.setVisibility(mSideStage.mRootLeash, bothStageVisible)
+ .setVisibility(mMainStage.mRootLeash, bothStageVisible);
+ applyDividerVisibility(t);
}
+ });
+ }
- if (sideStageVisible) {
- final Rect sideStageBounds = getSideStageBounds();
- t.show(sideStageLeash)
- .setPosition(sideStageLeash,
- sideStageBounds.left, sideStageBounds.top)
- .setWindowCrop(sideStageLeash,
- sideStageBounds.width(), sideStageBounds.height());
- } else {
- t.hide(sideStageLeash);
- }
+ private void applyDividerVisibility(SurfaceControl.Transaction t) {
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ if (dividerLeash == null) {
+ return;
+ }
- if (mainStageVisible) {
- final Rect mainStageBounds = getMainStageBounds();
- t.show(mainStageLeash);
- if (sideStageVisible) {
- t.setPosition(mainStageLeash, mainStageBounds.left, mainStageBounds.top)
- .setWindowCrop(mainStageLeash,
- mainStageBounds.width(), mainStageBounds.height());
- } else {
- // Clear window crop and position if side stage isn't visible.
- t.setPosition(mainStageLeash, 0, 0)
- .setWindowCrop(mainStageLeash, null);
- }
- } else {
- t.hide(mainStageLeash);
- }
- });
+ if (mDividerVisible) {
+ t.show(dividerLeash)
+ .setLayer(dividerLeash, SPLIT_DIVIDER_LAYER)
+ .setPosition(dividerLeash,
+ mSplitLayout.getDividerBounds().left,
+ mSplitLayout.getDividerBounds().top);
+ } else {
+ t.hide(dividerLeash);
+ }
}
private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
@@ -477,21 +752,25 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!hasChildren) {
if (isSideStage && mMainStageListener.mVisible) {
// Exit to main stage if side stage no longer has children.
- exitSplitScreen(mMainStage);
+ exitSplitScreen(mMainStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
} else if (!isSideStage && mSideStageListener.mVisible) {
// Exit to side stage if main stage no longer has children.
- exitSplitScreen(mSideStage);
+ exitSplitScreen(mSideStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
}
} else if (isSideStage) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
// Make sure the main stage is active.
- mMainStage.activate(getMainStageBounds(), wct);
- mSideStage.setBounds(getSideStageBounds(), wct);
- // Reorder side stage to the top whenever there's a new child task appeared in side
- // stage. This is needed to prevent main stage occludes side stage and makes main stage
- // flipping between fullscreen and multi-window windowing mode.
- wct.reorder(mSideStage.mRootTaskInfo.token, true);
- mTaskOrganizer.applyTransaction(wct);
+ mMainStage.activate(getMainStageBounds(), wct, true /* reparent */);
+ mSideStage.moveToTop(getSideStageBounds(), wct);
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(mSplitLayout, t));
+ }
+ if (!mLogger.hasStartedSession() && mMainStageListener.mHasChildren
+ && mSideStageListener.mHasChildren) {
+ mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
+ getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
}
}
@@ -511,38 +790,73 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
onSnappedToDismissTransition(mainStageToTop);
return;
}
- exitSplitScreen(mainStageToTop ? mMainStage : mSideStage);
+ exitSplitScreen(mainStageToTop ? mMainStage : mSideStage,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER);
}
@Override
public void onDoubleTappedDivider() {
setSideStagePosition(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
- ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT);
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT, null /* wct */);
+ mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
}
@Override
- public void onBoundsChanging(SplitLayout layout) {
+ public void onLayoutPositionChanging(SplitLayout layout) {
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
+ }
+
+ @Override
+ public void onLayoutSizeChanging(SplitLayout layout) {
+ mSyncQueue.runInSync(t -> {
+ updateSurfaceBounds(layout, t);
+ mMainStage.onResizing(getMainStageBounds(), t);
+ mSideStage.onResizing(getSideStageBounds(), t);
+ });
+ }
+
+ @Override
+ public void onLayoutSizeChanged(SplitLayout layout) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ updateWindowBounds(layout, wct);
+ updateUnfoldBounds();
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> {
+ updateSurfaceBounds(layout, t);
+ mMainStage.onResized(getMainStageBounds(), t);
+ mSideStage.onResized(getSideStageBounds(), t);
+ });
+ mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
+ }
+
+ private void updateUnfoldBounds() {
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.onLayoutChanged(getMainStageBounds());
+ mSideUnfoldController.onLayoutChanged(getSideStageBounds());
+ }
+ }
+
+ /**
+ * Populates `wct` with operations that match the split windows to the current layout.
+ * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
+ */
+ private void updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) {
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
-
- mSyncQueue.runInSync(t -> layout.applySurfaceChanges(t, topLeftStage.mRootLeash,
- bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer));
+ layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo);
}
- @Override
- public void onBoundsChanged(SplitLayout layout) {
+ void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t) {
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
-
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo);
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> layout.applySurfaceChanges(t, topLeftStage.mRootLeash,
- bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer));
+ (layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash,
+ bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer);
}
@Override
@@ -561,13 +875,30 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
@Override
+ public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo,
+ bottomRightStage.mRootTaskInfo);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ @Override
public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) {
mDisplayAreaInfo = displayAreaInfo;
if (mSplitLayout == null) {
mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
- mDisplayAreaInfo.configuration, this,
- b -> mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b),
- mDisplayImeController, mTaskOrganizer);
+ mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
+ mDisplayImeController, mTaskOrganizer, false /* applyDismissingParallax */);
+ mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
+
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.init();
+ mSideUnfoldController.init();
+ }
}
}
@@ -580,8 +911,20 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) {
mDisplayAreaInfo = displayAreaInfo;
if (mSplitLayout != null
- && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)) {
- onBoundsChanged(mSplitLayout);
+ && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)
+ && mMainStage.isActive()) {
+ onLayoutSizeChanged(mSplitLayout);
+ }
+ }
+
+ private void onFoldedStateChanged(boolean folded) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ if (!folded) return;
+
+ if (mMainStage.isFocused()) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
+ } else if (mSideStage.isFocused()) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
}
}
@@ -672,7 +1015,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public boolean startAnimation(@NonNull IBinder transition,
@NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (transition != mSplitTransitions.mPendingDismiss
&& transition != mSplitTransitions.mPendingEnter) {
@@ -717,14 +1061,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
boolean shouldAnimate = true;
if (mSplitTransitions.mPendingEnter == transition) {
- shouldAnimate = startPendingEnterAnimation(transition, info, t);
+ shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
} else if (mSplitTransitions.mPendingDismiss == transition) {
- shouldAnimate = startPendingDismissAnimation(transition, info, t);
+ shouldAnimate = startPendingDismissAnimation(transition, info, startTransaction);
}
if (!shouldAnimate) return false;
- mSplitTransitions.playAnimation(transition, info, t, finishCallback,
- mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+ mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction,
+ finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
return true;
}
@@ -754,7 +1098,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Update local states (before animating).
setDividerVisibility(true);
- setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateVisibility */);
+ setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */,
+ null /* wct */);
setSplitsVisible(true);
addDividerBarToTransition(info, t, true /* show */);
@@ -854,12 +1199,22 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Be default, make it visible. The remote animator can adjust alpha if it plans to animate.
if (show) {
t.setAlpha(leash, 1.f);
- t.setLayer(leash, Integer.MAX_VALUE);
+ t.setLayer(leash, SPLIT_DIVIDER_LAYER);
t.setPosition(leash, bounds.left, bounds.top);
t.show(leash);
}
}
+ RemoteAnimationTarget getDividerBarLegacyTarget() {
+ final Rect bounds = mSplitLayout.getDividerBounds();
+ return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
+ mSplitLayout.getDividerLeash(), false /* isTranslucent */, null /* clipRect */,
+ null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
+ new android.graphics.Point(0, 0) /* position */, bounds, bounds,
+ new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
+ null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
+ }
+
@Override
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
@@ -884,6 +1239,36 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible;
}
+ /**
+ * Sets drag info to be logged when splitscreen is next entered.
+ */
+ public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ mLogger.enterRequestedByDrag(position, dragSessionId);
+ }
+
+ /**
+ * Logs the exit of splitscreen.
+ */
+ private void logExit(int exitReason) {
+ mLogger.logExit(exitReason,
+ SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */,
+ SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */,
+ mSplitLayout.isLandscape());
+ }
+
+ /**
+ * Logs the exit of splitscreen to a specific stage. This must be called before the exit is
+ * executed.
+ */
+ private void logExitToStage(int exitReason, boolean toMainStage) {
+ mLogger.logExit(exitReason,
+ toMainStage ? getMainStagePosition() : SPLIT_POSITION_UNDEFINED,
+ toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */,
+ !toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED,
+ !toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */,
+ mSplitLayout.isLandscape());
+ }
+
class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
boolean mHasRootTask = false;
boolean mVisible = false;
@@ -923,7 +1308,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onNoLongerSupportMultiWindow() {
if (mMainStage.isActive()) {
- StageCoordinator.this.exitSplitScreen();
+ StageCoordinator.this.exitSplitScreen(null /* childrenToTop */,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 0fd8eca6290e..5100c5625db6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -24,7 +24,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.annotation.CallSuper;
+import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.SparseArray;
@@ -34,9 +36,11 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SurfaceUtils;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.split.SplitDecorManager;
import java.io.PrintWriter;
@@ -67,25 +71,36 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
void onRootTaskVanished();
+
void onNoLongerSupportMultiWindow();
}
+ private final Context mContext;
private final StageListenerCallbacks mCallbacks;
- private final SyncTransactionQueue mSyncQueue;
private final SurfaceSession mSurfaceSession;
+ private final SyncTransactionQueue mSyncQueue;
+ private final IconProvider mIconProvider;
protected ActivityManager.RunningTaskInfo mRootTaskInfo;
protected SurfaceControl mRootLeash;
protected SurfaceControl mDimLayer;
protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
+ // TODO(b/204308910): Extracts SplitDecorManager related code to common package.
+ private SplitDecorManager mSplitDecorManager;
- StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
+ private final StageTaskUnfoldController mStageTaskUnfoldController;
+
+ StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession) {
+ SurfaceSession surfaceSession, IconProvider iconProvider,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ mContext = context;
mCallbacks = callbacks;
mSyncQueue = syncQueue;
mSurfaceSession = surfaceSession;
+ mIconProvider = iconProvider;
+ mStageTaskUnfoldController = stageTaskUnfoldController;
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
}
@@ -97,12 +112,49 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
return mChildrenTaskInfo.contains(taskId);
}
+ /**
+ * Returns the top activity uid for the top child task.
+ */
+ int getTopChildTaskUid() {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i);
+ if (info.topActivityInfo == null) {
+ continue;
+ }
+ return info.topActivityInfo.applicationInfo.uid;
+ }
+ return 0;
+ }
+
+ /** @return {@code true} if this listener contains the currently focused task. */
+ boolean isFocused() {
+ if (mRootTaskInfo == null) {
+ return false;
+ }
+
+ if (mRootTaskInfo.isFocused) {
+ return true;
+ }
+
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ if (mChildrenTaskInfo.valueAt(i).isFocused) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
@Override
@CallSuper
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
if (mRootTaskInfo == null && !taskInfo.hasParentTask()) {
mRootLeash = leash;
mRootTaskInfo = taskInfo;
+ mSplitDecorManager = new SplitDecorManager(
+ mRootTaskInfo.configuration,
+ mIconProvider,
+ mSurfaceSession);
mCallbacks.onRootTaskAppeared();
sendStatusChanged();
mSyncQueue.runInSync(t -> mDimLayer =
@@ -122,6 +174,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
}
+
+ if (mStageTaskUnfoldController != null) {
+ mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash);
+ }
}
@Override
@@ -133,6 +189,17 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
return;
}
if (mRootTaskInfo.taskId == taskInfo.taskId) {
+ // Inflates split decor view only when the root task is visible.
+ if (mRootTaskInfo.isVisible != taskInfo.isVisible) {
+ mSyncQueue.runInSync(t -> {
+ if (taskInfo.isVisible) {
+ mSplitDecorManager.inflate(mContext, mRootLeash,
+ taskInfo.configuration.windowConfiguration.getBounds());
+ } else {
+ mSplitDecorManager.release(t);
+ }
+ });
+ }
mRootTaskInfo = taskInfo;
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
@@ -159,8 +226,11 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
final int taskId = taskInfo.taskId;
if (mRootTaskInfo.taskId == taskId) {
mCallbacks.onRootTaskVanished();
- mSyncQueue.runInSync(t -> t.remove(mDimLayer));
mRootTaskInfo = null;
+ mSyncQueue.runInSync(t -> {
+ t.remove(mDimLayer);
+ mSplitDecorManager.release(t);
+ });
} else if (mChildrenTaskInfo.contains(taskId)) {
mChildrenTaskInfo.remove(taskId);
mChildrenLeashes.remove(taskId);
@@ -174,6 +244,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
}
+
+ if (mStageTaskUnfoldController != null) {
+ mStageTaskUnfoldController.onTaskVanished(taskInfo);
+ }
}
@Override
@@ -187,10 +261,37 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
}
+ void onResizing(Rect newBounds, SurfaceControl.Transaction t) {
+ if (mSplitDecorManager != null && mRootTaskInfo != null) {
+ mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, t);
+ }
+ }
+
+ void onResized(Rect newBounds, SurfaceControl.Transaction t) {
+ if (mSplitDecorManager != null) {
+ mSplitDecorManager.onResized(newBounds, t);
+ }
+ }
+
void setBounds(Rect bounds, WindowContainerTransaction wct) {
wct.setBounds(mRootTaskInfo.token, bounds);
}
+ void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
+ if (!containsTask(taskId)) {
+ return;
+ }
+ wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
+ }
+
+ /** Collects all the current child tasks and prepares transaction to evict them to display. */
+ void evictAllChildren(WindowContainerTransaction wct) {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
+ final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
+ wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
+ }
+ }
+
void setVisibility(boolean visible, WindowContainerTransaction wct) {
wct.reorder(mRootTaskInfo.token, visible /* onTop */);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java
new file mode 100644
index 000000000000..e904f6a9e22c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java
@@ -0,0 +1,224 @@
+/*
+ * 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.wm.shell.splitscreen;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.animation.RectEvaluator;
+import android.animation.TypeEvaluator;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.unfold.UnfoldBackgroundController;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Controls transformations of the split screen task surfaces in response
+ * to the unfolding/folding action on foldable devices
+ */
+public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener {
+
+ private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
+ private static final float CROPPING_START_MARGIN_FRACTION = 0.05f;
+
+ private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
+ private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final UnfoldBackgroundController mBackgroundController;
+ private final Executor mExecutor;
+ private final int mExpandedTaskBarHeight;
+ private final float mWindowCornerRadiusPx;
+ private final Rect mStageBounds = new Rect();
+ private final TransactionPool mTransactionPool;
+
+ private InsetsSource mTaskbarInsetsSource;
+ private boolean mBothStagesVisible;
+
+ public StageTaskUnfoldController(@NonNull Context context,
+ @NonNull TransactionPool transactionPool,
+ @NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
+ @NonNull DisplayInsetsController displayInsetsController,
+ @NonNull UnfoldBackgroundController backgroundController,
+ @NonNull Executor executor) {
+ mUnfoldProgressProvider = unfoldProgressProvider;
+ mTransactionPool = transactionPool;
+ mExecutor = executor;
+ mBackgroundController = backgroundController;
+ mDisplayInsetsController = displayInsetsController;
+ mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.taskbar_frame_height);
+ }
+
+ /**
+ * Initializes the controller, starts listening for the external events
+ */
+ public void init() {
+ mUnfoldProgressProvider.addListener(mExecutor, this);
+ mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ context.update();
+ }
+ }
+
+ /**
+ * Called when split screen task appeared
+ * @param taskInfo info for the appeared task
+ * @param leash surface leash for the appeared task
+ */
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ AnimationContext context = new AnimationContext(leash);
+ mAnimationContextByTaskId.put(taskInfo.taskId, context);
+ }
+
+ /**
+ * Called when a split screen task vanished
+ * @param taskInfo info for the vanished task
+ */
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
+ if (context != null) {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ resetSurface(transaction, context);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ }
+ mAnimationContextByTaskId.remove(taskInfo.taskId);
+ }
+
+ @Override
+ public void onStateChangeProgress(float progress) {
+ if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return;
+
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ mBackgroundController.ensureBackground(transaction);
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+
+ context.mCurrentCropRect.set(RECT_EVALUATOR
+ .evaluate(progress, context.mStartCropRect, context.mEndCropRect));
+
+ transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
+ .setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
+ }
+
+ transaction.apply();
+
+ mTransactionPool.release(transaction);
+ }
+
+ @Override
+ public void onStateChangeFinished() {
+ resetTransformations();
+ }
+
+ /**
+ * Called when split screen visibility changes
+ * @param bothStagesVisible true if both stages of the split screen are visible
+ */
+ public void onSplitVisibilityChanged(boolean bothStagesVisible) {
+ mBothStagesVisible = bothStagesVisible;
+ if (!bothStagesVisible) {
+ resetTransformations();
+ }
+ }
+
+ /**
+ * Called when split screen stage bounds changed
+ * @param bounds new bounds for this stage
+ */
+ public void onLayoutChanged(Rect bounds) {
+ mStageBounds.set(bounds);
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ context.update();
+ }
+ }
+
+ private void resetTransformations() {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ resetSurface(transaction, context);
+ }
+ mBackgroundController.removeBackground(transaction);
+ transaction.apply();
+
+ mTransactionPool.release(transaction);
+ }
+
+ private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) {
+ transaction
+ .setWindowCrop(context.mLeash, null)
+ .setCornerRadius(context.mLeash, 0.0F);
+ }
+
+ private class AnimationContext {
+ final SurfaceControl mLeash;
+ final Rect mStartCropRect = new Rect();
+ final Rect mEndCropRect = new Rect();
+ final Rect mCurrentCropRect = new Rect();
+
+ private AnimationContext(SurfaceControl leash) {
+ this.mLeash = leash;
+ update();
+ }
+
+ private void update() {
+ mStartCropRect.set(mStageBounds);
+
+ if (mTaskbarInsetsSource != null) {
+ // Only insets the cropping window with taskbar when taskbar is expanded
+ if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ mStartCropRect.inset(mTaskbarInsetsSource
+ .calculateVisibleInsets(mStartCropRect));
+ }
+ }
+
+ // Offset to surface coordinates as layout bounds are in screen coordinates
+ mStartCropRect.offsetTo(0, 0);
+
+ mEndCropRect.set(mStartCropRect);
+
+ int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height());
+ int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION);
+ mStartCropRect.inset(margin, margin, margin, margin);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl
new file mode 100644
index 000000000000..45f6d3c8b154
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl
@@ -0,0 +1,103 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.window.RemoteTransition;
+
+import com.android.wm.shell.stagesplit.ISplitScreenListener;
+
+/**
+ * Interface that is exposed to remote callers to manipulate the splitscreen feature.
+ */
+interface ISplitScreen {
+
+ /**
+ * Registers a split screen listener.
+ */
+ oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1;
+
+ /**
+ * Unregisters a split screen listener.
+ */
+ oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
+
+ /**
+ * Hides the side-stage if it is currently visible.
+ */
+ oneway void setSideStageVisibility(boolean visible) = 3;
+
+ /**
+ * Removes a task from the side stage.
+ */
+ oneway void removeFromSideStage(int taskId) = 4;
+
+ /**
+ * Removes the split-screen stages and leaving indicated task to top. Passing INVALID_TASK_ID
+ * to indicate leaving no top task after leaving split-screen.
+ */
+ oneway void exitSplitScreen(int toTopTaskId) = 5;
+
+ /**
+ * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible.
+ */
+ oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6;
+
+ /**
+ * Starts a task in a stage.
+ */
+ oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7;
+
+ /**
+ * Starts a shortcut in a stage.
+ */
+ oneway void startShortcut(String packageName, String shortcutId, int stage, int position,
+ in Bundle options, in UserHandle user) = 8;
+
+ /**
+ * Starts an activity in a stage.
+ */
+ oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage,
+ int position, in Bundle options) = 9;
+
+ /**
+ * Starts tasks simultaneously in one transition.
+ */
+ oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
+ in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10;
+
+ /**
+ * Version of startTasks using legacy transition system.
+ */
+ oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
+ int sideTaskId, in Bundle sideOptions, int sidePosition,
+ in RemoteAnimationAdapter adapter) = 11;
+
+ /**
+ * Blocking call that notifies and gets additional split-screen targets when entering
+ * recents (for example: the dividerBar).
+ * @param cancel is true if leaving recents back to split (eg. the gesture was cancelled).
+ * @param appTargets apps that will be re-parented to display area
+ */
+ RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
+ in RemoteAnimationTarget[] appTargets) = 12;
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl
new file mode 100644
index 000000000000..46e4299f99fa
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
+ */
+oneway interface ISplitScreenListener {
+
+ /**
+ * Called when the stage position changes.
+ */
+ void onStagePositionChanged(int stage, int position);
+
+ /**
+ * Called when a task changes stages.
+ */
+ void onTaskStageChanged(int taskId, int stage, boolean visible);
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java
new file mode 100644
index 000000000000..83855be91e04
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java
@@ -0,0 +1,104 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.view.SurfaceSession;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Main stage for split-screen mode. When split-screen is active all standard activity types launch
+ * on the main stage, except for task that are explicitly pinned to the {@link SideStage}.
+ * @see StageCoordinator
+ */
+class MainStage extends StageTaskListener {
+ private static final String TAG = MainStage.class.getSimpleName();
+
+ private boolean mIsActive = false;
+
+ MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
+ SurfaceSession surfaceSession,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+ stageTaskUnfoldController);
+ }
+
+ boolean isActive() {
+ return mIsActive;
+ }
+
+ void activate(Rect rootBounds, WindowContainerTransaction wct) {
+ if (mIsActive) return;
+
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setBounds(rootToken, rootBounds)
+ .setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW)
+ .setLaunchRoot(
+ rootToken,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES)
+ .reparentTasks(
+ null /* currentParent */,
+ rootToken,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES,
+ true /* onTop */)
+ // Moving the root task to top after the child tasks were re-parented , or the root
+ // task cannot be visible and focused.
+ .reorder(rootToken, true /* onTop */);
+
+ mIsActive = true;
+ }
+
+ void deactivate(WindowContainerTransaction wct) {
+ deactivate(wct, false /* toTop */);
+ }
+
+ void deactivate(WindowContainerTransaction wct, boolean toTop) {
+ if (!mIsActive) return;
+ mIsActive = false;
+
+ if (mRootTaskInfo == null) return;
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setLaunchRoot(
+ rootToken,
+ null,
+ null)
+ .reparentTasks(
+ rootToken,
+ null /* newParent */,
+ CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
+ CONTROLLED_ACTIVITY_TYPES,
+ toTop)
+ // We want this re-order to the bottom regardless since we are re-parenting
+ // all its tasks.
+ .reorder(rootToken, false /* onTop */);
+ }
+
+ void updateConfiguration(int windowingMode, Rect bounds, WindowContainerTransaction wct) {
+ wct.setBounds(mRootTaskInfo.token, bounds)
+ .setWindowingMode(mRootTaskInfo.token, windowingMode);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java
new file mode 100644
index 000000000000..8fbad52c630f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java
@@ -0,0 +1,181 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.view.IWindow;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+import android.widget.FrameLayout;
+
+import com.android.wm.shell.R;
+
+/**
+ * Handles drawing outline of the bounds of provided root surface. The outline will be drown with
+ * the consideration of display insets like status bar, navigation bar and display cutout.
+ */
+class OutlineManager extends WindowlessWindowManager {
+ private static final String WINDOW_NAME = "SplitOutlineLayer";
+ private final Context mContext;
+ private final Rect mRootBounds = new Rect();
+ private final Rect mTempRect = new Rect();
+ private final Rect mLastOutlineBounds = new Rect();
+ private final InsetsState mInsetsState = new InsetsState();
+ private final int mExpandedTaskBarHeight;
+ private OutlineView mOutlineView;
+ private SurfaceControlViewHost mViewHost;
+ private SurfaceControl mHostLeash;
+ private SurfaceControl mLeash;
+
+ OutlineManager(Context context, Configuration configuration) {
+ super(configuration, null /* rootSurface */, null /* hostInputToken */);
+ mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
+ null /* options */);
+ mExpandedTaskBarHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.taskbar_frame_height);
+ }
+
+ @Override
+ protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+ b.setParent(mHostLeash);
+ }
+
+ void inflate(SurfaceControl rootLeash, Rect rootBounds) {
+ if (mLeash != null || mViewHost != null) return;
+
+ mHostLeash = rootLeash;
+ mRootBounds.set(rootBounds);
+ mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+
+ final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(mContext)
+ .inflate(R.layout.split_outline, null);
+ mOutlineView = rootLayout.findViewById(R.id.split_outline);
+
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
+ FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
+ lp.width = mRootBounds.width();
+ lp.height = mRootBounds.height();
+ lp.token = new Binder();
+ lp.setTitle(WINDOW_NAME);
+ lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+ // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
+ // TRUSTED_OVERLAY for windowless window without input channel.
+ mViewHost.setView(rootLayout, lp);
+ mLeash = getSurfaceControl(mViewHost.getWindowToken());
+
+ drawOutline();
+ }
+
+ void release() {
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+ mRootBounds.setEmpty();
+ mLastOutlineBounds.setEmpty();
+ mOutlineView = null;
+ mHostLeash = null;
+ mLeash = null;
+ }
+
+ @Nullable
+ SurfaceControl getOutlineLeash() {
+ return mLeash;
+ }
+
+ void setVisibility(boolean visible) {
+ if (mOutlineView != null) {
+ mOutlineView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ void setRootBounds(Rect rootBounds) {
+ if (mViewHost == null || mViewHost.getView() == null) {
+ return;
+ }
+
+ if (!mRootBounds.equals(rootBounds)) {
+ WindowManager.LayoutParams lp =
+ (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
+ lp.width = rootBounds.width();
+ lp.height = rootBounds.height();
+ mViewHost.relayout(lp);
+ mRootBounds.set(rootBounds);
+ drawOutline();
+ }
+ }
+
+ void onInsetsChanged(InsetsState insetsState) {
+ if (!mInsetsState.equals(insetsState)) {
+ mInsetsState.set(insetsState);
+ drawOutline();
+ }
+ }
+
+ private void computeOutlineBounds(Rect rootBounds, InsetsState insetsState, Rect outBounds) {
+ outBounds.set(rootBounds);
+ final InsetsSource taskBarInsetsSource =
+ insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ // Only insets the divider bar with task bar when it's expanded so that the rounded corners
+ // will be drawn against task bar.
+ if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ outBounds.inset(taskBarInsetsSource.calculateVisibleInsets(outBounds));
+ }
+
+ // Offset the coordinate from screen based to surface based.
+ outBounds.offset(-rootBounds.left, -rootBounds.top);
+ }
+
+ void drawOutline() {
+ if (mOutlineView == null) {
+ return;
+ }
+
+ computeOutlineBounds(mRootBounds, mInsetsState, mTempRect);
+ if (mTempRect.equals(mLastOutlineBounds)) {
+ return;
+ }
+
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) mOutlineView.getLayoutParams();
+ lp.leftMargin = mTempRect.left;
+ lp.topMargin = mTempRect.top;
+ lp.width = mTempRect.width();
+ lp.height = mTempRect.height();
+ mOutlineView.setLayoutParams(lp);
+ mLastOutlineBounds.set(mTempRect);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java
new file mode 100644
index 000000000000..92b1381fc808
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java
@@ -0,0 +1,82 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
+import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.RoundedCorner;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.R;
+
+/** View for drawing split outline. */
+public class OutlineView extends View {
+ private final Paint mPaint = new Paint();
+ private final Path mPath = new Path();
+ private final float[] mRadii = new float[8];
+
+ public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(
+ getResources().getDimension(R.dimen.accessibility_focus_highlight_stroke_width));
+ mPaint.setColor(getResources().getColor(R.color.system_accent1_100, null));
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ // TODO(b/200850654): match the screen corners with the actual display decor.
+ mRadii[0] = mRadii[1] = getCornerRadius(POSITION_TOP_LEFT);
+ mRadii[2] = mRadii[3] = getCornerRadius(POSITION_TOP_RIGHT);
+ mRadii[4] = mRadii[5] = getCornerRadius(POSITION_BOTTOM_RIGHT);
+ mRadii[6] = mRadii[7] = getCornerRadius(POSITION_BOTTOM_LEFT);
+ }
+
+ private int getCornerRadius(@RoundedCorner.Position int position) {
+ final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position);
+ return roundedCorner == null ? 0 : roundedCorner.getRadius();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (changed) {
+ mPath.reset();
+ mPath.addRoundRect(0, 0, getWidth(), getHeight(), mRadii, Path.Direction.CW);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawPath(mPath, mPaint);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java
new file mode 100644
index 000000000000..55c4f3aea19a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java
@@ -0,0 +1,144 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import android.annotation.CallSuper;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
+ * here. All other task are launch in the {@link MainStage}.
+ *
+ * @see StageCoordinator
+ */
+class SideStage extends StageTaskListener implements
+ DisplayInsetsController.OnInsetsChangedListener {
+ private static final String TAG = SideStage.class.getSimpleName();
+ private final Context mContext;
+ private OutlineManager mOutlineManager;
+
+ SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
+ SurfaceSession surfaceSession,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+ stageTaskUnfoldController);
+ mContext = context;
+ }
+
+ void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,
+ WindowContainerTransaction wct) {
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setBounds(rootToken, rootBounds)
+ .reparent(task.token, rootToken, true /* onTop*/)
+ // Moving the root task to top after the child tasks were reparented , or the root
+ // task cannot be visible and focused.
+ .reorder(rootToken, true /* onTop */);
+ }
+
+ boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
+ // No matter if the root task is empty or not, moving the root to bottom because it no
+ // longer preserves visible child task.
+ wct.reorder(mRootTaskInfo.token, false /* onTop */);
+ if (mChildrenTaskInfo.size() == 0) return false;
+ wct.reparentTasks(
+ mRootTaskInfo.token,
+ null /* newParent */,
+ CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
+ CONTROLLED_ACTIVITY_TYPES,
+ toTop);
+ return true;
+ }
+
+ boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
+ final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
+ if (task == null) return false;
+ wct.reparent(task.token, newParent, false /* onTop */);
+ return true;
+ }
+
+ @Nullable
+ public SurfaceControl getOutlineLeash() {
+ return mOutlineManager.getOutlineLeash();
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ super.onTaskAppeared(taskInfo, leash);
+ if (isRootTask(taskInfo)) {
+ mOutlineManager = new OutlineManager(mContext, taskInfo.configuration);
+ enableOutline(true);
+ }
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ super.onTaskInfoChanged(taskInfo);
+ if (isRootTask(taskInfo)) {
+ mOutlineManager.setRootBounds(taskInfo.configuration.windowConfiguration.getBounds());
+ }
+ }
+
+ private boolean isRootTask(ActivityManager.RunningTaskInfo taskInfo) {
+ return mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId;
+ }
+
+ void enableOutline(boolean enable) {
+ if (mOutlineManager == null) {
+ return;
+ }
+
+ if (enable) {
+ if (mRootTaskInfo != null) {
+ mOutlineManager.inflate(mRootLeash,
+ mRootTaskInfo.configuration.windowConfiguration.getBounds());
+ }
+ } else {
+ mOutlineManager.release();
+ }
+ }
+
+ void setOutlineVisibility(boolean visible) {
+ mOutlineManager.setVisibility(visible);
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ mOutlineManager.onInsetsChanged(insetsState);
+ }
+
+ @Override
+ public void insetsControlChanged(InsetsState insetsState,
+ InsetsSourceControl[] activeControls) {
+ insetsChanged(insetsState);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java
new file mode 100644
index 000000000000..aec81a1ee86a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java
@@ -0,0 +1,99 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface to engage split-screen feature.
+ * TODO: Figure out which of these are actually needed outside of the Shell
+ */
+@ExternalThread
+public interface SplitScreen {
+ /**
+ * Stage type isn't specified normally meaning to use what ever the default is.
+ * E.g. exit split-screen and launch the app in fullscreen.
+ */
+ int STAGE_TYPE_UNDEFINED = -1;
+ /**
+ * The main stage type.
+ * @see MainStage
+ */
+ int STAGE_TYPE_MAIN = 0;
+
+ /**
+ * The side stage type.
+ * @see SideStage
+ */
+ int STAGE_TYPE_SIDE = 1;
+
+ @IntDef(prefix = { "STAGE_TYPE_" }, value = {
+ STAGE_TYPE_UNDEFINED,
+ STAGE_TYPE_MAIN,
+ STAGE_TYPE_SIDE
+ })
+ @interface StageType {}
+
+ /** Callback interface for listening to changes in a split-screen stage. */
+ interface SplitScreenListener {
+ default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {}
+ default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {}
+ default void onSplitVisibilityChanged(boolean visible) {}
+ }
+
+ /** Registers listener that gets split screen callback. */
+ void registerSplitScreenListener(@NonNull SplitScreenListener listener,
+ @NonNull Executor executor);
+
+ /** Unregisters listener that gets split screen callback. */
+ void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
+
+ /**
+ * Returns a binder that can be passed to an external process to manipulate SplitScreen.
+ */
+ default ISplitScreen createExternalInterface() {
+ return null;
+ }
+
+ /**
+ * Called when the keyguard occluded state changes.
+ * @param occluded Indicates if the keyguard is now occluded.
+ */
+ void onKeyguardOccludedChanged(boolean occluded);
+
+ /**
+ * Called when the visibility of the keyguard changes.
+ * @param showing Indicates if the keyguard is now visible.
+ */
+ void onKeyguardVisibilityChanged(boolean showing);
+
+ /** Get a string representation of a stage type */
+ static String stageTypeToString(@StageType int stage) {
+ switch (stage) {
+ case STAGE_TYPE_UNDEFINED: return "UNDEFINED";
+ case STAGE_TYPE_MAIN: return "MAIN";
+ case STAGE_TYPE_SIDE: return "SIDE";
+ default: return "UNKNOWN(" + stage + ")";
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java
new file mode 100644
index 000000000000..94db9cd958a3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java
@@ -0,0 +1,595 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.WindowManager;
+import android.window.RemoteTransition;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+import com.android.wm.shell.draganddrop.DragAndDropPolicy;
+import com.android.wm.shell.transition.LegacyTransitions;
+import com.android.wm.shell.transition.Transitions;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
+import javax.inject.Provider;
+
+/**
+ * Class manages split-screen multitasking mode and implements the main interface
+ * {@link SplitScreen}.
+ * @see StageCoordinator
+ */
+// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
+public class SplitScreenController implements DragAndDropPolicy.Starter,
+ RemoteCallable<SplitScreenController> {
+ private static final String TAG = SplitScreenController.class.getSimpleName();
+
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final SyncTransactionQueue mSyncQueue;
+ private final Context mContext;
+ private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+ private final ShellExecutor mMainExecutor;
+ private final SplitScreenImpl mImpl = new SplitScreenImpl();
+ private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final Transitions mTransitions;
+ private final TransactionPool mTransactionPool;
+ private final SplitscreenEventLogger mLogger;
+ private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
+
+ private StageCoordinator mStageCoordinator;
+
+ public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue, Context context,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ ShellExecutor mainExecutor, DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
+ Transitions transitions, TransactionPool transactionPool,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ mTaskOrganizer = shellTaskOrganizer;
+ mSyncQueue = syncQueue;
+ mContext = context;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mMainExecutor = mainExecutor;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mTransitions = transitions;
+ mTransactionPool = transactionPool;
+ mUnfoldControllerProvider = unfoldControllerProvider;
+ mLogger = new SplitscreenEventLogger();
+ }
+
+ public SplitScreen asSplitScreen() {
+ return mImpl;
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
+ public void onOrganizerRegistered() {
+ if (mStageCoordinator == null) {
+ // TODO: Multi-display
+ mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
+ mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
+ mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
+ mUnfoldControllerProvider);
+ }
+ }
+
+ public boolean isSplitScreenVisible() {
+ return mStageCoordinator.isSplitScreenVisible();
+ }
+
+ public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
+ final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
+ if (task == null) {
+ throw new IllegalArgumentException("Unknown taskId" + taskId);
+ }
+ return moveToSideStage(task, sideStagePosition);
+ }
+
+ public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+ @SplitPosition int sideStagePosition) {
+ return mStageCoordinator.moveToSideStage(task, sideStagePosition);
+ }
+
+ public boolean removeFromSideStage(int taskId) {
+ return mStageCoordinator.removeFromSideStage(taskId);
+ }
+
+ public void setSideStageOutline(boolean enable) {
+ mStageCoordinator.setSideStageOutline(enable);
+ }
+
+ public void setSideStagePosition(@SplitPosition int sideStagePosition) {
+ mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
+ }
+
+ public void setSideStageVisibility(boolean visible) {
+ mStageCoordinator.setSideStageVisibility(visible);
+ }
+
+ public void enterSplitScreen(int taskId, boolean leftOrTop) {
+ moveToSideStage(taskId,
+ leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ }
+
+ public void exitSplitScreen(int toTopTaskId, int exitReason) {
+ mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
+ }
+
+ public void onKeyguardOccludedChanged(boolean occluded) {
+ mStageCoordinator.onKeyguardOccludedChanged(occluded);
+ }
+
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mStageCoordinator.onKeyguardVisibilityChanged(showing);
+ }
+
+ public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
+ }
+
+ public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
+ mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds);
+ }
+
+ public void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ mStageCoordinator.registerSplitScreenListener(listener);
+ }
+
+ public void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ mStageCoordinator.unregisterSplitScreenListener(listener);
+ }
+
+ public void startTask(int taskId, @SplitScreen.StageType int stage,
+ @SplitPosition int position, @Nullable Bundle options) {
+ options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */);
+
+ try {
+ ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to launch task", e);
+ }
+ }
+
+ public void startShortcut(String packageName, String shortcutId,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options, UserHandle user) {
+ options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */);
+
+ try {
+ LauncherApps launcherApps =
+ mContext.getSystemService(LauncherApps.class);
+ launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
+ options, user);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "Failed to launch shortcut", e);
+ }
+ }
+
+ public void startIntent(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options) {
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ startIntentLegacy(intent, fillInIntent, stage, position, options);
+ return;
+ }
+ mStageCoordinator.startIntent(intent, fillInIntent, stage, position, options,
+ null /* remote */);
+ }
+
+ private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options) {
+ LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback,
+ SurfaceControl.Transaction t) {
+ mStageCoordinator.updateSurfaceBounds(null /* layout */, t);
+
+ if (apps != null) {
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
+ }
+ }
+
+ t.apply();
+ if (finishedCallback != null) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error finishing legacy transition: ", e);
+ }
+ }
+ }
+ };
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ options = mStageCoordinator.resolveStartStage(stage, position, options, wct);
+ wct.sendPendingIntent(intent, fillInIntent, options);
+ mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
+ }
+
+ RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) {
+ if (!isSplitScreenVisible()) return null;
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+ .setContainerLayer()
+ .setName("RecentsAnimationSplitTasks")
+ .setHidden(false)
+ .setCallsite("SplitScreenController#onGoingtoRecentsLegacy");
+ mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder);
+ SurfaceControl sc = builder.build();
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+
+ // Ensure that we order these in the parent in the right z-order as their previous order
+ Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex);
+ int layer = 1;
+ for (RemoteAnimationTarget appTarget : apps) {
+ transaction.reparent(appTarget.leash, sc);
+ transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,
+ appTarget.screenSpaceBounds.top);
+ transaction.setLayer(appTarget.leash, layer++);
+ }
+ transaction.apply();
+ transaction.close();
+ return new RemoteAnimationTarget[]{
+ mStageCoordinator.getDividerBarLegacyTarget(),
+ mStageCoordinator.getOutlineLegacyTarget()};
+ }
+
+ /**
+ * Sets drag info to be logged when splitscreen is entered.
+ */
+ public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ mStageCoordinator.logOnDroppedToSplit(position, dragSessionId);
+ }
+
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ pw.println(prefix + TAG);
+ if (mStageCoordinator != null) {
+ mStageCoordinator.dump(pw, prefix);
+ }
+ }
+
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
+ @ExternalThread
+ private class SplitScreenImpl implements SplitScreen {
+ private ISplitScreenImpl mISplitScreen;
+ private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
+ private final SplitScreenListener mListener = new SplitScreenListener() {
+ @Override
+ public void onStagePositionChanged(int stage, int position) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onStagePositionChanged(stage, position);
+ });
+ }
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onTaskStageChanged(taskId, stage, visible);
+ });
+ }
+ }
+
+ @Override
+ public void onSplitVisibilityChanged(boolean visible) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onSplitVisibilityChanged(visible);
+ });
+ }
+ }
+ };
+
+ @Override
+ public ISplitScreen createExternalInterface() {
+ if (mISplitScreen != null) {
+ mISplitScreen.invalidate();
+ }
+ mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
+ return mISplitScreen;
+ }
+
+ @Override
+ public void onKeyguardOccludedChanged(boolean occluded) {
+ mMainExecutor.execute(() -> {
+ SplitScreenController.this.onKeyguardOccludedChanged(occluded);
+ });
+ }
+
+ @Override
+ public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
+ if (mExecutors.containsKey(listener)) return;
+
+ mMainExecutor.execute(() -> {
+ if (mExecutors.size() == 0) {
+ SplitScreenController.this.registerSplitScreenListener(mListener);
+ }
+
+ mExecutors.put(listener, executor);
+ });
+
+ executor.execute(() -> {
+ mStageCoordinator.sendStatusToListener(listener);
+ });
+ }
+
+ @Override
+ public void unregisterSplitScreenListener(SplitScreenListener listener) {
+ mMainExecutor.execute(() -> {
+ mExecutors.remove(listener);
+
+ if (mExecutors.size() == 0) {
+ SplitScreenController.this.unregisterSplitScreenListener(mListener);
+ }
+ });
+ }
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mMainExecutor.execute(() -> {
+ SplitScreenController.this.onKeyguardVisibilityChanged(showing);
+ });
+ }
+ }
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class ISplitScreenImpl extends ISplitScreen.Stub {
+ private SplitScreenController mController;
+ private ISplitScreenListener mListener;
+ private final SplitScreen.SplitScreenListener mSplitScreenListener =
+ new SplitScreen.SplitScreenListener() {
+ @Override
+ public void onStagePositionChanged(int stage, int position) {
+ try {
+ if (mListener != null) {
+ mListener.onStagePositionChanged(stage, position);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "onStagePositionChanged", e);
+ }
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ try {
+ if (mListener != null) {
+ mListener.onTaskStageChanged(taskId, stage, visible);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "onTaskStageChanged", e);
+ }
+ }
+ };
+ private final IBinder.DeathRecipient mListenerDeathRecipient =
+ new IBinder.DeathRecipient() {
+ @Override
+ @BinderThread
+ public void binderDied() {
+ final SplitScreenController controller = mController;
+ controller.getRemoteCallExecutor().execute(() -> {
+ mListener = null;
+ controller.unregisterSplitScreenListener(mSplitScreenListener);
+ });
+ }
+ };
+
+ public ISplitScreenImpl(SplitScreenController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public void registerSplitScreenListener(ISplitScreenListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
+ (controller) -> {
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ if (listener != null) {
+ try {
+ listener.asBinder().linkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death");
+ return;
+ }
+ }
+ mListener = listener;
+ controller.registerSplitScreenListener(mSplitScreenListener);
+ });
+ }
+
+ @Override
+ public void unregisterSplitScreenListener(ISplitScreenListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener",
+ (controller) -> {
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ mListener = null;
+ controller.unregisterSplitScreenListener(mSplitScreenListener);
+ });
+ }
+
+ @Override
+ public void exitSplitScreen(int toTopTaskId) {
+ executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
+ (controller) -> {
+ controller.exitSplitScreen(toTopTaskId,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT);
+ });
+ }
+
+ @Override
+ public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
+ (controller) -> {
+ controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
+ });
+ }
+
+ @Override
+ public void setSideStageVisibility(boolean visible) {
+ executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility",
+ (controller) -> {
+ controller.setSideStageVisibility(visible);
+ });
+ }
+
+ @Override
+ public void removeFromSideStage(int taskId) {
+ executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
+ (controller) -> {
+ controller.removeFromSideStage(taskId);
+ });
+ }
+
+ @Override
+ public void startTask(int taskId, int stage, int position, @Nullable Bundle options) {
+ executeRemoteCallWithTaskPermission(mController, "startTask",
+ (controller) -> {
+ controller.startTask(taskId, stage, position, options);
+ });
+ }
+
+ @Override
+ public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ RemoteAnimationAdapter adapter) {
+ executeRemoteCallWithTaskPermission(mController, "startTasks",
+ (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
+ mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
+ adapter));
+ }
+
+ @Override
+ public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions,
+ @SplitPosition int sidePosition,
+ @Nullable RemoteTransition remoteTransition) {
+ executeRemoteCallWithTaskPermission(mController, "startTasks",
+ (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
+ sideTaskId, sideOptions, sidePosition, remoteTransition));
+ }
+
+ @Override
+ public void startShortcut(String packageName, String shortcutId, int stage, int position,
+ @Nullable Bundle options, UserHandle user) {
+ executeRemoteCallWithTaskPermission(mController, "startShortcut",
+ (controller) -> {
+ controller.startShortcut(packageName, shortcutId, stage, position,
+ options, user);
+ });
+ }
+
+ @Override
+ public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
+ @Nullable Bundle options) {
+ executeRemoteCallWithTaskPermission(mController, "startIntent",
+ (controller) -> {
+ controller.startIntent(intent, fillInIntent, stage, position, options);
+ });
+ }
+
+ @Override
+ public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
+ RemoteAnimationTarget[] apps) {
+ final RemoteAnimationTarget[][] out = new RemoteAnimationTarget[][]{null};
+ executeRemoteCallWithTaskPermission(mController, "onGoingToRecentsLegacy",
+ (controller) -> out[0] = controller.onGoingToRecentsLegacy(cancel, apps),
+ true /* blocking */);
+ return out[0];
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
new file mode 100644
index 000000000000..af9a5aa501e8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
@@ -0,0 +1,298 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
+
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
+import static com.android.wm.shell.transition.Transitions.isOpeningType;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.RemoteTransition;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.transition.OneShotRemoteHandler;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.ArrayList;
+
+/** Manages transition animations for split-screen. */
+class SplitScreenTransitions {
+ private static final String TAG = "SplitScreenTransitions";
+
+ /** Flag applied to a transition change to identify it as a divider bar for animation. */
+ public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
+
+ private final TransactionPool mTransactionPool;
+ private final Transitions mTransitions;
+ private final Runnable mOnFinish;
+
+ IBinder mPendingDismiss = null;
+ IBinder mPendingEnter = null;
+
+ private IBinder mAnimatingTransition = null;
+ private OneShotRemoteHandler mRemoteHandler = null;
+
+ private Transitions.TransitionFinishCallback mRemoteFinishCB = (wct, wctCB) -> {
+ if (wct != null || wctCB != null) {
+ throw new UnsupportedOperationException("finish transactions not supported yet.");
+ }
+ onFinish();
+ };
+
+ /** Keeps track of currently running animations */
+ private final ArrayList<Animator> mAnimations = new ArrayList<>();
+
+ private Transitions.TransitionFinishCallback mFinishCallback = null;
+ private SurfaceControl.Transaction mFinishTransaction;
+
+ SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
+ @NonNull Runnable onFinishCallback) {
+ mTransactionPool = pool;
+ mTransitions = transitions;
+ mOnFinish = onFinishCallback;
+ }
+
+ void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) {
+ mFinishCallback = finishCallback;
+ mAnimatingTransition = transition;
+ if (mRemoteHandler != null) {
+ mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction,
+ mRemoteFinishCB);
+ mRemoteHandler = null;
+ return;
+ }
+ playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot);
+ }
+
+ private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
+ @NonNull WindowContainerToken sideRoot) {
+ mFinishTransaction = mTransactionPool.acquire();
+
+ // Play some place-holder fade animations
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final SurfaceControl leash = change.getLeash();
+ final int mode = info.getChanges().get(i).getMode();
+
+ if (mode == TRANSIT_CHANGE) {
+ if (change.getParent() != null) {
+ // This is probably reparented, so we want the parent to be immediately visible
+ final TransitionInfo.Change parentChange = info.getChange(change.getParent());
+ t.show(parentChange.getLeash());
+ t.setAlpha(parentChange.getLeash(), 1.f);
+ // and then animate this layer outside the parent (since, for example, this is
+ // the home task animating from fullscreen to part-screen).
+ t.reparent(leash, info.getRootLeash());
+ t.setLayer(leash, info.getChanges().size() - i);
+ // build the finish reparent/reposition
+ mFinishTransaction.reparent(leash, parentChange.getLeash());
+ mFinishTransaction.setPosition(leash,
+ change.getEndRelOffset().x, change.getEndRelOffset().y);
+ }
+ // TODO(shell-transitions): screenshot here
+ final Rect startBounds = new Rect(change.getStartAbsBounds());
+ if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
+ // Dismissing split via snap which means the still-visible task has been
+ // dragged to its end position at animation start so reflect that here.
+ startBounds.offsetTo(change.getEndAbsBounds().left,
+ change.getEndAbsBounds().top);
+ }
+ final Rect endBounds = new Rect(change.getEndAbsBounds());
+ startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
+ endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
+ startExampleResizeAnimation(leash, startBounds, endBounds);
+ }
+ if (change.getParent() != null) {
+ continue;
+ }
+
+ if (transition == mPendingEnter && (mainRoot.equals(change.getContainer())
+ || sideRoot.equals(change.getContainer()))) {
+ t.setWindowCrop(leash, change.getStartAbsBounds().width(),
+ change.getStartAbsBounds().height());
+ }
+ boolean isOpening = isOpeningType(info.getType());
+ if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
+ // fade in
+ startExampleAnimation(leash, true /* show */);
+ } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
+ // fade out
+ if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
+ // Dismissing via snap-to-top/bottom means that the dismissed task is already
+ // not-visible (usually cropped to oblivion) so immediately set its alpha to 0
+ // and don't animate it so it doesn't pop-in when reparented.
+ t.setAlpha(leash, 0.f);
+ } else {
+ startExampleAnimation(leash, false /* show */);
+ }
+ }
+ }
+ t.apply();
+ onFinish();
+ }
+
+ /** Starts a transition to enter split with a remote transition animator. */
+ IBinder startEnterTransition(@WindowManager.TransitionType int transitType,
+ @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition,
+ @NonNull Transitions.TransitionHandler handler) {
+ if (remoteTransition != null) {
+ // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
+ mRemoteHandler = new OneShotRemoteHandler(
+ mTransitions.getMainExecutor(), remoteTransition);
+ }
+ final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
+ mPendingEnter = transition;
+ if (mRemoteHandler != null) {
+ mRemoteHandler.setTransition(transition);
+ }
+ return transition;
+ }
+
+ /** Starts a transition for dismissing split after dragging the divider to a screen edge */
+ IBinder startSnapToDismiss(@NonNull WindowContainerTransaction wct,
+ @NonNull Transitions.TransitionHandler handler) {
+ final IBinder transition = mTransitions.startTransition(
+ TRANSIT_SPLIT_DISMISS_SNAP, wct, handler);
+ mPendingDismiss = transition;
+ return transition;
+ }
+
+ void onFinish() {
+ if (!mAnimations.isEmpty()) return;
+ mOnFinish.run();
+ if (mFinishTransaction != null) {
+ mFinishTransaction.apply();
+ mTransactionPool.release(mFinishTransaction);
+ mFinishTransaction = null;
+ }
+ mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ mFinishCallback = null;
+ if (mAnimatingTransition == mPendingEnter) {
+ mPendingEnter = null;
+ }
+ if (mAnimatingTransition == mPendingDismiss) {
+ mPendingDismiss = null;
+ }
+ mAnimatingTransition = null;
+ }
+
+ // TODO(shell-transitions): real animations
+ private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) {
+ final float end = show ? 1.f : 0.f;
+ final float start = 1.f - end;
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ final ValueAnimator va = ValueAnimator.ofFloat(start, end);
+ va.setDuration(500);
+ va.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
+ transaction.apply();
+ });
+ final Runnable finisher = () -> {
+ transaction.setAlpha(leash, end);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ mTransitions.getMainExecutor().execute(() -> {
+ mAnimations.remove(va);
+ onFinish();
+ });
+ };
+ va.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) { }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) { }
+ });
+ mAnimations.add(va);
+ mTransitions.getAnimExecutor().execute(va::start);
+ }
+
+ // TODO(shell-transitions): real animations
+ private void startExampleResizeAnimation(@NonNull SurfaceControl leash,
+ @NonNull Rect startBounds, @NonNull Rect endBounds) {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f);
+ va.setDuration(500);
+ va.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ transaction.setWindowCrop(leash,
+ (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction),
+ (int) (startBounds.height() * (1.f - fraction)
+ + endBounds.height() * fraction));
+ transaction.setPosition(leash,
+ startBounds.left * (1.f - fraction) + endBounds.left * fraction,
+ startBounds.top * (1.f - fraction) + endBounds.top * fraction);
+ transaction.apply();
+ });
+ final Runnable finisher = () -> {
+ transaction.setWindowCrop(leash, 0, 0);
+ transaction.setPosition(leash, endBounds.left, endBounds.top);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ mTransitions.getMainExecutor().execute(() -> {
+ mAnimations.remove(va);
+ onFinish();
+ });
+ };
+ va.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ finisher.run();
+ }
+ });
+ mAnimations.add(va);
+ mTransitions.getAnimExecutor().execute(va::start);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java
new file mode 100644
index 000000000000..aab7902232bf
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java
@@ -0,0 +1,324 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+
+/**
+ * Helper class that to log Drag & Drop UIEvents for a single session, see also go/uievent
+ */
+public class SplitscreenEventLogger {
+
+ // Used to generate instance ids for this drag if one is not provided
+ private final InstanceIdSequence mIdSequence;
+
+ // The instance id for the current splitscreen session (from start to end)
+ private InstanceId mLoggerSessionId;
+
+ // Drag info
+ private @SplitPosition int mDragEnterPosition;
+ private InstanceId mDragEnterSessionId;
+
+ // For deduping async events
+ private int mLastMainStagePosition = -1;
+ private int mLastMainStageUid = -1;
+ private int mLastSideStagePosition = -1;
+ private int mLastSideStageUid = -1;
+ private float mLastSplitRatio = -1f;
+
+ public SplitscreenEventLogger() {
+ mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE);
+ }
+
+ /**
+ * Return whether a splitscreen session has started.
+ */
+ public boolean hasStartedSession() {
+ return mLoggerSessionId != null;
+ }
+
+ /**
+ * May be called before logEnter() to indicate that the session was started from a drag.
+ */
+ public void enterRequestedByDrag(@SplitPosition int position, InstanceId dragSessionId) {
+ mDragEnterPosition = position;
+ mDragEnterSessionId = dragSessionId;
+ }
+
+ /**
+ * Logs when the user enters splitscreen.
+ */
+ public void logEnter(float splitRatio,
+ @SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid,
+ boolean isLandscape) {
+ mLoggerSessionId = mIdSequence.newInstanceId();
+ int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED
+ ? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape)
+ : SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
+ updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid);
+ updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid);
+ updateSplitRatioState(splitRatio);
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__ENTER,
+ enterReason,
+ 0 /* exitReason */,
+ splitRatio,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ mDragEnterSessionId != null ? mDragEnterSessionId.getId() : 0,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the user exits splitscreen. Only one of the main or side stages should be
+ * specified to indicate which position was focused as a part of exiting (both can be unset).
+ */
+ public void logExit(int exitReason, @SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if ((mainStagePosition != SPLIT_POSITION_UNDEFINED
+ && sideStagePosition != SPLIT_POSITION_UNDEFINED)
+ || (mainStageUid != 0 && sideStageUid != 0)) {
+ throw new IllegalArgumentException("Only main or side stage should be set");
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__EXIT,
+ 0 /* enterReason */,
+ exitReason,
+ 0f /* splitRatio */,
+ getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid,
+ getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+
+ // Reset states
+ mLoggerSessionId = null;
+ mDragEnterPosition = SPLIT_POSITION_UNDEFINED;
+ mDragEnterSessionId = null;
+ mLastMainStagePosition = -1;
+ mLastMainStageUid = -1;
+ mLastSideStagePosition = -1;
+ mLastSideStageUid = -1;
+ }
+
+ /**
+ * Logs when an app in the main stage changes.
+ */
+ public void logMainStageAppChange(@SplitPosition int mainStagePosition, int mainStageUid,
+ boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (!updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition,
+ isLandscape), mainStageUid)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ 0 /* sideStagePosition */,
+ 0 /* sideStageUid */,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when an app in the side stage changes.
+ */
+ public void logSideStageAppChange(@SplitPosition int sideStagePosition, int sideStageUid,
+ boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (!updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition,
+ isLandscape), sideStageUid)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ 0 /* mainStagePosition */,
+ 0 /* mainStageUid */,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the splitscreen ratio changes.
+ */
+ public void logResize(float splitRatio) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (splitRatio <= 0f || splitRatio >= 1f) {
+ // Don't bother reporting resizes that end up dismissing the split, that will be logged
+ // via the exit event
+ return;
+ }
+ if (!updateSplitRatioState(splitRatio)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__RESIZE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ mLastSplitRatio,
+ 0 /* mainStagePosition */, 0 /* mainStageUid */,
+ 0 /* sideStagePosition */, 0 /* sideStageUid */,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the apps in splitscreen are swapped.
+ */
+ public void logSwap(@SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+
+ updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid);
+ updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid);
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__SWAP,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ private boolean updateMainStageState(int mainStagePosition, int mainStageUid) {
+ boolean changed = (mLastMainStagePosition != mainStagePosition)
+ || (mLastMainStageUid != mainStageUid);
+ if (!changed) {
+ return false;
+ }
+
+ mLastMainStagePosition = mainStagePosition;
+ mLastMainStageUid = mainStageUid;
+ return true;
+ }
+
+ private boolean updateSideStageState(int sideStagePosition, int sideStageUid) {
+ boolean changed = (mLastSideStagePosition != sideStagePosition)
+ || (mLastSideStageUid != sideStageUid);
+ if (!changed) {
+ return false;
+ }
+
+ mLastSideStagePosition = sideStagePosition;
+ mLastSideStageUid = sideStageUid;
+ return true;
+ }
+
+ private boolean updateSplitRatioState(float splitRatio) {
+ boolean changed = Float.compare(mLastSplitRatio, splitRatio) != 0;
+ if (!changed) {
+ return false;
+ }
+
+ mLastSplitRatio = splitRatio;
+ return true;
+ }
+
+ public int getDragEnterReasonFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_BOTTOM;
+ }
+ }
+
+ private int getMainStagePositionFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (position == SPLIT_POSITION_UNDEFINED) {
+ return 0;
+ }
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__BOTTOM;
+ }
+ }
+
+ private int getSideStagePositionFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (position == SPLIT_POSITION_UNDEFINED) {
+ return 0;
+ }
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__BOTTOM;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
new file mode 100644
index 000000000000..60a6cd78c4fc
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
@@ -0,0 +1,1330 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+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.transitTypeToString;
+import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
+
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_MAIN;
+import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_SIDE;
+import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.stagesplit.SplitScreen.stageTypeToString;
+import static com.android.wm.shell.stagesplit.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+import static com.android.wm.shell.transition.Transitions.isClosingType;
+import static com.android.wm.shell.transition.Transitions.isOpeningType;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.WindowManager;
+import android.window.DisplayAreaInfo;
+import android.window.RemoteTransition;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+import com.android.wm.shell.common.split.SplitWindowManager;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.transition.Transitions;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import javax.inject.Provider;
+
+/**
+ * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
+ * {@link SideStage} stages.
+ * Some high-level rules:
+ * - The {@link StageCoordinator} is only considered active if the {@link SideStage} contains at
+ * least one child task.
+ * - The {@link MainStage} should only have children if the coordinator is active.
+ * - The {@link SplitLayout} divider is only visible if both the {@link MainStage}
+ * and {@link SideStage} are visible.
+ * - The {@link MainStage} configuration is fullscreen when the {@link SideStage} isn't visible.
+ * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and
+ * {@link #onStageHasChildrenChanged(StageListenerImpl).}
+ */
+class StageCoordinator implements SplitLayout.SplitLayoutHandler,
+ RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler {
+
+ private static final String TAG = StageCoordinator.class.getSimpleName();
+
+ /** internal value for mDismissTop that represents no dismiss */
+ private static final int NO_DISMISS = -2;
+
+ private final SurfaceSession mSurfaceSession = new SurfaceSession();
+
+ private final MainStage mMainStage;
+ private final StageListenerImpl mMainStageListener = new StageListenerImpl();
+ private final StageTaskUnfoldController mMainUnfoldController;
+ private final SideStage mSideStage;
+ private final StageListenerImpl mSideStageListener = new StageListenerImpl();
+ private final StageTaskUnfoldController mSideUnfoldController;
+ @SplitPosition
+ private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
+
+ private final int mDisplayId;
+ private SplitLayout mSplitLayout;
+ private boolean mDividerVisible;
+ private final SyncTransactionQueue mSyncQueue;
+ private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private DisplayAreaInfo mDisplayAreaInfo;
+ private final Context mContext;
+ private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
+ private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final SplitScreenTransitions mSplitTransitions;
+ private final SplitscreenEventLogger mLogger;
+ private boolean mExitSplitScreenOnHide;
+ private boolean mKeyguardOccluded;
+
+ // TODO(b/187041611): remove this flag after totally deprecated legacy split
+ /** Whether the device is supporting legacy split or not. */
+ private boolean mUseLegacySplit;
+
+ @SplitScreen.StageType private int mDismissTop = NO_DISMISS;
+
+ /** The target stage to dismiss to when unlock after folded. */
+ @SplitScreen.StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+
+ private final Runnable mOnTransitionAnimationComplete = () -> {
+ // If still playing, let it finish.
+ if (!isSplitScreenVisible()) {
+ // Update divider state after animation so that it is still around and positioned
+ // properly for the animation itself.
+ setDividerVisibility(false);
+ mSplitLayout.resetDividerPosition();
+ }
+ mDismissTop = NO_DISMISS;
+ };
+
+ private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
+ new SplitWindowManager.ParentContainerCallbacks() {
+ @Override
+ public void attachToParentSurface(SurfaceControl.Builder b) {
+ mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b);
+ }
+
+ @Override
+ public void onLeashReady(SurfaceControl leash) {
+ mSyncQueue.runInSync(t -> applyDividerVisibility(t));
+ }
+ };
+
+ StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController, Transitions transitions,
+ TransactionPool transactionPool, SplitscreenEventLogger logger,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ mContext = context;
+ mDisplayId = displayId;
+ mSyncQueue = syncQueue;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mTaskOrganizer = taskOrganizer;
+ mLogger = logger;
+ mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
+
+ mMainStage = new MainStage(
+ mTaskOrganizer,
+ mDisplayId,
+ mMainStageListener,
+ mSyncQueue,
+ mSurfaceSession,
+ mMainUnfoldController);
+ mSideStage = new SideStage(
+ mContext,
+ mTaskOrganizer,
+ mDisplayId,
+ mSideStageListener,
+ mSyncQueue,
+ mSurfaceSession,
+ mSideUnfoldController);
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage);
+ mRootTDAOrganizer.registerListener(displayId, this);
+ final DeviceStateManager deviceStateManager =
+ mContext.getSystemService(DeviceStateManager.class);
+ deviceStateManager.registerCallback(taskOrganizer.getExecutor(),
+ new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged));
+ mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
+ mOnTransitionAnimationComplete);
+ transitions.addHandler(this);
+ }
+
+ @VisibleForTesting
+ StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
+ MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
+ Transitions transitions, TransactionPool transactionPool,
+ SplitscreenEventLogger logger,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ mContext = context;
+ mDisplayId = displayId;
+ mSyncQueue = syncQueue;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mTaskOrganizer = taskOrganizer;
+ mMainStage = mainStage;
+ mSideStage = sideStage;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mRootTDAOrganizer.registerListener(displayId, this);
+ mSplitLayout = splitLayout;
+ mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
+ mOnTransitionAnimationComplete);
+ mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mLogger = logger;
+ transitions.addHandler(this);
+ }
+
+ @VisibleForTesting
+ SplitScreenTransitions getSplitTransitions() {
+ return mSplitTransitions;
+ }
+
+ boolean isSplitScreenVisible() {
+ return mSideStageListener.mVisible && mMainStageListener.mVisible;
+ }
+
+ boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+ @SplitPosition int sideStagePosition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ setSideStagePosition(sideStagePosition, wct);
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.addTask(task, getSideStageBounds(), wct);
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(null /* layout */, t));
+ return true;
+ }
+
+ boolean removeFromSideStage(int taskId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ /**
+ * {@link MainStage} will be deactivated in {@link #onStageHasChildrenChanged} if the
+ * {@link SideStage} no longer has children.
+ */
+ final boolean result = mSideStage.removeTask(taskId,
+ mMainStage.isActive() ? mMainStage.mRootTaskInfo.token : null,
+ wct);
+ mTaskOrganizer.applyTransaction(wct);
+ return result;
+ }
+
+ void setSideStageOutline(boolean enable) {
+ mSideStage.enableOutline(enable);
+ }
+
+ /** Starts 2 tasks in one transition. */
+ void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
+ @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ @Nullable RemoteTransition remoteTransition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mainOptions = mainOptions != null ? mainOptions : new Bundle();
+ sideOptions = sideOptions != null ? sideOptions : new Bundle();
+ setSideStagePosition(sidePosition, wct);
+
+ // Build a request WCT that will launch both apps such that task 0 is on the main stage
+ // while task 1 is on the side stage.
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.setBounds(getSideStageBounds(), wct);
+
+ // Make sure the launch options will put tasks in the corresponding split roots
+ addActivityOptions(mainOptions, mMainStage);
+ addActivityOptions(sideOptions, mSideStage);
+
+ // Add task launch requests
+ wct.startTask(mainTaskId, mainOptions);
+ wct.startTask(sideTaskId, sideOptions);
+
+ mSplitTransitions.startEnterTransition(
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this);
+ }
+
+ /** Starts 2 tasks in one legacy transition. */
+ void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ RemoteAnimationAdapter adapter) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Need to add another wrapper here in shell so that we can inject the divider bar
+ // and also manage the process elevation via setRunningRemote
+ IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
+ RemoteAnimationTarget[] augmentedNonApps =
+ new RemoteAnimationTarget[nonApps.length + 1];
+ for (int i = 0; i < nonApps.length; ++i) {
+ augmentedNonApps[i] = nonApps[i];
+ }
+ augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget();
+ try {
+ ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
+ adapter.getCallingApplication());
+ adapter.getRunner().onAnimationStart(transit, apps, wallpapers, nonApps,
+ finishedCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ try {
+ adapter.getRunner().onAnimationCancelled();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ }
+ };
+ RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
+ wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
+
+ if (mainOptions == null) {
+ mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
+ } else {
+ ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
+ mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
+ }
+
+ sideOptions = sideOptions != null ? sideOptions : new Bundle();
+ setSideStagePosition(sidePosition, wct);
+
+ // Build a request WCT that will launch both apps such that task 0 is on the main stage
+ // while task 1 is on the side stage.
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.setBounds(getSideStageBounds(), wct);
+
+ // Make sure the launch options will put tasks in the corresponding split roots
+ addActivityOptions(mainOptions, mMainStage);
+ addActivityOptions(sideOptions, mSideStage);
+
+ // Add task launch requests
+ wct.startTask(mainTaskId, mainOptions);
+ wct.startTask(sideTaskId, sideOptions);
+
+ // Using legacy transitions, so we can't use blast sync since it conflicts.
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ public void startIntent(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @androidx.annotation.Nullable Bundle options,
+ @Nullable RemoteTransition remoteTransition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ options = resolveStartStage(stage, position, options, wct);
+ wct.sendPendingIntent(intent, fillInIntent, options);
+ mSplitTransitions.startEnterTransition(
+ TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this);
+ }
+
+ Bundle resolveStartStage(@SplitScreen.StageType int stage,
+ @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
+ @androidx.annotation.Nullable WindowContainerTransaction wct) {
+ switch (stage) {
+ case STAGE_TYPE_UNDEFINED: {
+ // Use the stage of the specified position is valid.
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ if (position == getSideStagePosition()) {
+ options = resolveStartStage(STAGE_TYPE_SIDE, position, options, wct);
+ } else {
+ options = resolveStartStage(STAGE_TYPE_MAIN, position, options, wct);
+ }
+ } else {
+ // Exit split-screen and launch fullscreen since stage wasn't specified.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+ }
+ break;
+ }
+ case STAGE_TYPE_SIDE: {
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ setSideStagePosition(position, wct);
+ } else {
+ position = getSideStagePosition();
+ }
+ if (options == null) {
+ options = new Bundle();
+ }
+ updateActivityOptions(options, position);
+ break;
+ }
+ case STAGE_TYPE_MAIN: {
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ // Set the side stage opposite of what we want to the main stage.
+ final int sideStagePosition = position == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
+ setSideStagePosition(sideStagePosition, wct);
+ } else {
+ position = getMainStagePosition();
+ }
+ if (options == null) {
+ options = new Bundle();
+ }
+ updateActivityOptions(options, position);
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unknown stage=" + stage);
+ }
+
+ return options;
+ }
+
+ @SplitPosition
+ int getSideStagePosition() {
+ return mSideStagePosition;
+ }
+
+ @SplitPosition
+ int getMainStagePosition() {
+ return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
+ }
+
+ void setSideStagePosition(@SplitPosition int sideStagePosition,
+ @Nullable WindowContainerTransaction wct) {
+ setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
+ }
+
+ private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
+ @Nullable WindowContainerTransaction wct) {
+ if (mSideStagePosition == sideStagePosition) return;
+ mSideStagePosition = sideStagePosition;
+ sendOnStagePositionChanged();
+
+ if (mSideStageListener.mVisible && updateBounds) {
+ if (wct == null) {
+ // onLayoutSizeChanged builds/applies a wct with the contents of updateWindowBounds.
+ onLayoutSizeChanged(mSplitLayout);
+ } else {
+ updateWindowBounds(mSplitLayout, wct);
+ updateUnfoldBounds();
+ }
+ }
+ }
+
+ void setSideStageVisibility(boolean visible) {
+ if (mSideStageListener.mVisible == visible) return;
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mSideStage.setVisibility(visible, wct);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ void onKeyguardOccludedChanged(boolean occluded) {
+ // Do not exit split directly, because it needs to wait for task info update to determine
+ // which task should remain on top after split dismissed.
+ mKeyguardOccluded = occluded;
+ }
+
+ void onKeyguardVisibilityChanged(boolean showing) {
+ if (!showing && mMainStage.isActive()
+ && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+ exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED);
+ }
+ }
+
+ void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ mExitSplitScreenOnHide = exitSplitScreenOnHide;
+ }
+
+ void exitSplitScreen(int toTopTaskId, int exitReason) {
+ StageTaskListener childrenToTop = null;
+ if (mMainStage.containsTask(toTopTaskId)) {
+ childrenToTop = mMainStage;
+ } else if (mSideStage.containsTask(toTopTaskId)) {
+ childrenToTop = mSideStage;
+ }
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (childrenToTop != null) {
+ childrenToTop.reorderChild(toTopTaskId, true /* onTop */, wct);
+ }
+ applyExitSplitScreen(childrenToTop, wct, exitReason);
+ }
+
+ private void exitSplitScreen(StageTaskListener childrenToTop, int exitReason) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ applyExitSplitScreen(childrenToTop, wct, exitReason);
+ }
+
+ private void applyExitSplitScreen(
+ StageTaskListener childrenToTop,
+ WindowContainerTransaction wct, int exitReason) {
+ mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
+ mMainStage.deactivate(wct, childrenToTop == mMainStage);
+ mTaskOrganizer.applyTransaction(wct);
+ mSyncQueue.runInSync(t -> t
+ .setWindowCrop(mMainStage.mRootLeash, null)
+ .setWindowCrop(mSideStage.mRootLeash, null));
+ // Hide divider and reset its position.
+ setDividerVisibility(false);
+ mSplitLayout.resetDividerPosition();
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ if (childrenToTop != null) {
+ logExitToStage(exitReason, childrenToTop == mMainStage);
+ } else {
+ logExit(exitReason);
+ }
+ }
+
+ /**
+ * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
+ * an existing WindowContainerTransaction (rather than applying immediately). This is intended
+ * to be used when exiting split might be bundled with other window operations.
+ */
+ void prepareExitSplitScreen(@SplitScreen.StageType int stageToTop,
+ @NonNull WindowContainerTransaction wct) {
+ mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
+ mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
+ }
+
+ void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
+ outTopOrLeftBounds.set(mSplitLayout.getBounds1());
+ outBottomOrRightBounds.set(mSplitLayout.getBounds2());
+ }
+
+ private void addActivityOptions(Bundle opts, StageTaskListener stage) {
+ opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
+ }
+
+ void updateActivityOptions(Bundle opts, @SplitPosition int position) {
+ addActivityOptions(opts, position == mSideStagePosition ? mSideStage : mMainStage);
+ }
+
+ void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ if (mListeners.contains(listener)) return;
+ mListeners.add(listener);
+ sendStatusToListener(listener);
+ }
+
+ void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ mListeners.remove(listener);
+ }
+
+ void sendStatusToListener(SplitScreen.SplitScreenListener listener) {
+ listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+ listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+ listener.onSplitVisibilityChanged(isSplitScreenVisible());
+ mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
+ mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
+ }
+
+ private void sendOnStagePositionChanged() {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ final SplitScreen.SplitScreenListener l = mListeners.get(i);
+ l.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+ l.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+ }
+ }
+
+ private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId,
+ boolean present, boolean visible) {
+ int stage;
+ if (present) {
+ stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+ } else {
+ // No longer on any stage
+ stage = STAGE_TYPE_UNDEFINED;
+ }
+ if (stage == STAGE_TYPE_MAIN) {
+ mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ } else {
+ mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
+
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onTaskStageChanged(taskId, stage, visible);
+ }
+ }
+
+ private void sendSplitVisibilityChanged() {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ final SplitScreen.SplitScreenListener l = mListeners.get(i);
+ l.onSplitVisibilityChanged(mDividerVisible);
+ }
+
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+ mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+ }
+ }
+
+ private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
+ if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
+ mUseLegacySplit = mContext.getResources().getBoolean(R.bool.config_useLegacySplit);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Make the stages adjacent to each other so they occlude what's behind them.
+ wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+
+ // Only sets side stage as launch-adjacent-flag-root when the device is not using legacy
+ // split to prevent new split behavior confusing users.
+ if (!mUseLegacySplit) {
+ wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
+ }
+
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
+ private void onStageRootTaskVanished(StageListenerImpl stageListener) {
+ if (stageListener == mMainStageListener || stageListener == mSideStageListener) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Deactivate the main stage if it no longer has a root task.
+ mMainStage.deactivate(wct);
+
+ if (!mUseLegacySplit) {
+ wct.clearLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
+ }
+
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
+ private void setDividerVisibility(boolean visible) {
+ if (mDividerVisible == visible) return;
+ mDividerVisible = visible;
+ if (visible) {
+ mSplitLayout.init();
+ updateUnfoldBounds();
+ } else {
+ mSplitLayout.release();
+ }
+ sendSplitVisibilityChanged();
+ }
+
+ private void onStageVisibilityChanged(StageListenerImpl stageListener) {
+ final boolean sideStageVisible = mSideStageListener.mVisible;
+ final boolean mainStageVisible = mMainStageListener.mVisible;
+ final boolean bothStageVisible = sideStageVisible && mainStageVisible;
+ final boolean bothStageInvisible = !sideStageVisible && !mainStageVisible;
+ final boolean sameVisibility = sideStageVisible == mainStageVisible;
+ // Only add or remove divider when both visible or both invisible to avoid sometimes we only
+ // got one stage visibility changed for a moment and it will cause flicker.
+ if (sameVisibility) {
+ setDividerVisibility(bothStageVisible);
+ }
+
+ if (bothStageInvisible) {
+ if (mExitSplitScreenOnHide
+ // Don't dismiss staged split when both stages are not visible due to sleeping display,
+ // like the cases keyguard showing or screen off.
+ || (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping)) {
+ exitSplitScreen(null /* childrenToTop */,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME);
+ }
+ } else if (mKeyguardOccluded) {
+ // At least one of the stages is visible while keyguard occluded. Dismiss split because
+ // there's show-when-locked activity showing on top of keyguard. Also make sure the
+ // task contains show-when-locked activity remains on top after split dismissed.
+ final StageTaskListener toTop =
+ mainStageVisible ? mMainStage : (sideStageVisible ? mSideStage : null);
+ exitSplitScreen(toTop, SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP);
+ }
+
+ mSyncQueue.runInSync(t -> {
+ // Same above, we only set root tasks and divider leash visibility when both stage
+ // change to visible or invisible to avoid flicker.
+ if (sameVisibility) {
+ t.setVisibility(mSideStage.mRootLeash, bothStageVisible)
+ .setVisibility(mMainStage.mRootLeash, bothStageVisible);
+ applyDividerVisibility(t);
+ applyOutlineVisibility(t);
+ }
+ });
+ }
+
+ private void applyDividerVisibility(SurfaceControl.Transaction t) {
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ if (dividerLeash == null) {
+ return;
+ }
+
+ if (mDividerVisible) {
+ t.show(dividerLeash)
+ .setLayer(dividerLeash, SPLIT_DIVIDER_LAYER)
+ .setPosition(dividerLeash,
+ mSplitLayout.getDividerBounds().left,
+ mSplitLayout.getDividerBounds().top);
+ } else {
+ t.hide(dividerLeash);
+ }
+ }
+
+ private void applyOutlineVisibility(SurfaceControl.Transaction t) {
+ final SurfaceControl outlineLeash = mSideStage.getOutlineLeash();
+ if (outlineLeash == null) {
+ return;
+ }
+
+ if (mDividerVisible) {
+ t.show(outlineLeash).setLayer(outlineLeash, SPLIT_DIVIDER_LAYER);
+ } else {
+ t.hide(outlineLeash);
+ }
+ }
+
+ private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
+ final boolean hasChildren = stageListener.mHasChildren;
+ final boolean isSideStage = stageListener == mSideStageListener;
+ if (!hasChildren) {
+ if (isSideStage && mMainStageListener.mVisible) {
+ // Exit to main stage if side stage no longer has children.
+ exitSplitScreen(mMainStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
+ } else if (!isSideStage && mSideStageListener.mVisible) {
+ // Exit to side stage if main stage no longer has children.
+ exitSplitScreen(mSideStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
+ }
+ } else if (isSideStage) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Make sure the main stage is active.
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.setBounds(getSideStageBounds(), wct);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ if (!mLogger.hasStartedSession() && mMainStageListener.mHasChildren
+ && mSideStageListener.mHasChildren) {
+ mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
+ getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
+ }
+
+ @VisibleForTesting
+ IBinder onSnappedToDismissTransition(boolean mainStageToTop) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE, wct);
+ return mSplitTransitions.startSnapToDismiss(wct, this);
+ }
+
+ @Override
+ public void onSnappedToDismiss(boolean bottomOrRight) {
+ final boolean mainStageToTop =
+ bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
+ : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
+ if (ENABLE_SHELL_TRANSITIONS) {
+ onSnappedToDismissTransition(mainStageToTop);
+ return;
+ }
+ exitSplitScreen(mainStageToTop ? mMainStage : mSideStage,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER);
+ }
+
+ @Override
+ public void onDoubleTappedDivider() {
+ setSideStagePosition(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT, null /* wct */);
+ mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
+
+ @Override
+ public void onLayoutPositionChanging(SplitLayout layout) {
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
+ }
+
+ @Override
+ public void onLayoutSizeChanging(SplitLayout layout) {
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
+ mSideStage.setOutlineVisibility(false);
+ }
+
+ @Override
+ public void onLayoutSizeChanged(SplitLayout layout) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ updateWindowBounds(layout, wct);
+ updateUnfoldBounds();
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
+ mSideStage.setOutlineVisibility(true);
+ mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
+ }
+
+ private void updateUnfoldBounds() {
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.onLayoutChanged(getMainStageBounds());
+ mSideUnfoldController.onLayoutChanged(getSideStageBounds());
+ }
+ }
+
+ /**
+ * Populates `wct` with operations that match the split windows to the current layout.
+ * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
+ */
+ private void updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) {
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo);
+ }
+
+ void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t) {
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ (layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash,
+ bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer);
+ }
+
+ @Override
+ public int getSplitItemPosition(WindowContainerToken token) {
+ if (token == null) {
+ return SPLIT_POSITION_UNDEFINED;
+ }
+
+ if (token.equals(mMainStage.mRootTaskInfo.getToken())) {
+ return getMainStagePosition();
+ } else if (token.equals(mSideStage.mRootTaskInfo.getToken())) {
+ return getSideStagePosition();
+ }
+
+ return SPLIT_POSITION_UNDEFINED;
+ }
+
+ @Override
+ public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo,
+ bottomRightStage.mRootTaskInfo);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ @Override
+ public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) {
+ mDisplayAreaInfo = displayAreaInfo;
+ if (mSplitLayout == null) {
+ mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
+ mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
+ mDisplayImeController, mTaskOrganizer, true /* applyDismissingParallax */);
+ mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
+
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.init();
+ mSideUnfoldController.init();
+ }
+ }
+ }
+
+ @Override
+ public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) {
+ throw new IllegalStateException("Well that was unexpected...");
+ }
+
+ @Override
+ public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) {
+ mDisplayAreaInfo = displayAreaInfo;
+ if (mSplitLayout != null
+ && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)
+ && mMainStage.isActive()) {
+ onLayoutSizeChanged(mSplitLayout);
+ }
+ }
+
+ private void onFoldedStateChanged(boolean folded) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ if (!folded) return;
+
+ if (mMainStage.isFocused()) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
+ } else if (mSideStage.isFocused()) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
+ }
+ }
+
+ private Rect getSideStageBounds() {
+ return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
+ }
+
+ private Rect getMainStageBounds() {
+ return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? mSplitLayout.getBounds2() : mSplitLayout.getBounds1();
+ }
+
+ /**
+ * Get the stage that should contain this `taskInfo`. The stage doesn't necessarily contain
+ * this task (yet) so this can also be used to identify which stage to put a task into.
+ */
+ private StageTaskListener getStageOfTask(ActivityManager.RunningTaskInfo taskInfo) {
+ // TODO(b/184679596): Find a way to either include task-org information in the transition,
+ // or synchronize task-org callbacks so we can use stage.containsTask
+ if (mMainStage.mRootTaskInfo != null
+ && taskInfo.parentTaskId == mMainStage.mRootTaskInfo.taskId) {
+ return mMainStage;
+ } else if (mSideStage.mRootTaskInfo != null
+ && taskInfo.parentTaskId == mSideStage.mRootTaskInfo.taskId) {
+ return mSideStage;
+ }
+ return null;
+ }
+
+ @SplitScreen.StageType
+ private int getStageType(StageTaskListener stage) {
+ return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ }
+
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @Nullable TransitionRequestInfo request) {
+ final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
+ if (triggerTask == null) {
+ // still want to monitor everything while in split-screen, so return non-null.
+ return isSplitScreenVisible() ? new WindowContainerTransaction() : null;
+ }
+
+ WindowContainerTransaction out = null;
+ final @WindowManager.TransitionType int type = request.getType();
+ if (isSplitScreenVisible()) {
+ // try to handle everything while in split-screen, so return a WCT even if it's empty.
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split"
+ + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d"
+ + " sideChildren=%d", triggerTask.taskId, transitTypeToString(type),
+ mMainStage.getChildCount(), mSideStage.getChildCount());
+ out = new WindowContainerTransaction();
+ final StageTaskListener stage = getStageOfTask(triggerTask);
+ if (stage != null) {
+ // dismiss split if the last task in one of the stages is going away
+ if (isClosingType(type) && stage.getChildCount() == 1) {
+ // The top should be the opposite side that is closing:
+ mDismissTop = getStageType(stage) == STAGE_TYPE_MAIN
+ ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+ }
+ } else {
+ if (triggerTask.getActivityType() == ACTIVITY_TYPE_HOME && isOpeningType(type)) {
+ // Going home so dismiss both.
+ mDismissTop = STAGE_TYPE_UNDEFINED;
+ }
+ }
+ if (mDismissTop != NO_DISMISS) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ + " deduced Dismiss from request. toTop=%s",
+ stageTypeToString(mDismissTop));
+ prepareExitSplitScreen(mDismissTop, out);
+ mSplitTransitions.mPendingDismiss = transition;
+ }
+ } else {
+ // Not in split mode, so look for an open into a split stage just so we can whine and
+ // complain about how this isn't a supported operation.
+ if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)) {
+ if (getStageOfTask(triggerTask) != null) {
+ throw new IllegalStateException("Entering split implicitly with only one task"
+ + " isn't supported.");
+ }
+ }
+ }
+ return out;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (transition != mSplitTransitions.mPendingDismiss
+ && transition != mSplitTransitions.mPendingEnter) {
+ // Not entering or exiting, so just do some house-keeping and validation.
+
+ // If we're not in split-mode, just abort so something else can handle it.
+ if (!isSplitScreenVisible()) return false;
+
+ for (int iC = 0; iC < info.getChanges().size(); ++iC) {
+ final TransitionInfo.Change change = info.getChanges().get(iC);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || !taskInfo.hasParentTask()) continue;
+ final StageTaskListener stage = getStageOfTask(taskInfo);
+ if (stage == null) continue;
+ if (isOpeningType(change.getMode())) {
+ if (!stage.containsTask(taskInfo.taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called"
+ + " with " + taskInfo.taskId + " before startAnimation().");
+ }
+ } else if (isClosingType(change.getMode())) {
+ if (stage.containsTask(taskInfo.taskId)) {
+ Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
+ + " with " + taskInfo.taskId + " before startAnimation().");
+ }
+ }
+ }
+ if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
+ // TODO(shell-transitions): Implement a fallback behavior for now.
+ throw new IllegalStateException("Somehow removed the last task in a stage"
+ + " outside of a proper transition");
+ // This can happen in some pathological cases. For example:
+ // 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C]
+ // 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time
+ // In this case, the result *should* be that we leave split.
+ // TODO(b/184679596): Find a way to either include task-org information in
+ // the transition, or synchronize task-org callbacks.
+ }
+
+ // Use normal animations.
+ return false;
+ }
+
+ boolean shouldAnimate = true;
+ if (mSplitTransitions.mPendingEnter == transition) {
+ shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
+ } else if (mSplitTransitions.mPendingDismiss == transition) {
+ shouldAnimate = startPendingDismissAnimation(transition, info, startTransaction);
+ }
+ if (!shouldAnimate) return false;
+
+ mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction,
+ finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+ return true;
+ }
+
+ private boolean startPendingEnterAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ if (info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN) {
+ // First, verify that we actually have opened 2 apps in split.
+ TransitionInfo.Change mainChild = null;
+ TransitionInfo.Change sideChild = null;
+ for (int iC = 0; iC < info.getChanges().size(); ++iC) {
+ final TransitionInfo.Change change = info.getChanges().get(iC);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || !taskInfo.hasParentTask()) continue;
+ final @SplitScreen.StageType int stageType = getStageType(getStageOfTask(taskInfo));
+ if (stageType == STAGE_TYPE_MAIN) {
+ mainChild = change;
+ } else if (stageType == STAGE_TYPE_SIDE) {
+ sideChild = change;
+ }
+ }
+ if (mainChild == null || sideChild == null) {
+ throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
+ + " 2 tasks in transition. Possibly one of them failed to launch");
+ // TODO: fallback logic. Probably start a new transition to exit split before
+ // applying anything here. Ideally consolidate with transition-merging.
+ }
+
+ // Update local states (before animating).
+ setDividerVisibility(true);
+ setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */,
+ null /* wct */);
+ setSplitsVisible(true);
+
+ addDividerBarToTransition(info, t, true /* show */);
+
+ // Make some noise if things aren't totally expected. These states shouldn't effect
+ // transitions locally, but remotes (like Launcher) may get confused if they were
+ // depending on listener callbacks. This can happen because task-organizer callbacks
+ // aren't serialized with transition callbacks.
+ // TODO(b/184679596): Find a way to either include task-org information in
+ // the transition, or synchronize task-org callbacks.
+ if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
+ + " to have been called with " + mainChild.getTaskInfo().taskId
+ + " before startAnimation().");
+ }
+ if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + mSideStage
+ + " to have been called with " + sideChild.getTaskInfo().taskId
+ + " before startAnimation().");
+ }
+ return true;
+ } else {
+ // TODO: other entry method animations
+ throw new RuntimeException("Unsupported split-entry");
+ }
+ }
+
+ private boolean startPendingDismissAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ // Make some noise if things aren't totally expected. These states shouldn't effect
+ // transitions locally, but remotes (like Launcher) may get confused if they were
+ // depending on listener callbacks. This can happen because task-organizer callbacks
+ // aren't serialized with transition callbacks.
+ // TODO(b/184679596): Find a way to either include task-org information in
+ // the transition, or synchronize task-org callbacks.
+ if (mMainStage.getChildCount() != 0) {
+ final StringBuilder tasksLeft = new StringBuilder();
+ for (int i = 0; i < mMainStage.getChildCount(); ++i) {
+ tasksLeft.append(i != 0 ? ", " : "");
+ tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i));
+ }
+ Log.w(TAG, "Expected onTaskVanished on " + mMainStage
+ + " to have been called with [" + tasksLeft.toString()
+ + "] before startAnimation().");
+ }
+ if (mSideStage.getChildCount() != 0) {
+ final StringBuilder tasksLeft = new StringBuilder();
+ for (int i = 0; i < mSideStage.getChildCount(); ++i) {
+ tasksLeft.append(i != 0 ? ", " : "");
+ tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i));
+ }
+ Log.w(TAG, "Expected onTaskVanished on " + mSideStage
+ + " to have been called with [" + tasksLeft.toString()
+ + "] before startAnimation().");
+ }
+
+ // Update local states.
+ setSplitsVisible(false);
+ // Wait until after animation to update divider
+
+ if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
+ // Reset crops so they don't interfere with subsequent launches
+ t.setWindowCrop(mMainStage.mRootLeash, null);
+ t.setWindowCrop(mSideStage.mRootLeash, null);
+ }
+
+ if (mDismissTop == STAGE_TYPE_UNDEFINED) {
+ // Going home (dismissing both splits)
+
+ // TODO: Have a proper remote for this. Until then, though, reset state and use the
+ // normal animation stuff (which falls back to the normal launcher remote).
+ t.hide(mSplitLayout.getDividerLeash());
+ setDividerVisibility(false);
+ mSplitTransitions.mPendingDismiss = null;
+ return false;
+ }
+
+ addDividerBarToTransition(info, t, false /* show */);
+ // We're dismissing split by moving the other one to fullscreen.
+ // Since we don't have any animations for this yet, just use the internal example
+ // animations.
+ return true;
+ }
+
+ private void addDividerBarToTransition(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, boolean show) {
+ final SurfaceControl leash = mSplitLayout.getDividerLeash();
+ final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash);
+ final Rect bounds = mSplitLayout.getDividerBounds();
+ barChange.setStartAbsBounds(bounds);
+ barChange.setEndAbsBounds(bounds);
+ barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
+ barChange.setFlags(FLAG_IS_DIVIDER_BAR);
+ // Technically this should be order-0, but this is running after layer assignment
+ // and it's a special case, so just add to end.
+ info.addChange(barChange);
+ // Be default, make it visible. The remote animator can adjust alpha if it plans to animate.
+ if (show) {
+ t.setAlpha(leash, 1.f);
+ t.setLayer(leash, SPLIT_DIVIDER_LAYER);
+ t.setPosition(leash, bounds.left, bounds.top);
+ t.show(leash);
+ }
+ }
+
+ RemoteAnimationTarget getDividerBarLegacyTarget() {
+ final Rect bounds = mSplitLayout.getDividerBounds();
+ return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
+ mSplitLayout.getDividerLeash(), false /* isTranslucent */, null /* clipRect */,
+ null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
+ new android.graphics.Point(0, 0) /* position */, bounds, bounds,
+ new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
+ null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
+ }
+
+ RemoteAnimationTarget getOutlineLegacyTarget() {
+ final Rect bounds = mSideStage.mRootTaskInfo.configuration.windowConfiguration.getBounds();
+ // Leverage TYPE_DOCK_DIVIDER type when wrapping outline remote animation target in order to
+ // distinguish as a split auxiliary target in Launcher.
+ return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
+ mSideStage.getOutlineLeash(), false /* isTranslucent */, null /* clipRect */,
+ null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
+ new android.graphics.Point(0, 0) /* position */, bounds, bounds,
+ new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
+ null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
+ }
+
+ @Override
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ final String childPrefix = innerPrefix + " ";
+ pw.println(prefix + TAG + " mDisplayId=" + mDisplayId);
+ pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
+ pw.println(innerPrefix + "MainStage");
+ pw.println(childPrefix + "isActive=" + mMainStage.isActive());
+ mMainStageListener.dump(pw, childPrefix);
+ pw.println(innerPrefix + "SideStage");
+ mSideStageListener.dump(pw, childPrefix);
+ pw.println(innerPrefix + "mSplitLayout=" + mSplitLayout);
+ }
+
+ /**
+ * Directly set the visibility of both splits. This assumes hasChildren matches visibility.
+ * This is intended for batch use, so it assumes other state management logic is already
+ * handled.
+ */
+ private void setSplitsVisible(boolean visible) {
+ mMainStageListener.mVisible = mSideStageListener.mVisible = visible;
+ mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible;
+ }
+
+ /**
+ * Sets drag info to be logged when splitscreen is next entered.
+ */
+ public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ mLogger.enterRequestedByDrag(position, dragSessionId);
+ }
+
+ /**
+ * Logs the exit of splitscreen.
+ */
+ private void logExit(int exitReason) {
+ mLogger.logExit(exitReason,
+ SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */,
+ SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */,
+ mSplitLayout.isLandscape());
+ }
+
+ /**
+ * Logs the exit of splitscreen to a specific stage. This must be called before the exit is
+ * executed.
+ */
+ private void logExitToStage(int exitReason, boolean toMainStage) {
+ mLogger.logExit(exitReason,
+ toMainStage ? getMainStagePosition() : SPLIT_POSITION_UNDEFINED,
+ toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */,
+ !toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED,
+ !toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */,
+ mSplitLayout.isLandscape());
+ }
+
+ class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
+ boolean mHasRootTask = false;
+ boolean mVisible = false;
+ boolean mHasChildren = false;
+
+ @Override
+ public void onRootTaskAppeared() {
+ mHasRootTask = true;
+ StageCoordinator.this.onStageRootTaskAppeared(this);
+ }
+
+ @Override
+ public void onStatusChanged(boolean visible, boolean hasChildren) {
+ if (!mHasRootTask) return;
+
+ if (mHasChildren != hasChildren) {
+ mHasChildren = hasChildren;
+ StageCoordinator.this.onStageHasChildrenChanged(this);
+ }
+ if (mVisible != visible) {
+ mVisible = visible;
+ StageCoordinator.this.onStageVisibilityChanged(this);
+ }
+ }
+
+ @Override
+ public void onChildTaskStatusChanged(int taskId, boolean present, boolean visible) {
+ StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible);
+ }
+
+ @Override
+ public void onRootTaskVanished() {
+ reset();
+ StageCoordinator.this.onStageRootTaskVanished(this);
+ }
+
+ @Override
+ public void onNoLongerSupportMultiWindow() {
+ if (mMainStage.isActive()) {
+ StageCoordinator.this.exitSplitScreen(null /* childrenToTop */,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+ }
+ }
+
+ private void reset() {
+ mHasRootTask = false;
+ mVisible = false;
+ mHasChildren = false;
+ }
+
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ pw.println(prefix + "mHasRootTask=" + mHasRootTask);
+ pw.println(prefix + "mVisible=" + mVisible);
+ pw.println(prefix + "mHasChildren=" + mHasChildren);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
new file mode 100644
index 000000000000..8b36c9406b15
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
@@ -0,0 +1,288 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+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_UNDEFINED;
+
+import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+
+import android.annotation.CallSuper;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SurfaceUtils;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import java.io.PrintWriter;
+
+/**
+ * Base class that handle common task org. related for split-screen stages.
+ * Note that this class and its sub-class do not directly perform hierarchy operations.
+ * They only serve to hold a collection of tasks and provide APIs like
+ * {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator}
+ * to perform operations in-sync with other containers.
+ *
+ * @see StageCoordinator
+ */
+class StageTaskListener implements ShellTaskOrganizer.TaskListener {
+ private static final String TAG = StageTaskListener.class.getSimpleName();
+
+ protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
+ protected static final int[] CONTROLLED_WINDOWING_MODES =
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
+ protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
+
+ /** Callback interface for listening to changes in a split-screen stage. */
+ public interface StageListenerCallbacks {
+ void onRootTaskAppeared();
+
+ void onStatusChanged(boolean visible, boolean hasChildren);
+
+ void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
+
+ void onRootTaskVanished();
+ void onNoLongerSupportMultiWindow();
+ }
+
+ private final StageListenerCallbacks mCallbacks;
+ private final SurfaceSession mSurfaceSession;
+ protected final SyncTransactionQueue mSyncQueue;
+
+ protected ActivityManager.RunningTaskInfo mRootTaskInfo;
+ protected SurfaceControl mRootLeash;
+ protected SurfaceControl mDimLayer;
+ protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
+ private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
+
+ private final StageTaskUnfoldController mStageTaskUnfoldController;
+
+ StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
+ SurfaceSession surfaceSession,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ mCallbacks = callbacks;
+ mSyncQueue = syncQueue;
+ mSurfaceSession = surfaceSession;
+ mStageTaskUnfoldController = stageTaskUnfoldController;
+ taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
+ }
+
+ int getChildCount() {
+ return mChildrenTaskInfo.size();
+ }
+
+ boolean containsTask(int taskId) {
+ return mChildrenTaskInfo.contains(taskId);
+ }
+
+ /**
+ * Returns the top activity uid for the top child task.
+ */
+ int getTopChildTaskUid() {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i);
+ if (info.topActivityInfo == null) {
+ continue;
+ }
+ return info.topActivityInfo.applicationInfo.uid;
+ }
+ return 0;
+ }
+
+ /** @return {@code true} if this listener contains the currently focused task. */
+ boolean isFocused() {
+ if (mRootTaskInfo == null) {
+ return false;
+ }
+
+ if (mRootTaskInfo.isFocused) {
+ return true;
+ }
+
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ if (mChildrenTaskInfo.valueAt(i).isFocused) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (mRootTaskInfo == null && !taskInfo.hasParentTask()) {
+ mRootLeash = leash;
+ mRootTaskInfo = taskInfo;
+ mCallbacks.onRootTaskAppeared();
+ sendStatusChanged();
+ mSyncQueue.runInSync(t -> {
+ t.hide(mRootLeash);
+ mDimLayer =
+ SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession);
+ });
+ } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
+ final int taskId = taskInfo.taskId;
+ mChildrenLeashes.put(taskId, leash);
+ mChildrenTaskInfo.put(taskId, taskInfo);
+ updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
+ mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Status is managed/synchronized by the transition lifecycle.
+ return;
+ }
+ sendStatusChanged();
+ } else {
+ throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ + "\n mRootTaskInfo: " + mRootTaskInfo);
+ }
+
+ if (mStageTaskUnfoldController != null) {
+ mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash);
+ }
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (!taskInfo.supportsMultiWindow) {
+ // Leave split screen if the task no longer supports multi window.
+ mCallbacks.onNoLongerSupportMultiWindow();
+ return;
+ }
+ if (mRootTaskInfo.taskId == taskInfo.taskId) {
+ mRootTaskInfo = taskInfo;
+ } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
+ mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+ mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
+ taskInfo.isVisible);
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ updateChildTaskSurface(
+ taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
+ }
+ } else {
+ throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ + "\n mRootTaskInfo: " + mRootTaskInfo);
+ }
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Status is managed/synchronized by the transition lifecycle.
+ return;
+ }
+ sendStatusChanged();
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ final int taskId = taskInfo.taskId;
+ if (mRootTaskInfo.taskId == taskId) {
+ mCallbacks.onRootTaskVanished();
+ mSyncQueue.runInSync(t -> t.remove(mDimLayer));
+ mRootTaskInfo = null;
+ } else if (mChildrenTaskInfo.contains(taskId)) {
+ mChildrenTaskInfo.remove(taskId);
+ mChildrenLeashes.remove(taskId);
+ mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Status is managed/synchronized by the transition lifecycle.
+ return;
+ }
+ sendStatusChanged();
+ } else {
+ throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ + "\n mRootTaskInfo: " + mRootTaskInfo);
+ }
+
+ if (mStageTaskUnfoldController != null) {
+ mStageTaskUnfoldController.onTaskVanished(taskInfo);
+ }
+ }
+
+ @Override
+ public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ if (mRootTaskInfo.taskId == taskId) {
+ b.setParent(mRootLeash);
+ } else if (mChildrenLeashes.contains(taskId)) {
+ b.setParent(mChildrenLeashes.get(taskId));
+ } else {
+ throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+ }
+ }
+
+ void setBounds(Rect bounds, WindowContainerTransaction wct) {
+ wct.setBounds(mRootTaskInfo.token, bounds);
+ }
+
+ void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
+ if (!containsTask(taskId)) {
+ return;
+ }
+ wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
+ }
+
+ void setVisibility(boolean visible, WindowContainerTransaction wct) {
+ wct.reorder(mRootTaskInfo.token, visible /* onTop */);
+ }
+
+ void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
+ @SplitScreen.StageType int stage) {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ int taskId = mChildrenTaskInfo.keyAt(i);
+ listener.onTaskStageChanged(taskId, stage,
+ mChildrenTaskInfo.get(taskId).isVisible);
+ }
+ }
+
+ private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl leash, boolean firstAppeared) {
+ final Point taskPositionInParent = taskInfo.positionInParent;
+ mSyncQueue.runInSync(t -> {
+ t.setWindowCrop(leash, null);
+ t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
+ if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) {
+ t.setAlpha(leash, 1f);
+ t.setMatrix(leash, 1, 0, 0, 1);
+ t.show(leash);
+ }
+ });
+ }
+
+ private void sendStatusChanged() {
+ mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0);
+ }
+
+ @Override
+ @CallSuper
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ final String childPrefix = innerPrefix + " ";
+ pw.println(prefix + this);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java
new file mode 100644
index 000000000000..62b9da6d4715
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java
@@ -0,0 +1,224 @@
+/*
+ * 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.wm.shell.stagesplit;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.animation.RectEvaluator;
+import android.animation.TypeEvaluator;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.unfold.UnfoldBackgroundController;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Controls transformations of the split screen task surfaces in response
+ * to the unfolding/folding action on foldable devices
+ */
+public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener {
+
+ private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
+ private static final float CROPPING_START_MARGIN_FRACTION = 0.05f;
+
+ private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
+ private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final UnfoldBackgroundController mBackgroundController;
+ private final Executor mExecutor;
+ private final int mExpandedTaskBarHeight;
+ private final float mWindowCornerRadiusPx;
+ private final Rect mStageBounds = new Rect();
+ private final TransactionPool mTransactionPool;
+
+ private InsetsSource mTaskbarInsetsSource;
+ private boolean mBothStagesVisible;
+
+ public StageTaskUnfoldController(@NonNull Context context,
+ @NonNull TransactionPool transactionPool,
+ @NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
+ @NonNull DisplayInsetsController displayInsetsController,
+ @NonNull UnfoldBackgroundController backgroundController,
+ @NonNull Executor executor) {
+ mUnfoldProgressProvider = unfoldProgressProvider;
+ mTransactionPool = transactionPool;
+ mExecutor = executor;
+ mBackgroundController = backgroundController;
+ mDisplayInsetsController = displayInsetsController;
+ mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.taskbar_frame_height);
+ }
+
+ /**
+ * Initializes the controller, starts listening for the external events
+ */
+ public void init() {
+ mUnfoldProgressProvider.addListener(mExecutor, this);
+ mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ context.update();
+ }
+ }
+
+ /**
+ * Called when split screen task appeared
+ * @param taskInfo info for the appeared task
+ * @param leash surface leash for the appeared task
+ */
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ AnimationContext context = new AnimationContext(leash);
+ mAnimationContextByTaskId.put(taskInfo.taskId, context);
+ }
+
+ /**
+ * Called when a split screen task vanished
+ * @param taskInfo info for the vanished task
+ */
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
+ if (context != null) {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ resetSurface(transaction, context);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ }
+ mAnimationContextByTaskId.remove(taskInfo.taskId);
+ }
+
+ @Override
+ public void onStateChangeProgress(float progress) {
+ if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return;
+
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ mBackgroundController.ensureBackground(transaction);
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+
+ context.mCurrentCropRect.set(RECT_EVALUATOR
+ .evaluate(progress, context.mStartCropRect, context.mEndCropRect));
+
+ transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
+ .setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
+ }
+
+ transaction.apply();
+
+ mTransactionPool.release(transaction);
+ }
+
+ @Override
+ public void onStateChangeFinished() {
+ resetTransformations();
+ }
+
+ /**
+ * Called when split screen visibility changes
+ * @param bothStagesVisible true if both stages of the split screen are visible
+ */
+ public void onSplitVisibilityChanged(boolean bothStagesVisible) {
+ mBothStagesVisible = bothStagesVisible;
+ if (!bothStagesVisible) {
+ resetTransformations();
+ }
+ }
+
+ /**
+ * Called when split screen stage bounds changed
+ * @param bounds new bounds for this stage
+ */
+ public void onLayoutChanged(Rect bounds) {
+ mStageBounds.set(bounds);
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ context.update();
+ }
+ }
+
+ private void resetTransformations() {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ resetSurface(transaction, context);
+ }
+ mBackgroundController.removeBackground(transaction);
+ transaction.apply();
+
+ mTransactionPool.release(transaction);
+ }
+
+ private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) {
+ transaction
+ .setWindowCrop(context.mLeash, null)
+ .setCornerRadius(context.mLeash, 0.0F);
+ }
+
+ private class AnimationContext {
+ final SurfaceControl mLeash;
+ final Rect mStartCropRect = new Rect();
+ final Rect mEndCropRect = new Rect();
+ final Rect mCurrentCropRect = new Rect();
+
+ private AnimationContext(SurfaceControl leash) {
+ this.mLeash = leash;
+ update();
+ }
+
+ private void update() {
+ mStartCropRect.set(mStageBounds);
+
+ if (mTaskbarInsetsSource != null) {
+ // Only insets the cropping window with taskbar when taskbar is expanded
+ if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ mStartCropRect.inset(mTaskbarInsetsSource
+ .calculateVisibleInsets(mStartCropRect));
+ }
+ }
+
+ // Offset to surface coordinates as layout bounds are in screen coordinates
+ mStartCropRect.offsetTo(0, 0);
+
+ mEndCropRect.set(mStartCropRect);
+
+ int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height());
+ int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION);
+ mStartCropRect.inset(margin, margin, margin, margin);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 29326ec90e31..b191cabcf6aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -50,6 +50,7 @@ import android.os.Trace;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Slog;
+import android.view.ContextThemeWrapper;
import android.view.SurfaceControl;
import android.view.View;
import android.window.SplashScreenView;
@@ -109,9 +110,9 @@ public class SplashscreenContentDrawer {
@VisibleForTesting
final ColorCache mColorCache;
- SplashscreenContentDrawer(Context context, TransactionPool pool) {
+ SplashscreenContentDrawer(Context context, IconProvider iconProvider, TransactionPool pool) {
mContext = context;
- mIconProvider = new IconProvider(context);
+ mIconProvider = iconProvider;
mTransactionPool = pool;
// Initialize Splashscreen worker thread
@@ -137,12 +138,14 @@ public class SplashscreenContentDrawer {
* null if failed.
*/
void createContentView(Context context, @StartingWindowType int suggestType, ActivityInfo info,
- int taskId, Consumer<SplashScreenView> splashScreenViewConsumer) {
+ int taskId, Consumer<SplashScreenView> splashScreenViewConsumer,
+ Consumer<Runnable> uiThreadInitConsumer) {
mSplashscreenWorkerHandler.post(() -> {
SplashScreenView contentView;
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "makeSplashScreenContentView");
- contentView = makeSplashScreenContentView(context, info, suggestType);
+ contentView = makeSplashScreenContentView(context, info, suggestType,
+ uiThreadInitConsumer);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} catch (RuntimeException e) {
Slog.w(TAG, "failed creating starting window content at taskId: "
@@ -238,7 +241,7 @@ public class SplashscreenContentDrawer {
}
private SplashScreenView makeSplashScreenContentView(Context context, ActivityInfo ai,
- @StartingWindowType int suggestType) {
+ @StartingWindowType int suggestType, Consumer<Runnable> uiThreadInitConsumer) {
updateDensity();
getWindowAttrs(context, mTmpAttrs);
@@ -253,6 +256,7 @@ public class SplashscreenContentDrawer {
.setWindowBGColor(themeBGColor)
.overlayDrawable(legacyDrawable)
.chooseStyle(suggestType)
+ .setUiThreadInitConsumer(uiThreadInitConsumer)
.build();
}
@@ -299,6 +303,11 @@ public class SplashscreenContentDrawer {
}
}
+ /** Creates the wrapper with system theme to avoid unexpected styles from app. */
+ ContextThemeWrapper createViewContextWrapper(Context appContext) {
+ return new ContextThemeWrapper(appContext, mContext.getTheme());
+ }
+
/** The configuration of the splash screen window. */
public static class SplashScreenWindowAttrs {
private int mWindowBgResId = 0;
@@ -318,6 +327,7 @@ public class SplashscreenContentDrawer {
private int mThemeColor;
private Drawable[] mFinalIconDrawables;
private int mFinalIconSize = mIconSize;
+ private Consumer<Runnable> mUiThreadInitTask;
StartingWindowViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) {
mContext = context;
@@ -339,6 +349,11 @@ public class SplashscreenContentDrawer {
return this;
}
+ StartingWindowViewBuilder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) {
+ mUiThreadInitTask = uiThreadInitTask;
+ return this;
+ }
+
SplashScreenView build() {
Drawable iconDrawable;
final int animationDuration;
@@ -360,7 +375,7 @@ public class SplashscreenContentDrawer {
createIconDrawable(iconDrawable, false);
} else {
final float iconScale = (float) mIconSize / (float) mDefaultIconSize;
- final int densityDpi = mContext.getResources().getDisplayMetrics().densityDpi;
+ final int densityDpi = mContext.getResources().getConfiguration().densityDpi;
final int scaledIconDpi =
(int) (0.5f + iconScale * densityDpi * NO_BACKGROUND_SCALE);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "getIcon");
@@ -385,7 +400,8 @@ public class SplashscreenContentDrawer {
animationDuration = 0;
}
- return fillViewWithIcon(mFinalIconSize, mFinalIconDrawables, animationDuration);
+ return fillViewWithIcon(mFinalIconSize, mFinalIconDrawables, animationDuration,
+ mUiThreadInitTask);
}
private class ShapeIconFactory extends BaseIconFactory {
@@ -463,7 +479,7 @@ public class SplashscreenContentDrawer {
}
private SplashScreenView fillViewWithIcon(int iconSize, @Nullable Drawable[] iconDrawable,
- int animationDuration) {
+ int animationDuration, Consumer<Runnable> uiThreadInitTask) {
Drawable foreground = null;
Drawable background = null;
if (iconDrawable != null) {
@@ -472,13 +488,15 @@ public class SplashscreenContentDrawer {
}
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "fillViewWithIcon");
- final SplashScreenView.Builder builder = new SplashScreenView.Builder(mContext)
+ final ContextThemeWrapper wrapper = createViewContextWrapper(mContext);
+ final SplashScreenView.Builder builder = new SplashScreenView.Builder(wrapper)
.setBackgroundColor(mThemeColor)
.setOverlayDrawable(mOverlayDrawable)
.setIconSize(iconSize)
.setIconBackground(background)
.setCenterViewDrawable(foreground)
- .setAnimationDurationMillis(animationDuration);
+ .setAnimationDurationMillis(animationDuration)
+ .setUiThreadInitConsumer(uiThreadInitTask);
if (mSuggestType == STARTING_WINDOW_TYPE_SPLASH_SCREEN
&& mTmpAttrs.mBrandingImage != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index 951b97e791c9..e2a72bdc872a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -38,6 +38,7 @@ import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Trace;
+import android.util.Log;
import android.util.PathParser;
import android.window.SplashScreenView;
@@ -50,6 +51,8 @@ import com.android.internal.R;
*/
public class SplashscreenIconDrawableFactory {
+ private static final String TAG = "SplashscreenIconDrawableFactory";
+
/**
* @return An array containing the foreground drawable at index 0 and if needed a background
* drawable at index 1.
@@ -282,7 +285,12 @@ public class SplashscreenIconDrawableFactory {
if (startListener != null) {
startListener.run();
}
- mAnimatableIcon.start();
+ try {
+ mAnimatableIcon.start();
+ } catch (Exception ex) {
+ Log.e(TAG, "Error while running the splash screen animated icon", ex);
+ animation.cancel();
+ }
}
@Override
@@ -304,6 +312,13 @@ public class SplashscreenIconDrawableFactory {
return true;
}
+ @Override
+ public void stopAnimation() {
+ if (mIconAnimator != null && mIconAnimator.isRunning()) {
+ mIconAnimator.end();
+ }
+ }
+
private final Callback mCallback = new Callback() {
@Override
public void invalidateDrawable(@NonNull Drawable who) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
index 01c9b6630fa6..76105a39189b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
@@ -36,4 +36,12 @@ public interface StartingSurface {
default int getBackgroundColor(TaskInfo taskInfo) {
return Color.BLACK;
}
+
+ /** Set the proxy to communicate with SysUi side components. */
+ void setSysuiProxy(SysuiProxy proxy);
+
+ /** Callback to tell SysUi components execute some methods. */
+ interface SysuiProxy {
+ void requestTopUi(boolean requestTopUi, String componentTag);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 147f5e30f9d6..bd4869670bec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -37,7 +37,6 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PixelFormat;
-import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.IBinder;
import android.os.RemoteCallback;
@@ -48,19 +47,21 @@ import android.util.Slog;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.Display;
-import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.widget.FrameLayout;
import android.window.SplashScreenView;
import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.StartingWindowInfo;
import android.window.StartingWindowInfo.StartingWindowType;
+import android.window.StartingWindowRemovalInfo;
import android.window.TaskSnapshot;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
@@ -115,17 +116,22 @@ public class StartingSurfaceDrawer {
@VisibleForTesting
final SplashscreenContentDrawer mSplashscreenContentDrawer;
private Choreographer mChoreographer;
+ private final WindowManagerGlobal mWindowManagerGlobal;
+ private StartingSurface.SysuiProxy mSysuiProxy;
+ private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo();
/**
* @param splashScreenExecutor The thread used to control add and remove starting window.
*/
public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
- TransactionPool pool) {
+ IconProvider iconProvider, TransactionPool pool) {
mContext = context;
mDisplayManager = mContext.getSystemService(DisplayManager.class);
mSplashScreenExecutor = splashScreenExecutor;
- mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, pool);
+ mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool);
mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
+ mWindowManagerGlobal = WindowManagerGlobal.getInstance();
+ mDisplayManager.getDisplay(DEFAULT_DISPLAY);
}
private final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>();
@@ -137,29 +143,21 @@ public class StartingSurfaceDrawer {
private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts =
new SparseArray<>(1);
- /** Obtain proper context for showing splash screen on the provided display. */
- private Context getDisplayContext(Context context, int displayId) {
- if (displayId == DEFAULT_DISPLAY) {
- // The default context fits.
- return context;
- }
-
- final Display targetDisplay = mDisplayManager.getDisplay(displayId);
- if (targetDisplay == null) {
- // Failed to obtain the non-default display where splash screen should be shown,
- // lets not show at all.
- return null;
- }
-
- return context.createDisplayContext(targetDisplay);
+ private Display getDisplay(int displayId) {
+ return mDisplayManager.getDisplay(displayId);
}
- private int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) {
+ int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) {
return splashScreenThemeResId != 0
? splashScreenThemeResId
: activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
: com.android.internal.R.style.Theme_DeviceDefault_DayNight;
}
+
+ void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) {
+ mSysuiProxy = sysuiProxy;
+ }
+
/**
* Called when a task need a splash screen starting window.
*
@@ -177,7 +175,7 @@ public class StartingSurfaceDrawer {
final int displayId = taskInfo.displayId;
final int taskId = taskInfo.taskId;
- Context context = mContext;
+
// replace with the default theme if the application didn't set
final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
if (DEBUG_SPLASH_SCREEN) {
@@ -185,14 +183,16 @@ public class StartingSurfaceDrawer {
+ " theme=" + Integer.toHexString(theme) + " task=" + taskInfo.taskId
+ " suggestType=" + suggestType);
}
-
- // Obtain proper context to launch on the right display.
- final Context displayContext = getDisplayContext(context, displayId);
- if (displayContext == null) {
+ final Display display = getDisplay(displayId);
+ if (display == null) {
// Can't show splash screen on requested display, so skip showing at all.
return;
}
- context = displayContext;
+ Context context = displayId == DEFAULT_DISPLAY
+ ? mContext : mContext.createDisplayContext(display);
+ if (context == null) {
+ return;
+ }
if (theme != context.getThemeResId()) {
try {
context = context.createPackageContextAsUser(activityInfo.packageName,
@@ -303,7 +303,8 @@ public class StartingSurfaceDrawer {
// Record whether create splash screen view success, notify to current thread after
// create splash screen view finished.
final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
- final FrameLayout rootLayout = new FrameLayout(context);
+ final FrameLayout rootLayout = new FrameLayout(
+ mSplashscreenContentDrawer.createViewContextWrapper(context));
rootLayout.setPadding(0, 0, 0, 0);
rootLayout.setFitsSystemWindows(false);
final Runnable setViewSynchronized = () -> {
@@ -328,12 +329,13 @@ public class StartingSurfaceDrawer {
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
};
+ if (mSysuiProxy != null) {
+ mSysuiProxy.requestTopUi(true, TAG);
+ }
mSplashscreenContentDrawer.createContentView(context, suggestType, activityInfo, taskId,
- viewSupplier::setView);
-
+ viewSupplier::setView, viewSupplier::setUiThreadInitTask);
try {
- final WindowManager wm = context.getSystemService(WindowManager.class);
- if (addWindow(taskId, appToken, rootLayout, wm, params, suggestType)) {
+ if (addWindow(taskId, appToken, rootLayout, display, params, suggestType)) {
// We use the splash screen worker thread to create SplashScreenView while adding
// the window, as otherwise Choreographer#doFrame might be delayed on this thread.
// And since Choreographer#doFrame won't happen immediately after adding the window,
@@ -366,6 +368,7 @@ public class StartingSurfaceDrawer {
private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
private SplashScreenView mView;
private boolean mIsViewSet;
+ private Runnable mUiThreadInitTask;
void setView(SplashScreenView view) {
synchronized (this) {
mView = view;
@@ -374,6 +377,12 @@ public class StartingSurfaceDrawer {
}
}
+ void setUiThreadInitTask(Runnable initTask) {
+ synchronized (this) {
+ mUiThreadInitTask = initTask;
+ }
+ }
+
@Override
public @Nullable SplashScreenView get() {
synchronized (this) {
@@ -383,6 +392,10 @@ public class StartingSurfaceDrawer {
} catch (InterruptedException ignored) {
}
}
+ if (mUiThreadInitTask != null) {
+ mUiThreadInitTask.run();
+ mUiThreadInitTask = null;
+ }
return mView;
}
}
@@ -446,12 +459,13 @@ public class StartingSurfaceDrawer {
/**
* Called when the content of a task is ready to show, starting window can be removed.
*/
- public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
- boolean playRevealAnimation) {
+ public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
- Slog.d(TAG, "Task start finish, remove starting surface for task " + taskId);
+ Slog.d(TAG, "Task start finish, remove starting surface for task "
+ + removalInfo.taskId);
}
- removeWindowSynced(taskId, leash, frame, playRevealAnimation);
+ removeWindowSynced(removalInfo);
+
}
/**
@@ -505,15 +519,17 @@ public class StartingSurfaceDrawer {
Slog.v(TAG, reason + "the splash screen. Releasing SurfaceControlViewHost for task:"
+ taskId);
}
- viewHost.getView().post(viewHost::release);
+ SplashScreenView.releaseIconHost(viewHost);
}
- protected boolean addWindow(int taskId, IBinder appToken, View view, WindowManager wm,
+ protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
WindowManager.LayoutParams params, @StartingWindowType int suggestType) {
boolean shouldSaveView = true;
+ final Context context = view.getContext();
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView");
- wm.addView(view, params);
+ mWindowManagerGlobal.addView(view, params, display,
+ null /* parentWindow */, context.getUserId());
} catch (WindowManager.BadTokenException e) {
// ignore
Slog.w(TAG, appToken + " already running, starting window not displayed. "
@@ -521,9 +537,9 @@ public class StartingSurfaceDrawer {
shouldSaveView = false;
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- if (view != null && view.getParent() == null) {
+ if (view.getParent() == null) {
Slog.w(TAG, "view not successfully added to wm, removing view");
- wm.removeViewImmediate(view);
+ mWindowManagerGlobal.removeView(view, true /* immediate */);
shouldSaveView = false;
}
}
@@ -542,7 +558,8 @@ public class StartingSurfaceDrawer {
}
private void removeWindowNoAnimate(int taskId) {
- removeWindowSynced(taskId, null, null, false);
+ mTmpRemovalInfo.taskId = taskId;
+ removeWindowSynced(mTmpRemovalInfo);
}
void onImeDrawnOnTask(int taskId) {
@@ -554,8 +571,8 @@ public class StartingSurfaceDrawer {
mStartingWindowRecords.remove(taskId);
}
- protected void removeWindowSynced(int taskId, SurfaceControl leash, Rect frame,
- boolean playRevealAnimation) {
+ protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo) {
+ final int taskId = removalInfo.taskId;
final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
if (record != null) {
if (record.mDecorView != null) {
@@ -566,9 +583,9 @@ public class StartingSurfaceDrawer {
if (record.mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
removeWindowInner(record.mDecorView, false);
} else {
- if (playRevealAnimation) {
+ if (removalInfo.playRevealAnimation) {
mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
- leash, frame,
+ removalInfo.windowAnimationLeash, removalInfo.mainFrame,
() -> removeWindowInner(record.mDecorView, true));
} else {
// the SplashScreenView has been copied to client, hide the view to skip
@@ -588,19 +605,19 @@ public class StartingSurfaceDrawer {
Slog.v(TAG, "Removing task snapshot window for " + taskId);
}
record.mTaskSnapshotWindow.scheduleRemove(
- () -> mStartingWindowRecords.remove(taskId));
+ () -> mStartingWindowRecords.remove(taskId), removalInfo.deferRemoveForIme);
}
}
}
private void removeWindowInner(View decorView, boolean hideView) {
+ if (mSysuiProxy != null) {
+ mSysuiProxy.requestTopUi(false, TAG);
+ }
if (hideView) {
decorView.setVisibility(View.GONE);
}
- final WindowManager wm = decorView.getContext().getSystemService(WindowManager.class);
- if (wm != null) {
- wm.removeView(decorView);
- }
+ mWindowManagerGlobal.removeView(decorView, false /* immediate */);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index dee21b093dce..a86e07a5602d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -28,16 +28,14 @@ import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Color;
-import android.graphics.Rect;
-import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.Trace;
import android.util.Slog;
import android.util.SparseIntArray;
-import android.view.SurfaceControl;
import android.window.StartingWindowInfo;
import android.window.StartingWindowInfo.StartingWindowType;
+import android.window.StartingWindowRemovalInfo;
import android.window.TaskOrganizer;
import android.window.TaskSnapshot;
@@ -45,6 +43,7 @@ import androidx.annotation.BinderThread;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.function.TriConsumer;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
@@ -68,7 +67,7 @@ import com.android.wm.shell.common.TransactionPool;
public class StartingWindowController implements RemoteCallable<StartingWindowController> {
private static final String TAG = StartingWindowController.class.getSimpleName();
- public static final boolean DEBUG_SPLASH_SCREEN = Build.isDebuggable();
+ public static final boolean DEBUG_SPLASH_SCREEN = false;
public static final boolean DEBUG_TASK_SNAPSHOT = false;
private static final long TASK_BG_COLOR_RETAIN_TIME_MS = 5000;
@@ -87,9 +86,11 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
private final SparseIntArray mTaskBackgroundColors = new SparseIntArray();
public StartingWindowController(Context context, ShellExecutor splashScreenExecutor,
- StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, TransactionPool pool) {
+ StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
+ TransactionPool pool) {
mContext = context;
- mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor, pool);
+ mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor,
+ iconProvider, pool);
mStartingWindowTypeAlgorithm = startingWindowTypeAlgorithm;
mSplashScreenExecutor = splashScreenExecutor;
}
@@ -134,7 +135,7 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken,
suggestionType);
} else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
- final TaskSnapshot snapshot = windowInfo.mTaskSnapshot;
+ final TaskSnapshot snapshot = windowInfo.taskSnapshot;
mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken,
snapshot);
}
@@ -186,13 +187,12 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
/**
* Called when the content of a task is ready to show, starting window can be removed.
*/
- public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
- boolean playRevealAnimation) {
+ public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.removeStartingWindow(
- taskId, leash, frame, playRevealAnimation));
+ removalInfo));
mSplashScreenExecutor.executeDelayed(() -> {
synchronized (mTaskBackgroundColors) {
- mTaskBackgroundColors.delete(taskId);
+ mTaskBackgroundColors.delete(removalInfo.taskId);
}
}, TASK_BG_COLOR_RETAIN_TIME_MS);
}
@@ -224,6 +224,11 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
return color != Color.TRANSPARENT
? color : SplashscreenContentDrawer.getSystemBGColor();
}
+
+ @Override
+ public void setSysuiProxy(SysuiProxy proxy) {
+ mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.setSysuiProxy(proxy));
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index dfb1ae3ef2a0..3e88c464d359 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -130,7 +130,6 @@ public class TaskSnapshotWindow {
private final Window mWindow;
private final Runnable mClearWindowHandler;
- private final long mDelayRemovalTime;
private final ShellExecutor mSplashScreenExecutor;
private final SurfaceControl mSurfaceControl;
private final IWindowSession mSession;
@@ -210,7 +209,7 @@ public class TaskSnapshotWindow {
final SurfaceControl surfaceControl = new SurfaceControl();
final ClientWindowFrames tmpFrames = new ClientWindowFrames();
- final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0];
+ final InsetsSourceControl[] tmpControls = new InsetsSourceControl[0];
final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
final TaskDescription taskDescription;
@@ -221,22 +220,19 @@ public class TaskSnapshotWindow {
taskDescription.setBackgroundColor(WHITE);
}
- final long delayRemovalTime = snapshot.hasImeSurface() ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
- : DELAY_REMOVAL_TIME_GENERAL;
-
final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, appearance,
windowFlags, windowPrivateFlags, taskBounds, orientation, activityType,
- delayRemovalTime, topWindowInsetsState, clearWindowHandler, splashScreenExecutor);
+ topWindowInsetsState, clearWindowHandler, splashScreenExecutor);
final Window window = snapshotSurface.mWindow;
- final InsetsState mTmpInsetsState = new InsetsState();
+ final InsetsState tmpInsetsState = new InsetsState();
final InputChannel tmpInputChannel = new InputChannel();
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#addToDisplay");
final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
- mTmpInsetsState, tmpInputChannel, mTmpInsetsState, mTempControls);
+ info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (res < 0) {
Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
@@ -249,8 +245,8 @@ public class TaskSnapshotWindow {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, -1,
- tmpFrames, tmpMergedConfiguration, surfaceControl, mTmpInsetsState,
- mTempControls, TMP_SURFACE_SIZE);
+ tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
+ tmpControls, TMP_SURFACE_SIZE);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} catch (RemoteException e) {
snapshotSurface.clearWindowSynced();
@@ -265,9 +261,8 @@ public class TaskSnapshotWindow {
public TaskSnapshotWindow(SurfaceControl surfaceControl,
TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription,
int appearance, int windowFlags, int windowPrivateFlags, Rect taskBounds,
- int currentOrientation, int activityType, long delayRemovalTime,
- InsetsState topWindowInsetsState, Runnable clearWindowHandler,
- ShellExecutor splashScreenExecutor) {
+ int currentOrientation, int activityType, InsetsState topWindowInsetsState,
+ Runnable clearWindowHandler, ShellExecutor splashScreenExecutor) {
mSplashScreenExecutor = splashScreenExecutor;
mSession = WindowManagerGlobal.getWindowSession();
mWindow = new Window();
@@ -283,7 +278,6 @@ public class TaskSnapshotWindow {
mStatusBarColor = taskDescription.getStatusBarColor();
mOrientationOnCreation = currentOrientation;
mActivityType = activityType;
- mDelayRemovalTime = delayRemovalTime;
mTransaction = new SurfaceControl.Transaction();
mClearWindowHandler = clearWindowHandler;
mHasImeSurface = snapshot.hasImeSurface();
@@ -294,7 +288,7 @@ public class TaskSnapshotWindow {
}
boolean hasImeSurface() {
- return mHasImeSurface;
+ return mHasImeSurface;
}
/**
@@ -314,7 +308,7 @@ public class TaskSnapshotWindow {
mSystemBarBackgroundPainter.drawNavigationBarBackground(c);
}
- void scheduleRemove(Runnable onRemove) {
+ void scheduleRemove(Runnable onRemove, boolean deferRemoveForIme) {
// Show the latest content as soon as possible for unlocking to home.
if (mActivityType == ACTIVITY_TYPE_HOME) {
removeImmediately();
@@ -329,9 +323,12 @@ public class TaskSnapshotWindow {
TaskSnapshotWindow.this.removeImmediately();
onRemove.run();
};
- mSplashScreenExecutor.executeDelayed(mScheduledRunnable, mDelayRemovalTime);
+ final long delayRemovalTime = mHasImeSurface && deferRemoveForIme
+ ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
+ : DELAY_REMOVAL_TIME_GENERAL;
+ mSplashScreenExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
if (DEBUG) {
- Slog.d(TAG, "Defer removing snapshot surface in " + mDelayRemovalTime);
+ Slog.d(TAG, "Defer removing snapshot surface in " + delayRemovalTime);
}
}
@@ -362,7 +359,7 @@ public class TaskSnapshotWindow {
static Rect getSystemBarInsets(Rect frame, InsetsState state) {
return state.calculateInsets(frame, WindowInsets.Type.systemBars(),
- false /* ignoreVisibility */);
+ false /* ignoreVisibility */).toRect();
}
private void drawSnapshot() {
@@ -382,6 +379,7 @@ public class TaskSnapshotWindow {
// In case window manager leaks us, make sure we don't retain the snapshot.
mSnapshot = null;
+ mSurfaceControl.release();
}
private void drawSizeMatchSnapshot() {
@@ -449,6 +447,7 @@ public class TaskSnapshotWindow {
mTransaction.setBuffer(mSurfaceControl, background);
}
mTransaction.apply();
+ childSurfaceControl.release();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
index 848eff4b56f3..bde2b5ff4d60 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
@@ -71,23 +71,13 @@ public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgor
+ " topIsHome:" + topIsHome);
}
- final int visibleSplashScreenType = legacySplashScreen
- ? STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
- : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
-
if (!topIsHome) {
- if (!processRunning) {
+ if (!processRunning || newTask || (taskSwitch && !activityCreated)) {
return useEmptySplashScreen
? STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN
- : visibleSplashScreenType;
- }
- if (newTask) {
- return useEmptySplashScreen
- ? STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN
- : visibleSplashScreenType;
- }
- if (taskSwitch && !activityCreated) {
- return visibleSplashScreenType;
+ : legacySplashScreen
+ ? STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+ : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
}
}
if (taskSwitch && allowTaskSnapshot) {
@@ -107,7 +97,7 @@ public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgor
* rotation must be the same).
*/
private boolean isSnapshotCompatible(StartingWindowInfo windowInfo) {
- final TaskSnapshot snapshot = windowInfo.mTaskSnapshot;
+ final TaskSnapshot snapshot = windowInfo.taskSnapshot;
if (snapshot == null) {
if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) {
Slog.d(TAG, "isSnapshotCompatible no snapshot " + windowInfo.taskInfo.taskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index c6fb5af7d4be..7abda994bb5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -16,18 +16,38 @@
package com.android.wm.shell.transition;
+import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
+import static android.app.ActivityOptions.ANIM_CUSTOM;
+import static android.app.ActivityOptions.ANIM_NONE;
+import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
+import static android.app.ActivityOptions.ANIM_SCALE_UP;
+import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
+import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
+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.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static 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_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+import static android.window.TransitionInfo.isIndependent;
+
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -35,25 +55,37 @@ import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
import android.os.IBinder;
+import android.os.SystemProperties;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.view.Choreographer;
import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.window.TransitionInfo;
+import android.window.TransitionMetrics;
import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.AttributeCache;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.util.CounterRotator;
import java.util.ArrayList;
@@ -61,33 +93,179 @@ import java.util.ArrayList;
public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private static final int MAX_ANIMATION_DURATION = 3000;
+ /**
+ * Restrict ability of activities overriding transition animation in a way such that
+ * an activity can do it only when the transition happens within a same task.
+ *
+ * @see android.app.Activity#overridePendingTransition(int, int)
+ */
+ private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
+ "persist.wm.disable_custom_task_animation";
+
+ /**
+ * @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
+ */
+ static boolean sDisableCustomTaskAnimationProperty =
+ SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
+
private final TransactionPool mTransactionPool;
+ private final DisplayController mDisplayController;
+ private final Context mContext;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
private final TransitionAnimation mTransitionAnimation;
+ private final SurfaceSession mSurfaceSession = new SurfaceSession();
+
/** Keeps track of the currently-running animations associated with each transition. */
private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>();
private final Rect mInsets = new Rect(0, 0, 0, 0);
private float mTransitionAnimationScaleSetting = 1.0f;
- DefaultTransitionHandler(@NonNull TransactionPool transactionPool, Context context,
+ private final int mCurrentUserId;
+
+ private ScreenRotationAnimation mRotationAnimation;
+
+ DefaultTransitionHandler(@NonNull DisplayController displayController,
+ @NonNull TransactionPool transactionPool, Context context,
@NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+ mDisplayController = displayController;
mTransactionPool = transactionPool;
+ mContext = context;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);
+ mCurrentUserId = UserHandle.myUserId();
AttributeCache.init(context);
}
+ @VisibleForTesting
+ static boolean isRotationSeamless(@NonNull TransitionInfo info,
+ DisplayController displayController) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Display is changing, check if it should be seamless.");
+ boolean checkedDisplayLayout = false;
+ boolean hasTask = false;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+
+ // Only look at changing things. showing/hiding don't need to rotate.
+ if (change.getMode() != TRANSIT_CHANGE) continue;
+
+ // This container isn't rotating, so we can ignore it.
+ if (change.getEndRotation() == change.getStartRotation()) continue;
+
+ if ((change.getFlags() & FLAG_IS_DISPLAY) != 0) {
+ // In the presence of System Alert windows we can not seamlessly rotate.
+ if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " display has system alert windows, so not seamless.");
+ return false;
+ }
+ } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+ if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " wallpaper is participating but isn't seamless.");
+ return false;
+ }
+ } else if (change.getTaskInfo() != null) {
+ hasTask = true;
+ // We only enable seamless rotation if all the visible task windows requested it.
+ if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " task %s isn't requesting seamless, so not seamless.",
+ change.getTaskInfo().taskId);
+ return false;
+ }
+
+ // This is the only way to get display-id currently, so we will check display
+ // capabilities here
+ if (!checkedDisplayLayout) {
+ // only need to check display once.
+ checkedDisplayLayout = true;
+ final DisplayLayout displayLayout = displayController.getDisplayLayout(
+ change.getTaskInfo().displayId);
+ // For the upside down rotation we don't rotate seamlessly as the navigation
+ // bar moves position. Note most apps (using orientation:sensor or user as
+ // opposed to fullSensor) will not enter the reverse portrait orientation, so
+ // actually the orientation won't change at all.
+ int upsideDownRotation = displayLayout.getUpsideDownRotation();
+ if (change.getStartRotation() == upsideDownRotation
+ || change.getEndRotation() == upsideDownRotation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " rotation involves upside-down portrait, so not seamless.");
+ return false;
+ }
+
+ // If the navigation bar can't change sides, then it will jump when we change
+ // orientations and we don't rotate seamlessly - unless that is allowed, eg.
+ // with gesture navigation where the navbar is low-profile enough that this
+ // isn't very noticeable.
+ if (!displayLayout.allowSeamlessRotationDespiteNavBarMoving()
+ && (!(displayLayout.navigationBarCanMove()
+ && (change.getStartAbsBounds().width()
+ != change.getStartAbsBounds().height())))) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " nav bar changes sides, so not seamless.");
+ return false;
+ }
+ }
+ }
+ }
+
+ // ROTATION_ANIMATION_SEAMLESS can only be requested by task.
+ if (hasTask) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Rotation IS seamless.");
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Gets the rotation animation for the topmost task. Assumes that seamless is checked
+ * elsewhere, so it will default SEAMLESS to ROTATE.
+ */
+ private int getRotationAnimation(@NonNull TransitionInfo info) {
+ // Traverse in top-to-bottom order so that the first task is top-most
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+
+ // Only look at changing things. showing/hiding don't need to rotate.
+ if (change.getMode() != TRANSIT_CHANGE) continue;
+
+ // This container isn't rotating, so we can ignore it.
+ if (change.getEndRotation() == change.getStartRotation()) continue;
+
+ if (change.getTaskInfo() != null) {
+ final int anim = change.getRotationAnimation();
+ if (anim == ROTATION_ANIMATION_UNSPECIFIED
+ // Fallback animation for seamless should also be default.
+ || anim == ROTATION_ANIMATION_SEAMLESS) {
+ return ROTATION_ANIMATION_ROTATE;
+ }
+ return anim;
+ }
+ }
+ return ROTATION_ANIMATION_ROTATE;
+ }
+
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"start default transition animation, info = %s", info);
+ // If keyguard goes away, we should loadKeyguardExitAnimation. Otherwise this just
+ // immediately finishes since there is no animation for screen-wake.
+ if (info.getType() == WindowManager.TRANSIT_WAKE && !info.isKeyguardGoingAway()) {
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ return true;
+ }
+
if (mAnimations.containsKey(transition)) {
throw new IllegalStateException("Got a duplicate startAnimation call for "
+ transition);
@@ -95,21 +273,78 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final ArrayList<Animator> animations = new ArrayList<>();
mAnimations.put(transition, animations);
+ final ArrayMap<WindowContainerToken, CounterRotator> counterRotators = new ArrayMap<>();
+
final Runnable onAnimFinish = () -> {
if (!animations.isEmpty()) return;
+
+ for (int i = 0; i < counterRotators.size(); ++i) {
+ counterRotators.valueAt(i).cleanUp(info.getRootLeash());
+ }
+ counterRotators.clear();
+
+ if (mRotationAnimation != null) {
+ mRotationAnimation.kill();
+ mRotationAnimation = null;
+ }
+
mAnimations.remove(transition);
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
};
+
+ final int wallpaperTransit = getWallpaperTransitType(info);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
+
+ if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
+ int rotateDelta = change.getEndRotation() - change.getStartRotation();
+ int displayW = change.getEndAbsBounds().width();
+ int displayH = change.getEndAbsBounds().height();
+ if (info.getType() == TRANSIT_CHANGE) {
+ boolean isSeamless = isRotationSeamless(info, mDisplayController);
+ final int anim = getRotationAnimation(info);
+ if (!(isSeamless || anim == ROTATION_ANIMATION_JUMPCUT)) {
+ mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession,
+ mTransactionPool, startTransaction, change, info.getRootLeash());
+ mRotationAnimation.startAnimation(animations, onAnimFinish,
+ mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor);
+ continue;
+ }
+ } else {
+ // opening/closing an app into a new orientation. Counter-rotate all
+ // "going-away" things since they are still in the old orientation.
+ for (int j = info.getChanges().size() - 1; j >= 0; --j) {
+ final TransitionInfo.Change innerChange = info.getChanges().get(j);
+ if (!Transitions.isClosingType(innerChange.getMode())
+ || !isIndependent(innerChange, info)
+ || innerChange.getParent() == null) {
+ continue;
+ }
+ CounterRotator crot = counterRotators.get(innerChange.getParent());
+ if (crot == null) {
+ crot = new CounterRotator();
+ crot.setup(startTransaction,
+ info.getChange(innerChange.getParent()).getLeash(),
+ rotateDelta, displayW, displayH);
+ if (crot.getSurface() != null) {
+ int layer = info.getChanges().size() - j;
+ startTransaction.setLayer(crot.getSurface(), layer);
+ }
+ counterRotators.put(innerChange.getParent(), crot);
+ }
+ crot.addChild(startTransaction, innerChange.getLeash());
+ }
+ }
+ }
+
if (change.getMode() == TRANSIT_CHANGE) {
// No default animation for this, so just update bounds/position.
- t.setPosition(change.getLeash(),
+ startTransaction.setPosition(change.getLeash(),
change.getEndAbsBounds().left - change.getEndRelOffset().x,
change.getEndAbsBounds().top - change.getEndRelOffset().y);
if (change.getTaskInfo() != null) {
// Skip non-tasks since those usually have null bounds.
- t.setWindowCrop(change.getLeash(),
+ startTransaction.setWindowCrop(change.getLeash(),
change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
}
}
@@ -117,12 +352,18 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
// Don't animate anything that isn't independent.
if (!TransitionInfo.isIndependent(change, info)) continue;
- Animation a = loadAnimation(info.getType(), info.getFlags(), change);
+ Animation a = loadAnimation(info, change, wallpaperTransit);
if (a != null) {
- startAnimInternal(animations, a, change.getLeash(), onAnimFinish);
+ startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
+ mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */);
+
+ if (info.getAnimationOptions() != null) {
+ attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions());
+ }
}
}
- t.apply();
+ startTransaction.apply();
+ TransitionMetrics.getInstance().reportAnimationStart(transition);
// run finish now in-case there are no animations
onAnimFinish.run();
return true;
@@ -141,87 +382,134 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
@Nullable
- private Animation loadAnimation(int type, int flags, TransitionInfo.Change change) {
- // TODO(b/178678389): It should handle more type animation here
+ private Animation loadAnimation(TransitionInfo info, TransitionInfo.Change change,
+ int wallpaperTransit) {
Animation a = null;
- final boolean isOpening = Transitions.isOpeningType(type);
+ final int type = info.getType();
+ final int flags = info.getFlags();
final int changeMode = change.getMode();
final int changeFlags = change.getFlags();
+ final boolean isOpeningType = Transitions.isOpeningType(type);
+ final boolean enter = Transitions.isOpeningType(changeMode);
+ final boolean isTask = change.getTaskInfo() != null;
+ final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
+ final int overrideType = options != null ? options.getType() : ANIM_NONE;
+ final boolean canCustomContainer = isTask ? !sDisableCustomTaskAnimationProperty : true;
- if (type == TRANSIT_RELAUNCH) {
- a = mTransitionAnimation.createRelaunchAnimation(
- change.getStartAbsBounds(), mInsets, change.getEndAbsBounds());
- } else if (type == TRANSIT_KEYGUARD_GOING_AWAY) {
+ if (info.isKeyguardGoingAway()) {
a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
(changeFlags & FLAG_SHOW_WALLPAPER) != 0);
} else if (type == TRANSIT_KEYGUARD_UNOCCLUDE) {
a = mTransitionAnimation.loadKeyguardUnoccludeAnimation();
- } else if (changeMode == TRANSIT_OPEN && isOpening) {
- if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
- // This received a transferred starting window, so don't animate
- return null;
- }
-
- if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
- a = mTransitionAnimation.loadVoiceActivityOpenAnimation(true /** enter */);
- } else if (change.getTaskInfo() != null) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(
- R.styleable.WindowAnimation_taskOpenEnterAnimation);
- } else {
- a = mTransitionAnimation.loadDefaultAnimationRes(
- (changeFlags & FLAG_TRANSLUCENT) == 0
- ? R.anim.activity_open_enter : R.anim.activity_translucent_open_enter);
- }
- } else if (changeMode == TRANSIT_TO_FRONT && isOpening) {
- if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
- // This received a transferred starting window, so don't animate
- return null;
- }
-
- if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
- a = mTransitionAnimation.loadVoiceActivityOpenAnimation(true /** enter */);
- } else {
- a = mTransitionAnimation.loadDefaultAnimationAttr(
- R.styleable.WindowAnimation_taskToFrontEnterAnimation);
- }
- } else if (changeMode == TRANSIT_CLOSE && !isOpening) {
- if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
- a = mTransitionAnimation.loadVoiceActivityExitAnimation(false /** enter */);
- } else if (change.getTaskInfo() != null) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(
- R.styleable.WindowAnimation_taskCloseExitAnimation);
- } else {
- a = mTransitionAnimation.loadDefaultAnimationRes(
- (changeFlags & FLAG_TRANSLUCENT) == 0
- ? R.anim.activity_close_exit : R.anim.activity_translucent_close_exit);
- }
- } else if (changeMode == TRANSIT_TO_BACK && !isOpening) {
- if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
- a = mTransitionAnimation.loadVoiceActivityExitAnimation(false /** enter */);
+ } else if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
+ if (isOpeningType) {
+ a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter);
} else {
- a = mTransitionAnimation.loadDefaultAnimationAttr(
- R.styleable.WindowAnimation_taskToBackExitAnimation);
+ a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter);
}
} else if (changeMode == TRANSIT_CHANGE) {
// In the absence of a specific adapter, we just want to keep everything stationary.
a = new AlphaAnimation(1.f, 1.f);
a.setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION);
+ } else if (type == TRANSIT_RELAUNCH) {
+ a = mTransitionAnimation.createRelaunchAnimation(
+ change.getEndAbsBounds(), mInsets, change.getEndAbsBounds());
+ } else if (overrideType == ANIM_CUSTOM
+ && (canCustomContainer || options.getOverrideTaskTransition())) {
+ a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter
+ ? options.getEnterResId() : options.getExitResId());
+ } else if (overrideType == ANIM_OPEN_CROSS_PROFILE_APPS && enter) {
+ a = mTransitionAnimation.loadCrossProfileAppEnterAnimation();
+ } else if (overrideType == ANIM_CLIP_REVEAL) {
+ a = mTransitionAnimation.createClipRevealAnimationLocked(type, wallpaperTransit, enter,
+ change.getEndAbsBounds(), change.getEndAbsBounds(),
+ options.getTransitionBounds());
+ } else if (overrideType == ANIM_SCALE_UP) {
+ a = mTransitionAnimation.createScaleUpAnimationLocked(type, wallpaperTransit, enter,
+ change.getEndAbsBounds(), options.getTransitionBounds());
+ } else if (overrideType == ANIM_THUMBNAIL_SCALE_UP
+ || overrideType == ANIM_THUMBNAIL_SCALE_DOWN) {
+ final boolean scaleUp = overrideType == ANIM_THUMBNAIL_SCALE_UP;
+ a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked(enter, scaleUp,
+ change.getEndAbsBounds(), type, wallpaperTransit, options.getThumbnail(),
+ options.getTransitionBounds());
+ } else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) {
+ // This received a transferred starting window, so don't animate
+ return null;
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
+ a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+ ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
+ : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation);
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
+ a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+ ? R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
+ : R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation);
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN) {
+ a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+ ? R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
+ : R.styleable.WindowAnimation_wallpaperOpenExitAnimation);
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_CLOSE) {
+ a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+ ? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
+ : R.styleable.WindowAnimation_wallpaperCloseExitAnimation);
+ } else if (type == TRANSIT_OPEN) {
+ if (isTask) {
+ a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+ ? R.styleable.WindowAnimation_taskOpenEnterAnimation
+ : R.styleable.WindowAnimation_taskOpenExitAnimation);
+ } else {
+ if ((changeFlags & FLAG_TRANSLUCENT) != 0 && enter) {
+ a = mTransitionAnimation.loadDefaultAnimationRes(
+ R.anim.activity_translucent_open_enter);
+ } else {
+ a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+ ? R.styleable.WindowAnimation_activityOpenEnterAnimation
+ : R.styleable.WindowAnimation_activityOpenExitAnimation);
+ }
+ }
+ } else if (type == TRANSIT_TO_FRONT) {
+ a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+ ? R.styleable.WindowAnimation_taskToFrontEnterAnimation
+ : R.styleable.WindowAnimation_taskToFrontExitAnimation);
+ } else if (type == TRANSIT_CLOSE) {
+ if (isTask) {
+ a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+ ? R.styleable.WindowAnimation_taskCloseEnterAnimation
+ : R.styleable.WindowAnimation_taskCloseExitAnimation);
+ } else {
+ if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
+ a = mTransitionAnimation.loadDefaultAnimationRes(
+ R.anim.activity_translucent_close_exit);
+ } else {
+ a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+ ? R.styleable.WindowAnimation_activityCloseEnterAnimation
+ : R.styleable.WindowAnimation_activityCloseExitAnimation);
+ }
+ }
+ } else if (type == TRANSIT_TO_BACK) {
+ a = mTransitionAnimation.loadDefaultAnimationAttr(enter
+ ? R.styleable.WindowAnimation_taskToBackEnterAnimation
+ : R.styleable.WindowAnimation_taskToBackExitAnimation);
}
if (a != null) {
- Rect start = change.getStartAbsBounds();
- Rect end = change.getEndAbsBounds();
+ if (!a.isInitialized()) {
+ Rect end = change.getEndAbsBounds();
+ a.initialize(end.width(), end.height(), end.width(), end.height());
+ }
a.restrictDuration(MAX_ANIMATION_DURATION);
- a.initialize(end.width(), end.height(), start.width(), start.height());
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
}
return a;
}
- private void startAnimInternal(@NonNull ArrayList<Animator> animations, @NonNull Animation anim,
- @NonNull SurfaceControl leash, @NonNull Runnable finishCallback) {
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ static void startSurfaceAnimation(@NonNull ArrayList<Animator> animations,
+ @NonNull Animation anim, @NonNull SurfaceControl leash,
+ @NonNull Runnable finishCallback, @NonNull TransactionPool pool,
+ @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor,
+ @Nullable Point position) {
+ final SurfaceControl.Transaction transaction = pool.acquire();
final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
final Transformation transformation = new Transformation();
final float[] matrix = new float[9];
@@ -231,14 +519,16 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
va.addUpdateListener(animation -> {
final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
- applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix);
+ applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
+ position);
});
final Runnable finisher = () -> {
- applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix);
+ applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
+ position);
- mTransactionPool.release(transaction);
- mMainExecutor.execute(() -> {
+ pool.release(transaction);
+ mainExecutor.execute(() -> {
animations.remove(va);
finishCallback.run();
});
@@ -255,12 +545,116 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
});
animations.add(va);
- mAnimExecutor.execute(va::start);
+ animExecutor.execute(va::start);
+ }
+
+ private void attachThumbnail(@NonNull ArrayList<Animator> animations,
+ @NonNull Runnable finishCallback, TransitionInfo.Change change,
+ TransitionInfo.AnimationOptions options) {
+ final boolean isTask = change.getTaskInfo() != null;
+ final boolean isOpen = Transitions.isOpeningType(change.getMode());
+ final boolean isClose = Transitions.isClosingType(change.getMode());
+ if (isOpen) {
+ if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS && isTask) {
+ attachCrossProfileThunmbnailAnimation(animations, finishCallback, change);
+ } else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) {
+ attachThumbnailAnimation(animations, finishCallback, change, options);
+ }
+ } else if (isClose && options.getType() == ANIM_THUMBNAIL_SCALE_DOWN) {
+ attachThumbnailAnimation(animations, finishCallback, change, options);
+ }
+ }
+
+ private void attachCrossProfileThunmbnailAnimation(@NonNull ArrayList<Animator> animations,
+ @NonNull Runnable finishCallback, TransitionInfo.Change change) {
+ final int thumbnailDrawableRes = change.getTaskInfo().userId == mCurrentUserId
+ ? R.drawable.ic_account_circle : R.drawable.ic_corp_badge;
+ final Rect bounds = change.getEndAbsBounds();
+ final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail(
+ thumbnailDrawableRes, bounds);
+ if (thumbnail == null) {
+ return;
+ }
+
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession,
+ change.getLeash(), thumbnail, transaction);
+ final Animation a =
+ mTransitionAnimation.createCrossProfileAppsThumbnailAnimationLocked(bounds);
+ if (a == null) {
+ return;
+ }
+
+ final Runnable finisher = () -> {
+ wt.destroy(transaction);
+ mTransactionPool.release(transaction);
+
+ finishCallback.run();
+ };
+ a.restrictDuration(MAX_ANIMATION_DURATION);
+ a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
+ mMainExecutor, mAnimExecutor, new Point(bounds.left, bounds.top));
+ }
+
+ private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations,
+ @NonNull Runnable finishCallback, TransitionInfo.Change change,
+ TransitionInfo.AnimationOptions options) {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ final WindowThumbnail wt = WindowThumbnail.createAndAttach(mSurfaceSession,
+ change.getLeash(), options.getThumbnail(), transaction);
+ final Rect bounds = change.getEndAbsBounds();
+ final int orientation = mContext.getResources().getConfiguration().orientation;
+ final Animation a = mTransitionAnimation.createThumbnailAspectScaleAnimationLocked(bounds,
+ mInsets, options.getThumbnail(), orientation, null /* startRect */,
+ options.getTransitionBounds(), options.getType() == ANIM_THUMBNAIL_SCALE_UP);
+
+ final Runnable finisher = () -> {
+ wt.destroy(transaction);
+ mTransactionPool.release(transaction);
+
+ finishCallback.run();
+ };
+ a.restrictDuration(MAX_ANIMATION_DURATION);
+ a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+ startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
+ mMainExecutor, mAnimExecutor, null /* position */);
+ }
+
+ private static int getWallpaperTransitType(TransitionInfo info) {
+ boolean hasOpenWallpaper = false;
+ boolean hasCloseWallpaper = false;
+
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0) {
+ if (Transitions.isOpeningType(change.getMode())) {
+ hasOpenWallpaper = true;
+ } else if (Transitions.isClosingType(change.getMode())) {
+ hasCloseWallpaper = true;
+ }
+ }
+ }
+
+ if (hasOpenWallpaper && hasCloseWallpaper) {
+ return Transitions.isOpeningType(info.getType())
+ ? WALLPAPER_TRANSITION_INTRA_OPEN : WALLPAPER_TRANSITION_INTRA_CLOSE;
+ } else if (hasOpenWallpaper) {
+ return WALLPAPER_TRANSITION_OPEN;
+ } else if (hasCloseWallpaper) {
+ return WALLPAPER_TRANSITION_CLOSE;
+ } else {
+ return WALLPAPER_TRANSITION_NONE;
+ }
}
private static void applyTransformation(long time, SurfaceControl.Transaction t,
- SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix) {
+ SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix,
+ Point position) {
anim.getTransformation(time, transformation);
+ if (position != null) {
+ transformation.getMatrix().postTranslate(position.x, position.y);
+ }
t.setMatrix(leash, transformation.getMatrix(), matrix);
t.setAlpha(leash, transformation.getAlpha());
t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
index dffc700a3690..bdcdb63d2cd6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
@@ -16,7 +16,7 @@
package com.android.wm.shell.transition;
-import android.window.IRemoteTransition;
+import android.window.RemoteTransition;
import android.window.TransitionFilter;
/**
@@ -28,10 +28,10 @@ interface IShellTransitions {
* Registers a remote transition handler.
*/
oneway void registerRemote(in TransitionFilter filter,
- in IRemoteTransition remoteTransition) = 1;
+ in RemoteTransition remoteTransition) = 1;
/**
* Unregisters a remote transition handler.
*/
- oneway void unregisterRemote(in IRemoteTransition remoteTransition) = 2;
+ oneway void unregisterRemote(in RemoteTransition remoteTransition) = 2;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
new file mode 100644
index 000000000000..61e11e877b90
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
@@ -0,0 +1,124 @@
+/*
+ * 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.wm.shell.transition;
+
+import android.annotation.NonNull;
+import android.os.RemoteException;
+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.IWindowContainerTransactionCallback;
+
+/**
+ * Utilities and interfaces for transition-like usage on top of the legacy app-transition and
+ * synctransaction tools.
+ */
+public class LegacyTransitions {
+
+ /**
+ * Interface for a "legacy" transition. Effectively wraps a sync callback + remoteAnimation
+ * into one callback.
+ */
+ public interface ILegacyTransition {
+ /**
+ * Called when both the associated sync transaction finishes and the remote animation is
+ * ready.
+ */
+ void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback, SurfaceControl.Transaction t);
+ }
+
+ /**
+ * Makes sure that a remote animation and corresponding sync callback are called together
+ * such that the sync callback is called first. This assumes that both the callback receiver
+ * and the remoteanimation are in the same process so that order is preserved on both ends.
+ */
+ public static class LegacyTransition {
+ private final ILegacyTransition mLegacyTransition;
+ private int mSyncId = -1;
+ private SurfaceControl.Transaction mTransaction;
+ private int mTransit;
+ private RemoteAnimationTarget[] mApps;
+ private RemoteAnimationTarget[] mWallpapers;
+ private RemoteAnimationTarget[] mNonApps;
+ private IRemoteAnimationFinishedCallback mFinishCallback = null;
+ private boolean mCancelled = false;
+ private final SyncCallback mSyncCallback = new SyncCallback();
+ private final RemoteAnimationAdapter mAdapter =
+ new RemoteAnimationAdapter(new RemoteAnimationWrapper(), 0, 0);
+
+ public LegacyTransition(@WindowManager.TransitionType int type,
+ @NonNull ILegacyTransition legacyTransition) {
+ mLegacyTransition = legacyTransition;
+ mTransit = type;
+ }
+
+ public @WindowManager.TransitionType int getType() {
+ return mTransit;
+ }
+
+ public IWindowContainerTransactionCallback getSyncCallback() {
+ return mSyncCallback;
+ }
+
+ public RemoteAnimationAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ private class SyncCallback extends IWindowContainerTransactionCallback.Stub {
+ @Override
+ public void onTransactionReady(int id, SurfaceControl.Transaction t)
+ throws RemoteException {
+ mSyncId = id;
+ mTransaction = t;
+ checkApply();
+ }
+ }
+
+ private class RemoteAnimationWrapper extends IRemoteAnimationRunner.Stub {
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+ mTransit = transit;
+ mApps = apps;
+ mWallpapers = wallpapers;
+ mNonApps = nonApps;
+ mFinishCallback = finishedCallback;
+ checkApply();
+ }
+
+ @Override
+ public void onAnimationCancelled() throws RemoteException {
+ mCancelled = true;
+ mApps = mWallpapers = mNonApps = null;
+ checkApply();
+ }
+ }
+
+
+ private void checkApply() throws RemoteException {
+ if (mSyncId < 0 || (mFinishCallback == null && !mCancelled)) return;
+ mLegacyTransition.onAnimationStart(mTransit, mApps, mWallpapers,
+ mNonApps, mFinishCallback, mTransaction);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 4da6664aa3dc..3be896e4aca3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -24,6 +24,7 @@ import android.util.Log;
import android.view.SurfaceControl;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
+import android.window.RemoteTransition;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
@@ -43,10 +44,10 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
private IBinder mTransition = null;
/** The remote to delegate animation to */
- private final IRemoteTransition mRemote;
+ private final RemoteTransition mRemote;
public OneShotRemoteHandler(@NonNull ShellExecutor mainExecutor,
- @NonNull IRemoteTransition remote) {
+ @NonNull RemoteTransition remote) {
mMainExecutor = mainExecutor;
mRemote = remote;
}
@@ -57,7 +58,8 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (mTransition != transition) return false;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote"
@@ -70,19 +72,24 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
};
IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
@Override
- public void onTransitionFinished(WindowContainerTransaction wct) {
+ public void onTransitionFinished(WindowContainerTransaction wct,
+ SurfaceControl.Transaction sct) {
if (mRemote.asBinder() != null) {
mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
}
- mMainExecutor.execute(
- () -> finishCallback.onTransitionFinished(wct, null /* wctCB */));
+ mMainExecutor.execute(() -> {
+ if (sct != null) {
+ finishTransaction.merge(sct);
+ }
+ finishCallback.onTransitionFinished(wct, null /* wctCB */);
+ });
}
};
try {
if (mRemote.asBinder() != null) {
mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
}
- mRemote.startAnimation(transition, info, t, cb);
+ mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error running remote transition.", e);
if (mRemote.asBinder() != null) {
@@ -102,13 +109,14 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
@Override
- public void onTransitionFinished(WindowContainerTransaction wct) {
+ public void onTransitionFinished(WindowContainerTransaction wct,
+ SurfaceControl.Transaction sct) {
mMainExecutor.execute(
() -> finishCallback.onTransitionFinished(wct, null /* wctCB */));
}
};
try {
- mRemote.mergeAnimation(transition, info, t, mergeTarget, cb);
+ mRemote.getRemoteTransition().mergeAnimation(transition, info, t, mergeTarget, cb);
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error merging remote transition.", e);
}
@@ -118,8 +126,9 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
@Nullable
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@Nullable TransitionRequestInfo request) {
- IRemoteTransition remote = request.getRemoteTransition();
- if (remote != mRemote) return null;
+ RemoteTransition remote = request.getRemoteTransition();
+ IRemoteTransition iRemote = remote != null ? remote.getRemoteTransition() : null;
+ if (iRemote != mRemote.getRemoteTransition()) return null;
mTransition = transition;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested"
+ " for %s: %s", transition, remote);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 9bfb261fcb85..c798ace18b5f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -27,6 +27,7 @@ import android.util.Slog;
import android.view.SurfaceControl;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
+import android.window.RemoteTransition;
import android.window.TransitionFilter;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -50,45 +51,33 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
private final ShellExecutor mMainExecutor;
/** Includes remotes explicitly requested by, eg, ActivityOptions */
- private final ArrayMap<IBinder, IRemoteTransition> mRequestedRemotes = new ArrayMap<>();
+ private final ArrayMap<IBinder, RemoteTransition> mRequestedRemotes = new ArrayMap<>();
/** Ordered by specificity. Last filters will be checked first */
- private final ArrayList<Pair<TransitionFilter, IRemoteTransition>> mFilters =
+ private final ArrayList<Pair<TransitionFilter, RemoteTransition>> mFilters =
new ArrayList<>();
- private final IBinder.DeathRecipient mTransitionDeathRecipient =
- new IBinder.DeathRecipient() {
- @Override
- @BinderThread
- public void binderDied() {
- mMainExecutor.execute(() -> mFilters.clear());
- }
- };
+ private final ArrayMap<IBinder, RemoteDeathHandler> mDeathHandlers = new ArrayMap<>();
RemoteTransitionHandler(@NonNull ShellExecutor mainExecutor) {
mMainExecutor = mainExecutor;
}
- void addFiltered(TransitionFilter filter, IRemoteTransition remote) {
- try {
- remote.asBinder().linkToDeath(mTransitionDeathRecipient, 0 /* flags */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to link to death");
- return;
- }
+ void addFiltered(TransitionFilter filter, RemoteTransition remote) {
+ handleDeath(remote.asBinder(), null /* finishCallback */);
mFilters.add(new Pair<>(filter, remote));
}
- void removeFiltered(IRemoteTransition remote) {
+ void removeFiltered(RemoteTransition remote) {
boolean removed = false;
for (int i = mFilters.size() - 1; i >= 0; --i) {
- if (mFilters.get(i).second == remote) {
+ if (mFilters.get(i).second.asBinder().equals(remote.asBinder())) {
mFilters.remove(i);
removed = true;
}
}
if (removed) {
- remote.asBinder().unlinkToDeath(mTransitionDeathRecipient, 0 /* flags */);
+ unhandleDeath(remote.asBinder(), null /* finishCallback */);
}
}
@@ -99,9 +88,10 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- IRemoteTransition pendingRemote = mRequestedRemotes.get(transition);
+ RemoteTransition pendingRemote = mRequestedRemotes.get(transition);
if (pendingRemote == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s doesn't have "
+ "explicit remote, search filters for match for %s", transition, info);
@@ -110,6 +100,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Checking filter %s",
mFilters.get(i));
if (mFilters.get(i).first.matches(info)) {
+ Slog.d(TAG, "Found filter" + mFilters.get(i));
pendingRemote = mFilters.get(i).second;
// Add to requested list so that it can be found for merge requests.
mRequestedRemotes.put(transition, pendingRemote);
@@ -122,36 +113,27 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
if (pendingRemote == null) return false;
- final IRemoteTransition remote = pendingRemote;
- final IBinder.DeathRecipient remoteDied = () -> {
- Log.e(Transitions.TAG, "Remote transition died, finishing");
- mMainExecutor.execute(() -> {
- mRequestedRemotes.remove(transition);
- finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
- });
- };
+ final RemoteTransition remote = pendingRemote;
IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
@Override
- public void onTransitionFinished(WindowContainerTransaction wct) {
- if (remote.asBinder() != null) {
- remote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
- }
+ public void onTransitionFinished(WindowContainerTransaction wct,
+ SurfaceControl.Transaction sct) {
+ unhandleDeath(remote.asBinder(), finishCallback);
mMainExecutor.execute(() -> {
+ if (sct != null) {
+ finishTransaction.merge(sct);
+ }
mRequestedRemotes.remove(transition);
finishCallback.onTransitionFinished(wct, null /* wctCB */);
});
}
};
try {
- if (remote.asBinder() != null) {
- remote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
- }
- remote.startAnimation(transition, info, t, cb);
+ handleDeath(remote.asBinder(), finishCallback);
+ remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error running remote transition.", e);
- if (remote.asBinder() != null) {
- remote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
- }
+ unhandleDeath(remote.asBinder(), finishCallback);
mRequestedRemotes.remove(transition);
mMainExecutor.execute(
() -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */));
@@ -163,14 +145,15 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- final IRemoteTransition remote = mRequestedRemotes.get(mergeTarget);
+ final IRemoteTransition remote = mRequestedRemotes.get(mergeTarget).getRemoteTransition();
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt merge %s into %s",
transition, remote);
if (remote == null) return;
IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
@Override
- public void onTransitionFinished(WindowContainerTransaction wct) {
+ public void onTransitionFinished(WindowContainerTransaction wct,
+ SurfaceControl.Transaction sct) {
mMainExecutor.execute(() -> {
if (!mRequestedRemotes.containsKey(mergeTarget)) {
Log.e(TAG, "Merged transition finished after it's mergeTarget (the "
@@ -193,11 +176,98 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
@Nullable
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@Nullable TransitionRequestInfo request) {
- IRemoteTransition remote = request.getRemoteTransition();
+ RemoteTransition remote = request.getRemoteTransition();
if (remote == null) return null;
mRequestedRemotes.put(transition, remote);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested"
+ " for %s: %s", transition, remote);
return new WindowContainerTransaction();
}
+
+ private void handleDeath(@NonNull IBinder remote,
+ @Nullable Transitions.TransitionFinishCallback finishCallback) {
+ synchronized (mDeathHandlers) {
+ RemoteDeathHandler deathHandler = mDeathHandlers.get(remote);
+ if (deathHandler == null) {
+ deathHandler = new RemoteDeathHandler(remote);
+ try {
+ remote.linkToDeath(deathHandler, 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death");
+ return;
+ }
+ mDeathHandlers.put(remote, deathHandler);
+ }
+ deathHandler.addUser(finishCallback);
+ }
+ }
+
+ private void unhandleDeath(@NonNull IBinder remote,
+ @Nullable Transitions.TransitionFinishCallback finishCallback) {
+ synchronized (mDeathHandlers) {
+ RemoteDeathHandler deathHandler = mDeathHandlers.get(remote);
+ if (deathHandler == null) return;
+ deathHandler.removeUser(finishCallback);
+ if (deathHandler.getUserCount() == 0) {
+ if (!deathHandler.mPendingFinishCallbacks.isEmpty()) {
+ throw new IllegalStateException("Unhandling death for binder that still has"
+ + " pending finishCallback(s).");
+ }
+ remote.unlinkToDeath(deathHandler, 0 /* flags */);
+ mDeathHandlers.remove(remote);
+ }
+ }
+ }
+
+ /** NOTE: binder deaths can alter the filter order */
+ private class RemoteDeathHandler implements IBinder.DeathRecipient {
+ private final IBinder mRemote;
+ private final ArrayList<Transitions.TransitionFinishCallback> mPendingFinishCallbacks =
+ new ArrayList<>();
+ private int mUsers = 0;
+
+ RemoteDeathHandler(IBinder remote) {
+ mRemote = remote;
+ }
+
+ void addUser(@Nullable Transitions.TransitionFinishCallback finishCallback) {
+ if (finishCallback != null) {
+ mPendingFinishCallbacks.add(finishCallback);
+ }
+ ++mUsers;
+ }
+
+ void removeUser(@Nullable Transitions.TransitionFinishCallback finishCallback) {
+ if (finishCallback != null) {
+ mPendingFinishCallbacks.remove(finishCallback);
+ }
+ --mUsers;
+ }
+
+ int getUserCount() {
+ return mUsers;
+ }
+
+ @Override
+ @BinderThread
+ public void binderDied() {
+ mMainExecutor.execute(() -> {
+ for (int i = mFilters.size() - 1; i >= 0; --i) {
+ if (mRemote.equals(mFilters.get(i).second.asBinder())) {
+ mFilters.remove(i);
+ }
+ }
+ for (int i = mRequestedRemotes.size() - 1; i >= 0; --i) {
+ if (mRemote.equals(mRequestedRemotes.valueAt(i).asBinder())) {
+ mRequestedRemotes.removeAt(i);
+ }
+ }
+ for (int i = mPendingFinishCallbacks.size() - 1; i >= 0; --i) {
+ mPendingFinishCallbacks.get(i).onTransitionFinished(
+ null /* wct */, null /* wctCB */);
+ }
+ mPendingFinishCallbacks.clear();
+ });
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
new file mode 100644
index 000000000000..13c670a1ab1e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -0,0 +1,485 @@
+/*
+ * 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.wm.shell.transition;
+
+import static android.hardware.HardwareBuffer.RGBA_8888;
+import static android.hardware.HardwareBuffer.USAGE_PROTECTED_CONTENT;
+import static android.util.RotationUtils.deltaRotation;
+import static android.view.WindowManagerPolicyConstants.SCREEN_FREEZE_LAYER_BASE;
+
+import static com.android.wm.shell.transition.DefaultTransitionHandler.startSurfaceAnimation;
+import static com.android.wm.shell.transition.Transitions.TAG;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.media.Image;
+import android.media.ImageReader;
+import android.util.Slog;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+import android.view.SurfaceSession;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.window.TransitionInfo;
+
+import com.android.internal.R;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * This class handles the rotation animation when the device is rotated.
+ *
+ * <p>
+ * The screen rotation animation is composed of 4 different part:
+ * <ul>
+ * <li> The screenshot: <p>
+ * A screenshot of the whole screen prior the change of orientation is taken to hide the
+ * element resizing below. The screenshot is then animated to rotate and cross-fade to
+ * the new orientation with the content in the new orientation.
+ *
+ * <li> The windows on the display: <p>y
+ * Once the device is rotated, the screen and its content are in the new orientation. The
+ * animation first rotate the new content into the old orientation to then be able to
+ * animate to the new orientation
+ *
+ * <li> The Background color frame: <p>
+ * To have the animation seem more seamless, we add a color transitioning background behind the
+ * exiting and entering layouts. We compute the brightness of the start and end
+ * layouts and transition from the two brightness values as grayscale underneath the animation
+ *
+ * <li> The entering Blackframe: <p>
+ * The enter Blackframe is similar to the exit Blackframe but is only used when a custom
+ * rotation animation is used and matches the new content size instead of the screenshot.
+ * </ul>
+ */
+class ScreenRotationAnimation {
+ static final int MAX_ANIMATION_DURATION = 10 * 1000;
+
+ private final Context mContext;
+ private final TransactionPool mTransactionPool;
+ private final float[] mTmpFloats = new float[9];
+ // Complete transformations being applied.
+ private final Matrix mSnapshotInitialMatrix = new Matrix();
+ /** The leash of display. */
+ private final SurfaceControl mSurfaceControl;
+ private final Rect mStartBounds = new Rect();
+ private final Rect mEndBounds = new Rect();
+
+ private final int mStartWidth;
+ private final int mStartHeight;
+ private final int mEndWidth;
+ private final int mEndHeight;
+ private final int mStartRotation;
+ private final int mEndRotation;
+
+ /** This layer contains the actual screenshot that is to be faded out. */
+ private SurfaceControl mScreenshotLayer;
+ /**
+ * Only used for screen rotation and not custom animations. Layered behind all other layers
+ * to avoid showing any "empty" spots
+ */
+ private SurfaceControl mBackColorSurface;
+ /** The leash using to animate screenshot layer. */
+ private SurfaceControl mAnimLeash;
+ private Transaction mTransaction;
+
+ // The current active animation to move from the old to the new rotated
+ // state. Which animation is run here will depend on the old and new
+ // rotations.
+ private Animation mRotateExitAnimation;
+ private Animation mRotateEnterAnimation;
+
+ /** Intensity of light/whiteness of the layout before rotation occurs. */
+ private float mStartLuma;
+ /** Intensity of light/whiteness of the layout after rotation occurs. */
+ private float mEndLuma;
+
+ ScreenRotationAnimation(Context context, SurfaceSession session, TransactionPool pool,
+ Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash) {
+ mContext = context;
+ mTransactionPool = pool;
+
+ mSurfaceControl = change.getLeash();
+ mStartWidth = change.getStartAbsBounds().width();
+ mStartHeight = change.getStartAbsBounds().height();
+ mEndWidth = change.getEndAbsBounds().width();
+ mEndHeight = change.getEndAbsBounds().height();
+ mStartRotation = change.getStartRotation();
+ mEndRotation = change.getEndRotation();
+
+ mStartBounds.set(change.getStartAbsBounds());
+ mEndBounds.set(change.getEndAbsBounds());
+
+ mAnimLeash = new SurfaceControl.Builder(session)
+ .setParent(rootLeash)
+ .setEffectLayer()
+ .setCallsite("ShellRotationAnimation")
+ .setName("Animation leash of screenshot rotation")
+ .build();
+
+ try {
+ SurfaceControl.LayerCaptureArgs args =
+ new SurfaceControl.LayerCaptureArgs.Builder(mSurfaceControl)
+ .setCaptureSecureLayers(true)
+ .setAllowProtected(true)
+ .setSourceCrop(new Rect(0, 0, mStartWidth, mStartHeight))
+ .build();
+ SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
+ SurfaceControl.captureLayers(args);
+ if (screenshotBuffer == null) {
+ Slog.w(TAG, "Unable to take screenshot of display");
+ return;
+ }
+
+ mBackColorSurface = new SurfaceControl.Builder(session)
+ .setParent(rootLeash)
+ .setColorLayer()
+ .setCallsite("ShellRotationAnimation")
+ .setName("BackColorSurface")
+ .build();
+
+ mScreenshotLayer = new SurfaceControl.Builder(session)
+ .setParent(mAnimLeash)
+ .setBLASTLayer()
+ .setSecure(screenshotBuffer.containsSecureLayers())
+ .setCallsite("ShellRotationAnimation")
+ .setName("RotationLayer")
+ .build();
+
+ HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
+ mStartLuma = getMedianBorderLuma(hardwareBuffer, screenshotBuffer.getColorSpace());
+
+ GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(
+ screenshotBuffer.getHardwareBuffer());
+
+ t.setLayer(mBackColorSurface, -1);
+ t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
+ t.setAlpha(mBackColorSurface, 1);
+ t.show(mBackColorSurface);
+
+ t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
+ t.setPosition(mAnimLeash, 0, 0);
+ t.setAlpha(mAnimLeash, 1);
+ t.show(mAnimLeash);
+
+ t.setBuffer(mScreenshotLayer, buffer);
+ t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
+ t.show(mScreenshotLayer);
+
+ } catch (Surface.OutOfResourcesException e) {
+ Slog.w(TAG, "Unable to allocate freeze surface", e);
+ }
+
+ setRotation(t);
+ t.apply();
+ }
+
+ private void setRotation(SurfaceControl.Transaction t) {
+ // Compute the transformation matrix that must be applied
+ // to the snapshot to make it stay in the same original position
+ // with the current screen rotation.
+ int delta = deltaRotation(mEndRotation, mStartRotation);
+ createRotationMatrix(delta, mStartWidth, mStartHeight, mSnapshotInitialMatrix);
+ setRotationTransform(t, mSnapshotInitialMatrix);
+ }
+
+ private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) {
+ if (mScreenshotLayer == null) {
+ return;
+ }
+ matrix.getValues(mTmpFloats);
+ float x = mTmpFloats[Matrix.MTRANS_X];
+ float y = mTmpFloats[Matrix.MTRANS_Y];
+ t.setPosition(mScreenshotLayer, x, y);
+ t.setMatrix(mScreenshotLayer,
+ mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
+ mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
+
+ t.setAlpha(mScreenshotLayer, (float) 1.0);
+ t.show(mScreenshotLayer);
+ }
+
+ /**
+ * Returns true if animating.
+ */
+ public boolean startAnimation(@NonNull ArrayList<Animator> animations,
+ @NonNull Runnable finishCallback, float animationScale,
+ @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+ if (mScreenshotLayer == null) {
+ // Can't do animation.
+ return false;
+ }
+
+ // TODO : Found a way to get right end luma and re-enable color frame animation.
+ // End luma value is very not stable so it will cause more flicker is we run background
+ // color frame animation.
+ //mEndLuma = getLumaOfSurfaceControl(mEndBounds, mSurfaceControl);
+
+ // Figure out how the screen has moved from the original rotation.
+ int delta = deltaRotation(mEndRotation, mStartRotation);
+ switch (delta) { /* Counter-Clockwise Rotations */
+ case Surface.ROTATION_0:
+ mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.screen_rotate_0_exit);
+ mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.rotation_animation_enter);
+ break;
+ case Surface.ROTATION_90:
+ mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.screen_rotate_plus_90_exit);
+ mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.screen_rotate_plus_90_enter);
+ break;
+ case Surface.ROTATION_180:
+ mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.screen_rotate_180_exit);
+ mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.screen_rotate_180_enter);
+ break;
+ case Surface.ROTATION_270:
+ mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.screen_rotate_minus_90_exit);
+ mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ R.anim.screen_rotate_minus_90_enter);
+ break;
+ }
+
+ mRotateExitAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
+ mRotateExitAnimation.restrictDuration(MAX_ANIMATION_DURATION);
+ mRotateExitAnimation.scaleCurrentDuration(animationScale);
+ mRotateEnterAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
+ mRotateEnterAnimation.restrictDuration(MAX_ANIMATION_DURATION);
+ mRotateEnterAnimation.scaleCurrentDuration(animationScale);
+
+ mTransaction = mTransactionPool.acquire();
+ startDisplayRotation(animations, finishCallback, mainExecutor, animExecutor);
+ startScreenshotRotationAnimation(animations, finishCallback, mainExecutor, animExecutor);
+ //startColorAnimation(mTransaction, animationScale);
+
+ return true;
+ }
+
+ private void startDisplayRotation(@NonNull ArrayList<Animator> animations,
+ @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor,
+ @NonNull ShellExecutor animExecutor) {
+ startSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
+ mTransactionPool, mainExecutor, animExecutor, null /* position */);
+ }
+
+ private void startScreenshotRotationAnimation(@NonNull ArrayList<Animator> animations,
+ @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor,
+ @NonNull ShellExecutor animExecutor) {
+ startSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback,
+ mTransactionPool, mainExecutor, animExecutor, null /* position */);
+ }
+
+ private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) {
+ int colorTransitionMs = mContext.getResources().getInteger(
+ R.integer.config_screen_rotation_color_transition);
+ final float[] rgbTmpFloat = new float[3];
+ final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
+ final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
+ final long duration = colorTransitionMs * (long) animationScale;
+ final Transaction t = mTransactionPool.acquire();
+
+ final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
+ // Animation length is already expected to be scaled.
+ va.overrideDurationScale(1.0f);
+ va.setDuration(duration);
+ va.addUpdateListener(animation -> {
+ final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
+ final float fraction = currentPlayTime / va.getDuration();
+ applyColor(startColor, endColor, rgbTmpFloat, fraction, mBackColorSurface, t);
+ });
+ va.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
+ t);
+ mTransactionPool.release(t);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
+ t);
+ mTransactionPool.release(t);
+ }
+ });
+ animExecutor.execute(va::start);
+ }
+
+ public void kill() {
+ Transaction t = mTransaction != null ? mTransaction : mTransactionPool.acquire();
+ if (mAnimLeash.isValid()) {
+ t.remove(mAnimLeash);
+ }
+
+ if (mScreenshotLayer != null) {
+ if (mScreenshotLayer.isValid()) {
+ t.remove(mScreenshotLayer);
+ }
+ mScreenshotLayer = null;
+
+ if (mBackColorSurface != null) {
+ if (mBackColorSurface.isValid()) {
+ t.remove(mBackColorSurface);
+ }
+ mBackColorSurface = null;
+ }
+ }
+ t.apply();
+ mTransactionPool.release(t);
+ }
+
+ /**
+ * Converts the provided {@link HardwareBuffer} and converts it to a bitmap to then sample the
+ * luminance at the borders of the bitmap
+ * @return the average luminance of all the pixels at the borders of the bitmap
+ */
+ private static float getMedianBorderLuma(HardwareBuffer hardwareBuffer, ColorSpace colorSpace) {
+ // Cannot read content from buffer with protected usage.
+ if (hardwareBuffer == null || hardwareBuffer.getFormat() != RGBA_8888
+ || hasProtectedContent(hardwareBuffer)) {
+ return 0;
+ }
+
+ ImageReader ir = ImageReader.newInstance(hardwareBuffer.getWidth(),
+ hardwareBuffer.getHeight(), hardwareBuffer.getFormat(), 1);
+ ir.getSurface().attachAndQueueBufferWithColorSpace(hardwareBuffer, colorSpace);
+ Image image = ir.acquireLatestImage();
+ if (image == null || image.getPlanes().length == 0) {
+ return 0;
+ }
+
+ Image.Plane plane = image.getPlanes()[0];
+ ByteBuffer buffer = plane.getBuffer();
+ int width = image.getWidth();
+ int height = image.getHeight();
+ int pixelStride = plane.getPixelStride();
+ int rowStride = plane.getRowStride();
+ float[] borderLumas = new float[2 * width + 2 * height];
+
+ // Grab the top and bottom borders
+ int l = 0;
+ for (int x = 0; x < width; x++) {
+ borderLumas[l++] = getPixelLuminance(buffer, x, 0, pixelStride, rowStride);
+ borderLumas[l++] = getPixelLuminance(buffer, x, height - 1, pixelStride, rowStride);
+ }
+
+ // Grab the left and right borders
+ for (int y = 0; y < height; y++) {
+ borderLumas[l++] = getPixelLuminance(buffer, 0, y, pixelStride, rowStride);
+ borderLumas[l++] = getPixelLuminance(buffer, width - 1, y, pixelStride, rowStride);
+ }
+
+ // Cleanup
+ ir.close();
+
+ // Oh, is this too simple and inefficient for you?
+ // How about implementing a O(n) solution? https://en.wikipedia.org/wiki/Median_of_medians
+ Arrays.sort(borderLumas);
+ return borderLumas[borderLumas.length / 2];
+ }
+
+ /**
+ * @return whether the hardwareBuffer passed in is marked as protected.
+ */
+ private static boolean hasProtectedContent(HardwareBuffer hardwareBuffer) {
+ return (hardwareBuffer.getUsage() & USAGE_PROTECTED_CONTENT) == USAGE_PROTECTED_CONTENT;
+ }
+
+ private static float getPixelLuminance(ByteBuffer buffer, int x, int y,
+ int pixelStride, int rowStride) {
+ int offset = y * rowStride + x * pixelStride;
+ int pixel = 0;
+ pixel |= (buffer.get(offset) & 0xff) << 16; // R
+ pixel |= (buffer.get(offset + 1) & 0xff) << 8; // G
+ pixel |= (buffer.get(offset + 2) & 0xff); // B
+ pixel |= (buffer.get(offset + 3) & 0xff) << 24; // A
+ return Color.valueOf(pixel).luminance();
+ }
+
+ /**
+ * Gets the average border luma by taking a screenshot of the {@param surfaceControl}.
+ * @see #getMedianBorderLuma(HardwareBuffer, ColorSpace)
+ */
+ private static float getLumaOfSurfaceControl(Rect bounds, SurfaceControl surfaceControl) {
+ if (surfaceControl == null) {
+ return 0;
+ }
+
+ Rect crop = new Rect(0, 0, bounds.width(), bounds.height());
+ SurfaceControl.ScreenshotHardwareBuffer buffer =
+ SurfaceControl.captureLayers(surfaceControl, crop, 1);
+ if (buffer == null) {
+ return 0;
+ }
+
+ return getMedianBorderLuma(buffer.getHardwareBuffer(), buffer.getColorSpace());
+ }
+
+ private static void createRotationMatrix(int rotation, int width, int height,
+ Matrix outMatrix) {
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ outMatrix.reset();
+ break;
+ case Surface.ROTATION_90:
+ outMatrix.setRotate(90, 0, 0);
+ outMatrix.postTranslate(height, 0);
+ break;
+ case Surface.ROTATION_180:
+ outMatrix.setRotate(180, 0, 0);
+ outMatrix.postTranslate(width, height);
+ break;
+ case Surface.ROTATION_270:
+ outMatrix.setRotate(270, 0, 0);
+ outMatrix.postTranslate(0, width);
+ break;
+ }
+ }
+
+ private static void applyColor(int startColor, int endColor, float[] rgbFloat,
+ float fraction, SurfaceControl surface, SurfaceControl.Transaction t) {
+ final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor,
+ endColor);
+ Color middleColor = Color.valueOf(color);
+ rgbFloat[0] = middleColor.red();
+ rgbFloat[1] = middleColor.green();
+ rgbFloat[2] = middleColor.blue();
+ if (surface.isValid()) {
+ t.setColor(surface, rgbFloat);
+ }
+ t.apply();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
index bc42c6e2f12c..802d25f66340 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
@@ -17,7 +17,7 @@
package com.android.wm.shell.transition;
import android.annotation.NonNull;
-import android.window.IRemoteTransition;
+import android.window.RemoteTransition;
import android.window.TransitionFilter;
import com.android.wm.shell.common.annotations.ExternalThread;
@@ -39,10 +39,10 @@ public interface ShellTransitions {
* Registers a remote transition.
*/
void registerRemote(@NonNull TransitionFilter filter,
- @NonNull IRemoteTransition remoteTransition);
+ @NonNull RemoteTransition remoteTransition);
/**
* Unregisters a remote transition.
*/
- void unregisterRemote(@NonNull IRemoteTransition remoteTransition);
+ void unregisterRemote(@NonNull RemoteTransition remoteTransition);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 60707ccdca30..c36983189a71 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.transition;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -39,10 +40,11 @@ import android.provider.Settings;
import android.util.Log;
import android.view.SurfaceControl;
import android.view.WindowManager;
-import android.window.IRemoteTransition;
import android.window.ITransitionPlayer;
+import android.window.RemoteTransition;
import android.window.TransitionFilter;
import android.window.TransitionInfo;
+import android.window.TransitionMetrics;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;
@@ -54,6 +56,7 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
@@ -77,6 +80,15 @@ public class Transitions implements RemoteCallable<Transitions> {
/** Transition type for launching 2 tasks simultaneously. */
public static final int TRANSIT_SPLIT_SCREEN_PAIR_OPEN = TRANSIT_FIRST_CUSTOM + 2;
+ /** Transition type for exiting PIP via the Shell, via pressing the expand button. */
+ public static final int TRANSIT_EXIT_PIP = TRANSIT_FIRST_CUSTOM + 3;
+
+ /** Transition type for removing PIP via the Shell, either via Dismiss bubble or Close. */
+ public static final int TRANSIT_REMOVE_PIP = TRANSIT_FIRST_CUSTOM + 4;
+
+ /** Transition type for entering split by opening an app into side-stage. */
+ public static final int TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE = TRANSIT_FIRST_CUSTOM + 5;
+
private final WindowOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
@@ -91,27 +103,29 @@ public class Transitions implements RemoteCallable<Transitions> {
private float mTransitionAnimationScaleSetting = 1.0f;
private static final class ActiveTransition {
- IBinder mToken = null;
- TransitionHandler mHandler = null;
- boolean mMerged = false;
- TransitionInfo mInfo = null;
- SurfaceControl.Transaction mStartT = null;
- SurfaceControl.Transaction mFinishT = null;
+ IBinder mToken;
+ TransitionHandler mHandler;
+ boolean mMerged;
+ boolean mAborted;
+ TransitionInfo mInfo;
+ SurfaceControl.Transaction mStartT;
+ SurfaceControl.Transaction mFinishT;
}
/** Keeps track of currently playing transitions in the order of receipt. */
private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
- @NonNull Context context, @NonNull ShellExecutor mainExecutor,
- @NonNull ShellExecutor animExecutor) {
+ @NonNull DisplayController displayController, @NonNull Context context,
+ @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
mOrganizer = organizer;
mContext = context;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
mPlayerImpl = new TransitionPlayerImpl();
// The very last handler (0 in the list) should be the default one.
- mHandlers.add(new DefaultTransitionHandler(pool, context, mainExecutor, animExecutor));
+ mHandlers.add(new DefaultTransitionHandler(displayController, pool, context, mainExecutor,
+ animExecutor));
// Next lowest priority is remote transitions.
mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
mHandlers.add(mRemoteTransitionHandler);
@@ -163,13 +177,13 @@ public class Transitions implements RemoteCallable<Transitions> {
return new ShellTransitions() {
@Override
public void registerRemote(@androidx.annotation.NonNull TransitionFilter filter,
- @androidx.annotation.NonNull IRemoteTransition remoteTransition) {
+ @androidx.annotation.NonNull RemoteTransition remoteTransition) {
// Do nothing
}
@Override
public void unregisterRemote(
- @androidx.annotation.NonNull IRemoteTransition remoteTransition) {
+ @androidx.annotation.NonNull RemoteTransition remoteTransition) {
// Do nothing
}
};
@@ -179,6 +193,8 @@ public class Transitions implements RemoteCallable<Transitions> {
public void register(ShellTaskOrganizer taskOrganizer) {
if (mPlayerImpl == null) return;
taskOrganizer.registerTransitionPlayer(mPlayerImpl);
+ // Pre-load the instance.
+ TransitionMetrics.getInstance();
}
/**
@@ -205,12 +221,12 @@ public class Transitions implements RemoteCallable<Transitions> {
/** Register a remote transition to be used when `filter` matches an incoming transition */
public void registerRemote(@NonNull TransitionFilter filter,
- @NonNull IRemoteTransition remoteTransition) {
+ @NonNull RemoteTransition remoteTransition) {
mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
}
/** Unregisters a remote transition and all associated filters */
- public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) {
+ public void unregisterRemote(@NonNull RemoteTransition remoteTransition) {
mRemoteTransitionHandler.removeFiltered(remoteTransition);
}
@@ -218,7 +234,7 @@ public class Transitions implements RemoteCallable<Transitions> {
public static boolean isOpeningType(@WindowManager.TransitionType int type) {
return type == TRANSIT_OPEN
|| type == TRANSIT_TO_FRONT
- || type == WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+ || type == TRANSIT_KEYGUARD_GOING_AWAY;
}
/** @return true if the transition was triggered by closing something vs opening something */
@@ -382,7 +398,7 @@ public class Transitions implements RemoteCallable<Transitions> {
}
boolean startAnimation(@NonNull ActiveTransition active, TransitionHandler handler) {
- return handler.startAnimation(active.mToken, active.mInfo, active.mStartT,
+ return handler.startAnimation(active.mToken, active.mInfo, active.mStartT, active.mFinishT,
(wct, cb) -> onFinish(active.mToken, wct, cb));
}
@@ -416,17 +432,19 @@ public class Transitions implements RemoteCallable<Transitions> {
/** Special version of finish just for dealing with no-op/invalid transitions. */
private void onAbort(IBinder transition) {
- final int activeIdx = findActiveTransition(transition);
- if (activeIdx < 0) return;
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Transition animation aborted due to no-op, notifying core %s", transition);
- mActiveTransitions.remove(activeIdx);
- mOrganizer.finishTransition(transition, null /* wct */, null /* wctCB */);
+ onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */);
}
private void onFinish(IBinder transition,
@Nullable WindowContainerTransaction wct,
@Nullable WindowContainerTransactionCallback wctCB) {
+ onFinish(transition, wct, wctCB, false /* abort */);
+ }
+
+ private void onFinish(IBinder transition,
+ @Nullable WindowContainerTransaction wct,
+ @Nullable WindowContainerTransactionCallback wctCB,
+ boolean abort) {
int activeIdx = findActiveTransition(transition);
if (activeIdx < 0) {
Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
@@ -434,28 +452,37 @@ public class Transitions implements RemoteCallable<Transitions> {
return;
} else if (activeIdx > 0) {
// This transition was merged.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s",
- transition);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged (abort=%b:"
+ + " %s", abort, transition);
final ActiveTransition active = mActiveTransitions.get(activeIdx);
active.mMerged = true;
+ active.mAborted = abort;
if (active.mHandler != null) {
active.mHandler.onTransitionMerged(active.mToken);
}
return;
}
+ mActiveTransitions.get(activeIdx).mAborted = abort;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Transition animation finished, notifying core %s", transition);
+ "Transition animation finished (abort=%b), notifying core %s", abort, transition);
// Merge all relevant transactions together
SurfaceControl.Transaction fullFinish = mActiveTransitions.get(activeIdx).mFinishT;
for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
final ActiveTransition toMerge = mActiveTransitions.get(iA);
if (!toMerge.mMerged) break;
+ // aborted transitions have no start/finish transactions
+ if (mActiveTransitions.get(iA).mStartT == null) break;
+ if (fullFinish == null) {
+ fullFinish = new SurfaceControl.Transaction();
+ }
// Include start. It will be a no-op if it was already applied. Otherwise, we need it
// to maintain consistent state.
fullFinish.merge(mActiveTransitions.get(iA).mStartT);
fullFinish.merge(mActiveTransitions.get(iA).mFinishT);
}
- fullFinish.apply();
+ if (fullFinish != null) {
+ fullFinish.apply();
+ }
// Now perform all the finishes.
mActiveTransitions.remove(activeIdx);
mOrganizer.finishTransition(transition, wct, wctCB);
@@ -464,6 +491,12 @@ public class Transitions implements RemoteCallable<Transitions> {
ActiveTransition merged = mActiveTransitions.remove(activeIdx);
mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
}
+ // sift through aborted transitions
+ while (mActiveTransitions.size() > activeIdx
+ && mActiveTransitions.get(activeIdx).mAborted) {
+ ActiveTransition aborted = mActiveTransitions.remove(activeIdx);
+ mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
+ }
if (mActiveTransitions.size() <= activeIdx) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
+ "finished");
@@ -494,6 +527,12 @@ public class Transitions implements RemoteCallable<Transitions> {
int mergeIdx = activeIdx + 1;
while (mergeIdx < mActiveTransitions.size()) {
ActiveTransition mergeCandidate = mActiveTransitions.get(mergeIdx);
+ if (mergeCandidate.mAborted) {
+ // transition was aborted, so we can skip for now (still leave it in the list
+ // so that it gets cleaned-up in the right order).
+ ++mergeIdx;
+ continue;
+ }
if (mergeCandidate.mMerged) {
throw new IllegalStateException("Can't merge a transition after not-merging"
+ " a preceding one.");
@@ -566,12 +605,19 @@ public class Transitions implements RemoteCallable<Transitions> {
* Starts a transition animation. This is always called if handleRequest returned non-null
* for a particular transition. Otherwise, it is only called if no other handler before
* it handled the transition.
- *
+ * @param startTransaction the transaction given to the handler to be applied before the
+ * transition animation. Note the handler is expected to call on
+ * {@link SurfaceControl.Transaction#apply()} for startTransaction.
+ * @param finishTransaction the transaction given to the handler to be applied after the
+ * transition animation. Unlike startTransaction, the handler is NOT
+ * expected to apply this transaction. The Transition system will
+ * apply it when finishCallback is called.
* @param finishCallback Call this when finished. This MUST be called on main thread.
* @return true if transition was handled, false if not (falls-back to default).
*/
boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
@NonNull TransitionFinishCallback finishCallback);
/**
@@ -661,14 +707,14 @@ public class Transitions implements RemoteCallable<Transitions> {
@Override
public void registerRemote(@NonNull TransitionFilter filter,
- @NonNull IRemoteTransition remoteTransition) {
+ @NonNull RemoteTransition remoteTransition) {
mMainExecutor.execute(() -> {
mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
});
}
@Override
- public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) {
+ public void unregisterRemote(@NonNull RemoteTransition remoteTransition) {
mMainExecutor.execute(() -> {
mRemoteTransitionHandler.removeFiltered(remoteTransition);
});
@@ -695,7 +741,7 @@ public class Transitions implements RemoteCallable<Transitions> {
@Override
public void registerRemote(@NonNull TransitionFilter filter,
- @NonNull IRemoteTransition remoteTransition) {
+ @NonNull RemoteTransition remoteTransition) {
executeRemoteCallWithTaskPermission(mTransitions, "registerRemote",
(transitions) -> {
transitions.mRemoteTransitionHandler.addFiltered(filter, remoteTransition);
@@ -703,7 +749,7 @@ public class Transitions implements RemoteCallable<Transitions> {
}
@Override
- public void unregisterRemote(@NonNull IRemoteTransition remoteTransition) {
+ public void unregisterRemote(@NonNull RemoteTransition remoteTransition) {
executeRemoteCallWithTaskPermission(mTransitions, "unregisterRemote",
(transitions) -> {
transitions.mRemoteTransitionHandler.removeFiltered(remoteTransition);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/WindowThumbnail.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/WindowThumbnail.java
new file mode 100644
index 000000000000..2c668ed3d84d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/WindowThumbnail.java
@@ -0,0 +1,71 @@
+/*
+ * 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.wm.shell.transition;
+
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.hardware.HardwareBuffer;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+/**
+ * Represents a surface that is displayed over a transition surface.
+ */
+class WindowThumbnail {
+
+ private SurfaceControl mSurfaceControl;
+
+ private WindowThumbnail() {}
+
+ /** Create a thumbnail surface and attach it over a parent surface. */
+ static WindowThumbnail createAndAttach(SurfaceSession surfaceSession, SurfaceControl parent,
+ HardwareBuffer thumbnailHeader, SurfaceControl.Transaction t) {
+ WindowThumbnail windowThumbnail = new WindowThumbnail();
+ windowThumbnail.mSurfaceControl = new SurfaceControl.Builder(surfaceSession)
+ .setParent(parent)
+ .setName("WindowThumanil : " + parent.toString())
+ .setCallsite("WindowThumanil")
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .build();
+
+ GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(thumbnailHeader);
+ t.setBuffer(windowThumbnail.mSurfaceControl, graphicBuffer);
+ t.setColorSpace(windowThumbnail.mSurfaceControl, ColorSpace.get(ColorSpace.Named.SRGB));
+ t.setLayer(windowThumbnail.mSurfaceControl, Integer.MAX_VALUE);
+ t.show(windowThumbnail.mSurfaceControl);
+ t.apply();
+
+ return windowThumbnail;
+ }
+
+ SurfaceControl getSurface() {
+ return mSurfaceControl;
+ }
+
+ /** Remove the thumbnail surface and release the surface. */
+ void destroy(SurfaceControl.Transaction t) {
+ if (mSurfaceControl == null) {
+ return;
+ }
+
+ t.remove(mSurfaceControl);
+ t.apply();
+ mSurfaceControl.release();
+ mSurfaceControl = null;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
new file mode 100644
index 000000000000..74e48120bf1a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
@@ -0,0 +1,44 @@
+/*
+ * 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.wm.shell.unfold;
+
+import android.annotation.FloatRange;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Wrapper interface for unfold transition progress provider for the Shell
+ * @see com.android.systemui.unfold.UnfoldTransitionProgressProvider
+ */
+public interface ShellUnfoldProgressProvider {
+
+ /**
+ * Adds a transition listener
+ */
+ void addListener(Executor executor, UnfoldListener listener);
+
+ /**
+ * Listener for receiving unfold updates
+ */
+ interface UnfoldListener {
+ default void onStateChangeStarted() {}
+
+ default void onStateChangeProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
+
+ default void onStateChangeFinished() {}
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
new file mode 100644
index 000000000000..9faf454261d3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
@@ -0,0 +1,89 @@
+/*
+ * 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.wm.shell.unfold;
+
+import static android.graphics.Color.blue;
+import static android.graphics.Color.green;
+import static android.graphics.Color.red;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+
+/**
+ * Controls background color layer for the unfold animations
+ */
+public class UnfoldBackgroundController {
+
+ private static final int BACKGROUND_LAYER_Z_INDEX = -1;
+
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private final float[] mBackgroundColor;
+ private SurfaceControl mBackgroundLayer;
+
+ public UnfoldBackgroundController(
+ @NonNull Context context,
+ @NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ mBackgroundColor = getBackgroundColor(context);
+ }
+
+ /**
+ * Ensures that unfold animation background color layer is present,
+ * @param transaction where we should add the background if it is not added
+ */
+ public void ensureBackground(@NonNull SurfaceControl.Transaction transaction) {
+ if (mBackgroundLayer != null) return;
+
+ SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder()
+ .setName("app-unfold-background")
+ .setCallsite("AppUnfoldTransitionController")
+ .setColorLayer();
+ mRootTaskDisplayAreaOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, colorLayerBuilder);
+ mBackgroundLayer = colorLayerBuilder.build();
+
+ transaction
+ .setColor(mBackgroundLayer, mBackgroundColor)
+ .show(mBackgroundLayer)
+ .setLayer(mBackgroundLayer, BACKGROUND_LAYER_Z_INDEX);
+ }
+
+ /**
+ * Ensures that the background is not visible
+ * @param transaction as part of which the removal will happen if needed
+ */
+ public void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
+ if (mBackgroundLayer == null) return;
+ if (mBackgroundLayer.isValid()) {
+ transaction.remove(mBackgroundLayer);
+ }
+ mBackgroundLayer = null;
+ }
+
+ private float[] getBackgroundColor(Context context) {
+ int colorInt = context.getResources().getColor(R.color.unfold_transition_background);
+ return new float[]{
+ (float) red(colorInt) / 255.0F,
+ (float) green(colorInt) / 255.0F,
+ (float) blue(colorInt) / 255.0F
+ };
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
new file mode 100644
index 000000000000..b9b671635010
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
@@ -0,0 +1,87 @@
+/*
+ * 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.wm.shell.util;
+
+import android.view.SurfaceControl;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class that takes care of counter-rotating surfaces during a transition animation.
+ */
+public class CounterRotator {
+ SurfaceControl mSurface = null;
+ ArrayList<SurfaceControl> mRotateChildren = null;
+
+ /** Gets the surface with the counter-rotation. */
+ public SurfaceControl getSurface() {
+ return mSurface;
+ }
+
+ /**
+ * Sets up this rotator.
+ *
+ * @param rotateDelta is the forward rotation change (the rotation the display is making).
+ * @param displayW (and H) Is the size of the rotating display.
+ */
+ public void setup(SurfaceControl.Transaction t, SurfaceControl parent, int rotateDelta,
+ float displayW, float displayH) {
+ if (rotateDelta == 0) return;
+ mRotateChildren = new ArrayList<>();
+ // We want to counter-rotate, so subtract from 4
+ rotateDelta = 4 - (rotateDelta + 4) % 4;
+ mSurface = new SurfaceControl.Builder()
+ .setName("Transition Unrotate")
+ .setContainerLayer()
+ .setParent(parent)
+ .build();
+ // column-major
+ if (rotateDelta == 1) {
+ t.setMatrix(mSurface, 0, 1, -1, 0);
+ t.setPosition(mSurface, displayW, 0);
+ } else if (rotateDelta == 2) {
+ t.setMatrix(mSurface, -1, 0, 0, -1);
+ t.setPosition(mSurface, displayW, displayH);
+ } else if (rotateDelta == 3) {
+ t.setMatrix(mSurface, 0, -1, 1, 0);
+ t.setPosition(mSurface, 0, displayH);
+ }
+ t.show(mSurface);
+ }
+
+ /**
+ * Add a surface that needs to be counter-rotate.
+ */
+ public void addChild(SurfaceControl.Transaction t, SurfaceControl child) {
+ if (mSurface == null) return;
+ t.reparent(child, mSurface);
+ mRotateChildren.add(child);
+ }
+
+ /**
+ * Clean-up. This undoes any reparenting and effectively stops the counter-rotation.
+ */
+ public void cleanUp(SurfaceControl rootLeash) {
+ if (mSurface == null) return;
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (int i = mRotateChildren.size() - 1; i >= 0; --i) {
+ t.reparent(mRotateChildren.get(i), rootLeash);
+ }
+ t.remove(mSurface);
+ t.apply();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 9dd25fe0e6fe..3ca5b9c38aff 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -25,11 +25,17 @@ package {
android_test {
name: "WMShellFlickerTests",
- srcs: ["src/**/*.java", "src/**/*.kt"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
manifest: "AndroidManifest.xml",
test_config: "AndroidTest.xml",
platform_apis: true,
certificate: "platform",
+ optimize: {
+ enabled: false,
+ },
test_suites: ["device-tests"],
libs: ["android.test.runner"],
static_libs: [
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
index e6d32ff1166f..06df9568e01a 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
@@ -42,6 +42,9 @@
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
<!-- ATM.removeRootTasksWithActivityTypes() -->
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+ <!-- Enable bubble notification-->
+ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+
<!-- Allow the test to write directly to /sdcard/ -->
<application android:requestLegacyExternalStorage="true">
<uses-library android:name="android.test.runner"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index c5b5b91d570b..c4be785cff19 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -14,100 +14,104 @@
* limitations under the License.
*/
+@file:JvmName("CommonAssertions")
package com.android.wm.shell.flicker
import android.graphics.Region
import android.view.Surface
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.traces.layers.getVisibleBounds
+import com.android.server.wm.traces.common.FlickerComponentName
-fun FlickerTestParameter.appPairsDividerIsVisible() {
+fun FlickerTestParameter.appPairsDividerIsVisibleAtEnd() {
assertLayersEnd {
- this.isVisible(APP_PAIR_SPLIT_DIVIDER)
+ this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT)
}
}
-fun FlickerTestParameter.appPairsDividerIsInvisible() {
+fun FlickerTestParameter.appPairsDividerIsInvisibleAtEnd() {
assertLayersEnd {
- this.notContains(APP_PAIR_SPLIT_DIVIDER)
+ this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT)
}
}
fun FlickerTestParameter.appPairsDividerBecomesVisible() {
assertLayers {
- this.isInvisible(DOCKED_STACK_DIVIDER)
+ this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
.then()
- .isVisible(DOCKED_STACK_DIVIDER)
+ .isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
}
}
-fun FlickerTestParameter.dockedStackDividerIsVisible() {
+fun FlickerTestParameter.dockedStackDividerIsVisibleAtEnd() {
assertLayersEnd {
- this.isVisible(DOCKED_STACK_DIVIDER)
+ this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
}
}
fun FlickerTestParameter.dockedStackDividerBecomesVisible() {
assertLayers {
- this.isInvisible(DOCKED_STACK_DIVIDER)
+ this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
.then()
- .isVisible(DOCKED_STACK_DIVIDER)
+ .isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
}
}
fun FlickerTestParameter.dockedStackDividerBecomesInvisible() {
assertLayers {
- this.isVisible(DOCKED_STACK_DIVIDER)
+ this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
.then()
- .isInvisible(DOCKED_STACK_DIVIDER)
+ .isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
}
}
-fun FlickerTestParameter.dockedStackDividerIsInvisible() {
+fun FlickerTestParameter.dockedStackDividerNotExistsAtEnd() {
assertLayersEnd {
- this.notContains(DOCKED_STACK_DIVIDER)
+ this.notContains(DOCKED_STACK_DIVIDER_COMPONENT)
}
}
-fun FlickerTestParameter.appPairsPrimaryBoundsIsVisible(rotation: Int, primaryLayerName: String) {
+fun FlickerTestParameter.appPairsPrimaryBoundsIsVisibleAtEnd(
+ rotation: Int,
+ primaryComponent: FlickerComponentName
+) {
assertLayersEnd {
- val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
- visibleRegion(primaryLayerName)
- .coversExactly(getPrimaryRegion(dividerRegion, rotation))
+ val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region
+ visibleRegion(primaryComponent)
+ .overlaps(getPrimaryRegion(dividerRegion, rotation))
}
}
-fun FlickerTestParameter.dockedStackPrimaryBoundsIsVisible(
+fun FlickerTestParameter.dockedStackPrimaryBoundsIsVisibleAtEnd(
rotation: Int,
- primaryLayerName: String
+ primaryComponent: FlickerComponentName
) {
assertLayersEnd {
- val dividerRegion = entry.getVisibleBounds(DOCKED_STACK_DIVIDER)
- visibleRegion(primaryLayerName)
- .coversExactly(getPrimaryRegion(dividerRegion, rotation))
+ val dividerRegion = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region
+ visibleRegion(primaryComponent)
+ .overlaps(getPrimaryRegion(dividerRegion, rotation))
}
}
-fun FlickerTestParameter.appPairsSecondaryBoundsIsVisible(
+fun FlickerTestParameter.appPairsSecondaryBoundsIsVisibleAtEnd(
rotation: Int,
- secondaryLayerName: String
+ secondaryComponent: FlickerComponentName
) {
assertLayersEnd {
- val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
- visibleRegion(secondaryLayerName)
- .coversExactly(getSecondaryRegion(dividerRegion, rotation))
+ val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region
+ visibleRegion(secondaryComponent)
+ .overlaps(getSecondaryRegion(dividerRegion, rotation))
}
}
-fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisible(
+fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisibleAtEnd(
rotation: Int,
- secondaryLayerName: String
+ secondaryComponent: FlickerComponentName
) {
assertLayersEnd {
- val dividerRegion = entry.getVisibleBounds(DOCKED_STACK_DIVIDER)
- visibleRegion(secondaryLayerName)
- .coversExactly(getSecondaryRegion(dividerRegion, rotation))
+ val dividerRegion = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region
+ visibleRegion(secondaryComponent)
+ .overlaps(getSecondaryRegion(dividerRegion, rotation))
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
index 03b93c74233c..40891f36a5da 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
@@ -14,9 +14,11 @@
* limitations under the License.
*/
+@file:JvmName("CommonConstants")
package com.android.wm.shell.flicker
-const val IME_WINDOW_NAME = "InputMethod"
+import com.android.server.wm.traces.common.FlickerComponentName
+
const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
-const val APP_PAIR_SPLIT_DIVIDER = "AppPairSplitDivider"
-const val DOCKED_STACK_DIVIDER = "DockedStackDivider" \ No newline at end of file
+val APP_PAIR_SPLIT_DIVIDER_COMPONENT = FlickerComponentName("", "AppPairSplitDivider#")
+val DOCKED_STACK_DIVIDER_COMPONENT = FlickerComponentName("", "DockedStackDivider#") \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
index a6d67355f271..b63d9fffdb61 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+@file:JvmName("WaitUtils")
package com.android.wm.shell.flicker
import android.os.SystemClock
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
index ef9f7421fd60..038be9c190c2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.apppairs
-import android.os.SystemClock
import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -25,7 +24,7 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.wm.shell.flicker.appPairsDividerIsInvisible
+import com.android.wm.shell.flicker.appPairsDividerIsInvisibleAtEnd
import com.android.wm.shell.flicker.helpers.AppPairsHelper
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
@@ -61,7 +60,7 @@ class AppPairsTestCannotPairNonResizeableApps(
// TODO pair apps through normal UX flow
executeShellCommand(
composePairsCommand(primaryTaskId, nonResizeableTaskId, pair = true))
- SystemClock.sleep(AppPairsHelper.TIMEOUT_MS)
+ nonResizeableApp?.run { wmHelper.waitForFullScreenApp(nonResizeableApp.component) }
}
}
@@ -85,15 +84,13 @@ class AppPairsTestCannotPairNonResizeableApps(
@Test
override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
- @FlakyTest
+ @Presubmit
@Test
- override fun navBarLayerIsAlwaysVisible() {
- super.navBarLayerIsAlwaysVisible()
- }
+ override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
@Presubmit
@Test
- fun appPairsDividerIsInvisible() = testSpec.appPairsDividerIsInvisible()
+ fun appPairsDividerIsInvisibleAtEnd() = testSpec.appPairsDividerIsInvisibleAtEnd()
@Presubmit
@Test
@@ -103,8 +100,8 @@ class AppPairsTestCannotPairNonResizeableApps(
"Non resizeable app not initialized"
}
testSpec.assertWmEnd {
- isVisible(nonResizeableApp.defaultWindowName)
- isInvisible(primaryApp.defaultWindowName)
+ isAppWindowVisible(nonResizeableApp.component)
+ isAppWindowInvisible(primaryApp.component)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
index db63c4c43523..bbc6b2dbece8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.apppairs
-import android.os.SystemClock
import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -25,10 +24,10 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.traces.layers.getVisibleBounds
-import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER
-import com.android.wm.shell.flicker.appPairsDividerIsVisible
+import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.AppPairsHelper
+import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,10 +53,14 @@ class AppPairsTestPairPrimaryAndSecondaryApps(
// TODO pair apps through normal UX flow
executeShellCommand(
composePairsCommand(primaryTaskId, secondaryTaskId, pair = true))
- SystemClock.sleep(AppPairsHelper.TIMEOUT_MS)
+ waitAppsShown(primaryApp, secondaryApp)
}
}
+ @Presubmit
+ @Test
+ override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
+
@FlakyTest
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
@@ -68,14 +71,14 @@ class AppPairsTestPairPrimaryAndSecondaryApps(
@Presubmit
@Test
- fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible()
+ fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd()
@Presubmit
@Test
fun bothAppWindowsVisible() {
testSpec.assertWmEnd {
- isVisible(primaryApp.defaultWindowName)
- isVisible(secondaryApp.defaultWindowName)
+ isAppWindowVisible(primaryApp.component)
+ isAppWindowVisible(secondaryApp.component)
}
}
@@ -83,10 +86,10 @@ class AppPairsTestPairPrimaryAndSecondaryApps(
@Test
fun appsEndingBounds() {
testSpec.assertLayersEnd {
- val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
- visibleRegion(primaryApp.defaultWindowName)
+ val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region
+ visibleRegion(primaryApp.component)
.coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion))
- visibleRegion(secondaryApp.defaultWindowName)
+ visibleRegion(secondaryApp.component)
.coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion))
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
index c8d34237231c..bb784a809b7e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.apppairs
-import android.os.SystemClock
import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -25,7 +24,7 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.wm.shell.flicker.appPairsDividerIsVisible
+import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.AppPairsHelper
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
@@ -61,7 +60,7 @@ class AppPairsTestSupportPairNonResizeableApps(
// TODO pair apps through normal UX flow
executeShellCommand(
composePairsCommand(primaryTaskId, nonResizeableTaskId, pair = true))
- SystemClock.sleep(AppPairsHelper.TIMEOUT_MS)
+ nonResizeableApp?.run { wmHelper.waitForFullScreenApp(nonResizeableApp.component) }
}
}
@@ -77,6 +76,10 @@ class AppPairsTestSupportPairNonResizeableApps(
resetMultiWindowConfig(instrumentation)
}
+ @Presubmit
+ @Test
+ override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
+
@FlakyTest
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
@@ -87,7 +90,7 @@ class AppPairsTestSupportPairNonResizeableApps(
@Presubmit
@Test
- fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible()
+ fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd()
@Presubmit
@Test
@@ -97,8 +100,8 @@ class AppPairsTestSupportPairNonResizeableApps(
"Non resizeable app not initialized"
}
testSpec.assertWmEnd {
- isVisible(nonResizeableApp.defaultWindowName)
- isVisible(primaryApp.defaultWindowName)
+ isAppWindowVisible(nonResizeableApp.component)
+ isAppWindowVisible(primaryApp.component)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
index 83df83600d11..a1a4db112dfd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
@@ -25,10 +25,10 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.traces.layers.getVisibleBounds
-import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER
-import com.android.wm.shell.flicker.appPairsDividerIsInvisible
+import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appPairsDividerIsInvisibleAtEnd
import com.android.wm.shell.flicker.helpers.AppPairsHelper
+import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,9 +51,11 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps(
get() = {
super.transition(this, it)
setup {
- executeShellCommand(
- composePairsCommand(primaryTaskId, secondaryTaskId, pair = true))
- SystemClock.sleep(AppPairsHelper.TIMEOUT_MS)
+ eachRun {
+ executeShellCommand(
+ composePairsCommand(primaryTaskId, secondaryTaskId, pair = true))
+ waitAppsShown(primaryApp, secondaryApp)
+ }
}
transitions {
// TODO pair apps through normal UX flow
@@ -73,14 +75,14 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps(
@Presubmit
@Test
- fun appPairsDividerIsInvisible() = testSpec.appPairsDividerIsInvisible()
+ fun appPairsDividerIsInvisibleAtEnd() = testSpec.appPairsDividerIsInvisibleAtEnd()
@Presubmit
@Test
fun bothAppWindowsInvisible() {
testSpec.assertWmEnd {
- isInvisible(primaryApp.defaultWindowName)
- isInvisible(secondaryApp.defaultWindowName)
+ isAppWindowInvisible(primaryApp.component)
+ isAppWindowInvisible(secondaryApp.component)
}
}
@@ -88,10 +90,10 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps(
@Test
fun appsStartingBounds() {
testSpec.assertLayersStart {
- val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
- visibleRegion(primaryApp.defaultWindowName)
+ val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region
+ visibleRegion(primaryApp.component)
.coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion))
- visibleRegion(secondaryApp.defaultWindowName)
+ visibleRegion(secondaryApp.component)
.coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion))
}
}
@@ -100,16 +102,14 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps(
@Test
fun appsEndingBounds() {
testSpec.assertLayersEnd {
- notContains(primaryApp.defaultWindowName)
- notContains(secondaryApp.defaultWindowName)
+ notContains(primaryApp.component)
+ notContains(secondaryApp.component)
}
}
- @FlakyTest
+ @Presubmit
@Test
- override fun navBarLayerIsAlwaysVisible() {
- super.navBarLayerIsAlwaysVisible()
- }
+ override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
index 1935bb97849c..9e20bbbc1a1b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
@@ -20,24 +20,23 @@ import android.app.Instrumentation
import android.content.Context
import android.platform.test.annotations.Presubmit
import android.system.helpers.ActivityHelper
-import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.isRotated
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.wm.shell.flicker.helpers.AppPairsHelper
import com.android.wm.shell.flicker.helpers.BaseAppHelper
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow
@@ -55,7 +54,7 @@ abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter)
protected val activityHelper = ActivityHelper.getInstance()
protected val appPairsHelper = AppPairsHelper(instrumentation,
Components.SplitScreenActivity.LABEL,
- Components.SplitScreenActivity.COMPONENT)
+ Components.SplitScreenActivity.COMPONENT.toFlickerComponent())
protected val primaryApp = SplitScreenHelper.getPrimary(instrumentation)
protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
@@ -154,39 +153,33 @@ abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter)
@FlakyTest(bugId = 186510496)
@Test
- open fun navBarLayerIsAlwaysVisible() {
- testSpec.navBarLayerIsAlwaysVisible()
+ open fun navBarLayerIsVisible() {
+ testSpec.navBarLayerIsVisible()
}
@Presubmit
@Test
- open fun statusBarLayerIsAlwaysVisible() {
- testSpec.statusBarLayerIsAlwaysVisible()
+ open fun statusBarLayerIsVisible() {
+ testSpec.statusBarLayerIsVisible()
}
@Presubmit
@Test
- open fun navBarWindowIsAlwaysVisible() {
- testSpec.navBarWindowIsAlwaysVisible()
+ open fun navBarWindowIsVisible() {
+ testSpec.navBarWindowIsVisible()
}
@Presubmit
@Test
- open fun statusBarWindowIsAlwaysVisible() {
- testSpec.statusBarWindowIsAlwaysVisible()
+ open fun statusBarWindowIsVisible() {
+ testSpec.statusBarWindowIsVisible()
}
@Presubmit
@Test
- open fun navBarLayerRotatesAndScales() {
- testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0,
- testSpec.config.endRotation)
- }
+ open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- open fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0,
- testSpec.config.endRotation)
- }
+ open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
index c875c0006703..56a2531a3fe1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.apppairs
-import android.os.SystemClock
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
@@ -28,10 +27,10 @@ import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.wm.shell.flicker.appPairsDividerIsVisible
-import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisible
-import com.android.wm.shell.flicker.helpers.AppPairsHelper
+import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -57,41 +56,43 @@ class RotateTwoLaunchedAppsInAppPairsMode(
transitions {
executeShellCommand(composePairsCommand(
primaryTaskId, secondaryTaskId, true /* pair */))
- SystemClock.sleep(AppPairsHelper.TIMEOUT_MS)
+ waitAppsShown(primaryApp, secondaryApp)
setRotation(testSpec.config.endRotation)
}
}
- @FlakyTest
+ @Presubmit
@Test
- override fun statusBarLayerIsAlwaysVisible() {
- super.statusBarLayerIsAlwaysVisible()
- }
+ override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
+
+ @Presubmit
+ @Test
+ override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
@Presubmit
@Test
fun bothAppWindowsVisible() {
testSpec.assertWmEnd {
- isVisible(primaryApp.defaultWindowName)
- .isVisible(secondaryApp.defaultWindowName)
+ isAppWindowVisible(primaryApp.component)
+ isAppWindowVisible(secondaryApp.component)
}
}
@Presubmit
@Test
- fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible()
+ fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd()
- @FlakyTest(bugId = 172776659)
+ @Presubmit
@Test
- fun appPairsPrimaryBoundsIsVisible() =
- testSpec.appPairsPrimaryBoundsIsVisible(testSpec.config.endRotation,
- primaryApp.defaultWindowName)
+ fun appPairsPrimaryBoundsIsVisibleAtEnd() =
+ testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.config.endRotation,
+ primaryApp.component)
- @FlakyTest(bugId = 172776659)
+ @FlakyTest
@Test
- fun appPairsSecondaryBoundsIsVisible() =
- testSpec.appPairsSecondaryBoundsIsVisible(testSpec.config.endRotation,
- secondaryApp.defaultWindowName)
+ fun appPairsSecondaryBoundsIsVisibleAtEnd() =
+ testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.config.endRotation,
+ secondaryApp.component)
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
index c3360ca0f7d3..0699a4fd0512 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.apppairs
-import android.os.SystemClock
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
@@ -28,12 +27,10 @@ import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.appPairsDividerIsVisible
-import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisible
-import com.android.wm.shell.flicker.helpers.AppPairsHelper
+import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -60,48 +57,50 @@ class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode(
this.setRotation(testSpec.config.endRotation)
executeShellCommand(
composePairsCommand(primaryTaskId, secondaryTaskId, pair = true))
- SystemClock.sleep(AppPairsHelper.TIMEOUT_MS)
+ waitAppsShown(primaryApp, secondaryApp)
}
}
@Presubmit
@Test
- fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible()
+ fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd()
@Presubmit
@Test
- override fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
@Presubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
- @FlakyTest
+ @Presubmit
@Test
- override fun statusBarLayerIsAlwaysVisible() {
- super.statusBarLayerIsAlwaysVisible()
- }
+ override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
+
+ @Presubmit
+ @Test
+ override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
@Presubmit
@Test
fun bothAppWindowsVisible() {
testSpec.assertWmEnd {
- isVisible(primaryApp.defaultWindowName)
- isVisible(secondaryApp.defaultWindowName)
+ isAppWindowVisible(primaryApp.component)
+ isAppWindowVisible(secondaryApp.component)
}
}
@FlakyTest(bugId = 172776659)
@Test
- fun appPairsPrimaryBoundsIsVisible() =
- testSpec.appPairsPrimaryBoundsIsVisible(testSpec.config.endRotation,
- primaryApp.defaultWindowName)
+ fun appPairsPrimaryBoundsIsVisibleAtEnd() =
+ testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.config.endRotation,
+ primaryApp.component)
@FlakyTest(bugId = 172776659)
@Test
- fun appPairsSecondaryBoundsIsVisible() =
- testSpec.appPairsSecondaryBoundsIsVisible(testSpec.config.endRotation,
- secondaryApp.defaultWindowName)
+ fun appPairsSecondaryBoundsIsVisibleAtEnd() =
+ testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.config.endRotation,
+ secondaryApp.component)
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt
index 512fd9a58ea8..b95193a17265 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt
@@ -22,7 +22,10 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.Assume.assumeFalse
+import org.junit.Before
import org.junit.Test
abstract class RotateTwoLaunchedAppsTransition(
@@ -37,8 +40,8 @@ abstract class RotateTwoLaunchedAppsTransition(
test {
device.wakeUpAndGoToHomeScreen()
this.setRotation(Surface.ROTATION_0)
- primaryApp.launchViaIntent()
- secondaryApp.launchViaIntent()
+ primaryApp.launchViaIntent(wmHelper)
+ secondaryApp.launchViaIntent(wmHelper)
updateTasksId()
}
}
@@ -52,10 +55,17 @@ abstract class RotateTwoLaunchedAppsTransition(
}
}
+ @Before
+ override fun setup() {
+ // AppPairs hasn't been updated to Shell Transition. There will be conflict on rotation.
+ assumeFalse(isShellTransitionsEnabled())
+ super.setup()
+ }
+
@FlakyTest
@Test
- override fun navBarLayerIsAlwaysVisible() {
- super.navBarLayerIsAlwaysVisible()
+ override fun navBarLayerIsVisible() {
+ super.navBarLayerIsVisible()
}
@FlakyTest
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
new file mode 100644
index 000000000000..322d8b5e4dac
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.wm.shell.flicker.bubble
+
+import android.app.INotificationManager
+import android.app.Instrumentation
+import android.app.NotificationManager
+import android.content.Context
+import android.os.ServiceManager
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
+import com.android.server.wm.flicker.repetitions
+import com.android.wm.shell.flicker.helpers.LaunchBubbleHelper
+import org.junit.Test
+import org.junit.runners.Parameterized
+
+/**
+ * Base configurations for Bubble flicker tests
+ */
+abstract class BaseBubbleScreen(protected val testSpec: FlickerTestParameter) {
+
+ protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ protected val context: Context = instrumentation.context
+ protected val testApp = LaunchBubbleHelper(instrumentation)
+
+ protected val notifyManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE))
+
+ protected val packageManager = context.getPackageManager()
+ protected val uid = packageManager.getApplicationInfo(
+ testApp.component.packageName, 0).uid
+
+ protected lateinit var addBubbleBtn: UiObject2
+ protected lateinit var cancelAllBtn: UiObject2
+
+ protected abstract val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+
+ @JvmOverloads
+ protected open fun buildTransition(
+ extraSpec: FlickerBuilder.(Map<String, Any?>) -> Unit = {}
+ ): FlickerBuilder.(Map<String, Any?>) -> Unit {
+ return { configuration ->
+
+ setup {
+ test {
+ notifyManager.setBubblesAllowed(testApp.component.packageName,
+ uid, NotificationManager.BUBBLE_PREFERENCE_ALL)
+ testApp.launchViaIntent(wmHelper)
+ addBubbleBtn = device.wait(Until.findObject(
+ By.text("Add Bubble")), FIND_OBJECT_TIMEOUT)
+ cancelAllBtn = device.wait(Until.findObject(
+ By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT)
+ }
+ }
+
+ teardown {
+ notifyManager.setBubblesAllowed(testApp.component.packageName,
+ uid, NotificationManager.BUBBLE_PREFERENCE_NONE)
+ testApp.exit()
+ }
+
+ extraSpec(this, configuration)
+ }
+ }
+
+ @FlakyTest
+ @Test
+ fun testAppIsAlwaysVisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ }
+ }
+
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ repeat { testSpec.config.repetitions }
+ transition(this, testSpec.config)
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
+ repetitions = 5)
+ }
+
+ const val FIND_OBJECT_TIMEOUT = 2000L
+ const val SYSTEM_UI_PACKAGE = SYSTEMUI_PACKAGE
+ const val BUBBLE_RES_NAME = "bubble_view"
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
new file mode 100644
index 000000000000..bfdcb363a818
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.wm.shell.flicker.bubble
+
+import android.content.Context
+import android.graphics.Point
+import android.util.DisplayMetrics
+import android.view.WindowManager
+import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a new activity from bubble.
+ *
+ * To run this test: `atest WMShellFlickerTests:DismissBubbleScreen`
+ *
+ * Actions:
+ * Dismiss a bubble notification
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@Group4
+class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+
+ val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ val displaySize = DisplayMetrics()
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = buildTransition() {
+ setup {
+ eachRun {
+ addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Add Bubble not found")
+ }
+ }
+ transitions {
+ wm?.run { wm.getDefaultDisplay().getMetrics(displaySize) } ?: error("WM not found")
+ val dist = Point((displaySize.widthPixels / 2), displaySize.heightPixels)
+ val showBubble = device.wait(Until.findObject(
+ By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT)
+ showBubble?.run { drag(dist, 1000) } ?: error("Show bubble not found")
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
new file mode 100644
index 000000000000..42eeadf3ddd9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
@@ -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.wm.shell.flicker.bubble
+
+import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a new activity from bubble.
+ *
+ * To run this test: `atest WMShellFlickerTests:ExpandBubbleScreen`
+ *
+ * Actions:
+ * Launch an app and enable app's bubble notification
+ * Send a bubble notification
+ * The activity for the bubble is launched
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@Group4
+class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = buildTransition() {
+ setup {
+ test {
+ addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Bubble widget not found")
+ }
+ }
+ transitions {
+ val showBubble = device.wait(Until.findObject(
+ By.res("com.android.systemui", "bubble_view")), FIND_OBJECT_TIMEOUT)
+ showBubble?.run { showBubble.click() } ?: error("Bubble notify not found")
+ device.pressBack()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
index cf84a2c696d0..47e8c0c047a8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,47 +14,35 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.bubble
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group3
+import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test Pip launch.
- * To run this test: `atest WMShellFlickerTests:PipCloseWithDismissButton`
+ * Test creating a bubble notification
+ *
+ * To run this test: `atest WMShellFlickerTests:LaunchBubbleScreen`
+ *
+ * Actions:
+ * Launch an app and enable app's bubble notification
+ * Send a bubble notification
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
-class PipCloseWithDismissButtonTest(testSpec: FlickerTestParameter) : PipCloseTransition(testSpec) {
+@Group4
+class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = {
- super.transition(this, it)
+ get() = buildTransition() {
transitions {
- pipApp.closePipWindow(wmHelper)
+ addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Bubble widget not found")
}
}
-
- @FlakyTest
- @Test
- override fun pipLayerBecomesInvisible() {
- super.pipLayerBecomesInvisible()
- }
-
- @FlakyTest
- @Test
- override fun pipWindowBecomesInvisible() {
- super.pipWindowBecomesInvisible()
- }
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
new file mode 100644
index 000000000000..194e28fd6e8a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.wm.shell.flicker.bubble
+
+import android.os.SystemClock
+import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a new activity from bubble.
+ *
+ * To run this test: `atest WMShellFlickerTests:MultiBubblesScreen`
+ *
+ * Actions:
+ * Switch in different bubble notifications
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@Group4
+class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = buildTransition() {
+ setup {
+ test {
+ for (i in 1..3) {
+ addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Add Bubble not found")
+ }
+ val showBubble = device.wait(Until.findObject(
+ By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT)
+ showBubble?.run { showBubble.click() } ?: error("Show bubble not found")
+ SystemClock.sleep(1000)
+ }
+ }
+ transitions {
+ val bubbles = device.wait(Until.findObjects(
+ By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT)
+ for (entry in bubbles) {
+ entry?.run { entry.click() } ?: error("Bubble not found")
+ SystemClock.sleep(1000)
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
index 5b8cfb81016a..623055f659b9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
@@ -17,14 +17,15 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
-import android.content.ComponentName
import android.graphics.Region
+import com.android.server.wm.flicker.Flicker
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.traces.common.FlickerComponentName
class AppPairsHelper(
instrumentation: Instrumentation,
activityLabel: String,
- component: ComponentName
+ component: FlickerComponentName
) : BaseAppHelper(instrumentation, activityLabel, component) {
fun getPrimaryBounds(dividerBounds: Region): android.graphics.Region {
val primaryAppBounds = Region(0, 0, dividerBounds.bounds.right,
@@ -43,5 +44,17 @@ class AppPairsHelper(
companion object {
const val TEST_REPETITIONS = 1
const val TIMEOUT_MS = 3_000L
+
+ fun Flicker.waitAppsShown(app1: SplitScreenHelper?, app2: SplitScreenHelper?) {
+ wmHelper.waitFor("primaryAndSecondaryAppsVisible") { dump ->
+ val primaryAppVisible = app1?.let {
+ dump.wmState.isWindowSurfaceShown(app1.defaultWindowName)
+ } ?: false
+ val secondaryAppVisible = app2?.let {
+ dump.wmState.isWindowSurfaceShown(app2.defaultWindowName)
+ } ?: false
+ primaryAppVisible && secondaryAppVisible
+ }
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
index 4fe69ad7fabe..57bc0d580d72 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
@@ -17,9 +17,9 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
-import android.content.ComponentName
import android.content.pm.PackageManager.FEATURE_LEANBACK
import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY
+import android.os.SystemProperties
import android.support.test.launcherhelper.LauncherStrategyFactory
import android.util.Log
import androidx.test.uiautomator.By
@@ -27,13 +27,13 @@ import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
import com.android.compatibility.common.util.SystemUtil
import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.traces.parser.toWindowName
+import com.android.server.wm.traces.common.FlickerComponentName
import java.io.IOException
abstract class BaseAppHelper(
instrumentation: Instrumentation,
launcherName: String,
- component: ComponentName
+ component: FlickerComponentName
) : StandardAppHelper(
instrumentation,
launcherName,
@@ -60,6 +60,9 @@ abstract class BaseAppHelper(
companion object {
private const val APP_CLOSE_WAIT_TIME_MS = 3_000L
+ fun isShellTransitionsEnabled() =
+ SystemProperties.getBoolean("persist.debug.shell_transit", false)
+
fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
try {
SystemUtil.runShellCommand(instrumentation, cmd)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt
index b4ae18749b34..471e010cf560 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt
@@ -17,10 +17,11 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
+import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.wm.shell.flicker.testapp.Components
class FixedAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
instrumentation,
Components.FixedActivity.LABEL,
- Components.FixedActivity.COMPONENT
+ Components.FixedActivity.COMPONENT.toFlickerComponent()
) \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
index cac46fe676b3..0f00edea136f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
@@ -21,13 +21,14 @@ import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
+import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.testapp.Components
open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
instrumentation,
Components.ImeActivity.LABEL,
- Components.ImeActivity.COMPONENT
+ Components.ImeActivity.COMPONENT.toFlickerComponent()
) {
/**
* Opens the IME and wait for it to be displayed
@@ -61,7 +62,7 @@ open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
if (wmHelper == null) {
device.waitForIdle()
} else {
- require(wmHelper.waitImeWindowShown()) { "IME did not appear" }
+ require(wmHelper.waitImeShown()) { "IME did not appear" }
}
}
@@ -78,7 +79,7 @@ open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
if (wmHelper == null) {
uiDevice.waitForIdle()
} else {
- require(wmHelper.waitImeWindowGone()) { "IME did did not close" }
+ require(wmHelper.waitImeGone()) { "IME did did not close" }
}
} else {
// While pressing the back button should close the IME on TV as well, it may also lead
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt
new file mode 100644
index 000000000000..6695c17ed514
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.wm.shell.flicker.helpers
+
+import android.app.Instrumentation
+import com.android.server.wm.traces.parser.toFlickerComponent
+import com.android.wm.shell.flicker.testapp.Components
+
+class LaunchBubbleHelper(instrumentation: Instrumentation) : BaseAppHelper(
+ instrumentation,
+ Components.LaunchBubbleActivity.LABEL,
+ Components.LaunchBubbleActivity.COMPONENT.toFlickerComponent()
+) {
+
+ companion object {
+ const val TEST_REPETITIONS = 1
+ const val TIMEOUT_MS = 3_000L
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
index 7f99e62b36b0..12ccbafce651 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
@@ -17,14 +17,14 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
-import android.content.ComponentName
import android.content.Context
import android.provider.Settings
+import com.android.server.wm.traces.common.FlickerComponentName
class MultiWindowHelper(
instrumentation: Instrumentation,
activityLabel: String,
- componentsInfo: ComponentName
+ componentsInfo: FlickerComponentName
) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) {
companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
index f4dd7decb1b7..2357b0debb33 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
@@ -17,12 +17,16 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
+import android.graphics.Rect
import android.media.session.MediaController
import android.media.session.MediaSessionManager
import android.os.SystemClock
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
+import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild
@@ -31,7 +35,7 @@ import com.android.wm.shell.flicker.testapp.Components
class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
instrumentation,
Components.PipActivity.LABEL,
- Components.PipActivity.COMPONENT
+ Components.PipActivity.COMPONENT.toFlickerComponent()
) {
private val mediaSessionManager: MediaSessionManager
get() = context.getSystemService(MediaSessionManager::class.java)
@@ -62,7 +66,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
stringExtras: Map<String, String>
) {
super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras)
- wmHelper.waitFor { it.wmState.hasPipWindow() }
+ wmHelper.waitFor("hasPipWindow") { it.wmState.hasPipWindow() }
}
private fun focusOnObject(selector: BySelector): Boolean {
@@ -84,7 +88,11 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
clickObject(ENTER_PIP_BUTTON_ID)
// Wait on WMHelper or simply wait for 3 seconds
- wmHelper?.waitFor { it.wmState.hasPipWindow() } ?: SystemClock.sleep(3_000)
+ wmHelper?.waitFor("hasPipWindow") { it.wmState.hasPipWindow() } ?: SystemClock.sleep(3_000)
+ // when entering pip, the dismiss button is visible at the start. to ensure the pip
+ // animation is complete, wait until the pip dismiss button is no longer visible.
+ // b/176822698: dismiss-only state will be removed in the future
+ uiDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "dismiss")), FIND_TIMEOUT)
}
fun clickStartMediaSessionButton() {
@@ -113,61 +121,61 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
}
}
+ private fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect {
+ val windowRegion = wmHelper.getWindowRegion(component)
+ require(!windowRegion.isEmpty) {
+ "Unable to find a PIP window in the current state"
+ }
+ return windowRegion.bounds
+ }
+
/**
- * Expands the pip window and dismisses it by clicking on the X button.
- *
- * Note, currently the View coordinates reported by the accessibility are relative to
- * the window, so the correct coordinates need to be calculated
- *
- * For example, in a PIP window located at Rect(508, 1444 - 1036, 1741), the
- * dismiss button coordinates are shown as Rect(650, 0 - 782, 132), with center in
- * Point(716, 66), instead of Point(970, 1403)
- *
- * See b/179337864
+ * Taps the pip window and dismisses it by clicking on the X button.
*/
fun closePipWindow(wmHelper: WindowManagerStateHelper) {
if (isTelevision) {
uiDevice.closeTvPipWindow()
} else {
- expandPipWindow(wmHelper)
- val exitPipObject = uiDevice.findObject(By.res(SYSTEMUI_PACKAGE, "dismiss"))
- requireNotNull(exitPipObject) { "PIP window dismiss button not found" }
- val dismissButtonBounds = exitPipObject.visibleBounds
+ val windowRect = getWindowRect(wmHelper)
+ uiDevice.click(windowRect.centerX(), windowRect.centerY())
+ // search and interact with the dismiss button
+ val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
+ uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
+ val dismissPipObject = uiDevice.findObject(dismissSelector)
+ ?: error("PIP window dismiss button not found")
+ val dismissButtonBounds = dismissPipObject.visibleBounds
uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
}
// Wait for animation to complete.
- wmHelper.waitFor { !it.wmState.hasPipWindow() }
+ wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() }
wmHelper.waitForHomeActivityVisible()
}
/**
- * Click once on the PIP window to expand it
+ * Close the pip window by pressing the expand button
*/
- fun expandPipWindow(wmHelper: WindowManagerStateHelper) {
- val windowRegion = wmHelper.getWindowRegion(component)
- require(!windowRegion.isEmpty) {
- "Unable to find a PIP window in the current state"
- }
- val windowRect = windowRegion.bounds
+ fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
+ val windowRect = getWindowRect(wmHelper)
uiDevice.click(windowRect.centerX(), windowRect.centerY())
- // Ensure WindowManagerService wait until all animations have completed
+ // search and interact with the expand button
+ val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button")
+ uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT)
+ val expandPipObject = uiDevice.findObject(expandSelector)
+ ?: error("PIP window expand button not found")
+ val expandButtonBounds = expandPipObject.visibleBounds
+ uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY())
+ wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() }
wmHelper.waitForAppTransitionIdle()
- mInstrumentation.uiAutomation.syncInputTransactions()
}
/**
- * Double click on the PIP window to reopen to app
+ * Double click on the PIP window to expand it
*/
- fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
- val windowRegion = wmHelper.getWindowRegion(component)
- require(!windowRegion.isEmpty) {
- "Unable to find a PIP window in the current state"
- }
- val windowRect = windowRegion.bounds
+ fun doubleClickPipWindow(wmHelper: WindowManagerStateHelper) {
+ val windowRect = getWindowRect(wmHelper)
uiDevice.click(windowRect.centerX(), windowRect.centerY())
uiDevice.click(windowRect.centerX(), windowRect.centerY())
- wmHelper.waitFor { !it.wmState.hasPipWindow() }
wmHelper.waitForAppTransitionIdle()
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt
index ba13e38ae9e3..4d0fbc4a0e38 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt
@@ -17,10 +17,11 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
+import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.wm.shell.flicker.testapp.Components
class SimpleAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
instrumentation,
Components.SimpleActivity.LABEL,
- Components.SimpleActivity.COMPONENT
+ Components.SimpleActivity.COMPONENT.toFlickerComponent()
) \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
index 901b7a393291..0ec9b2d869a8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
@@ -17,32 +17,39 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
-import android.content.ComponentName
+import android.content.res.Resources
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.wm.shell.flicker.testapp.Components
class SplitScreenHelper(
instrumentation: Instrumentation,
activityLabel: String,
- componentsInfo: ComponentName
+ componentsInfo: FlickerComponentName
) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) {
companion object {
const val TEST_REPETITIONS = 1
const val TIMEOUT_MS = 3_000L
+ // TODO: remove all legacy split screen flicker tests when legacy split screen is fully
+ // deprecated.
+ fun isUsingLegacySplit(): Boolean =
+ Resources.getSystem().getBoolean(com.android.internal.R.bool.config_useLegacySplit)
+
fun getPrimary(instrumentation: Instrumentation): SplitScreenHelper =
SplitScreenHelper(instrumentation,
Components.SplitScreenActivity.LABEL,
- Components.SplitScreenActivity.COMPONENT)
+ Components.SplitScreenActivity.COMPONENT.toFlickerComponent())
fun getSecondary(instrumentation: Instrumentation): SplitScreenHelper =
SplitScreenHelper(instrumentation,
Components.SplitScreenSecondaryActivity.LABEL,
- Components.SplitScreenSecondaryActivity.COMPONENT)
+ Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent())
fun getNonResizeable(instrumentation: Instrumentation): SplitScreenHelper =
SplitScreenHelper(instrumentation,
Components.NonResizeableActivity.LABEL,
- Components.NonResizeableActivity.COMPONENT)
+ Components.NonResizeableActivity.COMPONENT.toFlickerComponent())
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
index 4f12f2bb9f5f..bd44d082a1aa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
@@ -18,20 +18,21 @@ package com.android.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.HOME_WINDOW_TITLE
-import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -48,7 +49,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
+@Group4
class EnterSplitScreenDockActivity(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
@@ -60,16 +61,16 @@ class EnterSplitScreenDockActivity(
}
}
- override val ignoredWindows: List<String>
- get() = listOf(LAUNCHER_PACKAGE_NAME, LIVE_WALLPAPER_PACKAGE_NAME,
- splitScreenApp.defaultWindowName, WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME, *HOME_WINDOW_TITLE)
+ override val ignoredWindows: List<FlickerComponentName>
+ get() = listOf(LAUNCHER_COMPONENT, LIVE_WALLPAPER_COMPONENT,
+ splitScreenApp.component, FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT, LAUNCHER_COMPONENT)
@Presubmit
@Test
- fun dockedStackPrimaryBoundsIsVisible() =
- testSpec.dockedStackPrimaryBoundsIsVisible(testSpec.config.startRotation,
- splitScreenApp.defaultWindowName)
+ fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
+ testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+ splitScreenApp.component)
@Presubmit
@Test
@@ -77,27 +78,39 @@ class EnterSplitScreenDockActivity(
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
fun appWindowIsVisible() {
testSpec.assertWmEnd {
- isVisible(splitScreenApp.defaultWindowName)
+ isAppWindowVisible(splitScreenApp.component)
}
}
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @Presubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910
+ supportedRotations = listOf(Surface.ROTATION_0), // bugId = 179116910
+ supportedNavigationModes = listOf(
+ WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt
index f91f634a00e5..625d48b8ab5a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt
@@ -22,10 +22,11 @@ import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -42,6 +43,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@Group4
class EnterSplitScreenFromDetachedRecentTask(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
@@ -61,24 +63,34 @@ class EnterSplitScreenFromDetachedRecentTask(
}
}
- override val ignoredWindows: List<String>
- get() = listOf(LAUNCHER_PACKAGE_NAME,
- WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME,
- splitScreenApp.defaultWindowName)
+ override val ignoredWindows: List<FlickerComponentName>
+ get() = listOf(LAUNCHER_COMPONENT,
+ FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT,
+ splitScreenApp.component)
@Presubmit
@Test
- fun dockedStackDividerIsVisible() = testSpec.dockedStackDividerIsVisible()
+ fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
@Presubmit
@Test
fun appWindowIsVisible() {
testSpec.assertWmEnd {
- isVisible(splitScreenApp.defaultWindowName)
+ isAppWindowVisible(splitScreenApp.component)
}
}
+ @Presubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @Presubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
index 85ded8a45233..2ed2806af528 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
@@ -22,18 +22,17 @@ import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -49,7 +48,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
+@Group4
class EnterSplitScreenLaunchToSide(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
@@ -62,22 +61,22 @@ class EnterSplitScreenLaunchToSide(
}
}
- override val ignoredWindows: List<String>
- get() = listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- secondaryApp.defaultWindowName, WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+ override val ignoredWindows: List<FlickerComponentName>
+ get() = listOf(LAUNCHER_COMPONENT, splitScreenApp.component,
+ secondaryApp.component, FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT)
@Presubmit
@Test
- fun dockedStackPrimaryBoundsIsVisible() =
- testSpec.dockedStackPrimaryBoundsIsVisible(testSpec.config.startRotation,
- splitScreenApp.defaultWindowName)
+ fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
+ testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+ splitScreenApp.component)
@Presubmit
@Test
- fun dockedStackSecondaryBoundsIsVisible() =
- testSpec.dockedStackSecondaryBoundsIsVisible(testSpec.config.startRotation,
- secondaryApp.defaultWindowName)
+ fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
+ testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+ secondaryApp.component)
@Presubmit
@Test
@@ -85,15 +84,35 @@ class EnterSplitScreenLaunchToSide(
@Presubmit
@Test
- fun appWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp.defaultWindowName)
+ fun appWindowBecomesVisible() {
+ testSpec.assertWm {
+ // when the app is launched, first the activity becomes visible, then the
+ // SnapshotStartingWindow appears and then the app window becomes visible.
+ // Because we log WM once per frame, sometimes the activity and the window
+ // become visible in the same entry, sometimes not, thus it is not possible to
+ // assert the visibility of the activity here
+ this.isAppWindowInvisible(secondaryApp.component)
+ .then()
+ // during re-parenting, the window may disappear and reappear from the
+ // trace, this occurs because we log only 1x per frame
+ .notContains(secondaryApp.component, isOptional = true)
+ .then()
+ .isAppWindowVisible(secondaryApp.component)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
index e958bf39930e..ee6cf341c9ff 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
@@ -22,11 +22,11 @@ import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.canSplitScreen
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
@@ -50,7 +50,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group1
+@Group4
class EnterSplitScreenNotSupportNonResizable(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
@@ -70,12 +70,12 @@ class EnterSplitScreenNotSupportNonResizable(
}
}
- override val ignoredWindows: List<String>
- get() = listOf(LAUNCHER_PACKAGE_NAME,
- WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME,
- nonResizeableApp.defaultWindowName,
- splitScreenApp.defaultWindowName)
+ override val ignoredWindows: List<FlickerComponentName>
+ get() = listOf(LAUNCHER_COMPONENT,
+ FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT,
+ nonResizeableApp.component,
+ splitScreenApp.component)
@Before
override fun setup() {
@@ -91,7 +91,12 @@ class EnterSplitScreenNotSupportNonResizable(
@Presubmit
@Test
- fun dockedStackDividerIsInvisible() = testSpec.dockedStackDividerIsInvisible()
+ fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd()
+
+ @Presubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
index d3acc82121b0..163b6ffda6e2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
@@ -25,8 +25,8 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
@@ -67,12 +67,12 @@ class EnterSplitScreenSupportNonResizable(
}
}
- override val ignoredWindows: List<String>
- get() = listOf(LAUNCHER_PACKAGE_NAME,
- WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME,
- nonResizeableApp.defaultWindowName,
- splitScreenApp.defaultWindowName)
+ override val ignoredWindows: List<FlickerComponentName>
+ get() = listOf(LAUNCHER_COMPONENT,
+ FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT,
+ nonResizeableApp.component,
+ splitScreenApp.component)
@Before
override fun setup() {
@@ -88,16 +88,21 @@ class EnterSplitScreenSupportNonResizable(
@Presubmit
@Test
- fun dockedStackDividerIsVisible() = testSpec.dockedStackDividerIsVisible()
+ fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
@Presubmit
@Test
fun appWindowIsVisible() {
testSpec.assertWmEnd {
- isVisible(nonResizeableApp.defaultWindowName)
+ isAppWindowVisible(nonResizeableApp.component)
}
}
+ @Presubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
index bad46836dcb7..2b629b0a7eb5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.flicker.legacysplitscreen
-import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.Postsubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -24,15 +24,13 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.appWindowBecomesInVisible
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom
import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.layerBecomesInvisible
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -67,31 +65,52 @@ class ExitLegacySplitScreenFromBottom(
}
}
transitions {
- device.exitSplitScreenFromBottom()
+ device.exitSplitScreenFromBottom(wmHelper)
}
}
- override val ignoredWindows: List<String>
- get() = listOf(LAUNCHER_PACKAGE_NAME, WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- splitScreenApp.defaultWindowName, secondaryApp.defaultWindowName,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+ override val ignoredWindows: List<FlickerComponentName>
+ get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN,
+ splitScreenApp.component, secondaryApp.component,
+ FlickerComponentName.SNAPSHOT)
- @Presubmit
+ @Postsubmit
@Test
- fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(DOCKED_STACK_DIVIDER)
+ fun layerBecomesInvisible() {
+ testSpec.assertLayers {
+ this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
+ .then()
+ .isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
+ }
+ }
@FlakyTest
@Test
- fun appWindowBecomesInVisible() =
- testSpec.appWindowBecomesInVisible(secondaryApp.defaultWindowName)
+ fun appWindowBecomesInVisible() {
+ testSpec.assertWm {
+ this.isAppWindowVisible(secondaryApp.component)
+ .then()
+ .isAppWindowInvisible(secondaryApp.component)
+ }
+ }
+
+ @Postsubmit
+ @Test
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
- @Presubmit
+ @Postsubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
- @Presubmit
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @FlakyTest
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
index 76dcd8b89242..95fe3bef4852 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
@@ -24,15 +24,13 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.appWindowBecomesInVisible
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.flicker.layerBecomesInvisible
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -71,31 +69,52 @@ class ExitPrimarySplitScreenShowSecondaryFullscreen(
}
}
- override val ignoredWindows: List<String>
- get() = listOf(LAUNCHER_PACKAGE_NAME, WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- splitScreenApp.defaultWindowName, secondaryApp.defaultWindowName,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+ override val ignoredWindows: List<FlickerComponentName>
+ get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN,
+ splitScreenApp.component, secondaryApp.component,
+ FlickerComponentName.SNAPSHOT)
- @FlakyTest(bugId = 175687842)
+ @Presubmit
@Test
- fun dockedStackDividerIsInvisible() = testSpec.dockedStackDividerIsInvisible()
+ fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd()
@FlakyTest
@Test
- fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName)
+ fun layerBecomesInvisible() {
+ testSpec.assertLayers {
+ this.isVisible(splitScreenApp.component)
+ .then()
+ .isInvisible(splitScreenApp.component)
+ }
+ }
@FlakyTest
@Test
- fun appWindowBecomesInVisible() =
- testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+ fun appWindowBecomesInVisible() {
+ testSpec.assertWm {
+ this.isAppWindowVisible(splitScreenApp.component)
+ .then()
+ .isAppWindowInvisible(splitScreenApp.component)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
+
+ @Presubmit
+ @Test
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
index d0a64b3774c7..f7d628d48769 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
@@ -23,15 +23,11 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.appWindowBecomesInVisible
-import com.android.server.wm.flicker.appWindowBecomesVisible
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.layerBecomesInvisible
-import com.android.server.wm.flicker.layerBecomesVisible
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
@@ -72,11 +68,11 @@ class LegacySplitScreenFromIntentNotSupportNonResizable(
}
}
- override val ignoredWindows: List<String>
- get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME,
- nonResizeableApp.defaultWindowName, splitScreenApp.defaultWindowName,
- WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+ override val ignoredWindows: List<FlickerComponentName>
+ get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT,
+ nonResizeableApp.component, splitScreenApp.component,
+ FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT)
@Before
override fun setup() {
@@ -92,44 +88,109 @@ class LegacySplitScreenFromIntentNotSupportNonResizable(
@Presubmit
@Test
- fun resizableAppLayerBecomesInvisible() =
- testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName)
+ fun resizableAppLayerBecomesInvisible() {
+ testSpec.assertLayers {
+ this.isVisible(splitScreenApp.component)
+ .then()
+ .isInvisible(splitScreenApp.component)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun nonResizableAppLayerBecomesVisible() {
+ testSpec.assertLayers {
+ this.notContains(nonResizeableApp.component)
+ .then()
+ .isInvisible(nonResizeableApp.component)
+ .then()
+ .isVisible(nonResizeableApp.component)
+ }
+ }
+ /**
+ * Assets that [splitScreenApp] exists at the start of the trace and, once it becomes
+ * invisible, it remains invisible until the end of the trace.
+ */
@Presubmit
@Test
- fun nonResizableAppLayerBecomesVisible() =
- testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName)
+ fun resizableAppWindowBecomesInvisible() {
+ testSpec.assertWm {
+ // when the activity gets PAUSED the window may still be marked as visible
+ // it will be updated in the next log entry. This occurs because we record 1x
+ // per frame, thus ignore activity check here
+ this.isAppWindowVisible(splitScreenApp.component)
+ .then()
+ // immediately after the window (after onResume and before perform relayout)
+ // the activity is invisible. This may or not be logged, since we record 1x
+ // per frame, thus ignore activity check here
+ .isAppWindowInvisible(splitScreenApp.component)
+ }
+ }
+ /**
+ * Assets that [nonResizeableApp] doesn't exist at the start of the trace, then
+ * [nonResizeableApp] is created (visible or not) and, once [nonResizeableApp] becomes
+ * visible, it remains visible until the end of the trace.
+ */
@Presubmit
@Test
- fun resizableAppWindowBecomesInvisible() =
- testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+ fun nonResizableAppWindowBecomesVisible() {
+ testSpec.assertWm {
+ this.notContains(nonResizeableApp.component)
+ .then()
+ // we log once per frame, upon logging, window may be visible or not depending
+ // on what was processed until that moment. Both behaviors are correct
+ .isAppWindowInvisible(nonResizeableApp.component, isOptional = true)
+ .then()
+ // immediately after the window (after onResume and before perform relayout)
+ // the activity is invisible. This may or not be logged, since we record 1x
+ // per frame, thus ignore activity check here
+ .isAppWindowVisible(nonResizeableApp.component)
+ }
+ }
+ /**
+ * Asserts that both the app window and the activity are visible at the end of the trace
+ */
@Presubmit
@Test
- fun nonResizableAppWindowBecomesVisible() =
- testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+ fun nonResizableAppWindowBecomesVisibleAtEnd() {
+ testSpec.assertWmEnd {
+ isAppWindowVisible(nonResizeableApp.component)
+ }
+ }
@Presubmit
@Test
- fun dockedStackDividerIsInvisibleAtEnd() = testSpec.dockedStackDividerIsInvisible()
+ fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd()
@Presubmit
@Test
fun onlyNonResizableAppWindowIsVisibleAtEnd() {
testSpec.assertWmEnd {
- isInvisible(splitScreenApp.defaultWindowName)
- isVisible(nonResizeableApp.defaultWindowName)
+ isAppWindowInvisible(splitScreenApp.component)
+ isAppWindowVisible(nonResizeableApp.component)
}
}
+ @Presubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @Presubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
index c26c05fa8db6..a5c6571f68de 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
@@ -23,13 +23,11 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.appWindowBecomesVisible
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.layerBecomesVisible
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
-import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
@@ -70,11 +68,11 @@ class LegacySplitScreenFromIntentSupportNonResizable(
}
}
- override val ignoredWindows: List<String>
- get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME,
- nonResizeableApp.defaultWindowName, splitScreenApp.defaultWindowName,
- WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+ override val ignoredWindows: List<FlickerComponentName>
+ get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT,
+ nonResizeableApp.component, splitScreenApp.component,
+ FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT)
@Before
override fun setup() {
@@ -90,27 +88,59 @@ class LegacySplitScreenFromIntentSupportNonResizable(
@Presubmit
@Test
- fun nonResizableAppLayerBecomesVisible() =
- testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName)
+ fun nonResizableAppLayerBecomesVisible() {
+ testSpec.assertLayers {
+ this.isInvisible(nonResizeableApp.component)
+ .then()
+ .isVisible(nonResizeableApp.component)
+ }
+ }
+ /**
+ * Assets that [nonResizeableApp] doesn't exist at the start of the trace, then
+ * [nonResizeableApp] is created (visible or not) and, once [nonResizeableApp] becomes
+ * visible, it remains visible until the end of the trace.
+ */
@Presubmit
@Test
- fun nonResizableAppWindowBecomesVisible() =
- testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+ fun nonResizableAppWindowBecomesVisible() {
+ testSpec.assertWm {
+ this.notContains(nonResizeableApp.component)
+ .then()
+ // we log once per frame, upon logging, window may be visible or not depending
+ // on what was processed until that moment. Both behaviors are correct
+ .isAppWindowInvisible(nonResizeableApp.component, isOptional = true)
+ .then()
+ // immediately after the window (after onResume and before perform relayout)
+ // the activity is invisible. This may or not be logged, since we record 1x
+ // per frame, thus ignore activity check here
+ .isAppWindowVisible(nonResizeableApp.component)
+ }
+ }
@Presubmit
@Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisible()
+ fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
@Presubmit
@Test
fun bothAppsWindowsAreVisibleAtEnd() {
testSpec.assertWmEnd {
- isVisible(splitScreenApp.defaultWindowName)
- isVisible(nonResizeableApp.defaultWindowName)
+ isAppWindowVisible(splitScreenApp.component)
+ isAppWindowVisible(nonResizeableApp.component)
}
}
+ @Presubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @Presubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
index fb1758975442..6f486b0ddfea 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.legacysplitscreen
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
@@ -23,16 +24,12 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.appWindowBecomesInVisible
-import com.android.server.wm.flicker.appWindowBecomesVisible
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.flicker.layerBecomesInvisible
-import com.android.server.wm.flicker.layerBecomesVisible
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
@@ -73,11 +70,11 @@ class LegacySplitScreenFromRecentNotSupportNonResizable(
}
}
- override val ignoredWindows: List<String>
- get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME, TOAST_NAME,
- splitScreenApp.defaultWindowName, nonResizeableApp.defaultWindowName,
- WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+ override val ignoredWindows: List<FlickerComponentName>
+ get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT,
+ TOAST_COMPONENT, splitScreenApp.component, nonResizeableApp.component,
+ FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT)
@Before
override fun setup() {
@@ -93,37 +90,73 @@ class LegacySplitScreenFromRecentNotSupportNonResizable(
@Presubmit
@Test
- fun resizableAppLayerBecomesInvisible() =
- testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName)
+ fun resizableAppLayerBecomesInvisible() {
+ testSpec.assertLayers {
+ this.isVisible(splitScreenApp.component)
+ .then()
+ .isInvisible(splitScreenApp.component)
+ }
+ }
@Presubmit
@Test
- fun nonResizableAppLayerBecomesVisible() =
- testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName)
+ fun nonResizableAppLayerBecomesVisible() {
+ testSpec.assertLayers {
+ this.isInvisible(nonResizeableApp.component)
+ .then()
+ .isVisible(nonResizeableApp.component)
+ }
+ }
@Presubmit
@Test
- fun resizableAppWindowBecomesInvisible() =
- testSpec.appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+ fun resizableAppWindowBecomesInvisible() {
+ testSpec.assertWm {
+ // when the activity gets PAUSED the window may still be marked as visible
+ // it will be updated in the next log entry. This occurs because we record 1x
+ // per frame, thus ignore activity check here
+ this.isAppWindowVisible(splitScreenApp.component)
+ .then()
+ // immediately after the window (after onResume and before perform relayout)
+ // the activity is invisible. This may or not be logged, since we record 1x
+ // per frame, thus ignore activity check here
+ .isAppWindowInvisible(splitScreenApp.component)
+ }
+ }
- @Presubmit
+ @Postsubmit
@Test
- fun nonResizableAppWindowBecomesVisible() =
- testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+ fun nonResizableAppWindowBecomesVisible() {
+ testSpec.assertWm {
+ this.isAppWindowInvisible(nonResizeableApp.component)
+ .then()
+ .isAppWindowVisible(nonResizeableApp.component)
+ }
+ }
@Presubmit
@Test
- fun dockedStackDividerIsInvisibleAtEnd() = testSpec.dockedStackDividerIsInvisible()
+ fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd()
@Presubmit
@Test
fun onlyNonResizableAppWindowIsVisibleAtEnd() {
testSpec.assertWmEnd {
- isInvisible(splitScreenApp.defaultWindowName)
- isVisible(nonResizeableApp.defaultWindowName)
+ isAppWindowInvisible(splitScreenApp.component)
+ isAppWindowVisible(nonResizeableApp.component)
}
}
+ @Presubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @Presubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
index a9c28efcdf44..f03c927b8d58 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
@@ -23,14 +23,12 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.appWindowBecomesVisible
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.flicker.layerBecomesVisible
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
-import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
@@ -71,11 +69,11 @@ class LegacySplitScreenFromRecentSupportNonResizable(
}
}
- override val ignoredWindows: List<String>
- get() = listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME, LETTERBOX_NAME, TOAST_NAME,
- splitScreenApp.defaultWindowName, nonResizeableApp.defaultWindowName,
- WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+ override val ignoredWindows: List<FlickerComponentName>
+ get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT,
+ TOAST_COMPONENT, splitScreenApp.component, nonResizeableApp.component,
+ FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT)
@Before
override fun setup() {
@@ -91,27 +89,60 @@ class LegacySplitScreenFromRecentSupportNonResizable(
@Presubmit
@Test
- fun nonResizableAppLayerBecomesVisible() =
- testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName)
+ fun nonResizableAppLayerBecomesVisible() {
+ testSpec.assertLayers {
+ this.isInvisible(nonResizeableApp.component)
+ .then()
+ .isVisible(nonResizeableApp.component)
+ }
+ }
@Presubmit
@Test
- fun nonResizableAppWindowBecomesVisible() =
- testSpec.appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+ fun nonResizableAppWindowBecomesVisible() {
+ testSpec.assertWm {
+ // when the app is launched, first the activity becomes visible, then the
+ // SnapshotStartingWindow appears and then the app window becomes visible.
+ // Because we log WM once per frame, sometimes the activity and the window
+ // become visible in the same entry, sometimes not, thus it is not possible to
+ // assert the visibility of the activity here
+ this.isAppWindowInvisible(nonResizeableApp.component)
+ .then()
+ // during re-parenting, the window may disappear and reappear from the
+ // trace, this occurs because we log only 1x per frame
+ .notContains(nonResizeableApp.component, isOptional = true)
+ .then()
+ // if the window reappears after re-parenting it will most likely not
+ // be visible in the first log entry (because we log only 1x per frame)
+ .isAppWindowInvisible(nonResizeableApp.component, isOptional = true)
+ .then()
+ .isAppWindowVisible(nonResizeableApp.component)
+ }
+ }
@Presubmit
@Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisible()
+ fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
@Presubmit
@Test
fun bothAppsWindowsAreVisibleAtEnd() {
testSpec.assertWmEnd {
- isVisible(splitScreenApp.defaultWindowName)
- isVisible(nonResizeableApp.defaultWindowName)
+ isAppWindowVisible(splitScreenApp.component)
+ isAppWindowVisible(nonResizeableApp.component)
}
}
+ @Presubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @Presubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
index a4d2ab51e358..2ccd03bf1d6a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
@@ -16,10 +16,9 @@
package com.android.wm.shell.flicker.legacysplitscreen
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.support.test.launcherhelper.LauncherStrategyFactory
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -27,21 +26,19 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.endRotation
-import com.android.server.wm.flicker.focusDoesNotChange
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.exitSplitScreen
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.layerBecomesInvisible
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.dockedStackDividerBecomesInvisible
import com.android.wm.shell.flicker.helpers.SimpleAppHelper
import org.junit.FixMethodOrder
@@ -62,8 +59,6 @@ import org.junit.runners.Parameterized
class LegacySplitScreenToLauncher(
testSpec: FlickerTestParameter
) : LegacySplitScreenTransition(testSpec) {
- private val launcherPackageName = LauncherStrategyFactory.getInstance(instrumentation)
- .launcherStrategy.supportedLauncherPackage
private val testApp = SimpleAppHelper(instrumentation)
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
@@ -90,51 +85,69 @@ class LegacySplitScreenToLauncher(
}
}
- override val ignoredWindows: List<String>
- get() = listOf(launcherPackageName, WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+ override val ignoredWindows: List<FlickerComponentName>
+ get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT)
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
+ fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
@Presubmit
@Test
- fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.endRotation)
+ fun entireScreenCovered() = testSpec.entireScreenCovered()
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.endRotation)
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.endRotation)
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
+ fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
- @Presubmit
+ @Postsubmit
@Test
fun dockedStackDividerBecomesInvisible() = testSpec.dockedStackDividerBecomesInvisible()
+ @Postsubmit
+ @Test
+ fun layerBecomesInvisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ .then()
+ .isInvisible(testApp.component)
+ }
+ }
+
+ @Postsubmit
+ @Test
+ fun focusDoesNotChange() {
+ testSpec.assertEventLog {
+ this.focusDoesNotChange()
+ }
+ }
+
@Presubmit
@Test
- fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(testApp.getPackage())
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
- @FlakyTest(bugId = 151179149)
+ @Presubmit
@Test
- fun focusDoesNotChange() = testSpec.focusDoesNotChange()
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
index e8d4d1e9ada2..661c8b69068e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
@@ -31,11 +31,14 @@ import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow
import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setDevEnableNonResizableMultiWindow
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.After
+import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Test
@@ -46,12 +49,17 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa
protected val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
protected val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation)
- protected val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation)
- .launcherStrategy.supportedLauncherPackage
+ protected val LAUNCHER_COMPONENT = FlickerComponentName("",
+ LauncherStrategyFactory.getInstance(instrumentation)
+ .launcherStrategy.supportedLauncherPackage)
private var prevDevEnableNonResizableMultiWindow = 0
@Before
open fun setup() {
+ // Only run legacy split tests when the system is using legacy split screen.
+ assumeTrue(SplitScreenHelper.isUsingLegacySplit())
+ // Legacy split is having some issue with Shell transition, and will be deprecated soon.
+ assumeFalse(isShellTransitionsEnabled())
prevDevEnableNonResizableMultiWindow = getDevEnableNonResizableMultiWindow(context)
if (prevDevEnableNonResizableMultiWindow != 0) {
// Turn off the development option
@@ -70,8 +78,9 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa
*
* b/182720234
*/
- open val ignoredWindows: List<String> = listOf(WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+ open val ignoredWindows: List<FlickerComponentName> = listOf(
+ FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT)
protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = { configuration ->
@@ -138,9 +147,9 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa
}
companion object {
- internal const val LIVE_WALLPAPER_PACKAGE_NAME =
- "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2"
- internal const val LETTERBOX_NAME = "Letterbox"
- internal const val TOAST_NAME = "Toast"
+ internal val LIVE_WALLPAPER_COMPONENT = FlickerComponentName("",
+ "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2")
+ internal val LETTERBOX_COMPONENT = FlickerComponentName("", "Letterbox")
+ internal val TOAST_COMPONENT = FlickerComponentName("", "Toast")
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
index 05eb5f49a641..34eff80a04bc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
@@ -24,15 +24,11 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.appWindowBecomesVisible
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.layerBecomesVisible
-import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.server.wm.flicker.statusBarLayerIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
@@ -62,22 +58,28 @@ class OpenAppToLegacySplitScreen(
}
}
- override val ignoredWindows: List<String>
- get() = listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
- WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME)
+ override val ignoredWindows: List<FlickerComponentName>
+ get() = listOf(LAUNCHER_COMPONENT, splitScreenApp.component,
+ FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT)
@FlakyTest
@Test
- fun appWindowBecomesVisible() = testSpec.appWindowBecomesVisible(splitScreenApp.getPackage())
+ fun appWindowBecomesVisible() {
+ testSpec.assertWm {
+ this.isAppWindowInvisible(splitScreenApp.component)
+ .then()
+ .isAppWindowVisible(splitScreenApp.component)
+ }
+ }
- @FlakyTest
+ @Presubmit
@Test
- fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation)
+ fun entireScreenCovered() = testSpec.entireScreenCovered()
@Presubmit
@Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
+ fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
@Presubmit
@Test
@@ -85,12 +87,27 @@ class OpenAppToLegacySplitScreen(
@FlakyTest
@Test
- fun layerBecomesVisible() = testSpec.layerBecomesVisible(splitScreenApp.getPackage())
+ fun layerBecomesVisible() {
+ testSpec.assertLayers {
+ this.isInvisible(splitScreenApp.component)
+ .then()
+ .isVisible(splitScreenApp.component)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun focusChanges() {
+ testSpec.assertEventLog {
+ this.focusChanges(splitScreenApp.`package`,
+ "recents_animation_input_consumer", "NexusLauncherActivity")
+ }
+ }
- @FlakyTest(bugId = 151179149)
+ @Presubmit
@Test
- fun focusChanges() = testSpec.focusChanges(splitScreenApp.`package`,
- "recents_animation_input_consumer", "NexusLauncherActivity")
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index 3e83b6382939..58e1def6f37a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -27,24 +27,24 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.resizeSplitScreen
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.traces.layers.getVisibleBounds
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.parser.toFlickerComponent
+import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.helpers.SimpleAppHelper
+import com.android.wm.shell.flicker.testapp.Components
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -101,16 +101,16 @@ class ResizeLegacySplitScreen(
}
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@FlakyTest(bugId = 156223549)
@Test
fun topAppWindowIsAlwaysVisible() {
testSpec.assertWm {
- this.showsAppWindow(sSimpleActivity)
+ this.isAppWindowVisible(Components.SimpleActivity.COMPONENT.toFlickerComponent())
}
}
@@ -118,45 +118,43 @@ class ResizeLegacySplitScreen(
@Test
fun bottomAppWindowIsAlwaysVisible() {
testSpec.assertWm {
- this.showsAppWindow(sImeActivity)
+ this.isAppWindowVisible(Components.ImeActivity.COMPONENT.toFlickerComponent())
}
}
@Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
+ fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
@Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
+ fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
@Test
- fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.endRotation)
+ fun entireScreenCovered() = testSpec.entireScreenCovered()
@Test
- fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.endRotation)
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Test
- fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.endRotation)
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Test
fun topAppLayerIsAlwaysVisible() {
testSpec.assertLayers {
- this.isVisible(sSimpleActivity)
+ this.isVisible(Components.SimpleActivity.COMPONENT.toFlickerComponent())
}
}
@Test
fun bottomAppLayerIsAlwaysVisible() {
testSpec.assertLayers {
- this.isVisible(sImeActivity)
+ this.isVisible(Components.ImeActivity.COMPONENT.toFlickerComponent())
}
}
@Test
fun dividerLayerIsAlwaysVisible() {
testSpec.assertLayers {
- this.isVisible(DOCKED_STACK_DIVIDER)
+ this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
}
}
@@ -166,7 +164,7 @@ class ResizeLegacySplitScreen(
testSpec.assertLayersStart {
val displayBounds = WindowUtils.displayBounds
val dividerBounds =
- entry.getVisibleBounds(DOCKED_STACK_DIVIDER).bounds
+ layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds
val topAppBounds = Region(0, 0, dividerBounds.right,
dividerBounds.top + WindowUtils.dockedStackDividerInset)
@@ -174,8 +172,10 @@ class ResizeLegacySplitScreen(
dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.right,
displayBounds.bottom - WindowUtils.navigationBarHeight)
- visibleRegion("SimpleActivity").coversExactly(topAppBounds)
- visibleRegion("ImeActivity").coversExactly(bottomAppBounds)
+ visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent())
+ .coversExactly(topAppBounds)
+ visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent())
+ .coversExactly(bottomAppBounds)
}
}
@@ -185,7 +185,7 @@ class ResizeLegacySplitScreen(
testSpec.assertLayersStart {
val displayBounds = WindowUtils.displayBounds
val dividerBounds =
- entry.getVisibleBounds(DOCKED_STACK_DIVIDER).bounds
+ layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds
val topAppBounds = Region(0, 0, dividerBounds.right,
dividerBounds.top + WindowUtils.dockedStackDividerInset)
@@ -194,8 +194,10 @@ class ResizeLegacySplitScreen(
displayBounds.right,
displayBounds.bottom - WindowUtils.navigationBarHeight)
- visibleRegion(sSimpleActivity).coversExactly(topAppBounds)
- visibleRegion(sImeActivity).coversExactly(bottomAppBounds)
+ visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent())
+ .coversExactly(topAppBounds)
+ visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent())
+ .coversExactly(bottomAppBounds)
}
}
@@ -207,8 +209,6 @@ class ResizeLegacySplitScreen(
}
companion object {
- private const val sSimpleActivity = "SimpleActivity"
- private const val sImeActivity = "ImeActivity"
private val startRatio = Rational(1, 3)
private val stopRatio = Rational(2, 3)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
index 58482eaae3f5..8a50bc0b20cf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
@@ -24,18 +24,16 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.appWindowBecomesVisible
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -66,38 +64,44 @@ class RotateOneLaunchedAppAndEnterSplitScreen(
@Presubmit
@Test
- fun dockedStackDividerIsVisible() = testSpec.dockedStackDividerIsVisible()
+ fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
@Presubmit
@Test
- fun dockedStackPrimaryBoundsIsVisible() =
- testSpec.dockedStackPrimaryBoundsIsVisible(testSpec.config.startRotation,
- splitScreenApp.defaultWindowName)
+ fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
+ testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+ splitScreenApp.component)
- @FlakyTest(bugId = 169271943)
+ @Presubmit
@Test
- fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation,
- testSpec.config.endRotation)
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
- @FlakyTest(bugId = 169271943)
+ @Presubmit
@Test
- fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation,
- testSpec.config.endRotation)
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@FlakyTest
@Test
- fun appWindowBecomesVisible() =
- testSpec.appWindowBecomesVisible(splitScreenApp.defaultWindowName)
+ fun appWindowBecomesVisible() {
+ testSpec.assertWm {
+ this.isAppWindowInvisible(splitScreenApp.component)
+ .then()
+ .isAppWindowVisible(splitScreenApp.component)
+ }
+ }
+
+ @Presubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
index 06828d6adb26..84676a9186be 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
@@ -24,18 +24,16 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.appWindowBecomesVisible
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -66,35 +64,43 @@ class RotateOneLaunchedAppInSplitScreenMode(
@Presubmit
@Test
- fun dockedStackDividerIsVisible() = testSpec.dockedStackDividerIsVisible()
+ fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
@Presubmit
@Test
- fun dockedStackPrimaryBoundsIsVisible() = testSpec.dockedStackPrimaryBoundsIsVisible(
- testSpec.config.startRotation, splitScreenApp.defaultWindowName)
+ fun dockedStackPrimaryBoundsIsVisibleAtEnd() = testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(
+ testSpec.config.startRotation, splitScreenApp.component)
- @FlakyTest(bugId = 169271943)
+ @Presubmit
@Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(
- testSpec.config.startRotation, testSpec.config.endRotation)
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
- @FlakyTest(bugId = 169271943)
+ @Presubmit
@Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(
- testSpec.config.startRotation, testSpec.config.endRotation)
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@FlakyTest
@Test
- fun appWindowBecomesVisible() =
- testSpec.appWindowBecomesVisible(splitScreenApp.defaultWindowName)
+ fun appWindowBecomesVisible() {
+ testSpec.assertWm {
+ this.isAppWindowInvisible(splitScreenApp.component)
+ .then()
+ .isAppWindowVisible(splitScreenApp.component)
+ }
+ }
+
+ @Presubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
index f8e32bf171d8..2abdca9216f9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
@@ -18,26 +18,23 @@ package com.android.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.appWindowBecomesVisible
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -69,42 +66,63 @@ class RotateTwoLaunchedAppAndEnterSplitScreen(
@Presubmit
@Test
- fun dockedStackDividerIsVisible() = testSpec.dockedStackDividerIsVisible()
+ fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
@Presubmit
@Test
- fun dockedStackPrimaryBoundsIsVisible() =
- testSpec.dockedStackPrimaryBoundsIsVisible(testSpec.config.startRotation,
- splitScreenApp.defaultWindowName)
+ fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
+ testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+ splitScreenApp.component)
@Presubmit
@Test
- fun dockedStackSecondaryBoundsIsVisible() =
- testSpec.dockedStackSecondaryBoundsIsVisible(testSpec.config.startRotation,
- secondaryApp.defaultWindowName)
+ fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
+ testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+ secondaryApp.component)
- @FlakyTest(bugId = 169271943)
+ @Presubmit
@Test
- fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation,
- testSpec.config.endRotation)
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
- @FlakyTest(bugId = 169271943)
+ @Presubmit
@Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(
- testSpec.config.startRotation, testSpec.config.endRotation)
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
+
+ @Presubmit
+ @Test
+ fun appWindowBecomesVisible() {
+ testSpec.assertWm {
+ // when the app is launched, first the activity becomes visible, then the
+ // SnapshotStartingWindow appears and then the app window becomes visible.
+ // Because we log WM once per frame, sometimes the activity and the window
+ // become visible in the same entry, sometimes not, thus it is not possible to
+ // assert the visibility of the activity here
+ this.isAppWindowInvisible(secondaryApp.component)
+ .then()
+ // during re-parenting, the window may disappear and reappear from the
+ // trace, this occurs because we log only 1x per frame
+ .notContains(secondaryApp.component, isOptional = true)
+ .then()
+ // if the window reappears after re-parenting it will most likely not
+ // be visible in the first log entry (because we log only 1x per frame)
+ .isAppWindowInvisible(secondaryApp.component, isOptional = true)
+ .then()
+ .isAppWindowVisible(secondaryApp.component)
+ }
+ }
@Presubmit
@Test
- fun appWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp.defaultWindowName)
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
index cb246ca0b694..fe9b9f514015 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
@@ -24,20 +24,18 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.appWindowBecomesVisible
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -74,44 +72,55 @@ class RotateTwoLaunchedAppInSplitScreenMode(
@Presubmit
@Test
- fun dockedStackDividerIsVisible() = testSpec.dockedStackDividerIsVisible()
+ fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
@Presubmit
@Test
- fun dockedStackPrimaryBoundsIsVisible() =
- testSpec.dockedStackPrimaryBoundsIsVisible(testSpec.config.startRotation,
- splitScreenApp.defaultWindowName)
+ fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
+ testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+ splitScreenApp.component)
@Presubmit
@Test
- fun dockedStackSecondaryBoundsIsVisible() =
- testSpec.dockedStackSecondaryBoundsIsVisible(testSpec.config.startRotation,
- secondaryApp.defaultWindowName)
+ fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
+ testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.config.startRotation,
+ secondaryApp.component)
- @FlakyTest(bugId = 169271943)
+ @Presubmit
@Test
- fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation,
- testSpec.config.endRotation)
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
- @FlakyTest(bugId = 169271943)
+ @Presubmit
@Test
- fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation,
- testSpec.config.endRotation)
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@FlakyTest
@Test
- fun appWindowBecomesVisible() =
- testSpec.appWindowBecomesVisible(secondaryApp.defaultWindowName)
+ fun appWindowBecomesVisible() {
+ testSpec.assertWm {
+ this.isAppWindowInvisible(secondaryApp.component)
+ .then()
+ .isAppWindowVisible(secondaryApp.component)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
+
+ @Presubmit
+ @Test
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt
index 2a660747bc1d..f9b08000290f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+@file:JvmName("CommonAssertions")
package com.android.wm.shell.flicker.pip
-internal const val PIP_WINDOW_TITLE = "PipMenuActivity"
+internal const val PIP_WINDOW_COMPONENT = "PipMenuActivity"
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index b6af26060050..52a744f3897d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -23,6 +23,7 @@ import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.FixMethodOrder
@@ -32,8 +33,21 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test Pip launch.
+ * Test entering pip from an app by interacting with the app UI
+ *
* To run this test: `atest WMShellFlickerTests:EnterPipTest`
+ *
+ * Actions:
+ * Launch an app in full screen
+ * Press an "enter pip" button to put [pipApp] in pip mode
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@@ -41,49 +55,121 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+ /**
+ * Defines the transition used to run the test
+ */
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = buildTransition(eachRun = true, stringExtras = emptyMap()) {
transitions {
- pipApp.clickEnterPipButton()
- pipApp.expandPipWindow(wmHelper)
+ pipApp.clickEnterPipButton(wmHelper)
}
}
- @FlakyTest
+ /**
+ * Checks [pipApp] window remains visible throughout the animation
+ */
+ @Presubmit
@Test
- override fun noUncoveredRegions() {
- super.noUncoveredRegions()
+ fun pipAppWindowAlwaysVisible() {
+ testSpec.assertWm {
+ this.isAppWindowVisible(pipApp.component)
+ }
}
+ /**
+ * Checks [pipApp] layer remains visible throughout the animation
+ */
@Presubmit
@Test
- fun pipAppWindowAlwaysVisible() {
+ fun pipAppLayerAlwaysVisible() {
+ testSpec.assertLayers {
+ this.isVisible(pipApp.component)
+ }
+ }
+
+ /**
+ * Checks that the pip app window remains inside the display bounds throughout the whole
+ * animation
+ */
+ @Presubmit
+ @Test
+ fun pipWindowRemainInsideVisibleBounds() {
testSpec.assertWm {
- this.showsAppWindow(pipApp.defaultWindowName)
+ coversAtMost(displayBounds, pipApp.component)
}
}
- @FlakyTest
+ /**
+ * Checks that the pip app layer remains inside the display bounds throughout the whole
+ * animation
+ */
+ @Presubmit
@Test
- fun pipLayerBecomesVisible() {
+ fun pipLayerRemainInsideVisibleBounds() {
testSpec.assertLayers {
- this.isVisible(pipApp.windowName)
+ coversAtMost(displayBounds, pipApp.component)
}
}
- @FlakyTest
+ /**
+ * Checks that the visible region of [pipApp] always reduces during the animation
+ */
+ @Presubmit
@Test
- fun pipWindowBecomesVisible() {
- testSpec.assertWm {
- invoke("pipWindowIsNotVisible") {
- verify("Has no pip window").that(it.wmState.hasPipWindow()).isTrue()
- }.then().invoke("pipWindowIsVisible") {
- verify("Has pip window").that(it.wmState.hasPipWindow()).isTrue()
+ fun pipLayerReduces() {
+ val layerName = pipApp.component.toLayerName()
+ testSpec.assertLayers {
+ val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ current.visibleRegion.coversAtMost(previous.visibleRegion.region)
}
}
}
+ /**
+ * Checks that [pipApp] window becomes pinned
+ */
+ @Presubmit
+ @Test
+ fun pipWindowBecomesPinned() {
+ testSpec.assertWm {
+ invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp.component) }
+ .then()
+ .invoke("pipWindowIsPinned") { it.isPinned(pipApp.component) }
+ }
+ }
+
+ /**
+ * Checks [LAUNCHER_COMPONENT] layer remains visible throughout the animation
+ */
+ @Presubmit
+ @Test
+ fun launcherLayerBecomesVisible() {
+ testSpec.assertLayers {
+ isInvisible(LAUNCHER_COMPONENT)
+ .then()
+ .isVisible(LAUNCHER_COMPONENT)
+ }
+ }
+
+ /**
+ * Checks the focus doesn't change during the animation
+ */
+ @FlakyTest
+ @Test
+ fun focusDoesNotChange() {
+ testSpec.assertEventLog {
+ this.focusDoesNotChange()
+ }
+ }
+
companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<FlickerTestParameter> {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index 3a1456e53f87..c8c3f4d64294 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -25,7 +25,11 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
@@ -38,8 +42,22 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test Pip with orientation changes.
- * To run this test: `atest WMShellFlickerTests:PipOrientationTest`
+ * Test entering pip while changing orientation (from app in landscape to pip window in portrait)
+ *
+ * To run this test: `atest EnterPipToOtherOrientationTest:EnterPipToOtherOrientationTest`
+ *
+ * Actions:
+ * Launch [testApp] on a fixed portrait orientation
+ * Launch [pipApp] on a fixed landscape orientation
+ * Broadcast action [ACTION_ENTER_PIP] to enter pip mode
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@@ -53,6 +71,9 @@ class EnterPipToOtherOrientationTest(
private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
+ /**
+ * Defines the transition used to run the test
+ */
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = { configuration ->
setupAndTeardown(this, configuration)
@@ -79,65 +100,125 @@ class EnterPipToOtherOrientationTest(
broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
wmHelper.waitFor { it.wmState.hasPipWindow() }
wmHelper.waitForAppTransitionIdle()
+ // during rotation the status bar becomes invisible and reappears at the end
+ wmHelper.waitForNavBarStatusBarVisible()
}
}
+ /**
+ * Checks that the [FlickerComponentName.NAV_BAR] has the correct position at
+ * the start and end of the transition
+ */
@FlakyTest
@Test
- override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
+ override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
- @FlakyTest
+ /**
+ * Checks that the [FlickerComponentName.STATUS_BAR] has the correct position at
+ * the start and end of the transition
+ */
+ @Presubmit
@Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+ override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
- @FlakyTest
+ /**
+ * Checks that all parts of the screen are covered at the start and end of the transition
+ *
+ * TODO b/197726599 Prevents all states from being checked
+ */
+ @Presubmit
@Test
- override fun noUncoveredRegions() {
- super.noUncoveredRegions()
- }
+ override fun entireScreenCovered() = testSpec.entireScreenCovered(allStates = false)
+ /**
+ * Checks [pipApp] window remains visible and on top throughout the transition
+ */
@Presubmit
@Test
fun pipAppWindowIsAlwaysOnTop() {
testSpec.assertWm {
- showsAppWindowOnTop(pipApp.defaultWindowName)
+ isAppWindowOnTop(pipApp.component)
}
}
+ /**
+ * Checks that [testApp] window is not visible at the start
+ */
@Presubmit
@Test
- fun pipAppHidesTestApp() {
+ fun testAppWindowInvisibleOnStart() {
testSpec.assertWmStart {
- isInvisible(testApp.defaultWindowName)
+ isAppWindowInvisible(testApp.component)
}
}
+ /**
+ * Checks that [testApp] window is visible at the end
+ */
@Presubmit
@Test
- fun testAppWindowIsVisible() {
+ fun testAppWindowVisibleOnEnd() {
testSpec.assertWmEnd {
- isVisible(testApp.defaultWindowName)
+ isAppWindowVisible(testApp.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp] layer is not visible at the start
+ */
+ @Presubmit
+ @Test
+ fun testAppLayerInvisibleOnStart() {
+ testSpec.assertLayersStart {
+ isInvisible(testApp.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp] layer is visible at the end
+ */
+ @Presubmit
+ @Test
+ fun testAppLayerVisibleOnEnd() {
+ testSpec.assertLayersEnd {
+ isVisible(testApp.component)
}
}
+ /**
+ * Checks that the visible region of [pipApp] covers the full display area at the start of
+ * the transition
+ */
@Presubmit
@Test
- fun pipAppLayerHidesTestApp() {
+ fun pipAppLayerCoversFullScreenOnStart() {
testSpec.assertLayersStart {
- visibleRegion(pipApp.defaultWindowName).coversExactly(startingBounds)
- isInvisible(testApp.defaultWindowName)
+ visibleRegion(pipApp.component).coversExactly(startingBounds)
}
}
+ /**
+ * Checks that the visible region of [testApp] plus the visible region of [pipApp]
+ * cover the full display area at the end of the transition
+ */
@Presubmit
@Test
- fun testAppLayerCoversFullScreen() {
+ fun testAppPlusPipLayerCoversFullScreenOnEnd() {
testSpec.assertLayersEnd {
- visibleRegion(testApp.defaultWindowName).coversExactly(endingBounds)
+ val pipRegion = visibleRegion(pipApp.component).region
+ visibleRegion(testApp.component)
+ .plus(pipRegion)
+ .coversExactly(endingBounds)
}
}
companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
new file mode 100644
index 000000000000..64b7eb53bd6f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Presubmit
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import org.junit.Test
+
+/**
+ * Base class for pip expand tests
+ */
+abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+ protected val testApp = FixedAppHelper(instrumentation)
+
+ /**
+ * Checks that the pip app window remains inside the display bounds throughout the whole
+ * animation
+ */
+ @Presubmit
+ @Test
+ open fun pipAppWindowRemainInsideVisibleBounds() {
+ testSpec.assertWm {
+ coversAtMost(displayBounds, pipApp.component)
+ }
+ }
+
+ /**
+ * Checks that the pip app layer remains inside the display bounds throughout the whole
+ * animation
+ */
+ @Presubmit
+ @Test
+ open fun pipAppLayerRemainInsideVisibleBounds() {
+ testSpec.assertLayers {
+ coversAtMost(displayBounds, pipApp.component)
+ }
+ }
+
+ /**
+ * Checks both app windows are visible at the start of the transition (with [pipApp] on top).
+ * Then, during the transition, [testApp] becomes invisible and [pipApp] remains visible
+ */
+ @Presubmit
+ @Test
+ open fun showBothAppWindowsThenHidePip() {
+ testSpec.assertWm {
+ // when the activity is STOPPING, sometimes it becomes invisible in an entry before
+ // the window, sometimes in the same entry. This occurs because we log 1x per frame
+ // thus we ignore activity here
+ isAppWindowVisible(testApp.component)
+ .isAppWindowOnTop(pipApp.component)
+ .then()
+ .isAppWindowInvisible(testApp.component)
+ .isAppWindowVisible(pipApp.component)
+ }
+ }
+
+ /**
+ * Checks both app layers are visible at the start of the transition. Then, during the
+ * transition, [testApp] becomes invisible and [pipApp] remains visible
+ */
+ @Presubmit
+ @Test
+ open fun showBothAppLayersThenHidePip() {
+ testSpec.assertLayers {
+ isVisible(testApp.component)
+ .isVisible(pipApp.component)
+ .then()
+ .isInvisible(testApp.component)
+ .isVisible(pipApp.component)
+ }
+ }
+
+ /**
+ * Checks that the visible region of [testApp] plus the visible region of [pipApp]
+ * cover the full display area at the start of the transition
+ */
+ @Presubmit
+ @Test
+ open fun testPlusPipAppsCoverFullScreenAtStart() {
+ testSpec.assertLayersStart {
+ val pipRegion = visibleRegion(pipApp.component).region
+ visibleRegion(testApp.component)
+ .plus(pipRegion)
+ .coversExactly(displayBounds)
+ }
+ }
+
+ /**
+ * Checks that the visible region of [pipApp] covers the full display area at the end of
+ * the transition
+ */
+ @Presubmit
+ @Test
+ open fun pipAppCoversFullScreenAtEnd() {
+ testSpec.assertLayersEnd {
+ visibleRegion(pipApp.component).coversExactly(displayBounds)
+ }
+ }
+
+ /**
+ * Checks that the visible region of [pipApp] always expands during the animation
+ */
+ @Presubmit
+ @Test
+ open fun pipLayerExpands() {
+ val layerName = pipApp.component.toLayerName()
+ testSpec.assertLayers {
+ val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ current.visibleRegion.coversAtLeast(previous.visibleRegion.region)
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
index eae7e973711c..5207fed59208 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
@@ -20,15 +20,16 @@ import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.focusChanges
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.startRotation
import org.junit.Test
-import org.junit.runners.Parameterized
-abstract class PipCloseTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+/**
+ * Base class for exiting pip (closing pip window) without returning to the app
+ */
+abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = buildTransition(eachRun = true) { configuration ->
setup {
@@ -43,37 +44,49 @@ abstract class PipCloseTransition(testSpec: FlickerTestParameter) : PipTransitio
}
}
+ /**
+ * Checks that [pipApp] window is pinned and visible at the start and then becomes
+ * unpinned and invisible at the same moment, and remains unpinned and invisible
+ * until the end of the transition
+ */
@Presubmit
@Test
open fun pipWindowBecomesInvisible() {
testSpec.assertWm {
- this.showsAppWindow(PIP_WINDOW_TITLE)
- .then()
- .hidesAppWindow(PIP_WINDOW_TITLE)
+ this.invoke("hasPipWindow") {
+ it.isPinned(pipApp.component).isAppWindowVisible(pipApp.component)
+ }.then().invoke("!hasPipWindow") {
+ it.isNotPinned(pipApp.component).isAppWindowInvisible(pipApp.component)
+ }
}
}
+ /**
+ * Checks that [pipApp] and [LAUNCHER_COMPONENT] layers are visible at the start
+ * of the transition. Then [pipApp] layer becomes invisible, and remains invisible
+ * until the end of the transition
+ */
@Presubmit
@Test
open fun pipLayerBecomesInvisible() {
testSpec.assertLayers {
- this.isVisible(PIP_WINDOW_TITLE)
+ this.isVisible(pipApp.component)
+ .isVisible(LAUNCHER_COMPONENT)
.then()
- .isInvisible(PIP_WINDOW_TITLE)
+ .isInvisible(pipApp.component)
+ .isVisible(LAUNCHER_COMPONENT)
}
}
+ /**
+ * Checks that the focus changes between the [pipApp] window and the launcher when
+ * closing the pip window
+ */
@FlakyTest(bugId = 151179149)
@Test
- open fun focusChanges() = testSpec.focusChanges(pipApp.launcherName, "NexusLauncherActivity")
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 5)
+ open fun focusChanges() {
+ testSpec.assertEventLog {
+ this.focusChanges(pipApp.launcherName, "NexusLauncherActivity")
}
}
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
new file mode 100644
index 000000000000..b53342d6f2f7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.wm.shell.flicker.pip
+
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group3
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test expanding a pip window back to full screen via the expand button
+ *
+ * To run this test: `atest WMShellFlickerTests:ExitPipViaExpandButtonClickTest`
+ *
+ * Actions:
+ * Launch an app in pip mode [pipApp],
+ * Launch another full screen mode [testApp]
+ * Expand [pipApp] app to full screen by clicking on the pip window and
+ * then on the expand button
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group3
+class ExitPipViaExpandButtonClickTest(
+ testSpec: FlickerTestParameter
+) : ExitPipToAppTransition(testSpec) {
+
+ /**
+ * Defines the transition used to run the test
+ */
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = buildTransition(eachRun = true) {
+ setup {
+ eachRun {
+ // launch an app behind the pip one
+ testApp.launchViaIntent(wmHelper)
+ }
+ }
+ transitions {
+ // This will bring PipApp to fullscreen
+ pipApp.expandPipWindowToApp(wmHelper)
+ // Wait until the other app is no longer visible
+ wmHelper.waitForSurfaceAppeared(testApp.component.toWindowName())
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 00e50e7fe3b5..1fec3cf85214 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -24,88 +23,62 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test Pip launch and exit.
- * To run this test: `atest WMShellFlickerTests:EnterExitPipTest`
+ * Test expanding a pip window back to full screen via an intent
+ *
+ * To run this test: `atest WMShellFlickerTests:ExitPipViaIntentTest`
+ *
+ * Actions:
+ * Launch an app in pip mode [pipApp],
+ * Launch another full screen mode [testApp]
+ * Expand [pipApp] app to full screen via an intent
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited from [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
-class EnterExitPipTest(
- testSpec: FlickerTestParameter
-) : PipTransition(testSpec) {
- private val testApp = FixedAppHelper(instrumentation)
+class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransition(testSpec) {
+ /**
+ * Defines the transition used to run the test
+ */
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = buildTransition(eachRun = true) {
setup {
eachRun {
+ // launch an app behind the pip one
testApp.launchViaIntent(wmHelper)
}
}
transitions {
// This will bring PipApp to fullscreen
pipApp.launchViaIntent(wmHelper)
+ // Wait until the other app is no longer visible
+ wmHelper.waitForSurfaceAppeared(testApp.component.toWindowName())
}
}
- @Presubmit
- @Test
- fun pipAppRemainInsideVisibleBounds() {
- testSpec.assertWm {
- coversAtMost(displayBounds, pipApp.defaultWindowName)
- }
- }
-
- @Presubmit
- @Test
- fun showBothAppWindowsThenHidePip() {
- testSpec.assertWm {
- showsAppWindow(testApp.defaultWindowName)
- .showsAppWindowOnTop(pipApp.defaultWindowName)
- .then()
- .hidesAppWindow(testApp.defaultWindowName)
- }
- }
-
- @Presubmit
- @Test
- fun showBothAppLayersThenHidePip() {
- testSpec.assertLayers {
- isVisible(testApp.defaultWindowName)
- .isVisible(pipApp.defaultWindowName)
- .then()
- .isInvisible(testApp.defaultWindowName)
- }
- }
-
- @Presubmit
- @Test
- fun testAppCoversFullScreenWithPipOnDisplay() {
- testSpec.assertLayersStart {
- visibleRegion(testApp.defaultWindowName).coversExactly(displayBounds)
- visibleRegion(pipApp.defaultWindowName).coversAtMost(displayBounds)
- }
- }
-
- @Presubmit
- @Test
- fun pipAppCoversFullScreen() {
- testSpec.assertLayersEnd {
- visibleRegion(pipApp.defaultWindowName).coversExactly(displayBounds)
- }
- }
-
companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<FlickerTestParameter> {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
new file mode 100644
index 000000000000..73626c23065a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.wm.shell.flicker.pip
+
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group3
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test closing a pip window via the dismiss button
+ *
+ * To run this test: `atest WMShellFlickerTests:ExitPipWithDismissButtonTest`
+ *
+ * Actions:
+ * Launch an app in pip mode [pipApp],
+ * Click on the pip window
+ * Click on dismiss button and wait window disappear
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group3
+class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = {
+ super.transition(this, it)
+ transitions {
+ pipApp.closePipWindow(wmHelper)
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
+ repetitions = 5)
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 524a1b404591..9e43deef8d99 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -22,9 +22,9 @@ import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import org.junit.FixMethodOrder
import org.junit.Test
@@ -33,42 +33,58 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test Pip launch.
- * To run this test: `atest WMShellFlickerTests:PipCloseWithSwipe`
+ * Test closing a pip window by swiping it to the bottom-center of the screen
+ *
+ * To run this test: `atest WMShellFlickerTests:ExitPipWithSwipeDownTest`
+ *
+ * Actions:
+ * Launch an app in pip mode [pipApp],
+ * Swipe the pip window to the bottom-center of the screen and wait it disappear
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
-class PipCloseWithSwipeTest(testSpec: FlickerTestParameter) : PipCloseTransition(testSpec) {
+class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = {
- super.transition(this, it)
+ get() = { args ->
+ super.transition(this, args)
transitions {
val pipRegion = wmHelper.getWindowRegion(pipApp.component).bounds
val pipCenterX = pipRegion.centerX()
val pipCenterY = pipRegion.centerY()
val displayCenterX = device.displayWidth / 2
- device.swipe(pipCenterX, pipCenterY, displayCenterX, device.displayHeight, 5)
+ device.swipe(pipCenterX, pipCenterY, displayCenterX, device.displayHeight, 10)
+ wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() }
+ wmHelper.waitForWindowSurfaceDisappeared(pipApp.component)
+ wmHelper.waitForAppTransitionIdle()
}
}
@Presubmit
@Test
- override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible()
+ override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
@Presubmit
@Test
- override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible()
+ override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
@Presubmit
@Test
- override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
@Presubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
@FlakyTest
@Test
@@ -80,14 +96,29 @@ class PipCloseWithSwipeTest(testSpec: FlickerTestParameter) : PipCloseTransition
@Presubmit
@Test
- override fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
+ override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
- override fun noUncoveredRegions() = super.noUncoveredRegions()
+ override fun entireScreenCovered() = super.entireScreenCovered()
@Presubmit
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
+ repetitions = 20)
+ }
+ }
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
new file mode 100644
index 000000000000..d0fee9a82093
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
+import com.android.server.wm.flicker.annotation.Group3
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test expanding a pip window by double clicking it
+ *
+ * To run this test: `atest WMShellFlickerTests:ExpandPipOnDoubleClickTest`
+ *
+ * Actions:
+ * Launch an app in pip mode [pipApp],
+ * Expand [pipApp] app to its maximum pip size by double clicking on it
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group3
+class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = buildTransition(eachRun = true) {
+ transitions {
+ pipApp.doubleClickPipWindow(wmHelper)
+ }
+ }
+
+ /**
+ * Checks that the pip app window remains inside the display bounds throughout the whole
+ * animation
+ */
+ @Presubmit
+ @Test
+ fun pipWindowRemainInsideVisibleBounds() {
+ testSpec.assertWm {
+ coversAtMost(displayBounds, pipApp.component)
+ }
+ }
+
+ /**
+ * Checks that the pip app layer remains inside the display bounds throughout the whole
+ * animation
+ */
+ @Presubmit
+ @Test
+ fun pipLayerRemainInsideVisibleBounds() {
+ testSpec.assertLayers {
+ coversAtMost(displayBounds, pipApp.component)
+ }
+ }
+
+ /**
+ * Checks [pipApp] window remains visible throughout the animation
+ */
+ @Presubmit
+ @Test
+ fun pipWindowIsAlwaysVisible() {
+ testSpec.assertWm {
+ isAppWindowVisible(pipApp.component)
+ }
+ }
+
+ /**
+ * Checks [pipApp] layer remains visible throughout the animation
+ */
+ @Presubmit
+ @Test
+ fun pipLayerIsAlwaysVisible() {
+ testSpec.assertLayers {
+ isVisible(pipApp.component)
+ }
+ }
+
+ /**
+ * Checks that the visible region of [pipApp] always expands during the animation
+ */
+ @Presubmit
+ @Test
+ fun pipLayerExpands() {
+ val layerName = pipApp.component.toLayerName()
+ testSpec.assertLayers {
+ val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ current.visibleRegion.coversAtLeast(previous.visibleRegion.region)
+ }
+ }
+ }
+
+ /**
+ * Checks [pipApp] window remains pinned throughout the animation
+ */
+ @Presubmit
+ @Test
+ fun windowIsAlwaysPinned() {
+ testSpec.assertWm {
+ this.invoke("hasPipWindow") { it.isPinned(pipApp.component) }
+ }
+ }
+
+ /**
+ * Checks [pipApp] layer remains visible throughout the animation
+ */
+ @Presubmit
+ @Test
+ fun launcherIsAlwaysVisible() {
+ testSpec.assertLayers {
+ isVisible(LAUNCHER_COMPONENT)
+ }
+ }
+
+ /**
+ * Checks that the focus doesn't change between windows during the transition
+ */
+ @FlakyTest
+ @Test
+ fun focusDoesNotChange() {
+ testSpec.assertEventLog {
+ this.focusDoesNotChange()
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
+ repetitions = 5)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
new file mode 100644
index 000000000000..0ab857d755ee
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.wm.shell.flicker.pip
+
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group3
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.traces.RegionSubject
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip movement with Launcher shelf height change (decrease).
+ *
+ * To run this test: `atest WMShellFlickerTests:MovePipDownShelfHeightChangeTest`
+ *
+ * Actions:
+ * Launch [pipApp] in pip mode
+ * Launch [testApp]
+ * Press home
+ * Check if pip window moves down (visually)
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group3
+class MovePipDownShelfHeightChangeTest(
+ testSpec: FlickerTestParameter
+) : MovePipShelfHeightTransition(testSpec) {
+ /**
+ * Defines the transition used to run the test
+ */
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = buildTransition(eachRun = false) {
+ teardown {
+ eachRun {
+ testApp.launchViaIntent(wmHelper)
+ }
+ test {
+ testApp.exit(wmHelper)
+ }
+ }
+ transitions {
+ taplInstrumentation.pressHome()
+ }
+ }
+
+ override fun assertRegionMovement(previous: RegionSubject, current: RegionSubject) {
+ current.isHigherOrEqual(previous.region)
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
new file mode 100644
index 000000000000..6e0324c17272
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Presubmit
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.traces.RegionSubject
+import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import org.junit.Test
+
+/**
+ * Base class for pip tests with Launcher shelf height change
+ */
+abstract class MovePipShelfHeightTransition(
+ testSpec: FlickerTestParameter
+) : PipTransition(testSpec) {
+ protected val taplInstrumentation = LauncherInstrumentation()
+ protected val testApp = FixedAppHelper(instrumentation)
+
+ /**
+ * Checks if the window movement direction is valid
+ */
+ protected abstract fun assertRegionMovement(previous: RegionSubject, current: RegionSubject)
+
+ /**
+ * Checks [pipApp] window remains visible throughout the animation
+ */
+ @Presubmit
+ @Test
+ open fun pipWindowIsAlwaysVisible() {
+ testSpec.assertWm {
+ isAppWindowVisible(pipApp.component)
+ }
+ }
+
+ /**
+ * Checks [pipApp] layer remains visible throughout the animation
+ */
+ @Presubmit
+ @Test
+ open fun pipLayerIsAlwaysVisible() {
+ testSpec.assertLayers {
+ isVisible(pipApp.component)
+ }
+ }
+
+ /**
+ * Checks that the pip app window remains inside the display bounds throughout the whole
+ * animation
+ */
+ @Presubmit
+ @Test
+ open fun pipWindowRemainInsideVisibleBounds() {
+ testSpec.assertWm {
+ coversAtMost(displayBounds, pipApp.component)
+ }
+ }
+
+ /**
+ * Checks that the pip app layer remains inside the display bounds throughout the whole
+ * animation
+ */
+ @Presubmit
+ @Test
+ open fun pipLayerRemainInsideVisibleBounds() {
+ testSpec.assertLayers {
+ coversAtMost(displayBounds, pipApp.component)
+ }
+ }
+
+ /**
+ * Checks that the visible region of [pipApp] always moves in the correct direction
+ * during the animation.
+ */
+ @Presubmit
+ @Test
+ open fun pipWindowMoves() {
+ val windowName = pipApp.component.toWindowName()
+ testSpec.assertWm {
+ val pipWindowList = this.windowStates { it.name.contains(windowName) && it.isVisible }
+ pipWindowList.zipWithNext { previous, current ->
+ assertRegionMovement(previous.frame, current.frame)
+ }
+ }
+ }
+
+ /**
+ * Checks that the visible region of [pipApp] always moves up during the animation
+ */
+ @Presubmit
+ @Test
+ open fun pipLayerMoves() {
+ val layerName = pipApp.component.toLayerName()
+ testSpec.assertLayers {
+ val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ assertRegionMovement(previous.visibleRegion, current.visibleRegion)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipShelfHeightTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
index 1294ac93f647..e507edfda48c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipShelfHeightTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
@@ -16,36 +16,49 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import com.google.common.truth.Truth
+import com.android.server.wm.flicker.traces.RegionSubject
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test Pip movement with Launcher shelf height change.
- * To run this test: `atest WMShellFlickerTests:PipShelfHeightTest`
+ * Test Pip movement with Launcher shelf height change (increase).
+ *
+ * To run this test: `atest WMShellFlickerTests:MovePipUpShelfHeightChangeTest`
+ *
+ * Actions:
+ * Launch [pipApp] in pip mode
+ * Press home
+ * Launch [testApp]
+ * Check if pip window moves up (visually)
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
-class PipShelfHeightTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
- private val taplInstrumentation = LauncherInstrumentation()
- private val testApp = FixedAppHelper(instrumentation)
-
+class MovePipUpShelfHeightChangeTest(
+ testSpec: FlickerTestParameter
+) : MovePipShelfHeightTransition(testSpec) {
+ /**
+ * Defines the transition used to run the test
+ */
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = buildTransition(eachRun = false) {
teardown {
@@ -61,33 +74,17 @@ class PipShelfHeightTest(testSpec: FlickerTestParameter) : PipTransition(testSpe
}
}
- @Presubmit
- @Test
- fun pipAlwaysVisible() = testSpec.assertWm { this.showsAppWindow(pipApp.windowName) }
-
- @Presubmit
- @Test
- fun pipLayerInsideDisplay() {
- testSpec.assertLayersStart {
- visibleRegion(pipApp.defaultWindowName).coversAtMost(displayBounds)
- }
- }
-
- @Presubmit
- @Test
- fun pipWindowMovesUp() = testSpec.assertWmEnd {
- val initialState = this.trace?.first()?.wmState
- ?: error("Trace should not be empty")
- val startPos = initialState.pinnedWindows.first().frame
- val currPos = this.wmState.pinnedWindows.first().frame
- val subject = Truth.assertWithMessage("Pip should have moved up")
- subject.that(currPos.top).isGreaterThan(startPos.top)
- subject.that(currPos.bottom).isGreaterThan(startPos.bottom)
- subject.that(currPos.left).isEqualTo(startPos.left)
- subject.that(currPos.right).isEqualTo(startPos.right)
+ override fun assertRegionMovement(previous: RegionSubject, current: RegionSubject) {
+ current.isLowerOrEqual(previous.region)
}
companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<FlickerTestParameter> {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index d88f94d5954a..aba8aced298f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -22,12 +22,12 @@ import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group3
+import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.startRotation
-import com.android.wm.shell.flicker.IME_WINDOW_NAME
+import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.helpers.ImeAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -43,7 +43,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
+@Group4
class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
private val imeApp = ImeAppHelper(instrumentation)
@@ -79,7 +79,7 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec)
fun pipInVisibleBounds() {
testSpec.assertWm {
val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
- coversAtMost(displayBounds, pipApp.defaultWindowName)
+ coversAtMost(displayBounds, pipApp.component)
}
}
@@ -90,7 +90,7 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec)
@Test
fun pipIsAboveAppWindow() {
testSpec.assertWmTag(TAG_IME_VISIBLE) {
- isAboveWindow(IME_WINDOW_NAME, pipApp.defaultWindowName)
+ isAboveWindow(FlickerComponentName.IME, pipApp.component)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
index 6833b96a802b..9bea5c03dadb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
@@ -23,15 +23,20 @@ import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group3
+import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.wm.shell.flicker.helpers.ImeAppHelper
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
+import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled
+import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import com.android.wm.shell.flicker.helpers.ImeAppHelper
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
+import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -46,12 +51,19 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 161435597)
-@Group3
+@Group4
class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
private val imeApp = ImeAppHelper(instrumentation)
private val testApp = FixedAppHelper(instrumentation)
+ @Before
+ open fun setup() {
+ // Only run legacy split tests when the system is using legacy split screen.
+ assumeTrue(SplitScreenHelper.isUsingLegacySplit())
+ // Legacy split is having some issue with Shell transition, and will be deprecated soon.
+ assumeFalse(isShellTransitionsEnabled())
+ }
+
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = {
withTestName { testSpec.name }
@@ -80,11 +92,11 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t
}
}
- @Presubmit
+ @FlakyTest(bugId = 161435597)
@Test
fun pipWindowInsideDisplayBounds() {
testSpec.assertWm {
- coversAtMost(displayBounds, pipApp.defaultWindowName)
+ coversAtMost(displayBounds, pipApp.component)
}
}
@@ -92,25 +104,17 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t
@Test
fun bothAppWindowsVisible() {
testSpec.assertWmEnd {
- isVisible(testApp.defaultWindowName)
- isVisible(imeApp.defaultWindowName)
- noWindowsOverlap(testApp.defaultWindowName, imeApp.defaultWindowName)
+ isAppWindowVisible(testApp.component)
+ isAppWindowVisible(imeApp.component)
+ doNotOverlap(testApp.component, imeApp.component)
}
}
- @Presubmit
- @Test
- override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
-
- @Presubmit
- @Test
- override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
-
- @Presubmit
+ @FlakyTest(bugId = 161435597)
@Test
fun pipLayerInsideDisplayBounds() {
testSpec.assertLayers {
- coversAtMost(displayBounds, pipApp.defaultWindowName)
+ coversAtMost(displayBounds, pipApp.component)
}
}
@@ -118,18 +122,14 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t
@Test
fun bothAppLayersVisible() {
testSpec.assertLayersEnd {
- visibleRegion(testApp.defaultWindowName).coversAtMost(displayBounds)
- visibleRegion(imeApp.defaultWindowName).coversAtMost(displayBounds)
+ visibleRegion(testApp.component).coversAtMost(displayBounds)
+ visibleRegion(imeApp.component).coversAtMost(displayBounds)
}
}
- @Presubmit
- @Test
- override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible()
-
- @Presubmit
+ @FlakyTest(bugId = 161435597)
@Test
- override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible()
+ override fun entireScreenCovered() = super.entireScreenCovered()
companion object {
const val TEST_REPETITIONS = 2
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index d531af28e2ad..669f37ad1e72 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -23,13 +23,13 @@ import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group3
+import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.noUncoveredRegions
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.wm.shell.flicker.helpers.FixedAppHelper
@@ -41,17 +41,32 @@ import org.junit.runners.Parameterized
/**
* Test Pip Stack in bounds after rotations.
+ *
* To run this test: `atest WMShellFlickerTests:PipRotationTest`
+ *
+ * Actions:
+ * Launch a [pipApp] in pip mode
+ * Launch another app [fixedApp] (appears below pip)
+ * Rotate the screen from [testSpec.config.startRotation] to [testSpec.config.endRotation]
+ * (usually, 0->90 and 90->0)
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited from [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
+@Group4
class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
private val fixedApp = FixedAppHelper(instrumentation)
- private val startingBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
- private val endingBounds = WindowUtils.getDisplayBounds(testSpec.config.endRotation)
+ private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
+ private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.config.endRotation)
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = buildTransition(eachRun = false) { configuration ->
@@ -66,49 +81,104 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec)
transitions {
setRotation(configuration.endRotation)
}
- teardown {
- eachRun {
- setRotation(Surface.ROTATION_0)
- }
- }
}
- @FlakyTest(bugId = 185400889)
+ /**
+ * Checks that all parts of the screen are covered at the start and end of the transition
+ */
+ @Presubmit
@Test
- override fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation,
- testSpec.config.endRotation, allStates = false)
+ override fun entireScreenCovered() = testSpec.entireScreenCovered()
+ /**
+ * Checks the position of the navigation bar at the start and end of the transition
+ */
@FlakyTest
@Test
- override fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation,
- testSpec.config.endRotation)
+ override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
+ /**
+ * Checks the position of the status bar at the start and end of the transition
+ */
@Presubmit
@Test
- override fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation,
- testSpec.config.endRotation)
+ override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
- @FlakyTest(bugId = 185400889)
+ /**
+ * Checks that [fixedApp] layer is within [screenBoundsStart] at the start of the transition
+ */
+ @Presubmit
@Test
fun appLayerRotates_StartingBounds() {
testSpec.assertLayersStart {
- visibleRegion(fixedApp.defaultWindowName).coversExactly(startingBounds)
- visibleRegion(pipApp.defaultWindowName).coversAtMost(startingBounds)
+ visibleRegion(fixedApp.component).coversExactly(screenBoundsStart)
}
}
- @FlakyTest(bugId = 185400889)
+ /**
+ * Checks that [fixedApp] layer is within [screenBoundsEnd] at the end of the transition
+ */
+ @Presubmit
@Test
fun appLayerRotates_EndingBounds() {
testSpec.assertLayersEnd {
- visibleRegion(fixedApp.defaultWindowName).coversExactly(endingBounds)
- visibleRegion(pipApp.defaultWindowName).coversAtMost(endingBounds)
+ visibleRegion(fixedApp.component).coversExactly(screenBoundsEnd)
+ }
+ }
+
+ /**
+ * Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition
+ */
+ @Presubmit
+ @Test
+ fun pipLayerRotates_StartingBounds() {
+ testSpec.assertLayersStart {
+ visibleRegion(pipApp.component).coversAtMost(screenBoundsStart)
+ }
+ }
+
+ /**
+ * Checks that [pipApp] layer is within [screenBoundsEnd] at the end of the transition
+ */
+ @Presubmit
+ @Test
+ fun pipLayerRotates_EndingBounds() {
+ testSpec.assertLayersEnd {
+ visibleRegion(pipApp.component).coversAtMost(screenBoundsEnd)
+ }
+ }
+
+ /**
+ * Ensure that the [pipApp] window does not obscure the [fixedApp] at the start of the
+ * transition
+ */
+ @Presubmit
+ @Test
+ fun pipIsAboveFixedAppWindow_Start() {
+ testSpec.assertWmStart {
+ isAboveWindow(pipApp.component, fixedApp.component)
+ }
+ }
+
+ /**
+ * Ensure that the [pipApp] window does not obscure the [fixedApp] at the end of the
+ * transition
+ */
+ @Presubmit
+ @Test
+ fun pipIsAboveFixedAppWindow_End() {
+ testSpec.assertWmEnd {
+ isAboveWindow(pipApp.component, fixedApp.component)
}
}
companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
deleted file mode 100644
index 55e5c4128967..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
+++ /dev/null
@@ -1,105 +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.wm.shell.flicker.pip
-
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group3
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.focusChanges
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.startRotation
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test Pip launch.
- * To run this test: `atest WMShellFlickerTests:PipToAppTest`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
-class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
- override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
- get() = buildTransition(eachRun = true) { configuration ->
- setup {
- eachRun {
- this.setRotation(configuration.startRotation)
- }
- }
- teardown {
- eachRun {
- this.setRotation(Surface.ROTATION_0)
- }
- }
- transitions {
- pipApp.expandPipWindowToApp(wmHelper)
- }
- }
-
- @FlakyTest
- @Test
- fun appReplacesPipWindow() {
- testSpec.assertWm {
- this.showsAppWindow(PIP_WINDOW_TITLE)
- .then()
- .showsAppWindowOnTop(pipApp.launcherName)
- }
- }
-
- @FlakyTest
- @Test
- fun appReplacesPipLayer() {
- testSpec.assertLayers {
- this.isVisible(PIP_WINDOW_TITLE)
- .then()
- .isVisible(pipApp.launcherName)
- }
- }
-
- @FlakyTest
- @Test
- fun testAppCoversFullScreen() {
- testSpec.assertLayersStart {
- visibleRegion(pipApp.defaultWindowName).coversExactly(displayBounds)
- }
- }
-
- @FlakyTest(bugId = 151179149)
- @Test
- fun focusChanges() = testSpec.focusChanges("NexusLauncherActivity",
- pipApp.launcherName, "NexusLauncherActivity")
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 5)
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index b4c75a6d1165..e8a61e8a1dae 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -20,25 +20,24 @@ import android.app.Instrumentation
import android.content.Intent
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.isRotated
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.wm.shell.flicker.helpers.PipAppHelper
import com.android.wm.shell.flicker.testapp.Components
import org.junit.Test
@@ -162,32 +161,29 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) {
@Presubmit
@Test
- open fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ open fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
- open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ open fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
- @FlakyTest
+ @Presubmit
@Test
- open fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
+ open fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
- @FlakyTest
+ @Presubmit
@Test
- open fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
+ open fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
@Presubmit
@Test
- open fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
+ open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- open fun statusBarLayerRotatesScales() =
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
+ open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
- open fun noUncoveredRegions() =
- testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0)
+ open fun entireScreenCovered() = testSpec.entireScreenCovered()
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 1f58bb2bf9db..d6dbc366aec0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -16,14 +16,13 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group3
+import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
@@ -44,7 +43,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
+@Group4
class SetRequestedOrientationWhilePinnedTest(
testSpec: FlickerTestParameter
) : PipTransition(testSpec) {
@@ -83,55 +82,69 @@ class SetRequestedOrientationWhilePinnedTest(
@FlakyTest
@Test
+ override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
+
+ @FlakyTest
+ @Test
+ override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
+
+ @FlakyTest
+ @Test
+ override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
+
+ @FlakyTest
+ @Test
+ override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
+
+ @FlakyTest
+ @Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
@FlakyTest
@Test
override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
- @Presubmit
+ @FlakyTest
@Test
fun pipWindowInsideDisplay() {
testSpec.assertWmStart {
- frameRegion(pipApp.defaultWindowName).coversAtMost(startingBounds)
+ frameRegion(pipApp.component).coversAtMost(startingBounds)
}
}
- @Presubmit
+ @FlakyTest
@Test
fun pipAppShowsOnTop() {
testSpec.assertWmEnd {
- showsAppWindowOnTop(pipApp.defaultWindowName)
+ isAppWindowOnTop(pipApp.component)
}
}
- @Presubmit
+ @FlakyTest
@Test
fun pipLayerInsideDisplay() {
testSpec.assertLayersStart {
- visibleRegion(pipApp.defaultWindowName).coversAtMost(startingBounds)
+ visibleRegion(pipApp.component).coversAtMost(startingBounds)
}
}
- @Presubmit
+ @FlakyTest
@Test
fun pipAlwaysVisible() = testSpec.assertWm {
- this.showsAppWindow(pipApp.windowName)
+ this.isAppWindowVisible(pipApp.component)
}
- @Presubmit
+ @FlakyTest
@Test
fun pipAppLayerCoversFullScreen() {
testSpec.assertLayersEnd {
- visibleRegion(pipApp.defaultWindowName).coversExactly(endingBounds)
+ visibleRegion(pipApp.component).coversExactly(endingBounds)
}
}
@FlakyTest
@Test
- override fun noUncoveredRegions() {
- super.noUncoveredRegions()
- }
+ override fun entireScreenCovered() = super.entireScreenCovered()
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
index 0110ba3f5b30..061218a015e4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
@@ -37,14 +37,17 @@ class TvPipMenuTests : TvPipTestBase() {
private val systemUiResources =
packageManager.getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
private val pipBoundsWhileInMenu: Rect = systemUiResources.run {
- val bounds = getString(getIdentifier("pip_menu_bounds", "string", SYSTEM_UI_PACKAGE_NAME))
+ val bounds = getString(getIdentifier("pip_menu_bounds", "string",
+ SYSTEM_UI_PACKAGE_NAME))
Rect.unflattenFromString(bounds) ?: error("Could not retrieve PiP menu bounds")
}
private val playButtonDescription = systemUiResources.run {
- getString(getIdentifier("pip_play", "string", SYSTEM_UI_PACKAGE_NAME))
+ getString(getIdentifier("pip_play", "string",
+ SYSTEM_UI_PACKAGE_NAME))
}
private val pauseButtonDescription = systemUiResources.run {
- getString(getIdentifier("pip_pause", "string", SYSTEM_UI_PACKAGE_NAME))
+ getString(getIdentifier("pip_pause", "string",
+ SYSTEM_UI_PACKAGE_NAME))
}
@Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
index 1b73920046dc..1c663409b913 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
@@ -70,7 +70,8 @@ fun UiDevice.waitForTvPipMenuElementWithDescription(desc: String): UiObject2? {
// descendant and then retrieve the element from the menu and return to the caller of this
// method.
val elementSelector = By.desc(desc)
- val menuContainingElementSelector = By.copy(TV_PIP_MENU_SELECTOR).hasDescendant(elementSelector)
+ val menuContainingElementSelector = By.copy(TV_PIP_MENU_SELECTOR)
+ .hasDescendant(elementSelector)
return wait(Until.findObject(menuContainingElementSelector), WAIT_TIME_MS)
?.findObject(elementSelector)
@@ -94,7 +95,8 @@ fun UiDevice.clickTvPipMenuFullscreenButton() {
}
fun UiDevice.clickTvPipMenuElementWithDescription(desc: String) {
- focusOnAndClickTvPipMenuElement(By.desc(desc).pkg(SYSTEM_UI_PACKAGE_NAME)) ||
+ focusOnAndClickTvPipMenuElement(By.desc(desc)
+ .pkg(SYSTEM_UI_PACKAGE_NAME)) ||
error("Could not focus on the Pip menu object with \"$desc\" description")
// So apparently Accessibility framework on TV is not very reliable and sometimes the state of
// the tree of accessibility nodes as seen by the accessibility clients kind of lags behind of
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
index 5549330df766..2cdbffa7589c 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
@@ -107,5 +107,20 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity
+ android:name=".LaunchBubbleActivity"
+ android:label="LaunchBubbleApp"
+ android:exported="true"
+ android:launchMode="singleTop">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <action android:name="android.intent.action.VIEW" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".BubbleActivity"
+ android:label="BubbleApp"
+ android:exported="false"
+ android:resizeableActivity="true" />
</application>
</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png
new file mode 100644
index 000000000000..d424a17b4157
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png
Binary files differ
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml
new file mode 100644
index 000000000000..b43f31da748d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M7.2,14.4m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M14.8,18m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M15.2,8.8m-4.8,0a4.8,4.8 0,1 1,9.6 0a4.8,4.8 0,1 1,-9.6 0"/>
+</vector>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml
new file mode 100644
index 000000000000..0e8c7a0fe64a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,4c-4.97,0 -9,3.58 -9,8c0,1.53 0.49,2.97 1.33,4.18c0.12,0.18 0.2,0.46 0.1,0.66c-0.33,0.68 -0.79,1.52 -1.38,2.39c-0.12,0.17 0.01,0.41 0.21,0.39c0.63,-0.05 1.86,-0.26 3.38,-0.91c0.17,-0.07 0.36,-0.06 0.52,0.03C8.55,19.54 10.21,20 12,20c4.97,0 9,-3.58 9,-8S16.97,4 12,4zM16.94,11.63l-3.29,3.29c-0.13,0.13 -0.34,0.04 -0.34,-0.14v-1.57c0,-0.11 -0.1,-0.21 -0.21,-0.2c-2.19,0.06 -3.65,0.65 -5.14,1.95c-0.15,0.13 -0.38,0 -0.33,-0.19c0.7,-2.57 2.9,-4.57 5.5,-4.75c0.1,-0.01 0.18,-0.09 0.18,-0.19V8.2c0,-0.18 0.22,-0.27 0.34,-0.14l3.29,3.29C17.02,11.43 17.02,11.55 16.94,11.63z"
+ android:fillColor="#000000"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml
new file mode 100644
index 000000000000..f8b0ca3da26e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <Button
+ android:id="@+id/button_finish"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_marginStart="8dp"
+ android:text="Finish" />
+ <Button
+ android:id="@+id/button_new_task"
+ android:layout_width="wrap_content"
+ android:layout_height="46dp"
+ android:layout_marginStart="8dp"
+ android:text="New Task" />
+ <Button
+ android:id="@+id/button_new_bubble"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="8dp"
+ android:text="New Bubble" />
+
+ <Button
+ android:id="@+id/button_activity_for_result"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="8dp"
+ android:layout_marginStart="8dp"
+ android:text="Activity For Result" />
+</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml
new file mode 100644
index 000000000000..f23c46455c63
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@android:color/black">
+
+ <Button
+ android:id="@+id/button_create"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:text="Add Bubble" />
+
+ <Button
+ android:id="@+id/button_cancel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/button_create"
+ android:layout_centerHorizontal="true"
+ android:layout_marginTop="20dp"
+ android:text="Cancel Bubble" />
+
+ <Button
+ android:id="@+id/button_cancel_all"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/button_cancel"
+ android:layout_centerHorizontal="true"
+ android:layout_marginTop="20dp"
+ android:text="Cancel All Bubble" />
+</RelativeLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java
new file mode 100644
index 000000000000..bc3bc75ab903
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java
@@ -0,0 +1,77 @@
+/*
+ * 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.wm.shell.flicker.testapp;
+
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Toast;
+
+public class BubbleActivity extends Activity {
+ private int mNotifId = 0;
+
+ public BubbleActivity() {
+ super();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ if (intent != null) {
+ mNotifId = intent.getIntExtra(BubbleHelper.EXTRA_BUBBLE_NOTIF_ID, -1);
+ } else {
+ mNotifId = -1;
+ }
+
+ setContentView(R.layout.activity_bubble);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ String result = resultCode == Activity.RESULT_OK ? "OK" : "CANCELLED";
+ Toast.makeText(this, "Activity result: " + result, Toast.LENGTH_SHORT).show();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
new file mode 100644
index 000000000000..d743dffd3c9e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
@@ -0,0 +1,178 @@
+/*
+ * 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.wm.shell.flicker.testapp;
+
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.drawable.Icon;
+import android.os.SystemClock;
+import android.service.notification.StatusBarNotification;
+import android.view.WindowManager;
+
+import java.util.HashMap;
+
+public class BubbleHelper {
+
+ static final String EXTRA_BUBBLE_NOTIF_ID = "EXTRA_BUBBLE_NOTIF_ID";
+ static final String CHANNEL_ID = "bubbles";
+ static final String CHANNEL_NAME = "Bubbles";
+ static final int DEFAULT_HEIGHT_DP = 300;
+
+ private static BubbleHelper sInstance;
+
+ private final Context mContext;
+ private NotificationManager mNotificationManager;
+ private float mDisplayHeight;
+
+ private HashMap<Integer, BubbleInfo> mBubbleMap = new HashMap<>();
+
+ private int mNextNotifyId = 0;
+ private int mColourIndex = 0;
+
+ public static class BubbleInfo {
+ public int id;
+ public int height;
+ public Icon icon;
+
+ public BubbleInfo(int id, int height, Icon icon) {
+ this.id = id;
+ this.height = height;
+ this.icon = icon;
+ }
+ }
+
+ public static BubbleHelper getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new BubbleHelper(context);
+ }
+ return sInstance;
+ }
+
+ private BubbleHelper(Context context) {
+ mContext = context;
+ mNotificationManager = context.getSystemService(NotificationManager.class);
+
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setDescription("Channel that posts bubbles");
+ channel.setAllowBubbles(true);
+ mNotificationManager.createNotificationChannel(channel);
+
+ Point p = new Point();
+ WindowManager wm = context.getSystemService(WindowManager.class);
+ wm.getDefaultDisplay().getRealSize(p);
+ mDisplayHeight = p.y;
+
+ }
+
+ private int getNextNotifyId() {
+ int id = mNextNotifyId;
+ mNextNotifyId++;
+ return id;
+ }
+
+ private Icon getIcon() {
+ return Icon.createWithResource(mContext, R.drawable.bg);
+ }
+
+ public int addNewBubble(boolean autoExpand, boolean suppressNotif) {
+ int id = getNextNotifyId();
+ BubbleInfo info = new BubbleInfo(id, DEFAULT_HEIGHT_DP, getIcon());
+ mBubbleMap.put(info.id, info);
+
+ Notification.BubbleMetadata data = getBubbleBuilder(info)
+ .setSuppressNotification(suppressNotif)
+ .setAutoExpandBubble(false)
+ .build();
+ Notification notification = getNotificationBuilder(info.id)
+ .setBubbleMetadata(data).build();
+
+ mNotificationManager.notify(info.id, notification);
+ return info.id;
+ }
+
+ private Notification.Builder getNotificationBuilder(int id) {
+ Person chatBot = new Person.Builder()
+ .setBot(true)
+ .setName("BubbleBot")
+ .setImportant(true)
+ .build();
+
+ RemoteInput remoteInput = new RemoteInput.Builder("key")
+ .setLabel("Reply")
+ .build();
+
+ String shortcutId = "BubbleChat";
+ return new Notification.Builder(mContext, CHANNEL_ID)
+ .setChannelId(CHANNEL_ID)
+ .setShortcutId(shortcutId)
+ .setContentIntent(PendingIntent.getActivity(mContext, 0,
+ new Intent(mContext, LaunchBubbleActivity.class),
+ PendingIntent.FLAG_UPDATE_CURRENT))
+ .setStyle(new Notification.MessagingStyle(chatBot)
+ .setConversationTitle("Bubble Chat")
+ .addMessage("Hello? This is bubble: " + id,
+ SystemClock.currentThreadTimeMillis() - 300000, chatBot)
+ .addMessage("Is it me, " + id + ", you're looking for?",
+ SystemClock.currentThreadTimeMillis(), chatBot)
+ )
+ .setSmallIcon(R.drawable.ic_bubble);
+ }
+
+ private Notification.BubbleMetadata.Builder getBubbleBuilder(BubbleInfo info) {
+ Intent target = new Intent(mContext, BubbleActivity.class);
+ target.putExtra(EXTRA_BUBBLE_NOTIF_ID, info.id);
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, info.id, target,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ return new Notification.BubbleMetadata.Builder()
+ .setIntent(bubbleIntent)
+ .setIcon(info.icon)
+ .setDesiredHeight(info.height);
+ }
+
+ public void cancel(int id) {
+ mNotificationManager.cancel(id);
+ }
+
+ public void cancelAll() {
+ mNotificationManager.cancelAll();
+ }
+
+ public void cancelLast() {
+ StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications();
+ if (activeNotifications.length > 0) {
+ mNotificationManager.cancel(
+ activeNotifications[activeNotifications.length - 1].getId());
+ }
+ }
+
+ public void cancelFirst() {
+ StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications();
+ if (activeNotifications.length > 0) {
+ mNotificationManager.cancel(activeNotifications[0].getId());
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
index 0ead91bb37de..0ed59bdafd1d 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
@@ -87,4 +87,16 @@ public class Components {
public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
PACKAGE_NAME + ".SplitScreenSecondaryActivity");
}
+
+ public static class LaunchBubbleActivity {
+ public static final String LABEL = "LaunchBubbleApp";
+ public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
+ PACKAGE_NAME + ".LaunchBubbleActivity");
+ }
+
+ public static class BubbleActivity {
+ public static final String LABEL = "BubbleApp";
+ public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
+ PACKAGE_NAME + ".BubbleActivity");
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java
new file mode 100644
index 000000000000..71fa66d8a61c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java
@@ -0,0 +1,82 @@
+/*
+ * 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.wm.shell.flicker.testapp;
+
+
+import android.app.Activity;
+import android.app.Person;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.view.View;
+
+import java.util.Arrays;
+
+public class LaunchBubbleActivity extends Activity {
+
+ private BubbleHelper mBubbleHelper;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addInboxShortcut(getApplicationContext());
+ mBubbleHelper = BubbleHelper.getInstance(this);
+ setContentView(R.layout.activity_main);
+ findViewById(R.id.button_create).setOnClickListener(this::add);
+ findViewById(R.id.button_cancel).setOnClickListener(this::cancel);
+ findViewById(R.id.button_cancel_all).setOnClickListener(this::cancelAll);
+ }
+
+ private void add(View v) {
+ mBubbleHelper.addNewBubble(false /* autoExpand */, false /* suppressNotif */);
+ }
+
+ private void cancel(View v) {
+ mBubbleHelper.cancelLast();
+ }
+
+ private void cancelAll(View v) {
+ mBubbleHelper.cancelAll();
+ }
+
+ private void addInboxShortcut(Context context) {
+ Icon icon = Icon.createWithResource(this, R.drawable.bg);
+ Person[] persons = new Person[4];
+ for (int i = 0; i < persons.length; i++) {
+ persons[i] = new Person.Builder()
+ .setBot(false)
+ .setIcon(icon)
+ .setName("google" + i)
+ .setImportant(true)
+ .build();
+ }
+
+ ShortcutInfo shortcut = new ShortcutInfo.Builder(context, "BubbleChat")
+ .setShortLabel("BubbleChat")
+ .setLongLived(true)
+ .setIntent(new Intent(Intent.ACTION_VIEW))
+ .setIcon(Icon.createWithResource(context, R.drawable.ic_message))
+ .setPersons(persons)
+ .build();
+ ShortcutManager scmanager = context.getSystemService(ShortcutManager.class);
+ scmanager.addDynamicShortcuts(Arrays.asList(shortcut));
+ }
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 6b74b620dad7..d5acbbcf7d2c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -197,6 +197,43 @@ public class ShellTaskOrganizerTests {
}
@Test
+ public void testAddListenerForMultipleTypes() {
+ RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(taskInfo1, null);
+ RunningTaskInfo taskInfo2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW);
+ mOrganizer.onTaskAppeared(taskInfo2, null);
+
+ TrackingTaskListener listener = new TrackingTaskListener();
+ mOrganizer.addListenerForType(listener,
+ TASK_LISTENER_TYPE_MULTI_WINDOW, TASK_LISTENER_TYPE_FULLSCREEN);
+
+ // onTaskAppeared event should be delivered once for each taskInfo.
+ assertTrue(listener.appeared.contains(taskInfo1));
+ assertTrue(listener.appeared.contains(taskInfo2));
+ assertEquals(2, listener.appeared.size());
+ }
+
+ @Test
+ public void testRemoveListenerForMultipleTypes() {
+ RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(taskInfo1, null);
+ RunningTaskInfo taskInfo2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW);
+ mOrganizer.onTaskAppeared(taskInfo2, null);
+
+ TrackingTaskListener listener = new TrackingTaskListener();
+ mOrganizer.addListenerForType(listener,
+ TASK_LISTENER_TYPE_MULTI_WINDOW, TASK_LISTENER_TYPE_FULLSCREEN);
+
+ mOrganizer.removeListener(listener);
+
+ // If listener is removed properly, onTaskInfoChanged event shouldn't be delivered.
+ mOrganizer.onTaskInfoChanged(taskInfo1);
+ assertTrue(listener.infoChanged.isEmpty());
+ mOrganizer.onTaskInfoChanged(taskInfo2);
+ assertTrue(listener.infoChanged.isEmpty());
+ }
+
+ @Test
public void testWindowingModeChange() {
RunningTaskInfo taskInfo = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
TrackingTaskListener mwListener = new TrackingTaskListener();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
index 20ac5bf8fa84..1cbad155ba7b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
@@ -47,6 +47,8 @@ import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.HandlerExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.SyncTransactionQueue.TransactionRunnable;
import org.junit.After;
import org.junit.Before;
@@ -71,6 +73,8 @@ public class TaskViewTest extends ShellTestCase {
ShellTaskOrganizer mOrganizer;
@Mock
HandlerExecutor mExecutor;
+ @Mock
+ SyncTransactionQueue mSyncQueue;
SurfaceSession mSession;
SurfaceControl mLeash;
@@ -99,7 +103,14 @@ public class TaskViewTest extends ShellTestCase {
}).when(mExecutor).execute(any());
when(mOrganizer.getExecutor()).thenReturn(mExecutor);
- mTaskView = new TaskView(mContext, mOrganizer);
+
+ doAnswer((InvocationOnMock invocationOnMock) -> {
+ final TransactionRunnable r = invocationOnMock.getArgument(0);
+ r.runWithTransaction(new SurfaceControl.Transaction());
+ return null;
+ }).when(mSyncQueue).runInSync(any());
+
+ mTaskView = new TaskView(mContext, mOrganizer, mSyncQueue);
mTaskView.setListener(mExecutor, mViewListener);
}
@@ -112,7 +123,7 @@ public class TaskViewTest extends ShellTestCase {
@Test
public void testSetPendingListener_throwsException() {
- TaskView taskView = new TaskView(mContext, mOrganizer);
+ TaskView taskView = new TaskView(mContext, mOrganizer, mSyncQueue);
taskView.setListener(mExecutor, mViewListener);
try {
taskView.setListener(mExecutor, mViewListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java
index 27c626170a4b..294bc1276291 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java
@@ -21,6 +21,7 @@ import static org.mockito.Mockito.mock;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -30,7 +31,7 @@ public class TestAppPairsController extends AppPairsController {
public TestAppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
DisplayController displayController) {
super(organizer, syncQueue, displayController, mock(ShellExecutor.class),
- mock(DisplayImeController.class));
+ mock(DisplayImeController.class), mock(DisplayInsetsController.class));
mPool = new TestAppPairsPool(this);
setPairsPool(mPool);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 3e3195fe8dc5..bc701d0c70bc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -131,7 +131,7 @@ public class BubbleDataTest extends ShellTestCase {
NotificationListenerService.Ranking ranking =
mock(NotificationListenerService.Ranking.class);
- when(ranking.visuallyInterruptive()).thenReturn(true);
+ when(ranking.isTextChanged()).thenReturn(true);
mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking);
mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null,
mMainExecutor);
@@ -793,7 +793,7 @@ public class BubbleDataTest extends ShellTestCase {
}
@Test
- public void test_expanded_removeLastBubble_collapsesStack() {
+ public void test_expanded_removeLastBubble_showsOverflowIfNotEmpty() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
changeExpandedStateAtTime(true, 2000);
@@ -802,6 +802,21 @@ public class BubbleDataTest extends ShellTestCase {
// Test
mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
verifyUpdateReceived();
+ assertThat(mBubbleData.getOverflowBubbles().size()).isGreaterThan(0);
+ assertSelectionChangedTo(mBubbleData.getOverflow());
+ }
+
+ @Test
+ public void test_expanded_removeLastBubble_collapsesIfOverflowEmpty() {
+ // Setup
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ changeExpandedStateAtTime(true, 2000);
+ mBubbleData.setListener(mListener);
+
+ // Test
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_NO_BUBBLE_UP);
+ verifyUpdateReceived();
+ assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
assertExpandedChangedTo(false);
}
@@ -869,6 +884,35 @@ public class BubbleDataTest extends ShellTestCase {
assertNotNull(mBubbleData.getOverflowBubbleWithKey(mBubbleA2.getKey()));
}
+ /**
+ * Verifies that after the stack is collapsed with the overflow selected, it will select
+ * the top bubble upon next expansion.
+ */
+ @Test
+ public void test_collapseWithOverflowSelected_nextExpansion() {
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ mBubbleData.setExpanded(true);
+
+ mBubbleData.setListener(mListener);
+
+ // Select the overflow
+ mBubbleData.setShowingOverflow(true);
+ mBubbleData.setSelectedBubble(mBubbleData.getOverflow());
+ verifyUpdateReceived();
+ assertSelectionChangedTo(mBubbleData.getOverflow());
+
+ // Collapse
+ mBubbleData.setExpanded(false);
+ verifyUpdateReceived();
+ assertSelectionNotChanged();
+
+ // Expand (here we should select the new bubble)
+ mBubbleData.setExpanded(true);
+ verifyUpdateReceived();
+ assertSelectionChangedTo(mBubbleA2);
+ }
+
private void verifyUpdateReceived() {
verify(mListener).applyUpdate(mUpdateCaptor.capture());
reset(mListener);
@@ -902,7 +946,7 @@ public class BubbleDataTest extends ShellTestCase {
assertWithMessage("selectionChanged").that(update.selectionChanged).isFalse();
}
- private void assertSelectionChangedTo(Bubble bubble) {
+ private void assertSelectionChangedTo(BubbleViewProvider bubble) {
BubbleData.Update update = mUpdateCaptor.getValue();
assertWithMessage("selectionChanged").that(update.selectionChanged).isTrue();
assertWithMessage("selectedBubble").that(update.selectedBubble).isEqualTo(bubble);
@@ -925,7 +969,6 @@ public class BubbleDataTest extends ShellTestCase {
assertThat(update.overflowBubbles).isEqualTo(bubbles);
}
-
private BubbleEntry createBubbleEntry(int userId, String notifKey, String packageName,
NotificationListenerService.Ranking ranking) {
return createBubbleEntry(userId, notifKey, packageName, ranking, 1000);
@@ -971,15 +1014,15 @@ public class BubbleDataTest extends ShellTestCase {
}
private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime) {
- sendUpdatedEntryAtTime(entry, postTime, true /* visuallyInterruptive */);
+ sendUpdatedEntryAtTime(entry, postTime, true /* isTextChanged */);
}
private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime,
- boolean visuallyInterruptive) {
+ boolean textChanged) {
setPostTime(entry, postTime);
// BubbleController calls this:
Bubble b = mBubbleData.getOrCreateBubble(entry, null /* persistedBubble */);
- b.setVisuallyInterruptiveForTest(visuallyInterruptive);
+ b.setTextChangedForTest(textChanged);
// And then this
mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/,
true /* showInShade */);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java
index 6644eaf28a62..5c1bcb9753a4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java
@@ -63,7 +63,7 @@ public class BubbleFlyoutViewTest extends ShellTestCase {
mFlyoutMessage.senderName = "Josh";
mFlyoutMessage.message = "Hello";
- mFlyout = new BubbleFlyoutView(getContext());
+ mFlyout = new BubbleFlyoutView(getContext(), mPositioner);
mFlyoutText = mFlyout.findViewById(R.id.bubble_flyout_text);
mSenderName = mFlyout.findViewById(R.id.bubble_flyout_name);
@@ -75,9 +75,8 @@ public class BubbleFlyoutViewTest extends ShellTestCase {
public void testShowFlyout_isVisible() {
mFlyout.setupFlyoutStartingAsDot(
mFlyoutMessage,
- new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter,
- false,
- mPositioner);
+ new PointF(100, 100), true, Color.WHITE, null, null, mDotCenter,
+ false);
mFlyout.setVisibility(View.VISIBLE);
assertEquals("Hello", mFlyoutText.getText());
@@ -89,9 +88,8 @@ public class BubbleFlyoutViewTest extends ShellTestCase {
public void testFlyoutHide_runsCallback() {
Runnable after = mock(Runnable.class);
mFlyout.setupFlyoutStartingAsDot(mFlyoutMessage,
- new PointF(100, 100), 500, true, Color.WHITE, null, after, mDotCenter,
- false,
- mPositioner);
+ new PointF(100, 100), true, Color.WHITE, null, after, mDotCenter,
+ false);
mFlyout.hideFlyout();
verify(after).run();
@@ -100,9 +98,8 @@ public class BubbleFlyoutViewTest extends ShellTestCase {
@Test
public void testSetCollapsePercent() {
mFlyout.setupFlyoutStartingAsDot(mFlyoutMessage,
- new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter,
- false,
- mPositioner);
+ new PointF(100, 100), true, Color.WHITE, null, null, mDotCenter,
+ false);
mFlyout.setVisibility(View.VISIBLE);
mFlyout.setCollapsePercent(1f);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
index 1eba3c266358..2b9bdce45a6c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -16,9 +16,12 @@
package com.android.wm.shell.bubbles.animation;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.annotation.SuppressLint;
import android.content.res.Configuration;
@@ -36,12 +39,12 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleStackView;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Spy;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -49,26 +52,30 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
private int mDisplayWidth = 500;
private int mDisplayHeight = 1000;
- private int mExpandedViewPadding = 10;
private Runnable mOnBubbleAnimatedOutAction = mock(Runnable.class);
- @Spy
ExpandedAnimationController mExpandedController;
private int mStackOffset;
private PointF mExpansionPoint;
+ private BubblePositioner mPositioner;
+ private BubbleStackView.StackViewState mStackViewState;
@SuppressLint("VisibleForTests")
@Before
public void setUp() throws Exception {
super.setUp();
- BubblePositioner positioner = new BubblePositioner(getContext(), mock(WindowManager.class));
- positioner.updateInternal(Configuration.ORIENTATION_PORTRAIT,
+ BubbleStackView stackView = mock(BubbleStackView.class);
+ when(stackView.getState()).thenReturn(getStackViewState());
+ mPositioner = new BubblePositioner(getContext(), mock(WindowManager.class));
+ mPositioner.updateInternal(Configuration.ORIENTATION_PORTRAIT,
Insets.of(0, 0, 0, 0),
new Rect(0, 0, mDisplayWidth, mDisplayHeight));
- mExpandedController = new ExpandedAnimationController(positioner, mExpandedViewPadding,
- mOnBubbleAnimatedOutAction);
+ mExpandedController = new ExpandedAnimationController(mPositioner,
+ mOnBubbleAnimatedOutAction,
+ stackView);
+ spyOn(mExpandedController);
addOneMoreThanBubbleLimitBubbles();
mLayout.setActiveController(mExpandedController);
@@ -78,6 +85,13 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
mExpansionPoint = new PointF(100, 100);
}
+ public BubbleStackView.StackViewState getStackViewState() {
+ mStackViewState.numberOfBubbles = mLayout.getChildCount();
+ mStackViewState.selectedIndex = 0;
+ mStackViewState.onLeft = mPositioner.isStackOnLeft(mExpansionPoint);
+ return mStackViewState;
+ }
+
@Test
@Ignore
public void testExpansionAndCollapse() throws InterruptedException {
@@ -143,11 +157,12 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
private void testBubblesInCorrectExpandedPositions() {
// Check all the visible bubbles to see if they're in the right place.
for (int i = 0; i < mLayout.getChildCount(); i++) {
- float expectedPosition = mExpandedController.getBubbleXOrYForOrientation(i);
- assertEquals(expectedPosition,
+ PointF expectedPosition = mPositioner.getExpandedBubbleXY(i,
+ getStackViewState());
+ assertEquals(expectedPosition.x,
mLayout.getChildAt(i).getTranslationX(),
2f);
- assertEquals(expectedPosition,
+ assertEquals(expectedPosition.y,
mLayout.getChildAt(i).getTranslationY(), 2f);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index ef046d48e1cf..b88845044263 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -58,7 +58,7 @@ public class DisplayImeControllerTest {
mT = mock(SurfaceControl.Transaction.class);
mMock = mock(IInputMethodManager.class);
mExecutor = spy(Runnable::run);
- mPerDisplay = new DisplayImeController(null, null, mExecutor, new TransactionPool() {
+ mPerDisplay = new DisplayImeController(null, null, null, mExecutor, new TransactionPool() {
@Override
public SurfaceControl.Transaction acquire() {
return mT;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
new file mode 100644
index 000000000000..b66c2b4aee9b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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.wm.shell.common;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.RemoteException;
+import android.util.SparseArray;
+import android.view.IDisplayWindowInsetsController;
+import android.view.IWindowManager;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.TestShellExecutor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+public class DisplayInsetsControllerTest {
+
+ private static final int SECOND_DISPLAY = DEFAULT_DISPLAY + 10;
+
+ @Mock
+ private IWindowManager mWm;
+ @Mock
+ private DisplayController mDisplayController;
+ private DisplayInsetsController mController;
+ private SparseArray<IDisplayWindowInsetsController> mInsetsControllersByDisplayId;
+ private TestShellExecutor mExecutor;
+
+ private ArgumentCaptor<Integer> mDisplayIdCaptor;
+ private ArgumentCaptor<IDisplayWindowInsetsController> mInsetsControllerCaptor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mExecutor = new TestShellExecutor();
+ mInsetsControllersByDisplayId = new SparseArray<>();
+ mDisplayIdCaptor = ArgumentCaptor.forClass(Integer.class);
+ mInsetsControllerCaptor = ArgumentCaptor.forClass(IDisplayWindowInsetsController.class);
+ mController = new DisplayInsetsController(mWm, mDisplayController, mExecutor);
+ addDisplay(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testOnDisplayAdded_setsDisplayWindowInsetsControllerOnWMService()
+ throws RemoteException {
+ addDisplay(SECOND_DISPLAY);
+
+ verify(mWm).setDisplayWindowInsetsController(eq(SECOND_DISPLAY), notNull());
+ }
+
+ @Test
+ public void testOnDisplayRemoved_unsetsDisplayWindowInsetsControllerInWMService()
+ throws RemoteException {
+ addDisplay(SECOND_DISPLAY);
+ removeDisplay(SECOND_DISPLAY);
+
+ verify(mWm).setDisplayWindowInsetsController(SECOND_DISPLAY, null);
+ }
+
+ @Test
+ public void testPerDisplayListenerCallback() throws RemoteException {
+ TrackedListener defaultListener = new TrackedListener();
+ TrackedListener secondListener = new TrackedListener();
+ addDisplay(SECOND_DISPLAY);
+ mController.addInsetsChangedListener(DEFAULT_DISPLAY, defaultListener);
+ mController.addInsetsChangedListener(SECOND_DISPLAY, secondListener);
+
+ mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).topFocusedWindowChanged(null);
+ mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null);
+ mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null);
+ mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false);
+ mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false);
+ mExecutor.flushAll();
+
+ assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
+ assertTrue(defaultListener.insetsChangedCount == 1);
+ assertTrue(defaultListener.insetsControlChangedCount == 1);
+ assertTrue(defaultListener.showInsetsCount == 1);
+ assertTrue(defaultListener.hideInsetsCount == 1);
+
+ assertTrue(secondListener.topFocusedWindowChangedCount == 0);
+ assertTrue(secondListener.insetsChangedCount == 0);
+ assertTrue(secondListener.insetsControlChangedCount == 0);
+ assertTrue(secondListener.showInsetsCount == 0);
+ assertTrue(secondListener.hideInsetsCount == 0);
+
+ mInsetsControllersByDisplayId.get(SECOND_DISPLAY).topFocusedWindowChanged(null);
+ mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null);
+ mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null);
+ mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false);
+ mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false);
+ mExecutor.flushAll();
+
+ assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
+ assertTrue(defaultListener.insetsChangedCount == 1);
+ assertTrue(defaultListener.insetsControlChangedCount == 1);
+ assertTrue(defaultListener.showInsetsCount == 1);
+ assertTrue(defaultListener.hideInsetsCount == 1);
+
+ assertTrue(secondListener.topFocusedWindowChangedCount == 1);
+ assertTrue(secondListener.insetsChangedCount == 1);
+ assertTrue(secondListener.insetsControlChangedCount == 1);
+ assertTrue(secondListener.showInsetsCount == 1);
+ assertTrue(secondListener.hideInsetsCount == 1);
+ }
+
+ private void addDisplay(int displayId) throws RemoteException {
+ mController.onDisplayAdded(displayId);
+ verify(mWm, times(mInsetsControllersByDisplayId.size() + 1))
+ .setDisplayWindowInsetsController(mDisplayIdCaptor.capture(),
+ mInsetsControllerCaptor.capture());
+ List<Integer> displayIds = mDisplayIdCaptor.getAllValues();
+ List<IDisplayWindowInsetsController> insetsControllers =
+ mInsetsControllerCaptor.getAllValues();
+ for (int i = 0; i < displayIds.size(); i++) {
+ mInsetsControllersByDisplayId.put(displayIds.get(i), insetsControllers.get(i));
+ }
+ }
+
+ private void removeDisplay(int displayId) {
+ mController.onDisplayRemoved(displayId);
+ mInsetsControllersByDisplayId.remove(displayId);
+ }
+
+ private static class TrackedListener implements
+ DisplayInsetsController.OnInsetsChangedListener {
+ int topFocusedWindowChangedCount = 0;
+ int insetsChangedCount = 0;
+ int insetsControlChangedCount = 0;
+ int showInsetsCount = 0;
+ int hideInsetsCount = 0;
+
+ @Override
+ public void topFocusedWindowChanged(String packageName) {
+ topFocusedWindowChangedCount++;
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ insetsChangedCount++;
+ }
+
+ @Override
+ public void insetsControlChanged(InsetsState insetsState,
+ InsetsSourceControl[] activeControls) {
+ insetsControlChangedCount++;
+ }
+
+ @Override
+ public void showInsets(int types, boolean fromIme) {
+ showInsetsCount++;
+ }
+
+ @Override
+ public void hideInsets(int types, boolean fromIme) {
+ hideInsetsCount++;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
index 88e754c58792..0ffa5b35331d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
@@ -21,9 +21,14 @@ import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -35,8 +40,12 @@ import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.internal.policy.SystemBarUtils;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
+import org.mockito.MockitoSession;
/**
* Tests for {@link DisplayLayout}.
@@ -46,29 +55,48 @@ import org.junit.Test;
*/
@SmallTest
public class DisplayLayoutTest {
+ private MockitoSession mMockitoSession;
+
+ @Before
+ public void setup() {
+ mMockitoSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(SystemBarUtils.class)
+ .startMocking();
+ }
+
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
@Test
public void testInsets() {
- Resources res = createResources(40, 50, false, 30, 40);
+ Resources res = createResources(40, 50, false);
// Test empty display, no bars or anything
DisplayInfo info = createDisplayInfo(1000, 1500, 0, ROTATION_0);
DisplayLayout dl = new DisplayLayout(info, res, false, false);
+ when(SystemBarUtils.getStatusBarHeight(eq(res), any())).thenReturn(40);
+ dl.recalcInsets(res);
assertEquals(new Rect(0, 0, 0, 0), dl.stableInsets());
assertEquals(new Rect(0, 0, 0, 0), dl.nonDecorInsets());
// Test with bars
dl = new DisplayLayout(info, res, true, true);
+ dl.recalcInsets(res);
assertEquals(new Rect(0, 40, 0, 50), dl.stableInsets());
assertEquals(new Rect(0, 0, 0, 50), dl.nonDecorInsets());
// Test just cutout
info = createDisplayInfo(1000, 1500, 60, ROTATION_0);
dl = new DisplayLayout(info, res, false, false);
+ dl.recalcInsets(res);
assertEquals(new Rect(0, 60, 0, 0), dl.stableInsets());
assertEquals(new Rect(0, 60, 0, 0), dl.nonDecorInsets());
// Test with bars and cutout
dl = new DisplayLayout(info, res, true, true);
+ dl.recalcInsets(res);
assertEquals(new Rect(0, 60, 0, 50), dl.stableInsets());
assertEquals(new Rect(0, 60, 0, 50), dl.nonDecorInsets());
}
@@ -76,27 +104,30 @@ public class DisplayLayoutTest {
@Test
public void testRotate() {
// Basic rotate utility
- Resources res = createResources(40, 50, false, 30, 40);
+ Resources res = createResources(40, 50, false);
DisplayInfo info = createDisplayInfo(1000, 1500, 60, ROTATION_0);
DisplayLayout dl = new DisplayLayout(info, res, true, true);
+ when(SystemBarUtils.getStatusBarHeight(eq(res), any())).thenReturn(40);
+ dl.recalcInsets(res);
assertEquals(new Rect(0, 60, 0, 50), dl.stableInsets());
assertEquals(new Rect(0, 60, 0, 50), dl.nonDecorInsets());
// Rotate to 90
+ when(SystemBarUtils.getStatusBarHeight(eq(res), any())).thenReturn(30);
dl.rotateTo(res, ROTATION_90);
assertEquals(new Rect(60, 30, 0, 40), dl.stableInsets());
assertEquals(new Rect(60, 0, 0, 40), dl.nonDecorInsets());
// Rotate with moving navbar
- res = createResources(40, 50, true, 30, 40);
+ res = createResources(40, 50, true);
dl = new DisplayLayout(info, res, true, true);
+ when(SystemBarUtils.getStatusBarHeight(eq(res), any())).thenReturn(30);
dl.rotateTo(res, ROTATION_270);
assertEquals(new Rect(40, 30, 60, 0), dl.stableInsets());
assertEquals(new Rect(40, 0, 60, 0), dl.nonDecorInsets());
}
- private Resources createResources(
- int navLand, int navPort, boolean navMoves, int statusLand, int statusPort) {
+ private Resources createResources(int navLand, int navPort, boolean navMoves) {
Configuration cfg = new Configuration();
cfg.uiMode = UI_MODE_TYPE_NORMAL;
Resources res = mock(Resources.class);
@@ -108,8 +139,6 @@ public class DisplayLayoutTest {
doReturn(navPort).when(res).getDimensionPixelSize(R.dimen.navigation_bar_height);
doReturn(navLand).when(res).getDimensionPixelSize(R.dimen.navigation_bar_width);
doReturn(navMoves).when(res).getBoolean(R.bool.config_navBarCanMove);
- doReturn(statusLand).when(res).getDimensionPixelSize(R.dimen.status_bar_height_landscape);
- doReturn(statusPort).when(res).getDimensionPixelSize(R.dimen.status_bar_height_portrait);
doReturn(cfg).when(res).getConfiguration();
return res;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 952dc31cdaee..73eebad040d8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -24,11 +24,11 @@ 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.eq;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.res.Configuration;
import android.graphics.Rect;
-import android.view.SurfaceControl;
import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -42,6 +42,8 @@ import com.android.wm.shell.common.DisplayImeController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -50,42 +52,57 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class SplitLayoutTests extends ShellTestCase {
@Mock SplitLayout.SplitLayoutHandler mSplitLayoutHandler;
- @Mock SurfaceControl mRootLeash;
+ @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks;
@Mock DisplayImeController mDisplayImeController;
@Mock ShellTaskOrganizer mTaskOrganizer;
+ @Captor ArgumentCaptor<Runnable> mRunnableCaptor;
private SplitLayout mSplitLayout;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mSplitLayout = new SplitLayout(
+ mSplitLayout = spy(new SplitLayout(
"TestSplitLayout",
mContext,
- getConfiguration(false),
+ getConfiguration(),
mSplitLayoutHandler,
- b -> b.setParent(mRootLeash),
+ mCallbacks,
mDisplayImeController,
- mTaskOrganizer);
+ mTaskOrganizer,
+ false /* applyDismissingParallax */));
}
@Test
@UiThreadTest
public void testUpdateConfiguration() {
- mSplitLayout.init();
- assertThat(mSplitLayout.updateConfiguration(getConfiguration(false))).isFalse();
- assertThat(mSplitLayout.updateConfiguration(getConfiguration(true))).isTrue();
+ final Configuration config = getConfiguration();
+
+ // Verify it returns true if new config won't affect split layout.
+ assertThat(mSplitLayout.updateConfiguration(config)).isFalse();
+
+ // Verify updateConfiguration returns true if the orientation changed.
+ config.orientation = ORIENTATION_LANDSCAPE;
+ assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
+
+ // Verify updateConfiguration returns true if it rotated.
+ config.windowConfiguration.setRotation(1);
+ assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
+
+ // Verify updateConfiguration returns true if the root bounds changed.
+ config.windowConfiguration.setBounds(new Rect(0, 0, 2160, 1080));
+ assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
}
@Test
public void testUpdateDivideBounds() {
mSplitLayout.updateDivideBounds(anyInt());
- verify(mSplitLayoutHandler).onBoundsChanging(any(SplitLayout.class));
+ verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class));
}
@Test
public void testSetDividePosition() {
mSplitLayout.setDividePosition(anyInt());
- verify(mSplitLayoutHandler).onBoundsChanged(any(SplitLayout.class));
+ verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class));
}
@Test
@@ -96,24 +113,40 @@ public class SplitLayoutTests extends ShellTestCase {
@Test
@UiThreadTest
- public void testSnapToDismissTarget() {
+ public void testSnapToDismissStart() {
// verify it callbacks properly when the snap target indicates dismissing split.
DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START);
+
mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget);
+ waitDividerFlingFinished();
verify(mSplitLayoutHandler).onSnappedToDismiss(eq(false));
- snapTarget = getSnapTarget(0 /* position */,
+ }
+
+ @Test
+ @UiThreadTest
+ public void testSnapToDismissEnd() {
+ // verify it callbacks properly when the snap target indicates dismissing split.
+ DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END);
+
mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget);
+ waitDividerFlingFinished();
verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true));
}
- private static Configuration getConfiguration(boolean isLandscape) {
+ private void waitDividerFlingFinished() {
+ verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), mRunnableCaptor.capture());
+ mRunnableCaptor.getValue().run();
+ }
+
+ private static Configuration getConfiguration() {
final Configuration configuration = new Configuration();
configuration.unset();
- configuration.orientation = isLandscape ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+ configuration.orientation = ORIENTATION_PORTRAIT;
+ configuration.windowConfiguration.setRotation(0);
configuration.windowConfiguration.setBounds(
- new Rect(0, 0, isLandscape ? 2160 : 1080, isLandscape ? 1080 : 2160));
+ new Rect(0, 0, 1080, 2160));
return configuration;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
index 698315a77d8e..9bb54a18063f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
@@ -22,7 +22,7 @@ import static org.mockito.Mockito.when;
import android.content.res.Configuration;
import android.graphics.Rect;
-import android.view.SurfaceControl;
+import android.view.InsetsState;
import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -40,8 +40,8 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class SplitWindowManagerTests extends ShellTestCase {
- @Mock SurfaceControl mSurfaceControl;
@Mock SplitLayout mSplitLayout;
+ @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks;
private SplitWindowManager mSplitWindowManager;
@Before
@@ -50,7 +50,7 @@ public class SplitWindowManagerTests extends ShellTestCase {
final Configuration configuration = new Configuration();
configuration.setToDefaults();
mSplitWindowManager = new SplitWindowManager("TestSplitDivider", mContext, configuration,
- b -> b.setParent(mSurfaceControl));
+ mCallbacks);
when(mSplitLayout.getDividerBounds()).thenReturn(
new Rect(0, 0, configuration.windowConfiguration.getBounds().width(),
configuration.windowConfiguration.getBounds().height()));
@@ -59,7 +59,7 @@ public class SplitWindowManagerTests extends ShellTestCase {
@Test
@UiThreadTest
public void testInitRelease() {
- mSplitWindowManager.init(mSplitLayout);
+ mSplitWindowManager.init(mSplitLayout, new InsetsState());
assertThat(mSplitWindowManager.getSurfaceControl()).isNotNull();
mSplitWindowManager.release();
assertThat(mSplitWindowManager.getSurfaceControl()).isNull();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index ba73d555e334..734b97b69c87 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -25,6 +25,7 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
@@ -64,6 +65,7 @@ import android.view.DisplayInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.InstanceId;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -95,6 +97,9 @@ public class DragAndDropPolicyTest {
@Mock
private SplitScreenController mSplitScreenStarter;
+ @Mock
+ private InstanceId mLoggerSessionId;
+
private DisplayLayout mLandscapeDisplayLayout;
private DisplayLayout mPortraitDisplayLayout;
private Insets mInsets;
@@ -200,7 +205,7 @@ public class DragAndDropPolicyTest {
@Test
public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() {
setRunningTask(mHomeTask);
- mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
+ mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
@@ -210,15 +215,15 @@ public class DragAndDropPolicyTest {
}
@Test
- public void testDragAppOverFullscreenApp_expectSplitScreenAndFullscreenTargets() {
+ public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
setRunningTask(mFullscreenAppTask);
- mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
+ mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
- mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
+ mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
- eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
+ eq(STAGE_TYPE_SIDE), eq(SPLIT_POSITION_TOP_OR_LEFT), any());
reset(mSplitScreenStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
@@ -227,15 +232,15 @@ public class DragAndDropPolicyTest {
}
@Test
- public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenAndFullscreenTargets() {
+ public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
setRunningTask(mFullscreenAppTask);
- mPolicy.start(mPortraitDisplayLayout, mActivityClipData);
+ mPolicy.start(mPortraitDisplayLayout, mActivityClipData, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
- mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
+ mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
- eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
+ eq(STAGE_TYPE_SIDE), eq(SPLIT_POSITION_TOP_OR_LEFT), any());
reset(mSplitScreenStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
@@ -244,71 +249,61 @@ public class DragAndDropPolicyTest {
}
@Test
- public void testDragAppOverFullscreenNonResizeableApp_expectOnlyFullscreenTargets() {
- setRunningTask(mNonResizeableFullscreenAppTask);
- mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
+ public void testDragAppOverSplitApp_expectSplitTargets_DropLeft() {
+ setInSplitScreen(true);
+ setRunningTask(mSplitPrimaryAppTask);
+ mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
- mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
+ mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
- eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
+ eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_TOP_OR_LEFT), any());
}
@Test
- public void testDragNonResizeableAppOverFullscreenApp_expectOnlyFullscreenTargets() {
- setRunningTask(mFullscreenAppTask);
- mPolicy.start(mLandscapeDisplayLayout, mNonResizeableActivityClipData);
+ public void testDragAppOverSplitApp_expectSplitTargets_DropRight() {
+ setInSplitScreen(true);
+ setRunningTask(mSplitPrimaryAppTask);
+ mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
- mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
+ mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
- eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
+ eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
}
@Test
- public void testDragAppOverSplitApp_expectFullscreenAndSplitTargets() {
+ public void testDragAppOverSplitAppPhone_expectVerticalSplitTargets_DropTop() {
setInSplitScreen(true);
setRunningTask(mSplitPrimaryAppTask);
- mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
+ mPolicy.start(mPortraitDisplayLayout, mActivityClipData, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
- mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
+ mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(),
- eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
- reset(mSplitScreenStarter);
-
- // TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
- eq(STAGE_TYPE_SIDE), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
+ eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_TOP_OR_LEFT), any());
}
@Test
- public void testDragAppOverSplitAppPhone_expectFullscreenAndVerticalSplitTargets() {
+ public void testDragAppOverSplitAppPhone_expectVerticalSplitTargets_DropBottom() {
setInSplitScreen(true);
setRunningTask(mSplitPrimaryAppTask);
- mPolicy.start(mPortraitDisplayLayout, mActivityClipData);
+ mPolicy.start(mPortraitDisplayLayout, mActivityClipData, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
- mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
+ mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(),
- eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
- reset(mSplitScreenStarter);
-
- // TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
verify(mSplitScreenStarter).startIntent(any(), any(),
- eq(STAGE_TYPE_SIDE), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
+ eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
}
@Test
public void testTargetHitRects() {
setRunningTask(mFullscreenAppTask);
- mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
+ mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
ArrayList<Target> targets = mPolicy.getTargets(mInsets);
for (Target t : targets) {
assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.left, t.hitRegion.top) == t);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java
new file mode 100644
index 000000000000..d6f7e54ae369
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.wm.shell.fullscreen;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.WindowConfiguration;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@SmallTest
+public class FullscreenTaskListenerTest {
+
+ @Mock
+ private SyncTransactionQueue mSyncQueue;
+ @Mock
+ private FullscreenUnfoldController mUnfoldController;
+ @Mock
+ private SurfaceControl mSurfaceControl;
+
+ private Optional<FullscreenUnfoldController> mFullscreenUnfoldController;
+
+ private FullscreenTaskListener mListener;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mFullscreenUnfoldController = Optional.of(mUnfoldController);
+ mListener = new FullscreenTaskListener(mSyncQueue, mFullscreenUnfoldController);
+ }
+
+ @Test
+ public void testAnimatableTaskAppeared_notifiesUnfoldController() {
+ RunningTaskInfo info = createTaskInfo(/* visible */ true, /* taskId */ 0);
+
+ mListener.onTaskAppeared(info, mSurfaceControl);
+
+ verify(mUnfoldController).onTaskAppeared(eq(info), any());
+ }
+
+ @Test
+ public void testMultipleAnimatableTasksAppeared_notifiesUnfoldController() {
+ RunningTaskInfo animatable1 = createTaskInfo(/* visible */ true, /* taskId */ 0);
+ RunningTaskInfo animatable2 = createTaskInfo(/* visible */ true, /* taskId */ 1);
+
+ mListener.onTaskAppeared(animatable1, mSurfaceControl);
+ mListener.onTaskAppeared(animatable2, mSurfaceControl);
+
+ InOrder order = inOrder(mUnfoldController);
+ order.verify(mUnfoldController).onTaskAppeared(eq(animatable1), any());
+ order.verify(mUnfoldController).onTaskAppeared(eq(animatable2), any());
+ }
+
+ @Test
+ public void testNonAnimatableTaskAppeared_doesNotNotifyUnfoldController() {
+ RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0);
+
+ mListener.onTaskAppeared(info, mSurfaceControl);
+
+ verifyNoMoreInteractions(mUnfoldController);
+ }
+
+ @Test
+ public void testNonAnimatableTaskChanged_doesNotNotifyUnfoldController() {
+ RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0);
+ mListener.onTaskAppeared(info, mSurfaceControl);
+
+ mListener.onTaskInfoChanged(info);
+
+ verifyNoMoreInteractions(mUnfoldController);
+ }
+
+ @Test
+ public void testNonAnimatableTaskVanished_doesNotNotifyUnfoldController() {
+ RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0);
+ mListener.onTaskAppeared(info, mSurfaceControl);
+
+ mListener.onTaskVanished(info);
+
+ verifyNoMoreInteractions(mUnfoldController);
+ }
+
+ @Test
+ public void testAnimatableTaskBecameInactive_notifiesUnfoldController() {
+ RunningTaskInfo animatableTask = createTaskInfo(/* visible */ true, /* taskId */ 0);
+ mListener.onTaskAppeared(animatableTask, mSurfaceControl);
+ RunningTaskInfo notAnimatableTask = createTaskInfo(/* visible */ false, /* taskId */ 0);
+
+ mListener.onTaskInfoChanged(notAnimatableTask);
+
+ verify(mUnfoldController).onTaskVanished(eq(notAnimatableTask));
+ }
+
+ @Test
+ public void testAnimatableTaskVanished_notifiesUnfoldController() {
+ RunningTaskInfo taskInfo = createTaskInfo(/* visible */ true, /* taskId */ 0);
+ mListener.onTaskAppeared(taskInfo, mSurfaceControl);
+
+ mListener.onTaskVanished(taskInfo);
+
+ verify(mUnfoldController).onTaskVanished(eq(taskInfo));
+ }
+
+ private RunningTaskInfo createTaskInfo(boolean visible, int taskId) {
+ final RunningTaskInfo info = spy(new RunningTaskInfo());
+ info.isVisible = visible;
+ info.positionInParent = new Point();
+ when(info.getWindowingMode()).thenReturn(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+ final Configuration configuration = new Configuration();
+ configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+ when(info.getConfiguration()).thenReturn(configuration);
+ info.taskId = taskId;
+ return info;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
index 3c124bafc18a..078e2b6cf574 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
@@ -48,7 +48,6 @@ import android.window.WindowContainerToken;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import com.android.internal.R;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
@@ -124,6 +123,7 @@ public class HideDisplayCutoutOrganizerTest {
@Test
public void testEnableHideDisplayCutout() {
+ doReturn(mFakeStatusBarHeightPortrait).when(mOrganizer).getStatusBarHeight();
mOrganizer.enableHideDisplayCutout();
verify(mOrganizer).registerOrganizer(DisplayAreaOrganizer.FEATURE_HIDE_DISPLAY_CUTOUT);
@@ -154,8 +154,7 @@ public class HideDisplayCutoutOrganizerTest {
doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation();
doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
.getDisplayCutoutInsetsOfNaturalOrientation();
- mContext.getOrCreateTestableResources().addOverride(
- R.dimen.status_bar_height_portrait, mFakeStatusBarHeightPortrait);
+ doReturn(mFakeStatusBarHeightPortrait).when(mOrganizer).getStatusBarHeight();
doReturn(Surface.ROTATION_0).when(mDisplayLayout).rotation();
mOrganizer.enableHideDisplayCutout();
@@ -173,8 +172,7 @@ public class HideDisplayCutoutOrganizerTest {
doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation();
doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
.getDisplayCutoutInsetsOfNaturalOrientation();
- mContext.getOrCreateTestableResources().addOverride(
- R.dimen.status_bar_height_landscape, mFakeStatusBarHeightLandscape);
+ doReturn(mFakeStatusBarHeightLandscape).when(mOrganizer).getStatusBarHeight();
doReturn(Surface.ROTATION_90).when(mDisplayLayout).rotation();
mOrganizer.enableHideDisplayCutout();
@@ -192,8 +190,7 @@ public class HideDisplayCutoutOrganizerTest {
doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation();
doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
.getDisplayCutoutInsetsOfNaturalOrientation();
- mContext.getOrCreateTestableResources().addOverride(
- R.dimen.status_bar_height_landscape, mFakeStatusBarHeightLandscape);
+ doReturn(mFakeStatusBarHeightLandscape).when(mOrganizer).getStatusBarHeight();
doReturn(Surface.ROTATION_270).when(mDisplayLayout).rotation();
mOrganizer.enableHideDisplayCutout();
@@ -211,8 +208,7 @@ public class HideDisplayCutoutOrganizerTest {
doReturn(mFakeDefaultBounds).when(mOrganizer).getDisplayBoundsOfNaturalOrientation();
doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
.getDisplayCutoutInsetsOfNaturalOrientation();
- mContext.getOrCreateTestableResources().addOverride(
- R.dimen.status_bar_height_portrait, mFakeStatusBarHeightPortrait);
+ doReturn(mFakeStatusBarHeightPortrait).when(mOrganizer).getStatusBarHeight();
mOrganizer.enableHideDisplayCutout();
// disable hide display cutout
@@ -230,8 +226,7 @@ public class HideDisplayCutoutOrganizerTest {
doReturn(200).when(mDisplayLayout).height();
doReturn(mFakeDefaultCutoutInsets).when(mOrganizer)
.getDisplayCutoutInsetsOfNaturalOrientation();
- mContext.getOrCreateTestableResources().addOverride(
- R.dimen.status_bar_height_portrait, mFakeStatusBarHeightPortrait);
+ doReturn(mFakeStatusBarHeightPortrait).when(mOrganizer).getStatusBarHeight();
doReturn(Surface.ROTATION_0).when(mDisplayLayout).rotation();
mOrganizer.enableHideDisplayCutout();
assertThat(mOrganizer.mCurrentDisplayBounds).isEqualTo(new Rect(0, 15, 100, 200));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 911fe0753845..0a3a84923053 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -42,6 +42,7 @@ import android.util.ArrayMap;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
@@ -332,6 +333,58 @@ public class OneHandedControllerTest extends OneHandedTestCase {
}
@Test
+ public void testOneHandedEnabledRotation90ShouldHandleRotate() {
+ when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn(true);
+ when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn(
+ false);
+ final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
+ mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0,
+ Surface.ROTATION_90, handlerWCT);
+
+ verify(mMockDisplayAreaOrganizer, atLeastOnce()).onRotateDisplay(eq(mContext),
+ eq(Surface.ROTATION_90), any(WindowContainerTransaction.class));
+ }
+
+ @Test
+ public void testOneHandedDisabledRotation90ShouldNotHandleRotate() {
+ when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn(false);
+ when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn(
+ false);
+ final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
+ mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0,
+ Surface.ROTATION_90, handlerWCT);
+
+ verify(mMockDisplayAreaOrganizer, never()).onRotateDisplay(eq(mContext),
+ eq(Surface.ROTATION_90), any(WindowContainerTransaction.class));
+ }
+
+ @Test
+ public void testSwipeToNotificationEnabledRotation90ShouldNotHandleRotate() {
+ when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn(true);
+ when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn(
+ true);
+ final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
+ mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0,
+ Surface.ROTATION_90, handlerWCT);
+
+ verify(mMockDisplayAreaOrganizer, never()).onRotateDisplay(eq(mContext),
+ eq(Surface.ROTATION_90), any(WindowContainerTransaction.class));
+ }
+
+ @Test
+ public void testSwipeToNotificationDisabledRotation90ShouldHandleRotate() {
+ when(mMockSettingsUitl.getSettingsOneHandedModeEnabled(any(), anyInt())).thenReturn(true);
+ when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn(
+ false);
+ final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
+ mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0,
+ Surface.ROTATION_90, handlerWCT);
+
+ verify(mMockDisplayAreaOrganizer, atLeastOnce()).onRotateDisplay(eq(mContext),
+ eq(Surface.ROTATION_90), any(WindowContainerTransaction.class));
+ }
+
+ @Test
public void testStateActive_shortcutRequestActivate_skipActions() {
when(mSpiedTransitionState.getState()).thenReturn(STATE_ACTIVE);
when(mSpiedTransitionState.isTransitioning()).thenReturn(false);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 9d7c82bb8550..0270093da938 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -79,6 +79,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
private TestShellExecutor mMainExecutor;
private PipBoundsState mPipBoundsState;
+ private PipTransitionState mPipTransitionState;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private ComponentName mComponent1;
@@ -90,11 +91,12 @@ public class PipTaskOrganizerTest extends ShellTestCase {
mComponent1 = new ComponentName(mContext, "component1");
mComponent2 = new ComponentName(mContext, "component2");
mPipBoundsState = new PipBoundsState(mContext);
+ mPipTransitionState = new PipTransitionState();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
new PipSnapAlgorithm());
mMainExecutor = new TestShellExecutor();
mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mContext,
- mMockSyncTransactionQueue, mPipBoundsState,
+ mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState,
mPipBoundsAlgorithm, mMockPhonePipMenuController,
mMockPipAnimationController, mMockPipSurfaceTransactionHelper,
mMockPipTransitionController, mMockOptionalSplitScreen, mMockDisplayController,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java
index 10fd7d705967..3a14a336190d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java
@@ -24,7 +24,7 @@ import static org.mockito.Mockito.verify;
import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
-import android.widget.Button;
+import android.widget.LinearLayout;
import androidx.test.filters.SmallTest;
@@ -77,8 +77,8 @@ public class SizeCompatHintPopupTest extends ShellTestCase {
public void testOnClick() {
doNothing().when(mLayout).dismissHint();
- final Button button = mHint.findViewById(R.id.got_it);
- button.performClick();
+ final LinearLayout hintPopup = mHint.findViewById(R.id.size_compat_hint_popup);
+ hintPopup.performClick();
verify(mLayout).dismissHint();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
index 1bb5fd1e49e7..c9720671f49c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
@@ -25,10 +25,13 @@ import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerTransaction;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -41,28 +44,31 @@ import org.mockito.MockitoAnnotations;
/** Tests for {@link MainStage} */
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class MainStageTests {
+public class MainStageTests extends ShellTestCase {
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private ActivityManager.RunningTaskInfo mRootTaskInfo;
@Mock private SurfaceControl mRootLeash;
+ @Mock private IconProvider mIconProvider;
private WindowContainerTransaction mWct = new WindowContainerTransaction();
private SurfaceSession mSurfaceSession = new SurfaceSession();
private MainStage mMainStage;
@Before
+ @UiThreadTest
public void setup() {
MockitoAnnotations.initMocks(this);
mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
- mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue,
- mSurfaceSession);
+ mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
+ mSyncQueue, mSurfaceSession, mIconProvider, null);
mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash);
}
@Test
public void testActiveDeactivate() {
- mMainStage.activate(mRootTaskInfo.configuration.windowConfiguration.getBounds(), mWct);
+ mMainStage.activate(mRootTaskInfo.configuration.windowConfiguration.getBounds(), mWct,
+ true /* reparent */);
assertThat(mMainStage.isActive()).isTrue();
mMainStage.deactivate(mWct);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
index 56a005642ce2..a31aa58bdc26 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
@@ -29,10 +29,13 @@ import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerTransaction;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -46,22 +49,24 @@ import org.mockito.Spy;
/** Tests for {@link SideStage} */
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class SideStageTests {
+public class SideStageTests extends ShellTestCase {
@Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private ActivityManager.RunningTaskInfo mRootTask;
@Mock private SurfaceControl mRootLeash;
+ @Mock private IconProvider mIconProvider;
@Spy private WindowContainerTransaction mWct;
private SurfaceSession mSurfaceSession = new SurfaceSession();
private SideStage mSideStage;
@Before
+ @UiThreadTest
public void setup() {
MockitoAnnotations.initMocks(this);
mRootTask = new TestRunningTaskInfoBuilder().build();
- mSideStage = new SideStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue,
- mSurfaceSession);
+ mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
+ mSyncQueue, mSurfaceSession, mIconProvider, null);
mSideStage.onTaskAppeared(mRootTask, mRootLeash);
}
@@ -69,7 +74,7 @@ public class SideStageTests {
public void testAddTask() {
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
- mSideStage.addTask(task, mRootTask.configuration.windowConfiguration.getBounds(), mWct);
+ mSideStage.addTask(task, mWct);
verify(mWct).reparent(eq(task.token), eq(mRootTask.token), eq(true));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index ab6f76996398..f90af239db01 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -18,7 +18,6 @@ package com.android.wm.shell.splitscreen;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
-
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -33,11 +32,16 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.transition.Transitions;
+import java.util.Optional;
+
+import javax.inject.Provider;
+
public class SplitTestUtils {
static SplitLayout createMockSplitLayout() {
@@ -65,9 +69,13 @@ public class SplitTestUtils {
TestStageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
MainStage mainStage, SideStage sideStage, DisplayImeController imeController,
- SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool) {
+ DisplayInsetsController insetsController, SplitLayout splitLayout,
+ Transitions transitions, TransactionPool transactionPool,
+ SplitscreenEventLogger logger,
+ Provider<Optional<StageTaskUnfoldController>> unfoldController) {
super(context, displayId, syncQueue, rootTDAOrganizer, taskOrganizer, mainStage,
- sideStage, imeController, splitLayout, transitions, transactionPool);
+ sideStage, imeController, insetsController, splitLayout, transitions,
+ transactionPool, logger, unfoldController);
// Prepare default TaskDisplayArea for testing.
mDisplayAreaInfo = new DisplayAreaInfo(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index aca80f3556b9..d5dee824ee9b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -46,18 +46,22 @@ import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
+import android.window.RemoteTransition;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
@@ -71,6 +75,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
+import java.util.Optional;
+
/** Tests for {@link StageCoordinator} */
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -79,9 +85,12 @@ public class SplitTransitionTests extends ShellTestCase {
@Mock private SyncTransactionQueue mSyncQueue;
@Mock private RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
@Mock private DisplayImeController mDisplayImeController;
+ @Mock private DisplayInsetsController mDisplayInsetsController;
@Mock private TransactionPool mTransactionPool;
@Mock private Transitions mTransitions;
@Mock private SurfaceSession mSurfaceSession;
+ @Mock private SplitscreenEventLogger mLogger;
+ @Mock private IconProvider mIconProvider;
private SplitLayout mSplitLayout;
private MainStage mMainStage;
private SideStage mSideStage;
@@ -92,6 +101,7 @@ public class SplitTransitionTests extends ShellTestCase {
private ActivityManager.RunningTaskInfo mSideChild;
@Before
+ @UiThreadTest
public void setup() {
MockitoAnnotations.initMocks(this);
final ShellExecutor mockExecutor = mock(ShellExecutor.class);
@@ -99,15 +109,19 @@ public class SplitTransitionTests extends ShellTestCase {
doReturn(mockExecutor).when(mTransitions).getAnimExecutor();
doReturn(mock(SurfaceControl.Transaction.class)).when(mTransactionPool).acquire();
mSplitLayout = SplitTestUtils.createMockSplitLayout();
- mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mock(
- StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession);
+ mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
+ StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
+ mIconProvider, null);
mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
- mSideStage = new SideStage(mTaskOrganizer, DEFAULT_DISPLAY, mock(
- StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession);
+ mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
+ StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
+ mIconProvider, null);
mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
- mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
- mDisplayImeController, mSplitLayout, mTransitions, mTransactionPool);
+ mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
+ mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
+ mTransactionPool,
+ mLogger, Optional::empty);
mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
.when(mTransitions).startTransition(anyInt(), any(), any());
@@ -125,12 +139,13 @@ public class SplitTransitionTests extends ShellTestCase {
TestRemoteTransition testRemote = new TestRemoteTransition();
IBinder transition = mSplitScreenTransitions.startEnterTransition(
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(), testRemote,
- mStageCoordinator);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
+ new RemoteTransition(testRemote), mStageCoordinator);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
mock(Transitions.TransitionFinishCallback.class));
assertTrue(accepted);
@@ -168,6 +183,7 @@ public class SplitTransitionTests extends ShellTestCase {
mSideStage.onTaskAppeared(newTask, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
mock(Transitions.TransitionFinishCallback.class));
assertFalse(accepted);
assertTrue(mStageCoordinator.isSplitScreenVisible());
@@ -188,6 +204,7 @@ public class SplitTransitionTests extends ShellTestCase {
mSideStage.onTaskVanished(newTask);
accepted = mStageCoordinator.startAnimation(transition, info,
mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
mock(Transitions.TransitionFinishCallback.class));
assertFalse(accepted);
assertTrue(mStageCoordinator.isSplitScreenVisible());
@@ -223,6 +240,7 @@ public class SplitTransitionTests extends ShellTestCase {
mSideStage.onTaskVanished(mSideChild);
mStageCoordinator.startAnimation(transition, info,
mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
mock(Transitions.TransitionFinishCallback.class));
assertFalse(mStageCoordinator.isSplitScreenVisible());
}
@@ -244,6 +262,7 @@ public class SplitTransitionTests extends ShellTestCase {
mSideStage.onTaskVanished(mSideChild);
boolean accepted = mStageCoordinator.startAnimation(transition, info,
mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
mock(Transitions.TransitionFinishCallback.class));
assertTrue(accepted);
assertFalse(mStageCoordinator.isSplitScreenVisible());
@@ -274,6 +293,7 @@ public class SplitTransitionTests extends ShellTestCase {
mSideStage.onTaskVanished(mSideChild);
boolean accepted = mStageCoordinator.startAnimation(transition, info,
mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
mock(Transitions.TransitionFinishCallback.class));
assertTrue(accepted);
assertFalse(mStageCoordinator.isSplitScreenVisible());
@@ -293,13 +313,15 @@ public class SplitTransitionTests extends ShellTestCase {
TransitionInfo enterInfo = createEnterPairInfo();
IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new TestRemoteTransition(), mStageCoordinator);
+ new RemoteTransition(new TestRemoteTransition()), mStageCoordinator);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
mStageCoordinator.startAnimation(enterTransit, enterInfo,
mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
mock(Transitions.TransitionFinishCallback.class));
- mMainStage.activate(new Rect(0, 0, 100, 100), new WindowContainerTransaction());
+ mMainStage.activate(new Rect(0, 0, 100, 100), new WindowContainerTransaction(),
+ true /* includingTopTask */);
}
private boolean containsSplitExit(@NonNull WindowContainerTransaction wct) {
@@ -335,10 +357,11 @@ public class SplitTransitionTests extends ShellTestCase {
@Override
public void startAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback)
+ SurfaceControl.Transaction startTransaction,
+ IRemoteTransitionFinishedCallback finishCallback)
throws RemoteException {
mCalled = true;
- finishCallback.onTransitionFinished(mRemoteFinishWCT);
+ finishCallback.onTransitionFinished(mRemoteFinishWCT, null /* sct */);
}
@Override
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 06b08686bf4c..617e94a4e0a4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -16,17 +16,24 @@
package com.android.wm.shell.splitscreen;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.graphics.Rect;
+import android.window.DisplayAreaInfo;
import android.window.WindowContainerTransaction;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -37,8 +44,10 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -47,26 +56,55 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-/** Tests for {@link StageCoordinator} */
+import java.util.Optional;
+
+import javax.inject.Provider;
+
+/**
+ * Tests for {@link StageCoordinator}
+ */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class StageCoordinatorTests extends ShellTestCase {
- @Mock private ShellTaskOrganizer mTaskOrganizer;
- @Mock private SyncTransactionQueue mSyncQueue;
- @Mock private RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
- @Mock private MainStage mMainStage;
- @Mock private SideStage mSideStage;
- @Mock private DisplayImeController mDisplayImeController;
- @Mock private Transitions mTransitions;
- @Mock private TransactionPool mTransactionPool;
+ @Mock
+ private ShellTaskOrganizer mTaskOrganizer;
+ @Mock
+ private SyncTransactionQueue mSyncQueue;
+ @Mock
+ private RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+ @Mock
+ private MainStage mMainStage;
+ @Mock
+ private SideStage mSideStage;
+ @Mock
+ private StageTaskUnfoldController mMainUnfoldController;
+ @Mock
+ private StageTaskUnfoldController mSideUnfoldController;
+ @Mock
+ private SplitLayout mSplitLayout;
+ @Mock
+ private DisplayImeController mDisplayImeController;
+ @Mock
+ private DisplayInsetsController mDisplayInsetsController;
+ @Mock
+ private Transitions mTransitions;
+ @Mock
+ private TransactionPool mTransactionPool;
+ @Mock
+ private SplitscreenEventLogger mLogger;
+
+ private final Rect mBounds1 = new Rect(10, 20, 30, 40);
+ private final Rect mBounds2 = new Rect(5, 10, 15, 20);
+
private StageCoordinator mStageCoordinator;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
- mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
- mDisplayImeController, null /* splitLayout */, mTransitions, mTransactionPool);
+ mStageCoordinator = createStageCoordinator(/* splitLayout */ null);
+
+ when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
+ when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
}
@Test
@@ -75,9 +113,39 @@ public class StageCoordinatorTests extends ShellTestCase {
mStageCoordinator.moveToSideStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT);
- verify(mMainStage).activate(any(Rect.class), any(WindowContainerTransaction.class));
- verify(mSideStage).addTask(eq(task), any(Rect.class),
- any(WindowContainerTransaction.class));
+ verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class));
+ }
+
+ @Test
+ public void testDisplayAreaAppeared_initializesUnfoldControllers() {
+ mStageCoordinator.onDisplayAreaAppeared(mock(DisplayAreaInfo.class));
+
+ verify(mMainUnfoldController).init();
+ verify(mSideUnfoldController).init();
+ }
+
+ @Test
+ public void testLayoutChanged_topLeftSplitPosition_updatesUnfoldStageBounds() {
+ mStageCoordinator = createStageCoordinator(mSplitLayout);
+ mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
+ clearInvocations(mMainUnfoldController, mSideUnfoldController);
+
+ mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
+
+ verify(mMainUnfoldController).onLayoutChanged(mBounds2);
+ verify(mSideUnfoldController).onLayoutChanged(mBounds1);
+ }
+
+ @Test
+ public void testLayoutChanged_bottomRightSplitPosition_updatesUnfoldStageBounds() {
+ mStageCoordinator = createStageCoordinator(mSplitLayout);
+ mStageCoordinator.setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, null);
+ clearInvocations(mMainUnfoldController, mSideUnfoldController);
+
+ mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
+
+ verify(mMainUnfoldController).onLayoutChanged(mBounds1);
+ verify(mSideUnfoldController).onLayoutChanged(mBounds2);
}
@Test
@@ -90,4 +158,61 @@ public class StageCoordinatorTests extends ShellTestCase {
verify(mSideStage).removeTask(
eq(task.taskId), any(), any(WindowContainerTransaction.class));
}
+
+ @Test
+ public void testExitSplitScreen() {
+ mStageCoordinator.exitSplitScreen(INVALID_TASK_ID,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME);
+ verify(mSideStage).removeAllTasks(any(WindowContainerTransaction.class), eq(false));
+ verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false));
+ }
+
+ @Test
+ public void testExitSplitScreenToMainStage() {
+ final int testTaskId = 12345;
+ when(mMainStage.containsTask(eq(testTaskId))).thenReturn(true);
+ when(mSideStage.containsTask(eq(testTaskId))).thenReturn(false);
+ mStageCoordinator.exitSplitScreen(testTaskId,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME);
+ verify(mMainStage).reorderChild(eq(testTaskId), eq(true),
+ any(WindowContainerTransaction.class));
+ verify(mSideStage).removeAllTasks(any(WindowContainerTransaction.class), eq(false));
+ verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(true));
+ }
+
+ @Test
+ public void testExitSplitScreenToSideStage() {
+ final int testTaskId = 12345;
+ when(mMainStage.containsTask(eq(testTaskId))).thenReturn(false);
+ when(mSideStage.containsTask(eq(testTaskId))).thenReturn(true);
+ mStageCoordinator.exitSplitScreen(testTaskId,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME);
+ verify(mSideStage).reorderChild(eq(testTaskId), eq(true),
+ any(WindowContainerTransaction.class));
+ verify(mSideStage).removeAllTasks(any(WindowContainerTransaction.class), eq(true));
+ verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false));
+ }
+
+ private StageCoordinator createStageCoordinator(SplitLayout splitLayout) {
+ return new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
+ mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
+ mDisplayImeController, mDisplayInsetsController, splitLayout,
+ mTransitions, mTransactionPool, mLogger, new UnfoldControllerProvider());
+ }
+
+ private class UnfoldControllerProvider implements
+ Provider<Optional<StageTaskUnfoldController>> {
+
+ private boolean isMain = true;
+
+ @Override
+ public Optional<StageTaskUnfoldController> get() {
+ if (isMain) {
+ isMain = false;
+ return Optional.of(mMainUnfoldController);
+ } else {
+ return Optional.of(mSideUnfoldController);
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 90b5b37694c6..53d5076f5835 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -21,18 +21,27 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
+import android.os.SystemProperties;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
+import android.window.WindowContainerTransaction;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -47,31 +56,48 @@ import org.mockito.MockitoAnnotations;
/**
* Tests for {@link StageTaskListener}
* Build/Install/Run:
- * atest WMShellUnitTests:StageTaskListenerTests
+ * atest WMShellUnitTests:StageTaskListenerTests
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
-public final class StageTaskListenerTests {
- @Mock private ShellTaskOrganizer mTaskOrganizer;
- @Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
- @Mock private SyncTransactionQueue mSyncQueue;
- @Captor private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor;
+public final class StageTaskListenerTests extends ShellTestCase {
+ private static final boolean ENABLE_SHELL_TRANSITIONS =
+ SystemProperties.getBoolean("persist.debug.shell_transit", false);
+
+ @Mock
+ private ShellTaskOrganizer mTaskOrganizer;
+ @Mock
+ private StageTaskListener.StageListenerCallbacks mCallbacks;
+ @Mock
+ private SyncTransactionQueue mSyncQueue;
+ @Mock
+ private IconProvider mIconProvider;
+ @Mock
+ private StageTaskUnfoldController mStageTaskUnfoldController;
+ @Captor
+ private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor;
private SurfaceSession mSurfaceSession = new SurfaceSession();
+ private SurfaceControl mSurfaceControl;
private ActivityManager.RunningTaskInfo mRootTask;
private StageTaskListener mStageTaskListener;
@Before
+ @UiThreadTest
public void setup() {
MockitoAnnotations.initMocks(this);
mStageTaskListener = new StageTaskListener(
+ mContext,
mTaskOrganizer,
DEFAULT_DISPLAY,
mCallbacks,
mSyncQueue,
- mSurfaceSession);
+ mSurfaceSession,
+ mIconProvider,
+ mStageTaskUnfoldController);
mRootTask = new TestRunningTaskInfoBuilder().build();
mRootTask.parentTaskId = INVALID_TASK_ID;
- mStageTaskListener.onTaskAppeared(mRootTask, new SurfaceControl());
+ mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
+ mStageTaskListener.onTaskAppeared(mRootTask, mSurfaceControl);
}
@Test
@@ -93,15 +119,39 @@ public final class StageTaskListenerTests {
@Test
public void testChildTaskAppeared() {
+ // With shell transitions, the transition manages status changes, so skip this test.
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
final ActivityManager.RunningTaskInfo childTask =
new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
- mStageTaskListener.onTaskAppeared(childTask, new SurfaceControl());
+ mStageTaskListener.onTaskAppeared(childTask, mSurfaceControl);
assertThat(mStageTaskListener.mChildrenTaskInfo.contains(childTask.taskId)).isTrue();
verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true));
}
+ @Test
+ public void testTaskAppeared_notifiesUnfoldListener() {
+ final ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
+
+ mStageTaskListener.onTaskAppeared(task, mSurfaceControl);
+
+ verify(mStageTaskUnfoldController).onTaskAppeared(eq(task), eq(mSurfaceControl));
+ }
+
+ @Test
+ public void testTaskVanished_notifiesUnfoldListener() {
+ final ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
+ mStageTaskListener.onTaskAppeared(task, mSurfaceControl);
+ clearInvocations(mStageTaskUnfoldController);
+
+ mStageTaskListener.onTaskVanished(task);
+
+ verify(mStageTaskUnfoldController).onTaskVanished(eq(task));
+ }
+
@Test(expected = IllegalArgumentException.class)
public void testUnknownTaskVanished() {
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
@@ -110,6 +160,8 @@ public final class StageTaskListenerTests {
@Test
public void testTaskVanished() {
+ // With shell transitions, the transition manages status changes, so skip this test.
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
final ActivityManager.RunningTaskInfo childTask =
new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
mStageTaskListener.mRootTaskInfo = mRootTask;
@@ -131,4 +183,18 @@ public final class StageTaskListenerTests {
mStageTaskListener.onTaskInfoChanged(childTask);
verify(mCallbacks).onNoLongerSupportMultiWindow();
}
+
+ @Test
+ public void testEvictAllChildren() {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mStageTaskListener.evictAllChildren(wct);
+ assertTrue(wct.isEmpty());
+
+ final ActivityManager.RunningTaskInfo childTask =
+ new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
+ mStageTaskListener.onTaskAppeared(childTask, mSurfaceControl);
+
+ mStageTaskListener.evictAllChildren(wct);
+ assertFalse(wct.isEmpty());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index eef0d9bb268f..b866bf9d4192 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -30,6 +30,7 @@ import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -52,21 +53,23 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.UserHandle;
import android.testing.TestableContext;
+import android.view.Display;
import android.view.IWindowSession;
import android.view.InsetsState;
import android.view.Surface;
-import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowMetrics;
import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
import android.window.TaskSnapshot;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
@@ -91,6 +94,8 @@ public class StartingSurfaceDrawerTests {
@Mock
private WindowManager mMockWindowManager;
@Mock
+ private IconProvider mIconProvider;
+ @Mock
private TransactionPool mTransactionPool;
private final Handler mTestHandler = new Handler(Looper.getMainLooper());
@@ -101,28 +106,25 @@ public class StartingSurfaceDrawerTests {
static final class TestStartingSurfaceDrawer extends StartingSurfaceDrawer{
int mAddWindowForTask = 0;
- int mViewThemeResId;
TestStartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
- TransactionPool pool) {
- super(context, splashScreenExecutor, pool);
+ IconProvider iconProvider, TransactionPool pool) {
+ super(context, splashScreenExecutor, iconProvider, pool);
}
@Override
- protected boolean addWindow(int taskId, IBinder appToken,
- View view, WindowManager wm, WindowManager.LayoutParams params, int suggestType) {
+ protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
+ WindowManager.LayoutParams params, int suggestType) {
// listen for addView
mAddWindowForTask = taskId;
- mViewThemeResId = view.getContext().getThemeResId();
// Do not wait for background color
return false;
}
@Override
- protected void removeWindowSynced(int taskId, SurfaceControl leash, Rect frame,
- boolean playRevealAnimation) {
+ protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo) {
// listen for removeView
- if (mAddWindowForTask == taskId) {
+ if (mAddWindowForTask == removalInfo.taskId) {
mAddWindowForTask = 0;
}
}
@@ -157,7 +159,8 @@ public class StartingSurfaceDrawerTests {
doNothing().when(mMockWindowManager).addView(any(), any());
mTestExecutor = new HandlerExecutor(mTestHandler);
mStartingSurfaceDrawer = spy(
- new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mTransactionPool));
+ new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mIconProvider,
+ mTransactionPool));
}
@Test
@@ -172,9 +175,11 @@ public class StartingSurfaceDrawerTests {
eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN));
assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
- mStartingSurfaceDrawer.removeStartingWindow(windowInfo.taskInfo.taskId, null, null, false);
+ StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo();
+ removalInfo.taskId = windowInfo.taskInfo.taskId;
+ mStartingSurfaceDrawer.removeStartingWindow(removalInfo);
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).removeWindowSynced(eq(taskId), any(), any(), eq(false));
+ verify(mStartingSurfaceDrawer).removeWindowSynced(any());
assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0);
}
@@ -183,12 +188,15 @@ public class StartingSurfaceDrawerTests {
final int taskId = 1;
final StartingWindowInfo windowInfo =
createWindowInfo(taskId, 0);
+ final int[] theme = new int[1];
+ doAnswer(invocation -> theme[0] = (Integer) invocation.callRealMethod())
+ .when(mStartingSurfaceDrawer).getSplashScreenTheme(eq(0), any());
+
mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
STARTING_WINDOW_TYPE_SPLASH_SCREEN);
waitHandlerIdle(mTestHandler);
- verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any(),
- eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN));
- assertNotEquals(mStartingSurfaceDrawer.mViewThemeResId, 0);
+ verify(mStartingSurfaceDrawer).getSplashScreenTheme(eq(0), any());
+ assertNotEquals(theme[0], 0);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
index a098a6863493..aad9528bd527 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
@@ -83,8 +83,7 @@ public class TaskSnapshotWindowTest {
createTaskDescription(Color.WHITE, Color.RED, Color.BLUE),
0 /* appearance */, windowFlags /* windowFlags */, 0 /* privateWindowFlags */,
taskBounds, ORIENTATION_PORTRAIT, ACTIVITY_TYPE_STANDARD,
- 100 /* delayRemovalTime */, new InsetsState(),
- null /* clearWindow */, new TestShellExecutor());
+ new InsetsState(), null /* clearWindow */, new TestShellExecutor());
}
private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 2d2ab2c9f674..e39171343bb9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -20,12 +20,20 @@ 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.view.Display.DEFAULT_DISPLAY;
+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_FIRST_CUSTOM;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
+import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
+import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -48,10 +56,14 @@ import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.view.IDisplayWindowListener;
+import android.view.IWindowManager;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
+import android.window.RemoteTransition;
import android.window.TransitionFilter;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -65,17 +77,23 @@ import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
/**
* Tests for the shell transitions.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ShellTransitionTests
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -97,8 +115,7 @@ public class ShellTransitionTests {
@Test
public void testBasicTransitionFlow() {
- Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
- mMainExecutor, mAnimExecutor);
+ Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
IBinder transitToken = new Binder();
@@ -117,8 +134,7 @@ public class ShellTransitionTests {
@Test
public void testNonDefaultHandler() {
- Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
- mMainExecutor, mAnimExecutor);
+ Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
@@ -127,11 +143,13 @@ public class ShellTransitionTests {
TestTransitionHandler testHandler = new TestTransitionHandler() {
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
for (TransitionInfo.Change chg : info.getChanges()) {
if (chg.getMode() == TRANSIT_CHANGE) {
- return super.startAnimation(transition, info, t, finishCallback);
+ return super.startAnimation(transition, info, startTransaction,
+ finishTransaction, finishCallback);
}
}
return false;
@@ -199,8 +217,7 @@ public class ShellTransitionTests {
@Test
public void testRequestRemoteTransition() {
- Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
- mMainExecutor, mAnimExecutor);
+ Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
final boolean[] remoteCalled = new boolean[]{false};
@@ -211,7 +228,7 @@ public class ShellTransitionTests {
SurfaceControl.Transaction t,
IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
remoteCalled[0] = true;
- finishCallback.onTransitionFinished(remoteFinishWCT);
+ finishCallback.onTransitionFinished(remoteFinishWCT, null /* sct */);
}
@Override
@@ -222,7 +239,8 @@ public class ShellTransitionTests {
};
IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
- new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, testRemote));
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */,
+ new RemoteTransition(testRemote)));
verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
@@ -273,9 +291,76 @@ public class ShellTransitionTests {
}
@Test
+ public void testTransitionFilterNotRequirement() {
+ // filter that requires one opening and NO translucent apps
+ TransitionFilter filter = new TransitionFilter();
+ filter.mRequirements = new TransitionFilter.Requirement[]{
+ new TransitionFilter.Requirement(), new TransitionFilter.Requirement()};
+ filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+ filter.mRequirements[1].mFlags = FLAG_TRANSLUCENT;
+ filter.mRequirements[1].mNot = true;
+
+ final TransitionInfo openOnly = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).build();
+ assertTrue(filter.matches(openOnly));
+
+ final TransitionInfo openAndTranslucent = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ openAndTranslucent.getChanges().get(1).setFlags(FLAG_TRANSLUCENT);
+ assertFalse(filter.matches(openAndTranslucent));
+ }
+
+ @Test
+ public void testTransitionFilterChecksTypeSet() {
+ TransitionFilter filter = new TransitionFilter();
+ filter.mTypeSet = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+
+ final TransitionInfo openOnly = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).build();
+ assertTrue(filter.matches(openOnly));
+
+ final TransitionInfo toFrontOnly = new TransitionInfoBuilder(TRANSIT_TO_FRONT)
+ .addChange(TRANSIT_TO_FRONT).build();
+ assertTrue(filter.matches(toFrontOnly));
+
+ final TransitionInfo closeOnly = new TransitionInfoBuilder(TRANSIT_CLOSE)
+ .addChange(TRANSIT_CLOSE).build();
+ assertFalse(filter.matches(closeOnly));
+ }
+
+ @Test
+ public void testTransitionFilterChecksFlags() {
+ TransitionFilter filter = new TransitionFilter();
+ filter.mFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+
+ final TransitionInfo withFlag = new TransitionInfoBuilder(TRANSIT_TO_BACK,
+ TRANSIT_FLAG_KEYGUARD_GOING_AWAY)
+ .addChange(TRANSIT_TO_BACK).build();
+ assertTrue(filter.matches(withFlag));
+
+ final TransitionInfo withoutFlag = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).build();
+ assertFalse(filter.matches(withoutFlag));
+ }
+
+ @Test
+ public void testTransitionFilterChecksNotFlags() {
+ TransitionFilter filter = new TransitionFilter();
+ filter.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+
+ final TransitionInfo withFlag = new TransitionInfoBuilder(TRANSIT_TO_BACK,
+ TRANSIT_FLAG_KEYGUARD_GOING_AWAY)
+ .addChange(TRANSIT_TO_BACK).build();
+ assertFalse(filter.matches(withFlag));
+
+ final TransitionInfo withoutFlag = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).build();
+ assertTrue(filter.matches(withoutFlag));
+ }
+
+ @Test
public void testRegisteredRemoteTransition() {
- Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
- mMainExecutor, mAnimExecutor);
+ Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
final boolean[] remoteCalled = new boolean[]{false};
@@ -285,7 +370,7 @@ public class ShellTransitionTests {
SurfaceControl.Transaction t,
IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
remoteCalled[0] = true;
- finishCallback.onTransitionFinished(null /* wct */);
+ finishCallback.onTransitionFinished(null /* wct */, null /* sct */);
}
@Override
@@ -300,7 +385,7 @@ public class ShellTransitionTests {
new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
- transitions.registerRemote(filter, testRemote);
+ transitions.registerRemote(filter, new RemoteTransition(testRemote));
mMainExecutor.flushAll();
IBinder transitToken = new Binder();
@@ -320,8 +405,7 @@ public class ShellTransitionTests {
@Test
public void testOneShotRemoteHandler() {
- Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
- mMainExecutor, mAnimExecutor);
+ Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
final boolean[] remoteCalled = new boolean[]{false};
@@ -332,7 +416,7 @@ public class ShellTransitionTests {
SurfaceControl.Transaction t,
IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
remoteCalled[0] = true;
- finishCallback.onTransitionFinished(remoteFinishWCT);
+ finishCallback.onTransitionFinished(remoteFinishWCT, null /* sct */);
}
@Override
@@ -344,11 +428,12 @@ public class ShellTransitionTests {
final int transitType = TRANSIT_FIRST_CUSTOM + 1;
- OneShotRemoteHandler oneShot = new OneShotRemoteHandler(mMainExecutor, testRemote);
+ OneShotRemoteHandler oneShot = new OneShotRemoteHandler(mMainExecutor,
+ new RemoteTransition(testRemote));
// Verify that it responds to the remote but not other things.
IBinder transitToken = new Binder();
assertNotNull(oneShot.handleRequest(transitToken,
- new TransitionRequestInfo(transitType, null, testRemote)));
+ new TransitionRequestInfo(transitType, null, new RemoteTransition(testRemote))));
assertNull(oneShot.handleRequest(transitToken,
new TransitionRequestInfo(transitType, null, null)));
@@ -358,15 +443,16 @@ public class ShellTransitionTests {
oneShot.setTransition(transitToken);
IBinder anotherToken = new Binder();
assertFalse(oneShot.startAnimation(anotherToken, new TransitionInfo(transitType, 0),
- mock(SurfaceControl.Transaction.class), testFinish));
+ mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class),
+ testFinish));
assertTrue(oneShot.startAnimation(transitToken, new TransitionInfo(transitType, 0),
- mock(SurfaceControl.Transaction.class), testFinish));
+ mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class),
+ testFinish));
}
@Test
public void testTransitionQueueing() {
- Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
- mMainExecutor, mAnimExecutor);
+ Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
IBinder transitToken1 = new Binder();
@@ -406,8 +492,7 @@ public class ShellTransitionTests {
@Test
public void testTransitionMerging() {
- Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext,
- mMainExecutor, mAnimExecutor);
+ Transitions transitions = createTestTransitions();
mDefaultHandler.setSimulateMerge(true);
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -443,11 +528,80 @@ public class ShellTransitionTests {
assertEquals(0, mDefaultHandler.activeCount());
}
+ @Test
+ public void testShouldRotateSeamlessly() throws Exception {
+ final RunningTaskInfo taskInfo =
+ createTaskInfo(1, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ final RunningTaskInfo taskInfoPip =
+ createTaskInfo(1, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+
+ final DisplayController displays = createTestDisplayController();
+ final @Surface.Rotation int upsideDown = displays
+ .getDisplayLayout(DEFAULT_DISPLAY).getUpsideDownRotation();
+
+ final TransitionInfo normalDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
+ .build())
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo).setRotate().build())
+ .build();
+ assertFalse(DefaultTransitionHandler.isRotationSeamless(normalDispRotate, displays));
+
+ // Seamless if all tasks are seamless
+ final TransitionInfo rotateSeamless = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
+ .build())
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+ .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
+ .build();
+ assertTrue(DefaultTransitionHandler.isRotationSeamless(rotateSeamless, displays));
+
+ // Not seamless if there is PiP (or any other non-seamless task)
+ final TransitionInfo pipDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
+ .build())
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+ .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfoPip)
+ .setRotate().build())
+ .build();
+ assertFalse(DefaultTransitionHandler.isRotationSeamless(pipDispRotate, displays));
+
+ // Not seamless if one of rotations is upside-down
+ final TransitionInfo seamlessUpsideDown = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
+ .setRotate(upsideDown, ROTATION_ANIMATION_UNSPECIFIED).build())
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+ .setRotate(upsideDown, ROTATION_ANIMATION_SEAMLESS).build())
+ .build();
+ assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessUpsideDown, displays));
+
+ // Not seamless if system alert windows
+ final TransitionInfo seamlessButAlert = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(
+ FLAG_IS_DISPLAY | FLAG_DISPLAY_HAS_ALERT_WINDOWS).setRotate().build())
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+ .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
+ .build();
+ assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessButAlert, displays));
+
+ // Not seamless if there is no changed task.
+ final TransitionInfo noTask = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
+ .setRotate().build())
+ .build();
+ assertFalse(DefaultTransitionHandler.isRotationSeamless(noTask, displays));
+ }
+
class TransitionInfoBuilder {
final TransitionInfo mInfo;
TransitionInfoBuilder(@WindowManager.TransitionType int type) {
- mInfo = new TransitionInfo(type, 0 /* flags */);
+ this(type, 0 /* flags */);
+ }
+
+ TransitionInfoBuilder(@WindowManager.TransitionType int type,
+ @WindowManager.TransitionFlags int flags) {
+ mInfo = new TransitionInfo(type, flags);
mInfo.setRootLeash(createMockSurface(true /* valid */), 0, 0);
}
@@ -465,11 +619,53 @@ public class ShellTransitionTests {
return addChange(mode, null /* taskInfo */);
}
+ TransitionInfoBuilder addChange(TransitionInfo.Change change) {
+ mInfo.addChange(change);
+ return this;
+ }
+
TransitionInfo build() {
return mInfo;
}
}
+ class ChangeBuilder {
+ final TransitionInfo.Change mChange;
+
+ ChangeBuilder(@WindowManager.TransitionType int mode) {
+ mChange = new TransitionInfo.Change(null /* token */, null /* leash */);
+ mChange.setMode(mode);
+ }
+
+ ChangeBuilder setFlags(@TransitionInfo.ChangeFlags int flags) {
+ mChange.setFlags(flags);
+ return this;
+ }
+
+ ChangeBuilder setTask(RunningTaskInfo taskInfo) {
+ mChange.setTaskInfo(taskInfo);
+ return this;
+ }
+
+ ChangeBuilder setRotate(int anim) {
+ return setRotate(Surface.ROTATION_90, anim);
+ }
+
+ ChangeBuilder setRotate() {
+ return setRotate(ROTATION_ANIMATION_UNSPECIFIED);
+ }
+
+ ChangeBuilder setRotate(@Surface.Rotation int target, int anim) {
+ mChange.setRotation(Surface.ROTATION_0, target);
+ mChange.setRotationAnimation(anim);
+ return this;
+ }
+
+ TransitionInfo.Change build() {
+ return mChange;
+ }
+ }
+
class TestTransitionHandler implements Transitions.TransitionHandler {
ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>();
final ArrayList<IBinder> mMerged = new ArrayList<>();
@@ -477,7 +673,8 @@ public class ShellTransitionTests {
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
mFinishes.add(finishCallback);
return true;
@@ -540,4 +737,34 @@ public class ShellTransitionTests {
return taskInfo;
}
+ private DisplayController createTestDisplayController() {
+ IWindowManager mockWM = mock(IWindowManager.class);
+ final IDisplayWindowListener[] displayListener = new IDisplayWindowListener[1];
+ try {
+ doReturn(new int[] {DEFAULT_DISPLAY}).when(mockWM).registerDisplayWindowListener(any());
+ } catch (RemoteException e) {
+ // No remote stuff happening, so this can't be hit
+ }
+ DisplayController out = new DisplayController(mContext, mockWM, mMainExecutor);
+ out.initialize();
+ return out;
+ }
+
+ private Transitions createTestTransitions() {
+ return new Transitions(mOrganizer, mTransactionPool, createTestDisplayController(),
+ mContext, mMainExecutor, mAnimExecutor);
+ }
+//
+// private class TestDisplayController extends DisplayController {
+// private final DisplayLayout mTestDisplayLayout;
+// TestDisplayController() {
+// super(mContext, mock(IWindowManager.class), mMainExecutor);
+// mTestDisplayLayout = new DisplayLayout();
+// mTestDisplayLayout.
+// }
+//
+// @Override
+// DisplayLayout
+// }
+
}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 2c299fa32315..2b31bcf78890 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -570,6 +570,7 @@ cc_defaults {
"renderthread/DrawFrameTask.cpp",
"renderthread/EglManager.cpp",
"renderthread/ReliableSurface.cpp",
+ "renderthread/RenderEffectCapabilityQuery.cpp",
"renderthread/VulkanManager.cpp",
"renderthread/VulkanSurface.cpp",
"renderthread/RenderProxy.cpp",
@@ -696,6 +697,7 @@ cc_test {
"tests/unit/MatrixTests.cpp",
"tests/unit/OpBufferTests.cpp",
"tests/unit/PathInterpolatorTests.cpp",
+ "tests/unit/RenderEffectCapabilityQueryTests.cpp",
"tests/unit/RenderNodeDrawableTests.cpp",
"tests/unit/RenderNodeTests.cpp",
"tests/unit/RenderPropertiesTests.cpp",
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 35449875d324..475fd700ccc9 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -50,7 +50,8 @@ bool Properties::showDirtyRegions = false;
bool Properties::skipEmptyFrames = true;
bool Properties::useBufferAge = true;
bool Properties::enablePartialUpdates = true;
-bool Properties::enableRenderEffectCache = false;
+// Default true unless otherwise specified in RenderThread Configuration
+bool Properties::enableRenderEffectCache = true;
DebugLevel Properties::debugLevel = kDebugDisabled;
OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default;
diff --git a/libs/hwui/apex/android_matrix.cpp b/libs/hwui/apex/android_matrix.cpp
index 693b22b62663..04ac3cf0ebc8 100644
--- a/libs/hwui/apex/android_matrix.cpp
+++ b/libs/hwui/apex/android_matrix.cpp
@@ -35,3 +35,10 @@ bool AMatrix_getContents(JNIEnv* env, jobject matrixObj, float values[9]) {
}
return false;
}
+
+jobject AMatrix_newInstance(JNIEnv* env, float values[9]) {
+ jobject matrixObj = android::android_graphics_Matrix_newInstance(env);
+ SkMatrix* m = android::android_graphics_Matrix_getSkMatrix(env, matrixObj);
+ m->set9(values);
+ return matrixObj;
+}
diff --git a/libs/hwui/apex/include/android/graphics/matrix.h b/libs/hwui/apex/include/android/graphics/matrix.h
index 987ad13f7635..5705ba485ba3 100644
--- a/libs/hwui/apex/include/android/graphics/matrix.h
+++ b/libs/hwui/apex/include/android/graphics/matrix.h
@@ -34,6 +34,16 @@ __BEGIN_DECLS
*/
ANDROID_API bool AMatrix_getContents(JNIEnv* env, jobject matrixObj, float values[9]);
+/**
+ * Returns a new Matrix jobject that contains the values passed in as initial values.
+ * @param values The 9 values of the 3x3 matrix in the following order.
+ * values[0] = scaleX values[1] = skewX values[2] = transX
+ * values[3] = skewY values[4] = scaleY values[5] = transY
+ * values[6] = persp0 values[7] = persp1 values[8] = persp2
+ * @return The matrix jobject
+ */
+ANDROID_API jobject AMatrix_newInstance(JNIEnv* env, float values[9]);
+
__END_DECLS
#endif // ANDROID_GRAPHICS_MATRIX_H
diff --git a/libs/hwui/jni/android_graphics_Matrix.cpp b/libs/hwui/jni/android_graphics_Matrix.cpp
index 7338ef24cb58..cf6702e45fff 100644
--- a/libs/hwui/jni/android_graphics_Matrix.cpp
+++ b/libs/hwui/jni/android_graphics_Matrix.cpp
@@ -378,13 +378,17 @@ static const JNINativeMethod methods[] = {
{"nEquals", "(JJ)Z", (void*) SkMatrixGlue::equals}
};
+static jclass sClazz;
static jfieldID sNativeInstanceField;
+static jmethodID sCtor;
int register_android_graphics_Matrix(JNIEnv* env) {
int result = RegisterMethodsOrDie(env, "android/graphics/Matrix", methods, NELEM(methods));
jclass clazz = FindClassOrDie(env, "android/graphics/Matrix");
+ sClazz = MakeGlobalRefOrDie(env, clazz);
sNativeInstanceField = GetFieldIDOrDie(env, clazz, "native_instance", "J");
+ sCtor = GetMethodIDOrDie(env, clazz, "<init>", "()V");
return result;
}
@@ -393,4 +397,7 @@ SkMatrix* android_graphics_Matrix_getSkMatrix(JNIEnv* env, jobject matrixObj) {
return reinterpret_cast<SkMatrix*>(env->GetLongField(matrixObj, sNativeInstanceField));
}
+jobject android_graphics_Matrix_newInstance(JNIEnv* env) {
+ return env->NewObject(sClazz, sCtor);
+}
}
diff --git a/libs/hwui/jni/android_graphics_Matrix.h b/libs/hwui/jni/android_graphics_Matrix.h
index fe90d2ef945d..79de48b46954 100644
--- a/libs/hwui/jni/android_graphics_Matrix.h
+++ b/libs/hwui/jni/android_graphics_Matrix.h
@@ -25,6 +25,9 @@ namespace android {
/* Gets the underlying SkMatrix from a Matrix object. */
SkMatrix* android_graphics_Matrix_getSkMatrix(JNIEnv* env, jobject matrixObj);
+/* Creates a new Matrix java object. */
+jobject android_graphics_Matrix_newInstance(JNIEnv* env);
+
} // namespace android
#endif // _ANDROID_GRAPHICS_MATRIX_H_
diff --git a/libs/hwui/libhwui.map.txt b/libs/hwui/libhwui.map.txt
index 73de0d12a60b..77b8a44d85a1 100644
--- a/libs/hwui/libhwui.map.txt
+++ b/libs/hwui/libhwui.map.txt
@@ -28,6 +28,7 @@ LIBHWUI {
register_android_graphics_GraphicsStatsService;
zygote_preload_graphics;
AMatrix_getContents;
+ AMatrix_newInstance;
APaint_createPaint;
APaint_destroyPaint;
APaint_setBlendMode;
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 383c79b27918..c7d7a17a23eb 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -28,6 +28,7 @@
#include "Frame.h"
#include "Properties.h"
+#include "RenderEffectCapabilityQuery.h"
#include "utils/Color.h"
#include "utils/StringUtils.h"
@@ -148,7 +149,11 @@ void EglManager::initialize() {
mHasWideColorGamutSupport = EglExtensions.glColorSpace && hasWideColorSpaceExtension;
auto* vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
- Properties::enableRenderEffectCache = (strcmp(vendor, "Qualcomm") != 0);
+ auto* version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
+ Properties::enableRenderEffectCache = supportsRenderEffectCache(
+ vendor, version);
+ ALOGV("RenderEffectCache supported %d on driver version %s",
+ Properties::enableRenderEffectCache, version);
}
EGLConfig EglManager::load8BitsConfig(EGLDisplay display, EglManager::SwapBehavior swapBehavior) {
diff --git a/libs/hwui/renderthread/RenderEffectCapabilityQuery.cpp b/libs/hwui/renderthread/RenderEffectCapabilityQuery.cpp
new file mode 100644
index 000000000000..a003988575c8
--- /dev/null
+++ b/libs/hwui/renderthread/RenderEffectCapabilityQuery.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <utils/Log.h>
+
+bool supportsRenderEffectCache(const char* vendor, const char* version) {
+ if (strcmp(vendor, "Qualcomm") != 0) {
+ return true;
+ }
+
+ int major;
+ int minor;
+ int driverMajor;
+ int driverMinor;
+ int n = sscanf(version,"OpenGL ES %d.%d V@%d.%d",
+ &major,
+ &minor,
+ &driverMajor,
+ &driverMinor);
+ // Ensure we have parsed the vendor string properly and we have either
+ // a newer major driver version, or the minor version is rev'ed
+ // Based on b/198227600#comment5 it appears that the corresponding fix
+ // is in driver version 571.0
+ return n == 4 && driverMajor >= 571;
+} \ No newline at end of file
diff --git a/libs/hwui/renderthread/RenderEffectCapabilityQuery.h b/libs/hwui/renderthread/RenderEffectCapabilityQuery.h
new file mode 100644
index 000000000000..ea673dd0386d
--- /dev/null
+++ b/libs/hwui/renderthread/RenderEffectCapabilityQuery.h
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+/**
+ * Verify if the provided vendor and version supports RenderEffect caching
+ * behavior.
+ *
+ * Certain Open GL Driver implementations run into blocking scenarios
+ * with Fence::waitForever without a corresponding signal to unblock
+ * This happens during attempts to cache SkImage instances across frames
+ * especially in circumstances using RenderEffect/SkImageFilter internally.
+ * So detect the corresponding GL Vendor and driver version to determine if
+ * caching SkImage instances across frames is supported.
+ * See b/197263715 & b/193145089
+ * @param vendor Vendor of the GL driver
+ * @param version Version of the GL driver from the given vendor
+ * @return True if a RenderEffect result can be cached across frames,
+ * false otherwise
+ */
+bool supportsRenderEffectCache(const char* vendor, const char* version);
diff --git a/libs/hwui/tests/unit/EglManagerTests.cpp b/libs/hwui/tests/unit/EglManagerTests.cpp
index f7f240663397..7f2e1589ae6c 100644
--- a/libs/hwui/tests/unit/EglManagerTests.cpp
+++ b/libs/hwui/tests/unit/EglManagerTests.cpp
@@ -17,6 +17,7 @@
#include <gtest/gtest.h>
#include "renderthread/EglManager.h"
+#include "renderthread/RenderEffectCapabilityQuery.h"
#include "tests/common/TestContext.h"
using namespace android;
@@ -41,4 +42,17 @@ TEST(EglManager, doesSurfaceLeak) {
}
eglManager.destroy();
+}
+
+TEST(EglManager, verifyRenderEffectCacheSupported) {
+ EglManager eglManager;
+ eglManager.initialize();
+ auto* vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
+ auto* version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
+ // Make sure that EglManager initializes Properties::enableRenderEffectCache
+ // based on the given gl vendor and version within EglManager->initialize()
+ bool renderEffectCacheSupported = supportsRenderEffectCache(vendor, version);
+ EXPECT_EQ(renderEffectCacheSupported,
+ Properties::enableRenderEffectCache);
+ eglManager.destroy();
} \ No newline at end of file
diff --git a/libs/hwui/tests/unit/RenderEffectCapabilityQueryTests.cpp b/libs/hwui/tests/unit/RenderEffectCapabilityQueryTests.cpp
new file mode 100644
index 000000000000..0ee654929b3b
--- /dev/null
+++ b/libs/hwui/tests/unit/RenderEffectCapabilityQueryTests.cpp
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+#include "renderthread/RenderEffectCapabilityQuery.h"
+#include "tests/common/TestContext.h"
+
+TEST(RenderEffectCapabilityQuery, testSupportedVendor) {
+ ASSERT_TRUE(supportsRenderEffectCache("Google", "OpenGL ES 1.4 V@0.0"));
+}
+
+TEST(RenderEffectCapabilityQuery, testSupportedVendorWithDifferentVersion) {
+ ASSERT_TRUE(supportsRenderEffectCache("Google", "OpenGL ES 1.3 V@571.0"));
+}
+
+TEST(RenderEffectCapabilityQuery, testVendorWithSupportedVersion) {
+ ASSERT_TRUE(supportsRenderEffectCache("Qualcomm", "OpenGL ES 1.5 V@571.0"));
+}
+
+TEST(RenderEffectCapabilityQuery, testVendorWithSupportedPatchVersion) {
+ ASSERT_TRUE(supportsRenderEffectCache("Qualcomm", "OpenGL ES 1.5 V@571.1"));
+}
+
+TEST(RenderEffectCapabilityQuery, testVendorWithNewerThanSupportedMajorVersion) {
+ ASSERT_TRUE(supportsRenderEffectCache("Qualcomm", "OpenGL ES 1.5 V@572.0"));
+}
+
+TEST(RenderEffectCapabilityQuery, testVendorWithNewerThanSupportedMinorVersion) {
+ ASSERT_TRUE(supportsRenderEffectCache("Qualcomm", "OpenGL ES 1.5 V@571.2"));
+}
+
+TEST(RenderEffectCapabilityQuery, testVendorWithUnsupportedMajorVersion) {
+ ASSERT_FALSE(supportsRenderEffectCache("Qualcomm", "OpenGL ES 1.0 V@570.1"));
+}
+
+TEST(RenderEffectCapabilityQuery, testVendorWithUnsupportedVersion) {
+ ASSERT_FALSE(supportsRenderEffectCache("Qualcomm", "OpenGL ES 1.1 V@570.0"));
+}
+
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index acd8bced0612..d10e68816d28 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -153,8 +153,7 @@ void SpriteController::doUpdateSprites() {
|| update.state.surfaceHeight < desiredHeight) {
needApplyTransaction = true;
- t.setSize(update.state.surfaceControl,
- desiredWidth, desiredHeight);
+ update.state.surfaceControl->updateDefaultBufferSize(desiredWidth, desiredHeight);
update.state.surfaceWidth = desiredWidth;
update.state.surfaceHeight = desiredHeight;
update.state.surfaceDrawn = false;
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index a031b4cfc911..c22ab9463736 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -447,13 +447,26 @@ public final class AudioAttributes implements Parcelable {
*/
public static final int FLAG_CAPTURE_PRIVATE = 0x1 << 13;
+ /**
+ * @hide
+ * Flag indicating the audio content has been processed to provide a virtual multichannel
+ * audio experience
+ */
+ public static final int FLAG_CONTENT_SPATIALIZED = 0x1 << 14;
+
+ /**
+ * @hide
+ * Flag indicating the audio content is to never be spatialized
+ */
+ public static final int FLAG_NEVER_SPATIALIZE = 0x1 << 15;
// Note that even though FLAG_MUTE_HAPTIC is stored as a flag bit, it is not here since
// it is known as a boolean value outside of AudioAttributes.
private static final int FLAG_ALL = FLAG_AUDIBILITY_ENFORCED | FLAG_SECURE | FLAG_SCO
| FLAG_BEACON | FLAG_HW_AV_SYNC | FLAG_HW_HOTWORD | FLAG_BYPASS_INTERRUPTION_POLICY
| FLAG_BYPASS_MUTE | FLAG_LOW_LATENCY | FLAG_DEEP_BUFFER | FLAG_NO_MEDIA_PROJECTION
- | FLAG_NO_SYSTEM_CAPTURE | FLAG_CAPTURE_PRIVATE;
+ | FLAG_NO_SYSTEM_CAPTURE | FLAG_CAPTURE_PRIVATE | FLAG_CONTENT_SPATIALIZED
+ | FLAG_NEVER_SPATIALIZE;
private final static int FLAG_ALL_PUBLIC = FLAG_AUDIBILITY_ENFORCED |
FLAG_HW_AV_SYNC | FLAG_LOW_LATENCY;
/* mask of flags that can be set by SDK and System APIs through the Builder */
@@ -615,6 +628,49 @@ public final class AudioAttributes implements Parcelable {
}
/**
+ * Return true if the audio content associated with these attributes has already been
+ * spatialized, that is it has already been processed to offer a binaural or transaural
+ * immersive audio experience.
+ * @return {@code true} if the content has been processed
+ */
+ public boolean isContentSpatialized() {
+ return (mFlags & FLAG_CONTENT_SPATIALIZED) != 0;
+ }
+
+ /** @hide */
+ @IntDef(flag = false, value = {
+ SPATIALIZATION_BEHAVIOR_AUTO,
+ SPATIALIZATION_BEHAVIOR_NEVER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SpatializationBehavior {};
+
+ /**
+ * Constant indicating the audio content associated with these attributes will follow the
+ * default platform behavior with regards to which content will be spatialized or not.
+ * @see #getSpatializationBehavior()
+ * @see Spatializer
+ */
+ public static final int SPATIALIZATION_BEHAVIOR_AUTO = 0;
+
+ /**
+ * Constant indicating the audio content associated with these attributes should never
+ * be virtualized.
+ * @see #getSpatializationBehavior()
+ * @see Spatializer
+ */
+ public static final int SPATIALIZATION_BEHAVIOR_NEVER = 1;
+
+ /**
+ * Return the behavior affecting whether spatialization will be used.
+ * @return the spatialization behavior
+ */
+ public @SpatializationBehavior int getSpatializationBehavior() {
+ return ((mFlags & FLAG_NEVER_SPATIALIZE) != 0)
+ ? SPATIALIZATION_BEHAVIOR_NEVER : SPATIALIZATION_BEHAVIOR_AUTO;
+ }
+
+ /**
* Return the capture policy.
* @return the capture policy set by {@link Builder#setAllowedCapturePolicy(int)} or
* the default if it was not called.
@@ -657,6 +713,8 @@ public final class AudioAttributes implements Parcelable {
private int mSource = MediaRecorder.AudioSource.AUDIO_SOURCE_INVALID;
private int mFlags = 0x0;
private boolean mMuteHapticChannels = true;
+ private boolean mIsContentSpatialized = false;
+ private int mSpatializationBehavior = SPATIALIZATION_BEHAVIOR_AUTO;
private HashSet<String> mTags = new HashSet<String>();
private Bundle mBundle;
private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT;
@@ -687,6 +745,8 @@ public final class AudioAttributes implements Parcelable {
mFlags = aa.getAllFlags();
mTags = (HashSet<String>) aa.mTags.clone();
mMuteHapticChannels = aa.areHapticChannelsMuted();
+ mIsContentSpatialized = aa.isContentSpatialized();
+ mSpatializationBehavior = aa.getSpatializationBehavior();
}
/**
@@ -719,6 +779,12 @@ public final class AudioAttributes implements Parcelable {
if (mMuteHapticChannels) {
aa.mFlags |= FLAG_MUTE_HAPTIC;
}
+ if (mIsContentSpatialized) {
+ aa.mFlags |= FLAG_CONTENT_SPATIALIZED;
+ }
+ if (mSpatializationBehavior == SPATIALIZATION_BEHAVIOR_NEVER) {
+ aa.mFlags |= FLAG_NEVER_SPATIALIZE;
+ }
if (mPrivacySensitive == PRIVACY_SENSITIVE_DEFAULT) {
// capturing for camcorder or communication is private by default to
@@ -906,6 +972,35 @@ public final class AudioAttributes implements Parcelable {
}
/**
+ * Specifies whether the content has already been processed for spatialization.
+ * If it has, setting this to true will prevent issues such as double-processing.
+ * @param isSpatialized
+ * @return the same Builder instance
+ */
+ public @NonNull Builder setIsContentSpatialized(boolean isSpatialized) {
+ mIsContentSpatialized = isSpatialized;
+ return this;
+ }
+
+ /**
+ * Sets the behavior affecting whether spatialization will be used.
+ * @param sb the spatialization behavior
+ * @return the same Builder instance
+ *
+ */
+ public @NonNull Builder setSpatializationBehavior(@SpatializationBehavior int sb) {
+ switch (sb) {
+ case SPATIALIZATION_BEHAVIOR_NEVER:
+ case SPATIALIZATION_BEHAVIOR_AUTO:
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid spatialization behavior " + sb);
+ }
+ mSpatializationBehavior = sb;
+ return this;
+ }
+
+ /**
* @hide
* Replaces flags.
* @param flags any combination of {@link AudioAttributes#FLAG_ALL}.
@@ -990,6 +1085,8 @@ public final class AudioAttributes implements Parcelable {
mContentType = attributes.mContentType;
mFlags = attributes.getAllFlags();
mMuteHapticChannels = attributes.areHapticChannelsMuted();
+ mIsContentSpatialized = attributes.isContentSpatialized();
+ mSpatializationBehavior = attributes.getSpatializationBehavior();
mTags = attributes.mTags;
mBundle = attributes.mBundle;
mSource = attributes.mSource;
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 1644ec892c7e..a8199c4d028e 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -175,6 +175,28 @@ import java.util.Objects;
* <br>These masks are an ORed composite of individual channel masks. For example
* {@link #CHANNEL_OUT_STEREO} is composed of {@link #CHANNEL_OUT_FRONT_LEFT} and
* {@link #CHANNEL_OUT_FRONT_RIGHT}.
+ * <p>
+ * The following diagram represents the layout of the output channels, as seen from above
+ * the listener (in the center at the "lis" position, facing the front-center channel).
+ * <pre>
+ * TFL ----- TFC ----- TFR T is Top
+ * | \ | / |
+ * | FL --- FC --- FR | F is Front
+ * | |\ | /| |
+ * | | BFL-BFC-BFR | | BF is Bottom Front
+ * | | | |
+ * | FWL lis FWR | W is Wide
+ * | | | |
+ * TSL SL TC SR TSR S is Side
+ * | | | |
+ * | BL --- BC -- BR | B is Back
+ * | / \ |
+ * TBL ----- TBC ----- TBR C is Center, L/R is Left/Right
+ * </pre>
+ * All "T" (top) channels are above the listener, all "BF" (bottom-front) channels are below the
+ * listener, all others are in the listener's horizontal plane. When used in conjunction, LFE1 and
+ * LFE2 are below the listener, when used alone, LFE plane is undefined.
+ * See the channel definitions for the abbreviations
*
* <h5 id="channelIndexMask">Channel index masks</h5>
* Channel index masks are introduced in API {@link android.os.Build.VERSION_CODES#M}. They allow
@@ -417,43 +439,62 @@ public final class AudioFormat implements Parcelable {
// Output channel mask definitions below are translated to the native values defined in
// in /system/media/audio/include/system/audio.h in the JNI code of AudioTrack
+ /** Front left output channel (see FL in channel diagram) */
public static final int CHANNEL_OUT_FRONT_LEFT = 0x4;
+ /** Front right output channel (see FR in channel diagram) */
public static final int CHANNEL_OUT_FRONT_RIGHT = 0x8;
+ /** Front center output channel (see FC in channel diagram) */
public static final int CHANNEL_OUT_FRONT_CENTER = 0x10;
+ /** LFE "low frequency effect" channel
+ * When used in conjunction with {@link #CHANNEL_OUT_LOW_FREQUENCY_2}, it is intended
+ * to contain the left low-frequency effect signal, also referred to as "LFE1"
+ * in ITU-R BS.2159-8 */
public static final int CHANNEL_OUT_LOW_FREQUENCY = 0x20;
+ /** Back left output channel (see BL in channel diagram) */
public static final int CHANNEL_OUT_BACK_LEFT = 0x40;
+ /** Back right output channel (see BR in channel diagram) */
public static final int CHANNEL_OUT_BACK_RIGHT = 0x80;
public static final int CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x100;
public static final int CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x200;
+ /** Back center output channel (see BC in channel diagram) */
public static final int CHANNEL_OUT_BACK_CENTER = 0x400;
+ /** Side left output channel (see SL in channel diagram) */
public static final int CHANNEL_OUT_SIDE_LEFT = 0x800;
+ /** Side right output channel (see SR in channel diagram) */
public static final int CHANNEL_OUT_SIDE_RIGHT = 0x1000;
- /** @hide */
+ /** Top center (above listener) output channel (see TC in channel diagram) */
public static final int CHANNEL_OUT_TOP_CENTER = 0x2000;
- /** @hide */
+ /** Top front left output channel (see TFL in channel diagram above FL) */
public static final int CHANNEL_OUT_TOP_FRONT_LEFT = 0x4000;
- /** @hide */
+ /** Top front center output channel (see TFC in channel diagram above FC) */
public static final int CHANNEL_OUT_TOP_FRONT_CENTER = 0x8000;
- /** @hide */
+ /** Top front right output channel (see TFR in channel diagram above FR) */
public static final int CHANNEL_OUT_TOP_FRONT_RIGHT = 0x10000;
- /** @hide */
+ /** Top back left output channel (see TBL in channel diagram above BL) */
public static final int CHANNEL_OUT_TOP_BACK_LEFT = 0x20000;
- /** @hide */
+ /** Top back center output channel (see TBC in channel diagram above BC) */
public static final int CHANNEL_OUT_TOP_BACK_CENTER = 0x40000;
- /** @hide */
+ /** Top back right output channel (see TBR in channel diagram above BR) */
public static final int CHANNEL_OUT_TOP_BACK_RIGHT = 0x80000;
- /** @hide */
+ /** Top side left output channel (see TSL in channel diagram above SL) */
public static final int CHANNEL_OUT_TOP_SIDE_LEFT = 0x100000;
- /** @hide */
+ /** Top side right output channel (see TSR in channel diagram above SR) */
public static final int CHANNEL_OUT_TOP_SIDE_RIGHT = 0x200000;
- /** @hide */
+ /** Bottom front left output channel (see BFL in channel diagram below FL) */
public static final int CHANNEL_OUT_BOTTOM_FRONT_LEFT = 0x400000;
- /** @hide */
+ /** Bottom front center output channel (see BFC in channel diagram below FC) */
public static final int CHANNEL_OUT_BOTTOM_FRONT_CENTER = 0x800000;
- /** @hide */
+ /** Bottom front right output channel (see BFR in channel diagram below FR) */
public static final int CHANNEL_OUT_BOTTOM_FRONT_RIGHT = 0x1000000;
- /** @hide */
+ /** The second LFE channel
+ * When used in conjunction with {@link #CHANNEL_OUT_LOW_FREQUENCY}, it is intended
+ * to contain the right low-frequency effect signal, also referred to as "LFE2"
+ * in ITU-R BS.2159-8 */
public static final int CHANNEL_OUT_LOW_FREQUENCY_2 = 0x2000000;
+ /** Front wide left output channel (see FWL in channel diagram) */
+ public static final int CHANNEL_OUT_FRONT_WIDE_LEFT = 0x4000000;
+ /** Front wide right output channel (see FWR in channel diagram) */
+ public static final int CHANNEL_OUT_FRONT_WIDE_RIGHT = 0x8000000;
public static final int CHANNEL_OUT_MONO = CHANNEL_OUT_FRONT_LEFT;
public static final int CHANNEL_OUT_STEREO = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT);
@@ -466,6 +507,7 @@ public final class AudioFormat implements Parcelable {
public static final int CHANNEL_OUT_SURROUND = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_BACK_CENTER);
// aka 5POINT1_BACK
+ /** Output channel mask for 5.1 */
public static final int CHANNEL_OUT_5POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT);
/** @hide */
@@ -477,26 +519,39 @@ public final class AudioFormat implements Parcelable {
@Deprecated public static final int CHANNEL_OUT_7POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT |
CHANNEL_OUT_FRONT_LEFT_OF_CENTER | CHANNEL_OUT_FRONT_RIGHT_OF_CENTER);
+ /** Output channel mask for 7.1 */
// matches AUDIO_CHANNEL_OUT_7POINT1
public static final int CHANNEL_OUT_7POINT1_SURROUND = (
CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_FRONT_RIGHT |
CHANNEL_OUT_SIDE_LEFT | CHANNEL_OUT_SIDE_RIGHT |
CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT |
CHANNEL_OUT_LOW_FREQUENCY);
- /** @hide */
+ /** Output channel mask for 5.1.2
+ * Same as 5.1 with the addition of left and right top channels */
public static final int CHANNEL_OUT_5POINT1POINT2 = (CHANNEL_OUT_5POINT1 |
CHANNEL_OUT_TOP_SIDE_LEFT | CHANNEL_OUT_TOP_SIDE_RIGHT);
- /** @hide */
+ /** Output channel mask for 5.1.4
+ * Same as 5.1 with the addition of four top channels */
public static final int CHANNEL_OUT_5POINT1POINT4 = (CHANNEL_OUT_5POINT1 |
CHANNEL_OUT_TOP_FRONT_LEFT | CHANNEL_OUT_TOP_FRONT_RIGHT |
CHANNEL_OUT_TOP_BACK_LEFT | CHANNEL_OUT_TOP_BACK_RIGHT);
- /** @hide */
+ /** Output channel mask for 7.1.2
+ * Same as 7.1 with the addition of left and right top channels*/
public static final int CHANNEL_OUT_7POINT1POINT2 = (CHANNEL_OUT_7POINT1_SURROUND |
CHANNEL_OUT_TOP_SIDE_LEFT | CHANNEL_OUT_TOP_SIDE_RIGHT);
- /** @hide */
+ /** Output channel mask for 7.1.4
+ * Same as 7.1 with the addition of four top channels */
public static final int CHANNEL_OUT_7POINT1POINT4 = (CHANNEL_OUT_7POINT1_SURROUND |
CHANNEL_OUT_TOP_FRONT_LEFT | CHANNEL_OUT_TOP_FRONT_RIGHT |
CHANNEL_OUT_TOP_BACK_LEFT | CHANNEL_OUT_TOP_BACK_RIGHT);
+ /** Output channel mask for 9.1.4
+ * Same as 7.1.4 with the addition of left and right front wide channels */
+ public static final int CHANNEL_OUT_9POINT1POINT4 = (CHANNEL_OUT_7POINT1POINT4
+ | CHANNEL_OUT_FRONT_WIDE_LEFT | CHANNEL_OUT_FRONT_WIDE_RIGHT);
+ /** Output channel mask for 9.1.6
+ * Same as 9.1.4 with the addition of left and right top side channels */
+ public static final int CHANNEL_OUT_9POINT1POINT6 = (CHANNEL_OUT_9POINT1POINT4
+ | CHANNEL_OUT_TOP_SIDE_LEFT | CHANNEL_OUT_TOP_SIDE_RIGHT);
/** @hide */
public static final int CHANNEL_OUT_13POINT_360RA = (
CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_FRONT_RIGHT |
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 3b9c05bbe64f..38f9607c9529 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -805,7 +805,7 @@ public class AudioManager {
}
@UnsupportedAppUsage
- private static IAudioService getService()
+ static IAudioService getService()
{
if (sService != null) {
return sService;
@@ -2439,6 +2439,19 @@ public class AudioManager {
}
//====================================================================
+ // Immersive audio
+
+ /**
+ * Return a handle to the optional platform's {@link Spatializer}
+ * @return the {@code Spatializer} instance.
+ * @see Spatializer#getImmersiveAudioLevel() to check for the level of support of the effect
+ * on the platform
+ */
+ public @NonNull Spatializer getSpatializer() {
+ return new Spatializer(this);
+ }
+
+ //====================================================================
// Bluetooth SCO control
/**
* Sticky broadcast intent action indicating that the Bluetooth SCO audio
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 69d1889d5762..5d9f2909903b 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -18,6 +18,7 @@ package android.media;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
import android.bluetooth.BluetoothCodecConfig;
@@ -2000,6 +2001,46 @@ public class AudioSystem
*/
public static native int setVibratorInfos(@NonNull List<Vibrator> vibrators);
+ /**
+ * @hide
+ * If a spatializer effect is present on the platform, this will return an
+ * ISpatializer interface to control this feature.
+ * If no spatializer is present, a null interface is returned.
+ * The INativeSpatializerCallback passed must not be null.
+ * Only one ISpatializer interface can exist at a given time. The native audio policy
+ * service will reject the request if an interface was already acquired and previous owner
+ * did not die or call ISpatializer.release().
+ * @param callback the callback to receive state updates if the ISpatializer
+ * interface is acquired.
+ * @return the ISpatializer interface made available to control the
+ * platform spatializer
+ */
+ @Nullable
+ public static ISpatializer getSpatializer(INativeSpatializerCallback callback) {
+ return ISpatializer.Stub.asInterface(nativeGetSpatializer(callback));
+ }
+ private static native IBinder nativeGetSpatializer(INativeSpatializerCallback callback);
+
+ /**
+ * @hide
+ * Queries if some kind of spatialization will be performed if the audio playback context
+ * described by the provided arguments is present.
+ * The context is made of:
+ * - The audio attributes describing the playback use case.
+ * - The audio configuration describing the audio format, channels, sampling rate ...
+ * - The devices describing the sink audio device selected for playback.
+ * All arguments are optional and only the specified arguments are used to match against
+ * supported criteria. For instance, supplying no argument will tell if spatialization is
+ * supported or not in general.
+ * @param attributes audio attributes describing the playback use case
+ * @param format audio configuration describing the audio format, channels, sampling rate...
+ * @param devices the sink audio device selected for playback
+ * @return true if spatialization is enabled for this context, false otherwise.
+ */
+ public static native boolean canBeSpatialized(AudioAttributes attributes,
+ AudioFormat format,
+ AudioDeviceAttributes[] devices);
+
// Items shared with audio service
/**
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 23d9532e11a0..476a9a58ef4b 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1613,7 +1613,9 @@ public class AudioTrack extends PlayerBase
AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT |
AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_CENTER |
AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT |
- AudioFormat.CHANNEL_OUT_LOW_FREQUENCY_2;
+ AudioFormat.CHANNEL_OUT_LOW_FREQUENCY_2 |
+ AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT |
+ AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT;
// Returns a boolean whether the attributes, format, bufferSizeInBytes, mode allow
// power saving to be automatically enabled for an AudioTrack. Returns false if
@@ -1787,6 +1789,8 @@ public class AudioTrack extends PlayerBase
| AudioFormat.CHANNEL_OUT_TOP_SIDE_RIGHT);
put("bottom front", AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT
| AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT);
+ put("front wide", AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT
+ | AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT);
}};
/**
@@ -1801,9 +1805,15 @@ public class AudioTrack extends PlayerBase
return false;
}
final int channelCount = AudioFormat.channelCountFromOutChannelMask(channelConfig);
- final int channelCountLimit = AudioFormat.isEncodingLinearFrames(encoding)
- ? AudioSystem.OUT_CHANNEL_COUNT_MAX // PCM limited to OUT_CHANNEL_COUNT_MAX
- : AudioSystem.FCC_24; // Compressed limited to 24 channels
+ final int channelCountLimit;
+ try {
+ channelCountLimit = AudioFormat.isEncodingLinearFrames(encoding)
+ ? AudioSystem.OUT_CHANNEL_COUNT_MAX // PCM limited to OUT_CHANNEL_COUNT_MAX
+ : AudioSystem.FCC_24; // Compressed limited to 24 channels
+ } catch (IllegalArgumentException iae) {
+ loge("Unsupported encoding " + iae);
+ return false;
+ }
if (channelCount > channelCountLimit) {
loge("Channel configuration contains too many channels for encoding "
+ encoding + "(" + channelCount + " > " + channelCountLimit + ")");
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 0b35ebed966a..abd067cfe2f3 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -20,6 +20,7 @@ import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
+import android.media.AudioFormat;
import android.media.AudioFocusInfo;
import android.media.AudioPlaybackConfiguration;
import android.media.AudioRecordingConfiguration;
@@ -34,6 +35,10 @@ import android.media.IPlaybackConfigDispatcher;
import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer;
import android.media.IStrategyPreferredDevicesDispatcher;
+import android.media.ISpatializerCallback;
+import android.media.ISpatializerHeadTrackingModeCallback;
+import android.media.ISpatializerHeadToSoundStagePoseCallback;
+import android.media.ISpatializerOutputCallback;
import android.media.IVolumeController;
import android.media.IVolumeController;
import android.media.PlayerBase;
@@ -392,4 +397,54 @@ interface IAudioService {
void registerModeDispatcher(IAudioModeDispatcher dispatcher);
oneway void unregisterModeDispatcher(IAudioModeDispatcher dispatcher);
+
+ int getSpatializerImmersiveAudioLevel();
+
+ boolean isSpatializerEnabled();
+
+ boolean isSpatializerAvailable();
+
+ void setSpatializerEnabled(boolean enabled);
+
+ boolean canBeSpatialized(in AudioAttributes aa, in AudioFormat af);
+
+ void registerSpatializerCallback(in ISpatializerCallback cb);
+
+ void unregisterSpatializerCallback(in ISpatializerCallback cb);
+
+ void registerSpatializerHeadTrackingCallback(in ISpatializerHeadTrackingModeCallback cb);
+
+ void unregisterSpatializerHeadTrackingCallback(in ISpatializerHeadTrackingModeCallback cb);
+
+ void registerHeadToSoundstagePoseCallback(in ISpatializerHeadToSoundStagePoseCallback cb);
+
+ void unregisterHeadToSoundstagePoseCallback(in ISpatializerHeadToSoundStagePoseCallback cb);
+
+ List<AudioDeviceAttributes> getSpatializerCompatibleAudioDevices();
+
+ void addSpatializerCompatibleAudioDevice(in AudioDeviceAttributes ada);
+
+ void removeSpatializerCompatibleAudioDevice(in AudioDeviceAttributes ada);
+
+ void setDesiredHeadTrackingMode(int mode);
+
+ int getDesiredHeadTrackingMode();
+
+ int[] getSupportedHeadTrackingModes();
+
+ int getActualHeadTrackingMode();
+
+ oneway void setSpatializerGlobalTransform(in float[] transform);
+
+ oneway void recenterHeadTracker();
+
+ void setSpatializerParameter(int key, in byte[] value);
+
+ void getSpatializerParameter(int key, inout byte[] value);
+
+ int getSpatializerOutput();
+
+ void registerSpatializerOutputCallback(in ISpatializerOutputCallback cb);
+
+ void unregisterSpatializerOutputCallback(in ISpatializerOutputCallback cb);
}
diff --git a/media/java/android/media/IMediaRouterClient.aidl b/media/java/android/media/IMediaRouterClient.aidl
index 9b4912373122..6b754e157cfb 100644
--- a/media/java/android/media/IMediaRouterClient.aidl
+++ b/media/java/android/media/IMediaRouterClient.aidl
@@ -23,4 +23,5 @@ oneway interface IMediaRouterClient {
void onStateChanged();
void onRestoreRoute();
void onGroupRouteSelected(String routeId);
+ void onGlobalA2dpChanged(boolean a2dpOn);
}
diff --git a/media/java/android/media/ISpatializerCallback.aidl b/media/java/android/media/ISpatializerCallback.aidl
new file mode 100644
index 000000000000..50f91e737fc5
--- /dev/null
+++ b/media/java/android/media/ISpatializerCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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 android.media;
+
+/**
+ * AIDL for the AudioService to signal Spatializer state changes.
+ *
+ * {@hide}
+ */
+oneway interface ISpatializerCallback {
+
+ void dispatchSpatializerEnabledChanged(boolean enabled);
+
+ void dispatchSpatializerAvailableChanged(boolean available);
+}
diff --git a/media/java/android/media/ISpatializerHeadToSoundStagePoseCallback.aidl b/media/java/android/media/ISpatializerHeadToSoundStagePoseCallback.aidl
new file mode 100644
index 000000000000..01a146599394
--- /dev/null
+++ b/media/java/android/media/ISpatializerHeadToSoundStagePoseCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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 android.media;
+
+/**
+ * AIDL for the AudioService to signal Spatializer state changes.
+ *
+ * {@hide}
+ */
+oneway interface ISpatializerHeadToSoundStagePoseCallback {
+
+ /**
+ * The pose is sent as an array of 6 float values, the first 3 are the translation vector, the
+ * other 3 are the rotation vector.
+ */
+ void dispatchPoseChanged(in float[] pose);
+}
diff --git a/media/java/android/media/ISpatializerHeadTrackingModeCallback.aidl b/media/java/android/media/ISpatializerHeadTrackingModeCallback.aidl
new file mode 100644
index 000000000000..c61f86e4c60e
--- /dev/null
+++ b/media/java/android/media/ISpatializerHeadTrackingModeCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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 android.media;
+
+/**
+ * AIDL for the AudioService to signal Spatializer head tracking mode changes.
+ *
+ * {@hide}
+ */
+oneway interface ISpatializerHeadTrackingModeCallback {
+
+ void dispatchSpatializerActualHeadTrackingModeChanged(int mode);
+
+ void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode);
+}
diff --git a/media/java/android/media/ISpatializerOutputCallback.aidl b/media/java/android/media/ISpatializerOutputCallback.aidl
new file mode 100644
index 000000000000..57572a81a366
--- /dev/null
+++ b/media/java/android/media/ISpatializerOutputCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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 android.media;
+
+/**
+ * AIDL for the AudioService to signal Spatializer output changes.
+ *
+ * {@hide}
+ */
+oneway interface ISpatializerOutputCallback {
+
+ void dispatchSpatializerOutputChanged(int output);
+}
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 9bf0db52f66d..0847be34d90f 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -683,6 +683,19 @@ public final class MediaFormat {
public static final String KEY_CHANNEL_MASK = "channel-mask";
/**
+ * A key describing the maximum number of channels that can be output by an audio decoder.
+ * By default, the decoder will output the same number of channels as present in the encoded
+ * stream, if supported. Set this value to limit the number of output channels, and use
+ * the downmix information in the stream, if available.
+ * <p>Values larger than the number of channels in the content to decode behave like the number
+ * of channels in the content (if applicable), for instance passing 99 for a 5.1 audio stream
+ * behaves like passing 6.
+ * <p>This key is only used during decoding.
+ */
+ public static final String KEY_MAX_OUTPUT_CHANNEL_COUNT =
+ "max-output-channel-count";
+
+ /**
* A key describing the number of frames to trim from the start of the decoded audio stream.
* The associated value is an integer.
*/
diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java
index 3a5216e1c4e7..6eb1af8ac980 100644
--- a/media/java/android/media/MediaMetrics.java
+++ b/media/java/android/media/MediaMetrics.java
@@ -53,6 +53,7 @@ public class MediaMetrics {
public static final String AUDIO_VOLUME = AUDIO + SEPARATOR + "volume";
public static final String AUDIO_VOLUME_EVENT = AUDIO_VOLUME + SEPARATOR + "event";
public static final String AUDIO_MODE = AUDIO + SEPARATOR + "mode";
+ public static final String METRICS_MANAGER = "metrics" + SEPARATOR + "manager";
}
/**
@@ -120,10 +121,11 @@ public class MediaMetrics {
createKey("gainDb", Double.class);
public static final Key<String> GROUP =
createKey("group", String.class);
- // For volume
- public static final Key<Integer> INDEX = createKey("index", Integer.class);
- public static final Key<Integer> MAX_INDEX = createKey("maxIndex", Integer.class);
- public static final Key<Integer> MIN_INDEX = createKey("minIndex", Integer.class);
+
+ public static final Key<Integer> INDEX = createKey("index", Integer.class); // volume
+ public static final Key<String> LOG_SESSION_ID = createKey("logSessionId", String.class);
+ public static final Key<Integer> MAX_INDEX = createKey("maxIndex", Integer.class); // vol
+ public static final Key<Integer> MIN_INDEX = createKey("minIndex", Integer.class); // vol
public static final Key<String> MODE =
createKey("mode", String.class); // audio_mode
public static final Key<String> MUTE =
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index dfdfeacb065a..748ae52d6c0c 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -654,12 +654,9 @@ public class MediaRouter {
final class Client extends IMediaRouterClient.Stub {
@Override
public void onStateChanged() {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- if (Client.this == mClient) {
- updateClientState();
- }
+ mHandler.post(() -> {
+ if (Client.this == mClient) {
+ updateClientState();
}
});
}
@@ -693,6 +690,26 @@ public class MediaRouter {
}
});
}
+
+ // Called when the selection of a connected device (phone speaker or BT devices)
+ // is changed.
+ @Override
+ public void onGlobalA2dpChanged(boolean a2dpOn) {
+ mHandler.post(() -> {
+ if (mSelectedRoute == null || mBluetoothA2dpRoute == null) {
+ return;
+ }
+ if (mSelectedRoute.isDefault() && a2dpOn) {
+ setSelectedRoute(mBluetoothA2dpRoute, /*explicit=*/ false);
+ dispatchRouteUnselected(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
+ dispatchRouteSelected(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
+ } else if (mSelectedRoute.isBluetooth() && !a2dpOn) {
+ setSelectedRoute(mDefaultAudioVideo, /*explicit=*/ false);
+ dispatchRouteUnselected(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
+ dispatchRouteSelected(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
+ }
+ });
+ }
}
}
@@ -1351,6 +1368,9 @@ public class MediaRouter {
}
static void dispatchRouteSelected(int type, RouteInfo info) {
+ if (DEBUG) {
+ Log.d(TAG, "Dispatching route selected: " + info);
+ }
for (CallbackInfo cbi : sStatic.mCallbacks) {
if (cbi.filterRouteEvent(info)) {
cbi.cb.onRouteSelected(cbi.router, type, info);
@@ -1359,6 +1379,9 @@ public class MediaRouter {
}
static void dispatchRouteUnselected(int type, RouteInfo info) {
+ if (DEBUG) {
+ Log.d(TAG, "Dispatching route unselected: " + info);
+ }
for (CallbackInfo cbi : sStatic.mCallbacks) {
if (cbi.filterRouteEvent(info)) {
cbi.cb.onRouteUnselected(cbi.router, type, info);
diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java
new file mode 100644
index 000000000000..e6fff392c264
--- /dev/null
+++ b/media/java/android/media/Spatializer.java
@@ -0,0 +1,1089 @@
+/*
+ * 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 android.media;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.media.permission.ClearCallingIdentityContext;
+import android.media.permission.SafeCloseable;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Spatializer provides access to querying capabilities and behavior of sound spatialization
+ * on the device.
+ * Sound spatialization simulates sounds originating around the listener as if they were coming
+ * from virtual speakers placed around the listener.<br>
+ * Support for spatialization is optional, use {@link AudioManager#getSpatializer()} to obtain an
+ * instance of this class if the feature is supported.
+ *
+ */
+public class Spatializer {
+
+ private final @NonNull AudioManager mAm;
+
+ private static final String TAG = "Spatializer";
+
+ /**
+ * @hide
+ * Constructor with AudioManager acting as proxy to AudioService
+ * @param am a non-null AudioManager
+ */
+ protected Spatializer(@NonNull AudioManager am) {
+ mAm = Objects.requireNonNull(am);
+ }
+
+ /**
+ * Returns whether spatialization is enabled or not.
+ * A false value can originate for instance from the user electing to
+ * disable the feature, or when the feature is not supported on the device (indicated
+ * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}).
+ * <br>
+ * Note that this state reflects a platform-wide state of the "desire" to use spatialization,
+ * but availability of the audio processing is still dictated by the compatibility between
+ * the effect and the hardware configuration, as indicated by {@link #isAvailable()}.
+ * @return {@code true} if spatialization is enabled
+ * @see #isAvailable()
+ */
+ public boolean isEnabled() {
+ try {
+ return mAm.getService().isSpatializerEnabled();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error querying isSpatializerEnabled, returning false", e);
+ return false;
+ }
+ }
+
+ /**
+ * Returns whether spatialization is available.
+ * Reasons for spatialization being unavailable include situations where audio output is
+ * incompatible with sound spatialization, such as playback on a monophonic speaker.<br>
+ * Note that spatialization can be available, but disabled by the user, in which case this
+ * method would still return {@code true}, whereas {@link #isEnabled()}
+ * would return {@code false}.<br>
+ * Also when the feature is not supported on the device (indicated
+ * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}),
+ * the return value will be false.
+ * @return {@code true} if the spatializer effect is available and capable
+ * of processing the audio for the current configuration of the device,
+ * {@code false} otherwise.
+ * @see #isEnabled()
+ */
+ public boolean isAvailable() {
+ try {
+ return mAm.getService().isSpatializerAvailable();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error querying isSpatializerAvailable, returning false", e);
+ return false;
+ }
+ }
+
+ /** @hide */
+ @IntDef(flag = false, value = {
+ SPATIALIZER_IMMERSIVE_LEVEL_OTHER,
+ SPATIALIZER_IMMERSIVE_LEVEL_NONE,
+ SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ImmersiveAudioLevel {};
+
+ /**
+ * Constant indicating the {@code Spatializer} on this device supports a spatialization
+ * mode that differs from the ones available at this SDK level.
+ * @see #getImmersiveAudioLevel()
+ */
+ public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1;
+
+ /**
+ * Constant indicating there are no spatialization capabilities supported on this device.
+ * @see #getImmersiveAudioLevel()
+ */
+ public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0;
+
+ /**
+ * Constant indicating the {@code Spatializer} on this device supports multichannel
+ * spatialization.
+ * @see #getImmersiveAudioLevel()
+ */
+ public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1;
+
+ /**
+ * @hide
+ * Constant indicating the {@code Spatializer} on this device supports the spatialization of
+ * multichannel bed plus objects.
+ * @see #getImmersiveAudioLevel()
+ */
+ public static final int SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS = 2;
+
+ /** @hide */
+ @IntDef(flag = false, value = {
+ HEAD_TRACKING_MODE_UNSUPPORTED,
+ HEAD_TRACKING_MODE_DISABLED,
+ HEAD_TRACKING_MODE_RELATIVE_WORLD,
+ HEAD_TRACKING_MODE_RELATIVE_DEVICE,
+ }) public @interface HeadTrackingMode {};
+
+ /** @hide */
+ @IntDef(flag = false, value = {
+ HEAD_TRACKING_MODE_DISABLED,
+ HEAD_TRACKING_MODE_RELATIVE_WORLD,
+ HEAD_TRACKING_MODE_RELATIVE_DEVICE,
+ }) public @interface HeadTrackingModeSet {};
+
+ /** @hide */
+ @IntDef(flag = false, value = {
+ HEAD_TRACKING_MODE_RELATIVE_WORLD,
+ HEAD_TRACKING_MODE_RELATIVE_DEVICE,
+ }) public @interface HeadTrackingModeSupported {};
+
+ /**
+ * @hide
+ * Constant indicating head tracking is not supported by this {@code Spatializer}
+ * @see #getHeadTrackingMode()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public static final int HEAD_TRACKING_MODE_UNSUPPORTED = -2;
+
+ /**
+ * @hide
+ * Constant indicating head tracking is disabled on this {@code Spatializer}
+ * @see #getHeadTrackingMode()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public static final int HEAD_TRACKING_MODE_DISABLED = -1;
+
+ /**
+ * @hide
+ * Constant indicating head tracking is in a mode whose behavior is unknown. This is not an
+ * error state but represents a customized behavior not defined by this API.
+ * @see #getHeadTrackingMode()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public static final int HEAD_TRACKING_MODE_OTHER = 0;
+
+ /**
+ * @hide
+ * Constant indicating head tracking is tracking the user's position / orientation relative to
+ * the world around them
+ * @see #getHeadTrackingMode()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public static final int HEAD_TRACKING_MODE_RELATIVE_WORLD = 1;
+
+ /**
+ * @hide
+ * Constant indicating head tracking is tracking the user's position / orientation relative to
+ * the device
+ * @see #getHeadTrackingMode()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2;
+
+ /**
+ * Return the level of support for the spatialization feature on this device.
+ * This level of support is independent of whether the {@code Spatializer} is currently
+ * enabled or available and will not change over time.
+ * @return the level of spatialization support
+ * @see #isEnabled()
+ * @see #isAvailable()
+ */
+ public @ImmersiveAudioLevel int getImmersiveAudioLevel() {
+ int level = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ try {
+ level = mAm.getService().getSpatializerImmersiveAudioLevel();
+ } catch (Exception e) { /* using NONE */ }
+ return level;
+ }
+
+ /**
+ * @hide
+ * Enables / disables the spatializer effect.
+ * Changing the enabled state will trigger the public
+ * {@link OnSpatializerStateChangedListener#onSpatializerEnabledChanged(Spatializer, boolean)}
+ * registered listeners.
+ * @param enabled {@code true} for enabling the effect
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void setEnabled(boolean enabled) {
+ try {
+ mAm.getService().setSpatializerEnabled(enabled);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling setSpatializerEnabled", e);
+ }
+ }
+
+ /**
+ * An interface to be notified of changes to the state of the spatializer effect.
+ */
+ public interface OnSpatializerStateChangedListener {
+ /**
+ * Called when the enabled state of the spatializer effect changes
+ * @param spat the {@code Spatializer} instance whose state changed
+ * @param enabled {@code true} if the spatializer effect is enabled on the device,
+ * {@code false} otherwise
+ * @see #isEnabled()
+ */
+ void onSpatializerEnabledChanged(@NonNull Spatializer spat, boolean enabled);
+
+ /**
+ * Called when the availability of the spatializer effect changes
+ * @param spat the {@code Spatializer} instance whose state changed
+ * @param available {@code true} if the spatializer effect is available and capable
+ * of processing the audio for the current configuration of the device,
+ * {@code false} otherwise.
+ * @see #isAvailable()
+ */
+ void onSpatializerAvailableChanged(@NonNull Spatializer spat, boolean available);
+ }
+
+ /**
+ * @hide
+ * An interface to be notified of changes to the head tracking mode, used by the spatializer
+ * effect.
+ * Changes to the mode may come from explicitly setting a different mode
+ * (see {@link #setDesiredHeadTrackingMode(int)}) or a change in system conditions (see
+ * {@link #getHeadTrackingMode()}
+ * @see #addOnHeadTrackingModeChangedListener(Executor, OnHeadTrackingModeChangedListener)
+ * @see #removeOnHeadTrackingModeChangedListener(OnHeadTrackingModeChangedListener)
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ public interface OnHeadTrackingModeChangedListener {
+ /**
+ * Called when the actual head tracking mode of the spatializer changed.
+ * @param spatializer the {@code Spatializer} instance whose head tracking mode is changing
+ * @param mode the new head tracking mode
+ */
+ void onHeadTrackingModeChanged(@NonNull Spatializer spatializer,
+ @HeadTrackingMode int mode);
+
+ /**
+ * Called when the desired head tracking mode of the spatializer changed
+ * @param spatializer the {@code Spatializer} instance whose head tracking mode was set
+ * @param mode the newly set head tracking mode
+ */
+ void onDesiredHeadTrackingModeChanged(@NonNull Spatializer spatializer,
+ @HeadTrackingModeSet int mode);
+ }
+
+
+ /**
+ * @hide
+ * An interface to be notified of changes to the output stream used by the spatializer
+ * effect.
+ * @see #getOutput()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ public interface OnSpatializerOutputChangedListener {
+ /**
+ * Called when the id of the output stream of the spatializer effect changed.
+ * @param spatializer the {@code Spatializer} instance whose output is updated
+ * @param output the id of the output stream, or 0 when there is no spatializer output
+ */
+ void onSpatializerOutputChanged(@NonNull Spatializer spatializer,
+ @IntRange(from = 0) int output);
+ }
+
+ /**
+ * @hide
+ * An interface to be notified of updates to the head to soundstage pose, as represented by the
+ * current head tracking mode.
+ * @see #setOnHeadToSoundstagePoseUpdatedListener(Executor, OnHeadToSoundstagePoseUpdatedListener)
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ public interface OnHeadToSoundstagePoseUpdatedListener {
+ /**
+ * Called when the head to soundstage transform is updated
+ * @param spatializer the {@code Spatializer} instance affected by the pose update
+ * @param pose the new pose data representing the transform between the frame
+ * of reference for the current head tracking mode (see
+ * {@link #getHeadTrackingMode()}) and the device being tracked (for
+ * instance a pair of headphones with a head tracker).<br>
+ * The head pose data is represented as an array of six float values, where
+ * the first three values are the translation vector, and the next three
+ * are the rotation vector.
+ */
+ void onHeadToSoundstagePoseUpdated(@NonNull Spatializer spatializer,
+ @NonNull float[] pose);
+ }
+
+ /**
+ * Returns whether audio of the given {@link AudioFormat}, played with the given
+ * {@link AudioAttributes} can be spatialized.
+ * Note that the result reflects the capabilities of the device and may change when
+ * audio accessories are connected/disconnected (e.g. wired headphones plugged in or not).
+ * The result is independent from whether spatialization processing is enabled or not.
+ * @param attributes the {@code AudioAttributes} of the content as used for playback
+ * @param format the {@code AudioFormat} of the content as used for playback
+ * @return {@code true} if the device is capable of spatializing the combination of audio format
+ * and attributes, {@code false} otherwise.
+ */
+ public boolean canBeSpatialized(
+ @NonNull AudioAttributes attributes, @NonNull AudioFormat format) {
+ try {
+ return mAm.getService().canBeSpatialized(
+ Objects.requireNonNull(attributes), Objects.requireNonNull(format));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error querying canBeSpatialized for attr:" + attributes
+ + " format:" + format + " returning false", e);
+ return false;
+ }
+ }
+
+ /**
+ * Adds a listener to be notified of changes to the enabled state of the
+ * {@code Spatializer}.
+ * @param executor the {@code Executor} handling the callback
+ * @param listener the listener to receive enabled state updates
+ * @see #isEnabled()
+ */
+ public void addOnSpatializerStateChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnSpatializerStateChangedListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ synchronized (mStateListenerLock) {
+ if (hasSpatializerStateListener(listener)) {
+ throw new IllegalArgumentException(
+ "Called addOnSpatializerStateChangedListener() "
+ + "on a previously registered listener");
+ }
+ // lazy initialization of the list of strategy-preferred device listener
+ if (mStateListeners == null) {
+ mStateListeners = new ArrayList<>();
+ }
+ mStateListeners.add(new StateListenerInfo(listener, executor));
+ if (mStateListeners.size() == 1) {
+ // register binder for callbacks
+ if (mInfoDispatcherStub == null) {
+ mInfoDispatcherStub =
+ new SpatializerInfoDispatcherStub();
+ }
+ try {
+ mAm.getService().registerSpatializerCallback(
+ mInfoDispatcherStub);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes a previously added listener for changes to the enabled state of the
+ * {@code Spatializer}.
+ * @param listener the listener to receive enabled state updates
+ * @see #isEnabled()
+ */
+ public void removeOnSpatializerStateChangedListener(
+ @NonNull OnSpatializerStateChangedListener listener) {
+ Objects.requireNonNull(listener);
+ synchronized (mStateListenerLock) {
+ if (!removeStateListener(listener)) {
+ throw new IllegalArgumentException(
+ "Called removeOnSpatializerStateChangedListener() "
+ + "on an unregistered listener");
+ }
+ if (mStateListeners.size() == 0) {
+ // unregister binder for callbacks
+ try {
+ mAm.getService().unregisterSpatializerCallback(mInfoDispatcherStub);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ mInfoDispatcherStub = null;
+ mStateListeners = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Returns the list of playback devices that are compatible with the playback of multichannel
+ * audio through virtualization
+ * @return a list of devices. An empty list indicates virtualization is not supported.
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() {
+ try {
+ return mAm.getService().getSpatializerCompatibleAudioDevices();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error querying getSpatializerCompatibleAudioDevices(), "
+ + " returning empty list", e);
+ return new ArrayList<AudioDeviceAttributes>(0);
+ }
+ }
+
+ /**
+ * @hide
+ * Adds a playback device to the list of devices compatible with the playback of multichannel
+ * audio through spatialization.
+ * @see #getCompatibleAudioDevices()
+ * @param ada the audio device compatible with spatialization
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
+ try {
+ mAm.getService().addSpatializerCompatibleAudioDevice(Objects.requireNonNull(ada));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling addSpatializerCompatibleAudioDevice(), ", e);
+ }
+ }
+
+ /**
+ * @hide
+ * Remove a playback device from the list of devices compatible with the playback of
+ * multichannel audio through spatialization.
+ * @see #getCompatibleAudioDevices()
+ * @param ada the audio device incompatible with spatialization
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
+ try {
+ mAm.getService().removeSpatializerCompatibleAudioDevice(Objects.requireNonNull(ada));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling removeSpatializerCompatibleAudioDevice(), ", e);
+ }
+ }
+
+ private final Object mStateListenerLock = new Object();
+ /**
+ * List of listeners for state listener and their associated Executor.
+ * List is lazy-initialized on first registration
+ */
+ @GuardedBy("mStateListenerLock")
+ private @Nullable ArrayList<StateListenerInfo> mStateListeners;
+
+ @GuardedBy("mStateListenerLock")
+ private @Nullable SpatializerInfoDispatcherStub mInfoDispatcherStub;
+
+ private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub {
+ @Override
+ public void dispatchSpatializerEnabledChanged(boolean enabled) {
+ // make a shallow copy of listeners so callback is not executed under lock
+ final ArrayList<StateListenerInfo> stateListeners;
+ synchronized (mStateListenerLock) {
+ if (mStateListeners == null || mStateListeners.size() == 0) {
+ return;
+ }
+ stateListeners = (ArrayList<StateListenerInfo>) mStateListeners.clone();
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ for (StateListenerInfo info : stateListeners) {
+ info.mExecutor.execute(() ->
+ info.mListener.onSpatializerEnabledChanged(Spatializer.this, enabled));
+ }
+ }
+ }
+
+ @Override
+ public void dispatchSpatializerAvailableChanged(boolean available) {
+ // make a shallow copy of listeners so callback is not executed under lock
+ final ArrayList<StateListenerInfo> stateListeners;
+ synchronized (mStateListenerLock) {
+ if (mStateListeners == null || mStateListeners.size() == 0) {
+ return;
+ }
+ stateListeners = (ArrayList<StateListenerInfo>) mStateListeners.clone();
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ for (StateListenerInfo info : stateListeners) {
+ info.mExecutor.execute(() ->
+ info.mListener.onSpatializerAvailableChanged(
+ Spatializer.this, available));
+ }
+ }
+ }
+ }
+
+ private static class StateListenerInfo {
+ final @NonNull OnSpatializerStateChangedListener mListener;
+ final @NonNull Executor mExecutor;
+
+ StateListenerInfo(@NonNull OnSpatializerStateChangedListener listener,
+ @NonNull Executor exe) {
+ mListener = listener;
+ mExecutor = exe;
+ }
+ }
+
+ @GuardedBy("mStateListenerLock")
+ private boolean hasSpatializerStateListener(OnSpatializerStateChangedListener listener) {
+ return getStateListenerInfo(listener) != null;
+ }
+
+ @GuardedBy("mStateListenerLock")
+ private @Nullable StateListenerInfo getStateListenerInfo(
+ OnSpatializerStateChangedListener listener) {
+ if (mStateListeners == null) {
+ return null;
+ }
+ for (StateListenerInfo info : mStateListeners) {
+ if (info.mListener == listener) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ @GuardedBy("mStateListenerLock")
+ /**
+ * @return true if the listener was removed from the list
+ */
+ private boolean removeStateListener(OnSpatializerStateChangedListener listener) {
+ final StateListenerInfo infoToRemove = getStateListenerInfo(listener);
+ if (infoToRemove != null) {
+ mStateListeners.remove(infoToRemove);
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * @hide
+ * Return the current head tracking mode as used by the system.
+ * Note this may differ from the desired head tracking mode. Reasons for the two to differ
+ * include: a head tracking device is not available for the current audio output device,
+ * the transmission conditions between the tracker and device have deteriorated and tracking
+ * has been disabled.
+ * @see #getDesiredHeadTrackingMode()
+ * @return the current head tracking mode
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public @HeadTrackingMode int getHeadTrackingMode() {
+ try {
+ return mAm.getService().getActualHeadTrackingMode();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getActualHeadTrackingMode", e);
+ return HEAD_TRACKING_MODE_UNSUPPORTED;
+ }
+
+ }
+
+ /**
+ * @hide
+ * Return the desired head tracking mode.
+ * Note this may differ from the actual head tracking mode, reflected by
+ * {@link #getHeadTrackingMode()}.
+ * @return the desired head tring mode
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public @HeadTrackingMode int getDesiredHeadTrackingMode() {
+ try {
+ return mAm.getService().getDesiredHeadTrackingMode();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getDesiredHeadTrackingMode", e);
+ return HEAD_TRACKING_MODE_UNSUPPORTED;
+ }
+ }
+
+ /**
+ * @hide
+ * Returns the list of supported head tracking modes.
+ * @return the list of modes that can be used in {@link #setDesiredHeadTrackingMode(int)} to
+ * enable head tracking. The list will be empty if {@link #getHeadTrackingMode()}
+ * is {@link #HEAD_TRACKING_MODE_UNSUPPORTED}. Values can be
+ * {@link #HEAD_TRACKING_MODE_OTHER},
+ * {@link #HEAD_TRACKING_MODE_RELATIVE_WORLD} or
+ * {@link #HEAD_TRACKING_MODE_RELATIVE_DEVICE}
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public @NonNull List<Integer> getSupportedHeadTrackingModes() {
+ try {
+ final int[] modes = mAm.getService().getSupportedHeadTrackingModes();
+ final ArrayList<Integer> list = new ArrayList<>(0);
+ for (int mode : modes) {
+ list.add(mode);
+ }
+ return list;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getSupportedHeadTrackModes", e);
+ return new ArrayList(0);
+ }
+ }
+
+ /**
+ * @hide
+ * Sets the desired head tracking mode.
+ * Note a set desired mode may differ from the actual head tracking mode.
+ * @see #getHeadTrackingMode()
+ * @param mode the desired head tracking mode, one of the values returned by
+ * {@link #getSupportedHeadTrackModes()}, or {@link #HEAD_TRACKING_MODE_DISABLED} to
+ * disable head tracking.
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void setDesiredHeadTrackingMode(@HeadTrackingModeSet int mode) {
+ try {
+ mAm.getService().setDesiredHeadTrackingMode(mode);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling setDesiredHeadTrackingMode to " + mode, e);
+ }
+ }
+
+ /**
+ * @hide
+ * Recenters the head tracking at the current position / orientation.
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void recenterHeadTracker() {
+ try {
+ mAm.getService().recenterHeadTracker();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling recenterHeadTracker", e);
+ }
+ }
+
+ /**
+ * @hide
+ * Adds a listener to be notified of changes to the head tracking mode of the
+ * {@code Spatializer}
+ * @param executor the {@code Executor} handling the callbacks
+ * @param listener the listener to register
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void addOnHeadTrackingModeChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnHeadTrackingModeChangedListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ synchronized (mHeadTrackingListenerLock) {
+ if (hasListener(listener, mHeadTrackingListeners)) {
+ throw new IllegalArgumentException(
+ "Called addOnHeadTrackingModeChangedListener() "
+ + "on a previously registered listener");
+ }
+ // lazy initialization of the list of strategy-preferred device listener
+ if (mHeadTrackingListeners == null) {
+ mHeadTrackingListeners = new ArrayList<>();
+ }
+ mHeadTrackingListeners.add(
+ new ListenerInfo<OnHeadTrackingModeChangedListener>(listener, executor));
+ if (mHeadTrackingListeners.size() == 1) {
+ // register binder for callbacks
+ if (mHeadTrackingDispatcherStub == null) {
+ mHeadTrackingDispatcherStub =
+ new SpatializerHeadTrackingDispatcherStub();
+ }
+ try {
+ mAm.getService().registerSpatializerHeadTrackingCallback(
+ mHeadTrackingDispatcherStub);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Removes a previously added listener for changes to the head tracking mode of the
+ * {@code Spatializer}.
+ * @param listener the listener to unregister
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void removeOnHeadTrackingModeChangedListener(
+ @NonNull OnHeadTrackingModeChangedListener listener) {
+ Objects.requireNonNull(listener);
+ synchronized (mHeadTrackingListenerLock) {
+ if (!removeListener(listener, mHeadTrackingListeners)) {
+ throw new IllegalArgumentException(
+ "Called removeOnHeadTrackingModeChangedListener() "
+ + "on an unregistered listener");
+ }
+ if (mHeadTrackingListeners.size() == 0) {
+ // unregister binder for callbacks
+ try {
+ mAm.getService().unregisterSpatializerHeadTrackingCallback(
+ mHeadTrackingDispatcherStub);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ mHeadTrackingDispatcherStub = null;
+ mHeadTrackingListeners = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Set the listener to receive head to soundstage pose updates.
+ * @param executor the {@code Executor} handling the callbacks
+ * @param listener the listener to register
+ * @see #clearOnHeadToSoundstagePoseUpdatedListener()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void setOnHeadToSoundstagePoseUpdatedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnHeadToSoundstagePoseUpdatedListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ synchronized (mPoseListenerLock) {
+ if (mPoseListener != null) {
+ throw new IllegalStateException("Trying to overwrite existing listener");
+ }
+ mPoseListener =
+ new ListenerInfo<OnHeadToSoundstagePoseUpdatedListener>(listener, executor);
+ mPoseDispatcher = new SpatializerPoseDispatcherStub();
+ try {
+ mAm.getService().registerHeadToSoundstagePoseCallback(mPoseDispatcher);
+ } catch (RemoteException e) {
+ mPoseListener = null;
+ mPoseDispatcher = null;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Clears the listener for head to soundstage pose updates
+ * @see #setOnHeadToSoundstagePoseUpdatedListener(Executor, OnHeadToSoundstagePoseUpdatedListener)
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void clearOnHeadToSoundstagePoseUpdatedListener() {
+ synchronized (mPoseListenerLock) {
+ if (mPoseDispatcher == null) {
+ throw (new IllegalStateException("No listener to clear"));
+ }
+ try {
+ mAm.getService().unregisterHeadToSoundstagePoseCallback(mPoseDispatcher);
+ } catch (RemoteException e) { }
+ mPoseListener = null;
+ mPoseDispatcher = null;
+ }
+ }
+
+ /**
+ * @hide
+ * Sets an additional transform over the soundstage.
+ * The transform represents the pose of the soundstage, relative
+ * to either the device (in {@link #HEAD_TRACKING_MODE_RELATIVE_DEVICE} mode), the world (in
+ * {@link #HEAD_TRACKING_MODE_RELATIVE_WORLD}) or the listener’s head (in
+ * {@link #HEAD_TRACKING_MODE_DISABLED} mode).
+ * @param transform an array of 6 float values, the first 3 are the translation vector, the
+ * other 3 are the rotation vector.
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void setGlobalTransform(@NonNull float[] transform) {
+ if (Objects.requireNonNull(transform).length != 6) {
+ throw new IllegalArgumentException("transform array must be of size 6, was "
+ + transform.length);
+ }
+ try {
+ mAm.getService().setSpatializerGlobalTransform(transform);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling setGlobalTransform", e);
+ }
+ }
+
+ /**
+ * @hide
+ * Sets a parameter on the platform spatializer effect implementation.
+ * This is to be used for vendor-specific configurations of their effect, keys and values are
+ * not reuseable across implementations.
+ * @param key the parameter to change
+ * @param value an array for the value of the parameter to change
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void setEffectParameter(int key, @NonNull byte[] value) {
+ Objects.requireNonNull(value);
+ try {
+ mAm.getService().setSpatializerParameter(key, value);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling setEffectParameter", e);
+ }
+ }
+
+ /**
+ * @hide
+ * Retrieves a parameter value from the platform spatializer effect implementation.
+ * This is to be used for vendor-specific configurations of their effect, keys and values are
+ * not reuseable across implementations.
+ * @param key the parameter for which the value is queried
+ * @param value a non-empty array to contain the return value. The caller is responsible for
+ * passing an array of size matching the parameter.
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void getEffectParameter(int key, @NonNull byte[] value) {
+ Objects.requireNonNull(value);
+ try {
+ mAm.getService().getSpatializerParameter(key, value);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getEffectParameter", e);
+ }
+ }
+
+ /**
+ * @hide
+ * Returns the id of the output stream used for the spatializer effect playback
+ * @return id of the output stream, or 0 if no spatializer playback is active
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public @IntRange(from = 0) int getOutput() {
+ try {
+ return mAm.getService().getSpatializerOutput();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getSpatializerOutput", e);
+ return 0;
+ }
+ }
+
+ /**
+ * @hide
+ * Sets the listener to receive spatializer effect output updates
+ * @param executor the {@code Executor} handling the callbacks
+ * @param listener the listener to register
+ * @see #clearOnSpatializerOutputChangedListener()
+ * @see #getOutput()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void setOnSpatializerOutputChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnSpatializerOutputChangedListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ synchronized (mOutputListenerLock) {
+ if (mOutputListener != null) {
+ throw new IllegalStateException("Trying to overwrite existing listener");
+ }
+ mOutputListener =
+ new ListenerInfo<OnSpatializerOutputChangedListener>(listener, executor);
+ mOutputDispatcher = new SpatializerOutputDispatcherStub();
+ try {
+ mAm.getService().registerSpatializerOutputCallback(mOutputDispatcher);
+ } catch (RemoteException e) {
+ mOutputListener = null;
+ mOutputDispatcher = null;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Clears the listener for spatializer effect output updates
+ * @see #setOnSpatializerOutputChangedListener(Executor, OnSpatializerOutputChangedListener)
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void clearOnSpatializerOutputChangedListener() {
+ synchronized (mOutputListenerLock) {
+ if (mOutputDispatcher == null) {
+ throw (new IllegalStateException("No listener to clear"));
+ }
+ try {
+ mAm.getService().unregisterSpatializerOutputCallback(mOutputDispatcher);
+ } catch (RemoteException e) { }
+ mOutputListener = null;
+ mOutputDispatcher = null;
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ // callback helper definitions
+
+ private static class ListenerInfo<T> {
+ final @NonNull T mListener;
+ final @NonNull Executor mExecutor;
+
+ ListenerInfo(T listener, Executor exe) {
+ mListener = listener;
+ mExecutor = exe;
+ }
+ }
+
+ private static <T> ListenerInfo<T> getListenerInfo(
+ T listener, ArrayList<ListenerInfo<T>> listeners) {
+ if (listeners == null) {
+ return null;
+ }
+ for (ListenerInfo<T> info : listeners) {
+ if (info.mListener == listener) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ private static <T> boolean hasListener(T listener, ArrayList<ListenerInfo<T>> listeners) {
+ return getListenerInfo(listener, listeners) != null;
+ }
+
+ private static <T> boolean removeListener(T listener, ArrayList<ListenerInfo<T>> listeners) {
+ final ListenerInfo<T> infoToRemove = getListenerInfo(listener, listeners);
+ if (infoToRemove != null) {
+ listeners.remove(infoToRemove);
+ return true;
+ }
+ return false;
+ }
+
+ //-----------------------------------------------------------------------------
+ // head tracking callback management and stub
+
+ private final Object mHeadTrackingListenerLock = new Object();
+ /**
+ * List of listeners for head tracking mode listener and their associated Executor.
+ * List is lazy-initialized on first registration
+ */
+ @GuardedBy("mHeadTrackingListenerLock")
+ private @Nullable ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>
+ mHeadTrackingListeners;
+
+ @GuardedBy("mHeadTrackingListenerLock")
+ private @Nullable SpatializerHeadTrackingDispatcherStub mHeadTrackingDispatcherStub;
+
+ private final class SpatializerHeadTrackingDispatcherStub
+ extends ISpatializerHeadTrackingModeCallback.Stub {
+ @Override
+ public void dispatchSpatializerActualHeadTrackingModeChanged(int mode) {
+ // make a shallow copy of listeners so callback is not executed under lock
+ final ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>> headTrackingListeners;
+ synchronized (mHeadTrackingListenerLock) {
+ if (mHeadTrackingListeners == null || mHeadTrackingListeners.size() == 0) {
+ return;
+ }
+ headTrackingListeners = (ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>)
+ mHeadTrackingListeners.clone();
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ for (ListenerInfo<OnHeadTrackingModeChangedListener> info : headTrackingListeners) {
+ info.mExecutor.execute(() -> info.mListener
+ .onHeadTrackingModeChanged(Spatializer.this, mode));
+ }
+ }
+ }
+
+ @Override
+ public void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode) {
+ // make a shallow copy of listeners so callback is not executed under lock
+ final ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>> headTrackingListeners;
+ synchronized (mHeadTrackingListenerLock) {
+ if (mHeadTrackingListeners == null || mHeadTrackingListeners.size() == 0) {
+ return;
+ }
+ headTrackingListeners = (ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>)
+ mHeadTrackingListeners.clone();
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ for (ListenerInfo<OnHeadTrackingModeChangedListener> info : headTrackingListeners) {
+ info.mExecutor.execute(() -> info.mListener
+ .onDesiredHeadTrackingModeChanged(Spatializer.this, mode));
+ }
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ // head pose callback management and stub
+ private final Object mPoseListenerLock = new Object();
+ /**
+ * Listener for head to soundstage updates
+ */
+ @GuardedBy("mPoseListenerLock")
+ private @Nullable ListenerInfo<OnHeadToSoundstagePoseUpdatedListener> mPoseListener;
+ @GuardedBy("mPoseListenerLock")
+ private @Nullable SpatializerPoseDispatcherStub mPoseDispatcher;
+
+ private final class SpatializerPoseDispatcherStub
+ extends ISpatializerHeadToSoundStagePoseCallback.Stub {
+
+ @Override
+ public void dispatchPoseChanged(float[] pose) {
+ // make a copy of ref to listener so callback is not executed under lock
+ final ListenerInfo<OnHeadToSoundstagePoseUpdatedListener> listener;
+ synchronized (mPoseListenerLock) {
+ listener = mPoseListener;
+ }
+ if (listener == null) {
+ return;
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ listener.mExecutor.execute(() -> listener.mListener
+ .onHeadToSoundstagePoseUpdated(Spatializer.this, pose));
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ // output callback management and stub
+ private final Object mOutputListenerLock = new Object();
+ /**
+ * Listener for output updates
+ */
+ @GuardedBy("mOutputListenerLock")
+ private @Nullable ListenerInfo<OnSpatializerOutputChangedListener> mOutputListener;
+ @GuardedBy("mOutputListenerLock")
+ private @Nullable SpatializerOutputDispatcherStub mOutputDispatcher;
+
+ private final class SpatializerOutputDispatcherStub
+ extends ISpatializerOutputCallback.Stub {
+
+ @Override
+ public void dispatchSpatializerOutputChanged(int output) {
+ // make a copy of ref to listener so callback is not executed under lock
+ final ListenerInfo<OnSpatializerOutputChangedListener> listener;
+ synchronized (mOutputListenerLock) {
+ listener = mOutputListener;
+ }
+ if (listener == null) {
+ return;
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ listener.mExecutor.execute(() -> listener.mListener
+ .onSpatializerOutputChanged(Spatializer.this, output));
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 37e141537c79..5259c4f07639 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -16,15 +16,16 @@
package android.media.projection;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.hardware.display.VirtualDisplayConfig;
-import android.media.projection.IMediaProjection;
-import android.media.projection.IMediaProjectionCallback;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
@@ -100,19 +101,22 @@ public final class MediaProjection {
public VirtualDisplay createVirtualDisplay(@NonNull String name,
int width, int height, int dpi, boolean isSecure, @Nullable Surface surface,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
- DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
| DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
if (isSecure) {
flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
}
- final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
- height, dpi);
+ Context windowContext = mContext.createWindowContext(mContext.getDisplayNoVerify(),
+ TYPE_APPLICATION, null /* options */);
+ final VirtualDisplayConfig.Builder builder = buildMirroredVirtualDisplay(name, width,
+ height, dpi, windowContext.getWindowContextToken());
builder.setFlags(flags);
if (surface != null) {
builder.setSurface(surface);
}
- return dm.createVirtualDisplay(this, builder.build(), callback, handler);
+ VirtualDisplay virtualDisplay = createVirtualDisplay(builder.build(), callback, handler,
+ windowContext);
+ return virtualDisplay;
}
/**
@@ -141,13 +145,35 @@ public final class MediaProjection {
public VirtualDisplay createVirtualDisplay(@NonNull String name,
int width, int height, int dpi, int flags, @Nullable Surface surface,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
- final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
- height, dpi);
+ Context windowContext = mContext.createWindowContext(mContext.getDisplayNoVerify(),
+ TYPE_APPLICATION, null /* options */);
+ final VirtualDisplayConfig.Builder builder = buildMirroredVirtualDisplay(name, width,
+ height, dpi, windowContext.getWindowContextToken());
builder.setFlags(flags);
if (surface != null) {
builder.setSurface(surface);
}
- return createVirtualDisplay(builder.build(), callback, handler);
+ VirtualDisplay virtualDisplay = createVirtualDisplay(builder.build(), callback, handler,
+ windowContext);
+ return virtualDisplay;
+ }
+
+ /**
+ * Constructs a {@link VirtualDisplayConfig.Builder}, which will mirror the contents of a
+ * DisplayArea. The DisplayArea to mirror is from the DisplayArea the caller is launched on.
+ *
+ * @param name The name of the virtual display, must be non-empty.
+ * @param width The width of the virtual display in pixels. Must be greater than 0.
+ * @param height The height of the virtual display in pixels. Must be greater than 0.
+ * @param dpi The density of the virtual display in dpi. Must be greater than 0.
+ * @return a config representing a VirtualDisplay
+ */
+ private VirtualDisplayConfig.Builder buildMirroredVirtualDisplay(@NonNull String name,
+ int width, int height, int dpi, IBinder windowContextToken) {
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
+ height, dpi);
+ builder.setWindowTokenClientToMirror(windowContextToken);
+ return builder;
}
/**
@@ -156,20 +182,22 @@ public final class MediaProjection {
*
* @param virtualDisplayConfig The arguments for the virtual display configuration. See
* {@link VirtualDisplayConfig} for using it.
- * @param callback Callback to call when the virtual display's state
- * changes, or null if none.
- * @param handler The {@link android.os.Handler} on which the callback should be
- * invoked, or null if the callback should be invoked on the calling
- * thread's main {@link android.os.Looper}.
+ * @param callback Callback to call when the virtual display's state changes, or null if none.
+ * @param handler The {@link android.os.Handler} on which the callback should be invoked, or
+ * null if the callback should be invoked on the calling thread's main
+ * {@link android.os.Looper}.
+ * @param windowContext the WindowContext associated with the caller.
*
* @see android.hardware.display.VirtualDisplay
* @hide
*/
@Nullable
public VirtualDisplay createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
- @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
+ @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler,
+ Context windowContext) {
DisplayManager dm = mContext.getSystemService(DisplayManager.class);
- return dm.createVirtualDisplay(this, virtualDisplayConfig, callback, handler);
+ return dm.createVirtualDisplay(this, virtualDisplayConfig, callback, handler,
+ windowContext);
}
/**
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index b4cafd8548f4..edbfd2a8f03e 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -254,8 +254,14 @@ public class CompanionDeviceActivity extends Activity {
Log.i(LOG_TAG, "onDeviceConfirmed(selectedDevice = " + selectedDevice + ")");
getService().onDeviceSelected(
getCallingPackage(), getDeviceMacAddress(selectedDevice.device));
+ }
+
+ void setResultAndFinish() {
+ Log.i(LOG_TAG, "setResultAndFinish(selectedDevice = "
+ + getService().mSelectedDevice.device + ")");
setResult(RESULT_OK,
- new Intent().putExtra(CompanionDeviceManager.EXTRA_DEVICE, selectedDevice.device));
+ new Intent().putExtra(
+ CompanionDeviceManager.EXTRA_DEVICE, getService().mSelectedDevice.device));
finish();
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index c24782e8b310..5df8e3c83a7a 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -117,6 +117,11 @@ public class CompanionDeviceDiscoveryService extends Service {
CompanionDeviceDiscoveryService::startDiscovery,
CompanionDeviceDiscoveryService.this, request));
}
+
+ @Override
+ public void onAssociationCreated() {
+ Handler.getMain().post(CompanionDeviceDiscoveryService.this::onAssociationCreated);
+ }
};
private ScanCallback mBLEScanCallback;
@@ -222,6 +227,11 @@ public class CompanionDeviceDiscoveryService extends Service {
SCAN_TIMEOUT);
}
+ @MainThread
+ private void onAssociationCreated() {
+ mActivity.setResultAndFinish();
+ }
+
private boolean shouldScan(List<? extends DeviceFilter> mediumSpecificFilters) {
return !isEmpty(mediumSpecificFilters) || isEmpty(mFilters);
}
diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
index 8216edf872f3..fe7988f7e500 100644
--- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
@@ -43,6 +43,7 @@ public class FooterPreference extends Preference {
View.OnClickListener mLearnMoreListener;
private CharSequence mContentDescription;
private CharSequence mLearnMoreContentDescription;
+ private FooterLearnMoreSpan mLearnMoreSpan;
public FooterPreference(Context context, AttributeSet attrs) {
super(context, attrs, R.attr.footerPreferenceStyle);
@@ -68,7 +69,11 @@ public class FooterPreference extends Preference {
if (learnMore != null && mLearnMoreListener != null) {
learnMore.setVisibility(View.VISIBLE);
SpannableString learnMoreText = new SpannableString(learnMore.getText());
- learnMoreText.setSpan(new FooterLearnMoreSpan(mLearnMoreListener), 0,
+ if (mLearnMoreSpan != null) {
+ learnMoreText.removeSpan(mLearnMoreSpan);
+ }
+ mLearnMoreSpan = new FooterLearnMoreSpan(mLearnMoreListener);
+ learnMoreText.setSpan(mLearnMoreSpan, 0,
learnMoreText.length(), 0);
learnMore.setText(learnMoreText);
if (!TextUtils.isEmpty(mLearnMoreContentDescription)) {
diff --git a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
index 54145d60fb32..eecb4bff16ae 100644
--- a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
+++ b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml
@@ -34,17 +34,21 @@
android:orientation="vertical">
<ImageView
+ android:id="@+id/background_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:scaleType="centerInside"
+ android:layout_gravity="center"
+ android:adjustViewBounds="true"
android:src="@drawable/protection_background"/>
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/lottie_view"
- android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center" />
+ android:layout_gravity="center"
+ android:maxWidth="@dimen/settingslib_illustration_width"
+ android:maxHeight="@dimen/settingslib_illustration_height"
+ android:adjustViewBounds="true"/>
<FrameLayout
android:id="@+id/middleground_layout"
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index 266fc78b2b6a..468a97630e19 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -17,6 +17,7 @@
package com.android.settingslib.widget;
import android.content.Context;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Animatable2;
@@ -50,7 +51,9 @@ public class IllustrationPreference extends Preference {
private static final String TAG = "IllustrationPreference";
private static final boolean IS_ENABLED_LOTTIE_ADAPTIVE_COLOR = false;
+ private static final int SIZE_UNSPECIFIED = -1;
+ private int mMaxHeight = SIZE_UNSPECIFIED;
private int mImageResId;
private boolean mIsAutoScale;
private Uri mImageUri;
@@ -98,6 +101,8 @@ public class IllustrationPreference extends Preference {
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
+ final ImageView backgroundView =
+ (ImageView) holder.findViewById(R.id.background_view);
final FrameLayout middleGroundLayout =
(FrameLayout) holder.findViewById(R.id.middleground_layout);
final LottieAnimationView illustrationView =
@@ -115,6 +120,7 @@ public class IllustrationPreference extends Preference {
illustrationFrame.setLayoutParams(lp);
handleImageWithAnimation(illustrationView);
+ handleImageFrameMaxHeight(backgroundView, illustrationView);
if (mIsAutoScale) {
illustrationView.setScaleType(mIsAutoScale
@@ -170,7 +176,14 @@ public class IllustrationPreference extends Preference {
}
/**
- * Sets image drawable to display image in {@link LottieAnimationView}
+ * Gets the lottie illustration resource id.
+ */
+ public int getLottieAnimationResId() {
+ return mImageResId;
+ }
+
+ /**
+ * Sets the image drawable to display image in {@link LottieAnimationView}.
*
* @param imageDrawable the drawable of an image
*/
@@ -183,7 +196,16 @@ public class IllustrationPreference extends Preference {
}
/**
- * Sets image uri to display image in {@link LottieAnimationView}
+ * Gets the image drawable from display image in {@link LottieAnimationView}.
+ *
+ * @return the drawable of an image
+ */
+ public Drawable getImageDrawable() {
+ return mImageDrawable;
+ }
+
+ /**
+ * Sets the image uri to display image in {@link LottieAnimationView}.
*
* @param imageUri the Uri of an image
*/
@@ -195,6 +217,28 @@ public class IllustrationPreference extends Preference {
}
}
+ /**
+ * Gets the image uri from display image in {@link LottieAnimationView}.
+ *
+ * @return the Uri of an image
+ */
+ public Uri getImageUri() {
+ return mImageUri;
+ }
+
+ /**
+ * Sets the maximum height of the views, still use the specific one if the maximum height was
+ * larger than the specific height from XML.
+ *
+ * @param maxHeight the maximum height of the frame views in terms of pixels.
+ */
+ public void setMaxHeight(int maxHeight) {
+ if (maxHeight != mMaxHeight) {
+ mMaxHeight = maxHeight;
+ notifyChanged();
+ }
+ }
+
private void resetImageResourceCache() {
mImageDrawable = null;
mImageUri = null;
@@ -249,6 +293,23 @@ public class IllustrationPreference extends Preference {
}
}
+ private void handleImageFrameMaxHeight(ImageView backgroundView, ImageView illustrationView) {
+ if (mMaxHeight == SIZE_UNSPECIFIED) {
+ return;
+ }
+
+ final Resources res = backgroundView.getResources();
+ final int frameWidth = res.getDimensionPixelSize(R.dimen.settingslib_illustration_width);
+ final int frameHeight = res.getDimensionPixelSize(R.dimen.settingslib_illustration_height);
+ final int restrictedMaxHeight = Math.min(mMaxHeight, frameHeight);
+ backgroundView.setMaxHeight(restrictedMaxHeight);
+ illustrationView.setMaxHeight(restrictedMaxHeight);
+
+ // Ensures the illustration view size is smaller than or equal to the background view size.
+ final float aspectRatio = (float) frameWidth / frameHeight;
+ illustrationView.setMaxWidth((int) (restrictedMaxHeight * aspectRatio));
+ }
+
private void startAnimation(Drawable drawable) {
if (!(drawable instanceof Animatable)) {
return;
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
index 8f3e4bd87aa7..220cf6b5c38a 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
@@ -18,6 +18,7 @@ package com.android.settingslib.drawer;
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
+import static com.android.settingslib.drawer.TileUtils.META_DATA_NEW_TASK;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
@@ -329,6 +330,18 @@ public abstract class Tile implements Parcelable {
}
/**
+ * Whether the {@link Activity} should be launched in a separate task.
+ */
+ public boolean isNewTask(Context context) {
+ ensureMetadataNotStale(context);
+ if (mMetaData != null
+ && mMetaData.containsKey(META_DATA_NEW_TASK)) {
+ return mMetaData.getBoolean(META_DATA_NEW_TASK);
+ }
+ return false;
+ }
+
+ /**
* Ensures metadata is not stale for this tile.
*/
private void ensureMetadataNotStale(Context context) {
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index a2bec334f206..acc0087f6dcf 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -230,6 +230,13 @@ public class TileUtils {
public static final String META_DATA_KEY_PROFILE = "com.android.settings.profile";
/**
+ * Name of the meta-data item that should be set in the AndroidManifest.xml
+ * to specify whether the {@link android.app.Activity} should be launched in a separate task.
+ * This should be a boolean value {@code true} or {@code false}, set using {@code android:value}
+ */
+ public static final String META_DATA_NEW_TASK = "com.android.settings.new_task";
+
+ /**
* Build a list of DashboardCategory.
*/
public static List<DashboardCategory> getCategories(Context context,
diff --git a/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target_divider.xml b/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target_divider.xml
index f21e51cf847a..46414217d76e 100644
--- a/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target_divider.xml
+++ b/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target_divider.xml
@@ -22,6 +22,8 @@
android:layout_height="match_parent"
android:gravity="start|center_vertical"
android:orientation="horizontal"
+ android:paddingStart="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingEnd"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<View
diff --git a/packages/SettingsLib/Utils/src/com/android/settingslib/utils/BuildCompatUtils.java b/packages/SettingsLib/Utils/src/com/android/settingslib/utils/BuildCompatUtils.java
index 44f6f5439af3..9dcb5bc8c7fa 100644
--- a/packages/SettingsLib/Utils/src/com/android/settingslib/utils/BuildCompatUtils.java
+++ b/packages/SettingsLib/Utils/src/com/android/settingslib/utils/BuildCompatUtils.java
@@ -52,9 +52,9 @@ public final class BuildCompatUtils {
}
return (VERSION.CODENAME.equals("REL") && VERSION.SDK_INT >= 31)
- || (VERSION.CODENAME.length() == 1
- && VERSION.CODENAME.compareTo("S") >= 0
- && VERSION.CODENAME.compareTo("Z") <= 0);
+ || (VERSION.CODENAME.length() >= 1
+ && VERSION.CODENAME.toUpperCase().charAt(0) >= 'S'
+ && VERSION.CODENAME.toUpperCase().charAt(0) <= 'Z');
}
private BuildCompatUtils() {}
diff --git a/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java b/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java
index bc0c6f36e96a..794b0eb66519 100644
--- a/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java
+++ b/packages/SettingsLib/src/com/android/settingslib/SignalIcon.java
@@ -15,6 +15,8 @@
*/
package com.android.settingslib;
+import com.android.settingslib.mobile.TelephonyIcons;
+
import java.text.SimpleDateFormat;
import java.util.Objects;
@@ -40,9 +42,17 @@ public class SignalIcon {
// For logging.
public final String name;
- public IconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
- int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
- int discContentDesc) {
+ public IconGroup(
+ String name,
+ int[][] sbIcons,
+ int[][] qsIcons,
+ int[] contentDesc,
+ int sbNullState,
+ int qsNullState,
+ int sbDiscState,
+ int qsDiscState,
+ int discContentDesc
+ ) {
this.name = name;
this.sbIcons = sbIcons;
this.qsIcons = qsIcons;
@@ -131,6 +141,19 @@ public class SignalIcon {
&& other.activityOut == activityOut
&& other.rssi == rssi;
}
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ connected,
+ enabled,
+ level,
+ inetCondition,
+ iconGroup,
+ activityIn,
+ activityOut,
+ rssi);
+ }
}
/**
@@ -139,18 +162,31 @@ public class SignalIcon {
public static class MobileIconGroup extends IconGroup {
public final int dataContentDescription; // mContentDescriptionDataType
public final int dataType;
- public final boolean isWide;
- public final int qsDataType;
-
- public MobileIconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
- int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
- int discContentDesc, int dataContentDesc, int dataType, boolean isWide) {
- super(name, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, sbDiscState,
- qsDiscState, discContentDesc);
+
+ public MobileIconGroup(
+ String name,
+ int[][] sbIcons,
+ int[][] qsIcons,
+ int[] contentDesc,
+ int sbNullState,
+ int qsNullState,
+ int sbDiscState,
+ int qsDiscState,
+ int discContentDesc,
+ int dataContentDesc,
+ int dataType
+ ) {
+ super(name,
+ sbIcons,
+ qsIcons,
+ contentDesc,
+ sbNullState,
+ qsNullState,
+ sbDiscState,
+ qsDiscState,
+ discContentDesc);
this.dataContentDescription = dataContentDesc;
this.dataType = dataType;
- this.isWide = isWide;
- this.qsDataType = dataType; // TODO: remove this field
}
}
@@ -187,6 +223,27 @@ public class SignalIcon {
defaultDataOff = state.defaultDataOff;
}
+ /** @return true if this state is disabled or not default data */
+ public boolean isDataDisabledOrNotDefault() {
+ return (iconGroup == TelephonyIcons.DATA_DISABLED
+ || (iconGroup == TelephonyIcons.NOT_DEFAULT_DATA)) && userSetup;
+ }
+
+ /** @return if this state is considered to have inbound activity */
+ public boolean hasActivityIn() {
+ return dataConnected && !carrierNetworkChangeMode && activityIn;
+ }
+
+ /** @return if this state is considered to have outbound activity */
+ public boolean hasActivityOut() {
+ return dataConnected && !carrierNetworkChangeMode && activityOut;
+ }
+
+ /** @return true if this state should show a RAT icon in quick settings */
+ public boolean showQuickSettingsRatIcon() {
+ return dataConnected || isDataDisabledOrNotDefault();
+ }
+
@Override
protected void toString(StringBuilder builder) {
super.toString(builder);
@@ -202,23 +259,40 @@ public class SignalIcon {
builder.append("carrierNetworkChangeMode=").append(carrierNetworkChangeMode)
.append(',');
builder.append("userSetup=").append(userSetup).append(',');
- builder.append("defaultDataOff=").append(defaultDataOff);
+ builder.append("defaultDataOff=").append(defaultDataOff).append(',');
+ builder.append("showQuickSettingsRatIcon=").append(showQuickSettingsRatIcon());
}
@Override
public boolean equals(Object o) {
return super.equals(o)
- && Objects.equals(((MobileState) o).networkName, networkName)
- && Objects.equals(((MobileState) o).networkNameData, networkNameData)
- && ((MobileState) o).dataSim == dataSim
- && ((MobileState) o).dataConnected == dataConnected
- && ((MobileState) o).isEmergency == isEmergency
- && ((MobileState) o).airplaneMode == airplaneMode
- && ((MobileState) o).carrierNetworkChangeMode == carrierNetworkChangeMode
- && ((MobileState) o).userSetup == userSetup
- && ((MobileState) o).isDefault == isDefault
- && ((MobileState) o).roaming == roaming
- && ((MobileState) o).defaultDataOff == defaultDataOff;
+ && Objects.equals(((MobileState) o).networkName, networkName)
+ && Objects.equals(((MobileState) o).networkNameData, networkNameData)
+ && ((MobileState) o).dataSim == dataSim
+ && ((MobileState) o).dataConnected == dataConnected
+ && ((MobileState) o).isEmergency == isEmergency
+ && ((MobileState) o).airplaneMode == airplaneMode
+ && ((MobileState) o).carrierNetworkChangeMode == carrierNetworkChangeMode
+ && ((MobileState) o).userSetup == userSetup
+ && ((MobileState) o).isDefault == isDefault
+ && ((MobileState) o).roaming == roaming
+ && ((MobileState) o).defaultDataOff == defaultDataOff;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(),
+ networkName,
+ networkNameData,
+ dataSim,
+ dataConnected,
+ isEmergency,
+ airplaneMode,
+ carrierNetworkChangeMode,
+ userSetup,
+ isDefault,
+ roaming,
+ defaultDataOff);
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
index 877dd2dfa26e..2e7cfcb13d8c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationAccesses.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-package com.android.settingslib.location;
+package com.android.settingslib.applications;
+
import android.app.AppOpsManager;
import android.content.Context;
@@ -39,14 +40,24 @@ import java.util.Comparator;
import java.util.List;
/**
- * Retrieves the information of applications which accessed location recently.
+ * Retrieval of app ops information for the specified ops.
*/
-public class RecentLocationAccesses {
- private static final String TAG = RecentLocationAccesses.class.getSimpleName();
+public class RecentAppOpsAccess {
@VisibleForTesting
- static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
+ static final int[] LOCATION_OPS = new int[]{
+ AppOpsManager.OP_FINE_LOCATION,
+ AppOpsManager.OP_COARSE_LOCATION,
+ };
+ private static final int[] MICROPHONE_OPS = new int[]{
+ AppOpsManager.OP_RECORD_AUDIO,
+ };
+
- // Keep last 24 hours of location app information.
+ private static final String TAG = RecentAppOpsAccess.class.getSimpleName();
+ @VisibleForTesting
+ public static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
+
+ // Keep last 24 hours of access app information.
private static final long RECENT_TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS;
/** The flags for querying ops that are trusted for showing in the UI. */
@@ -54,47 +65,55 @@ public class RecentLocationAccesses {
| AppOpsManager.OP_FLAG_UNTRUSTED_PROXY
| AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
- @VisibleForTesting
- static final int[] LOCATION_OPS = new int[]{
- AppOpsManager.OP_FINE_LOCATION,
- AppOpsManager.OP_COARSE_LOCATION,
- };
-
private final PackageManager mPackageManager;
private final Context mContext;
+ private final int[] mOps;
private final IconDrawableFactory mDrawableFactory;
private final Clock mClock;
- public RecentLocationAccesses(Context context) {
- this(context, Clock.systemDefaultZone());
+ public RecentAppOpsAccess(Context context, int[] ops) {
+ this(context, Clock.systemDefaultZone(), ops);
}
@VisibleForTesting
- RecentLocationAccesses(Context context, Clock clock) {
+ RecentAppOpsAccess(Context context, Clock clock, int[] ops) {
mContext = context;
mPackageManager = context.getPackageManager();
+ mOps = ops;
mDrawableFactory = IconDrawableFactory.newInstance(context);
mClock = clock;
}
/**
- * Fills a list of applications which queried location recently within specified time.
- * Apps are sorted by recency. Apps with more recent location accesses are in the front.
+ * Creates an instance of {@link RecentAppOpsAccess} for location (coarse and fine) access.
+ */
+ public static RecentAppOpsAccess createForLocation(Context context) {
+ return new RecentAppOpsAccess(context, LOCATION_OPS);
+ }
+
+ /**
+ * Creates an instance of {@link RecentAppOpsAccess} for microphone access.
+ */
+ public static RecentAppOpsAccess createForMicrophone(Context context) {
+ return new RecentAppOpsAccess(context, MICROPHONE_OPS);
+ }
+
+ /**
+ * Fills a list of applications which queried for access recently within specified time.
+ * Apps are sorted by recency. Apps with more recent accesses are in the front.
*/
@VisibleForTesting
- List<Access> getAppList(boolean showSystemApps) {
- // Retrieve a location usage list from AppOps
- PackageManager pm = mContext.getPackageManager();
- AppOpsManager aoManager =
- (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
- List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_OPS);
+ public List<Access> getAppList(boolean showSystemApps) {
+ // Retrieve a access usage list from AppOps
+ AppOpsManager aoManager = mContext.getSystemService(AppOpsManager.class);
+ List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(mOps);
final int appOpsCount = appOps != null ? appOps.size() : 0;
// Process the AppOps list and generate a preference list.
ArrayList<Access> accesses = new ArrayList<>(appOpsCount);
final long now = mClock.millis();
- final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ final UserManager um = mContext.getSystemService(UserManager.class);
final List<UserHandle> profiles = um.getUserProfiles();
for (int i = 0; i < appOpsCount; ++i) {
@@ -111,9 +130,10 @@ public class RecentLocationAccesses {
// Don't show apps that do not have user sensitive location permissions
boolean showApp = true;
if (!showSystemApps) {
- for (int op : LOCATION_OPS) {
+ for (int op : mOps) {
final String permission = AppOpsManager.opToPermission(op);
- final int permissionFlags = pm.getPermissionFlags(permission, packageName,
+ final int permissionFlags = mPackageManager.getPermissionFlags(permission,
+ packageName,
user);
if (PermissionChecker.checkPermissionForPreflight(mContext, permission,
PermissionChecker.PID_UNKNOWN, uid, packageName)
@@ -144,12 +164,11 @@ public class RecentLocationAccesses {
return accesses;
}
-
/**
- * Gets a list of apps that accessed location recently, sorting by recency.
+ * Gets a list of apps that accessed the app op recently, sorting by recency.
*
* @param showSystemApps whether includes system apps in the list.
- * @return the list of apps that recently accessed location.
+ * @return the list of apps that recently accessed the app op.
*/
public List<Access> getAppListSorted(boolean showSystemApps) {
List<Access> accesses = getAppList(showSystemApps);
@@ -174,18 +193,18 @@ public class RecentLocationAccesses {
AppOpsManager.PackageOps ops) {
String packageName = ops.getPackageName();
List<AppOpsManager.OpEntry> entries = ops.getOps();
- long locationAccessFinishTime = 0L;
- // Earliest time for a location access to end and still be shown in list.
- long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
+ long accessFinishTime = 0L;
+ // Earliest time for a access to end and still be shown in list.
+ long recentAccessCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
// Compute the most recent access time from all op entries.
for (AppOpsManager.OpEntry entry : entries) {
long lastAccessTime = entry.getLastAccessTime(TRUSTED_STATE_FLAGS);
- if (lastAccessTime > locationAccessFinishTime) {
- locationAccessFinishTime = lastAccessTime;
+ if (lastAccessTime > accessFinishTime) {
+ accessFinishTime = lastAccessTime;
}
}
// Bail out if the entry is out of date.
- if (locationAccessFinishTime < recentLocationCutoffTime) {
+ if (accessFinishTime < recentAccessCutoffTime) {
return null;
}
@@ -213,13 +232,16 @@ public class RecentLocationAccesses {
badgedAppLabel = null;
}
access = new Access(packageName, userHandle, icon, appLabel, badgedAppLabel,
- locationAccessFinishTime);
+ accessFinishTime);
} catch (NameNotFoundException e) {
Log.w(TAG, "package name not found for " + packageName + ", userId " + userId);
}
return access;
}
+ /**
+ * Information about when an app last accessed a particular app op.
+ */
public static class Access {
public final String packageName;
public final UserHandle userHandle;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index fe92e2664eda..7beb82457e18 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -106,6 +106,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
private boolean mIsA2dpProfileConnectedFail = false;
private boolean mIsHeadsetProfileConnectedFail = false;
private boolean mIsHearingAidProfileConnectedFail = false;
+ private boolean mUnpairing = false;
// Group second device for Hearing Aid
private CachedBluetoothDevice mSubDevice;
@VisibleForTesting
@@ -402,6 +403,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
if (state != BluetoothDevice.BOND_NONE) {
final BluetoothDevice dev = mDevice;
if (dev != null) {
+ mUnpairing = true;
final boolean successful = dev.removeBond();
if (successful) {
releaseLruCache();
@@ -1243,4 +1245,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
void releaseLruCache() {
mDrawableCache.evictAll();
}
+
+ boolean getUnpairing() {
+ return mUnpairing;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index 20ece69d7281..818f5ca33ebf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -180,6 +180,9 @@ public class HearingAidDeviceManager {
break;
case BluetoothProfile.STATE_DISCONNECTED:
mainDevice = findMainDevice(cachedDevice);
+ if (cachedDevice.getUnpairing()) {
+ return true;
+ }
if (mainDevice != null) {
// When main device exists, receiving sub device disconnection
// To update main device UI
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
index 274696bfec0e..468aa05052ad 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
@@ -183,6 +183,20 @@ public class UserIconDrawable extends Drawable implements Drawable.Callback {
return setBadge(badge);
}
+ /**
+ * Sets the managed badge to this user icon if the device has a device owner.
+ */
+ public UserIconDrawable setBadgeIfManagedDevice(Context context) {
+ Drawable badge = null;
+ boolean deviceOwnerExists = context.getSystemService(DevicePolicyManager.class)
+ .getDeviceOwnerComponentOnAnyUser() != null;
+ if (deviceOwnerExists) {
+ badge = getDrawableForDisplayDensity(
+ context, com.android.internal.R.drawable.ic_corp_badge_case);
+ }
+ return setBadge(badge);
+ }
+
public void setBadgeRadius(float radius) {
mBadgeRadius = radius;
onBoundsChange(getBounds());
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
index c501b3aab4d4..2e8f36834584 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
@@ -110,6 +110,18 @@ public class PowerAllowlistBackend {
}
/**
+ * Check if target package is in allow list except idle app
+ */
+ public boolean isAllowlistedExceptIdle(String pkg) {
+ try {
+ return mDeviceIdleService.isPowerSaveWhitelistExceptIdleApp(pkg);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+ return true;
+ }
+ }
+
+ /**
*
* @param pkgs a list of packageName
* @return true when one of package is in allow list
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
index a0c8663a8271..ea5105bd9e0d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
@@ -319,23 +319,11 @@ public class SettingsInjector {
@Override
public boolean onPreferenceClick(Preference preference) {
- // Activity to start if they click on the preference. Must start in new task to ensure
- // that "android.settings.LOCATION_SOURCE_SETTINGS" brings user back to
- // Settings > Location.
+ // Activity to start if they click on the preference.
Intent settingIntent = new Intent();
settingIntent.setClassName(mInfo.packageName, mInfo.settingsActivity);
+ // No flags set to ensure the activity is launched within the same settings task.
logPreferenceClick(settingIntent);
- // Sometimes the user may navigate back to "Settings" and launch another different
- // injected setting after one injected setting has been launched.
- //
- // FLAG_ACTIVITY_CLEAR_TOP allows multiple Activities to stack on each other. When
- // "back" button is clicked, the user will navigate through all the injected settings
- // launched before. Such behavior could be quite confusing sometimes.
- //
- // In order to avoid such confusion, we use FLAG_ACTIVITY_CLEAR_TASK, which always clear
- // up all existing injected settings and make sure that "back" button always brings the
- // user back to "Settings" directly.
- settingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mContext.startActivityAsUser(settingIntent, mInfo.mUserHandle);
return true;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 22001c9c925a..c40b7b7d88af 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -479,9 +479,9 @@ public class LocalMediaManager implements BluetoothCallback {
@Override
public void onDeviceListAdded(List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
+ Collections.sort(devices, COMPARATOR);
mMediaDevices.clear();
mMediaDevices.addAll(devices);
- Collections.sort(devices, COMPARATOR);
// Add disconnected bluetooth devices only when phone output device is available.
for (MediaDevice device : devices) {
final int type = device.getDeviceType();
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
index f8565bc2279f..d4e58f7a2fc4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/TelephonyIcons.java
@@ -50,178 +50,194 @@ public class TelephonyIcons {
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
+ 0,
+ 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.carrier_network_change_mode,
- 0,
- false);
+ 0
+ );
public static final MobileIconGroup THREE_G = new MobileIconGroup(
"3G",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
+ 0,
+ 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_3g,
- TelephonyIcons.ICON_3G,
- true);
+ TelephonyIcons.ICON_3G
+ );
public static final MobileIconGroup WFC = new MobileIconGroup(
"WFC",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
+ 0,
+ 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
- 0, 0, false);
+ 0,
+ 0);
public static final MobileIconGroup UNKNOWN = new MobileIconGroup(
"Unknown",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
+ 0,
+ 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
- 0, 0, false);
+ 0,
+ 0);
public static final MobileIconGroup E = new MobileIconGroup(
"E",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
+ 0,
+ 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_edge,
- TelephonyIcons.ICON_E,
- false);
+ TelephonyIcons.ICON_E
+ );
public static final MobileIconGroup ONE_X = new MobileIconGroup(
"1X",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
+ 0,
+ 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_cdma,
- TelephonyIcons.ICON_1X,
- true);
+ TelephonyIcons.ICON_1X
+ );
public static final MobileIconGroup G = new MobileIconGroup(
"G",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
+ 0,
+ 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_gprs,
- TelephonyIcons.ICON_G,
- false);
+ TelephonyIcons.ICON_G
+ );
public static final MobileIconGroup H = new MobileIconGroup(
"H",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
+ 0,
+ 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_3_5g,
- TelephonyIcons.ICON_H,
- false);
+ TelephonyIcons.ICON_H
+ );
public static final MobileIconGroup H_PLUS = new MobileIconGroup(
"H+",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
+ 0,
+ 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_3_5g_plus,
- TelephonyIcons.ICON_H_PLUS,
- false);
+ TelephonyIcons.ICON_H_PLUS
+ );
public static final MobileIconGroup FOUR_G = new MobileIconGroup(
"4G",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
+ 0,
+ 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_4g,
- TelephonyIcons.ICON_4G,
- true);
+ TelephonyIcons.ICON_4G
+ );
public static final MobileIconGroup FOUR_G_PLUS = new MobileIconGroup(
"4G+",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
+ 0,
+ 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_4g_plus,
- TelephonyIcons.ICON_4G_PLUS,
- true);
+ TelephonyIcons.ICON_4G_PLUS
+ );
public static final MobileIconGroup LTE = new MobileIconGroup(
"LTE",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
+ 0,
+ 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_lte,
- TelephonyIcons.ICON_LTE,
- true);
+ TelephonyIcons.ICON_LTE
+ );
public static final MobileIconGroup LTE_PLUS = new MobileIconGroup(
"LTE+",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
+ 0,
+ 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_lte_plus,
- TelephonyIcons.ICON_LTE_PLUS,
- true);
+ TelephonyIcons.ICON_LTE_PLUS
+ );
public static final MobileIconGroup LTE_CA_5G_E = new MobileIconGroup(
"5Ge",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
+ 0,
+ 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_5ge_html,
- TelephonyIcons.ICON_5G_E,
- true);
+ TelephonyIcons.ICON_5G_E
+ );
public static final MobileIconGroup NR_5G = new MobileIconGroup(
"5G",
@@ -234,8 +250,8 @@ public class TelephonyIcons {
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_5g,
- TelephonyIcons.ICON_5G,
- true);
+ TelephonyIcons.ICON_5G
+ );
public static final MobileIconGroup NR_5G_PLUS = new MobileIconGroup(
"5G_PLUS",
@@ -248,34 +264,36 @@ public class TelephonyIcons {
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_5g_plus,
- TelephonyIcons.ICON_5G_PLUS,
- true);
+ TelephonyIcons.ICON_5G_PLUS
+ );
public static final MobileIconGroup DATA_DISABLED = new MobileIconGroup(
"DataDisabled",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
+ 0,
+ 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.cell_data_off_content_description,
- 0,
- false);
+ 0
+ );
public static final MobileIconGroup NOT_DEFAULT_DATA = new MobileIconGroup(
"NotDefaultData",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
- 0, 0,
+ 0,
+ 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.not_default_data_content_description,
- 0,
- false);
+ 0
+ );
public static final MobileIconGroup CARRIER_MERGED_WIFI = new MobileIconGroup(
"CWF",
@@ -288,8 +306,8 @@ public class TelephonyIcons {
/* qsDiscState= */ 0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.data_connection_carrier_wifi,
- TelephonyIcons.ICON_CWF,
- /* isWide= */ true);
+ TelephonyIcons.ICON_CWF
+ );
// When adding a new MobileIconGround, check if the dataContentDescription has to be filtered
// in QSCarrier#hasValidTypeContentDescription
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java
index b65637f4c45f..5ee919bfe538 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/StringUtil.java
@@ -19,6 +19,7 @@ package com.android.settingslib.utils;
import android.content.Context;
import android.icu.text.MeasureFormat;
import android.icu.text.MeasureFormat.FormatWidth;
+import android.icu.text.MessageFormat;
import android.icu.text.RelativeDateTimeFormatter;
import android.icu.text.RelativeDateTimeFormatter.RelativeUnit;
import android.icu.util.Measure;
@@ -31,7 +32,9 @@ import android.text.style.TtsSpan;
import com.android.settingslib.R;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Locale;
+import java.util.Map;
/** Utility class for generally useful string methods **/
public class StringUtil {
@@ -183,4 +186,37 @@ public class StringUtil {
return formatRelativeTime(context, millis, withSeconds,
RelativeDateTimeFormatter.Style.LONG);
}
+
+ /**
+ * Get ICU plural string without additional arguments
+ *
+ * @param context Context used to get the string
+ * @param count The number used to get the correct string for the current language's plural
+ * rules.
+ * @param resId Resource id of the string
+ *
+ * @return Formatted plural string
+ */
+ public static String getIcuPluralsString(Context context, int count, int resId) {
+ MessageFormat msgFormat = new MessageFormat(context.getResources().getString(resId),
+ Locale.getDefault());
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put("count", count);
+ return msgFormat.format(arguments);
+ }
+
+ /**
+ * Get ICU plural string with additional arguments
+ *
+ * @param context Context used to get the string
+ * @param args String arguments
+ * @param resId Resource id of the string
+ *
+ * @return Formatted plural string
+ */
+ public static String getIcuPluralsString(Context context, Map<String, Object> args, int resId) {
+ MessageFormat msgFormat = new MessageFormat(context.getResources().getString(resId),
+ Locale.getDefault());
+ return msgFormat.format(args);
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileStateTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileStateTest.java
new file mode 100644
index 000000000000..92a32bce1799
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileStateTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.settingslib;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import com.android.settingslib.mobile.TelephonyIcons;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class MobileStateTest {
+
+ private SignalIcon.MobileState mState = new SignalIcon.MobileState();
+
+ @Before
+ public void setUp() {
+ }
+
+ @Test
+ public void testIsDataDisabledOrNotDefault_dataDisabled() {
+ mState.iconGroup = TelephonyIcons.DATA_DISABLED;
+ mState.userSetup = true;
+
+ assertTrue(mState.isDataDisabledOrNotDefault());
+ }
+
+ @Test
+ public void testIsDataDisabledOrNotDefault_notDefaultData() {
+ mState.iconGroup = TelephonyIcons.NOT_DEFAULT_DATA;
+ mState.userSetup = true;
+
+ assertTrue(mState.isDataDisabledOrNotDefault());
+ }
+
+ @Test
+ public void testIsDataDisabledOrNotDefault_notDisabled() {
+ mState.iconGroup = TelephonyIcons.G;
+ mState.userSetup = true;
+
+ assertFalse(mState.isDataDisabledOrNotDefault());
+ }
+
+ @Test
+ public void testHasActivityIn_noData_noActivity() {
+ mState.dataConnected = false;
+ mState.carrierNetworkChangeMode = false;
+ mState.activityIn = false;
+
+ assertFalse(mState.hasActivityIn());
+ }
+
+ @Test
+ public void testHasActivityIn_noData_activityIn() {
+ mState.dataConnected = false;
+ mState.carrierNetworkChangeMode = false;
+ mState.activityIn = true;
+
+ assertFalse(mState.hasActivityIn());
+ }
+
+ @Test
+ public void testHasActivityIn_dataConnected_activityIn() {
+ mState.dataConnected = true;
+ mState.carrierNetworkChangeMode = false;
+ mState.activityIn = true;
+
+ assertTrue(mState.hasActivityIn());
+ }
+
+ @Test
+ public void testHasActivityIn_carrierNetworkChange() {
+ mState.dataConnected = true;
+ mState.carrierNetworkChangeMode = true;
+ mState.activityIn = true;
+
+ assertFalse(mState.hasActivityIn());
+ }
+
+ @Test
+ public void testHasActivityOut_noData_noActivity() {
+ mState.dataConnected = false;
+ mState.carrierNetworkChangeMode = false;
+ mState.activityOut = false;
+
+ assertFalse(mState.hasActivityOut());
+ }
+
+ @Test
+ public void testHasActivityOut_noData_activityOut() {
+ mState.dataConnected = false;
+ mState.carrierNetworkChangeMode = false;
+ mState.activityOut = true;
+
+ assertFalse(mState.hasActivityOut());
+ }
+
+ @Test
+ public void testHasActivityOut_dataConnected_activityOut() {
+ mState.dataConnected = true;
+ mState.carrierNetworkChangeMode = false;
+ mState.activityOut = true;
+
+ assertTrue(mState.hasActivityOut());
+ }
+
+ @Test
+ public void testHasActivityOut_carrierNetworkChange() {
+ mState.dataConnected = true;
+ mState.carrierNetworkChangeMode = true;
+ mState.activityOut = true;
+
+ assertFalse(mState.hasActivityOut());
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java
index 16d73a39d551..cb62a735434d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/RecentAppOpsAccessesTest.java
@@ -1,15 +1,34 @@
-package com.android.settingslib.location;
+/*
+ * 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.settingslib.applications;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.Manifest;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.PackageOps;
import android.content.Context;
+import android.content.PermissionChecker;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -19,13 +38,14 @@ import android.os.UserManager;
import android.util.LongSparseArray;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowPermissionChecker;
import java.time.Clock;
import java.util.ArrayList;
@@ -34,7 +54,8 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
@RunWith(RobolectricTestRunner.class)
-public class RecentLocationAccessesTest {
+@Config(shadows = {ShadowPermissionChecker.class})
+public class RecentAppOpsAccessesTest {
private static final int TEST_UID = 1234;
private static final long NOW = 1_000_000_000; // Approximately 9/8/2001
@@ -54,7 +75,7 @@ public class RecentLocationAccessesTest {
private Clock mClock;
private Context mContext;
private int mTestUserId;
- private RecentLocationAccesses mRecentLocationAccesses;
+ private RecentAppOpsAccess mRecentAppOpsAccess;
@Before
public void setUp() throws NameNotFoundException {
@@ -69,24 +90,37 @@ public class RecentLocationAccessesTest {
.thenReturn("testApplicationLabel");
when(mPackageManager.getUserBadgedLabel(isA(CharSequence.class), isA(UserHandle.class)))
.thenReturn("testUserBadgedLabel");
+ when(mPackageManager.getPermissionFlags(any(), any(), any()))
+ .thenReturn(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
+ | PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED);
+ for (String testPackageName : TEST_PACKAGE_NAMES) {
+ ShadowPermissionChecker.setResult(
+ testPackageName,
+ Manifest.permission.ACCESS_COARSE_LOCATION,
+ PermissionChecker.PERMISSION_GRANTED);
+ ShadowPermissionChecker.setResult(
+ testPackageName,
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ PermissionChecker.PERMISSION_GRANTED);
+ }
mTestUserId = UserHandle.getUserId(TEST_UID);
when(mUserManager.getUserProfiles())
.thenReturn(Collections.singletonList(new UserHandle(mTestUserId)));
long[] testRequestTime = {ONE_MIN_AGO, TWENTY_THREE_HOURS_AGO, TWO_DAYS_AGO};
List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
- when(mAppOpsManager.getPackagesForOps(RecentLocationAccesses.LOCATION_OPS)).thenReturn(
+ when(mAppOpsManager.getPackagesForOps(RecentAppOpsAccess.LOCATION_OPS)).thenReturn(
appOps);
mockTestApplicationInfos(mTestUserId, TEST_PACKAGE_NAMES);
when(mClock.millis()).thenReturn(NOW);
- mRecentLocationAccesses = new RecentLocationAccesses(mContext, mClock);
+ mRecentAppOpsAccess = new RecentAppOpsAccess(mContext, mClock,
+ RecentAppOpsAccess.LOCATION_OPS);
}
@Test
- @Ignore
public void testGetAppList_shouldFilterRecentAccesses() {
- List<RecentLocationAccesses.Access> requests = mRecentLocationAccesses.getAppList(false);
+ List<RecentAppOpsAccess.Access> requests = mRecentAppOpsAccess.getAppList(false);
// Only two of the apps have requested location within 15 min.
assertThat(requests).hasSize(2);
// Make sure apps are ordered by recency
@@ -97,12 +131,11 @@ public class RecentLocationAccessesTest {
}
@Test
- @Ignore
public void testGetAppList_shouldNotShowAndroidOS() throws NameNotFoundException {
// Add android OS to the list of apps.
PackageOps androidSystemPackageOps =
createPackageOps(
- RecentLocationAccesses.ANDROID_SYSTEM_PACKAGE_NAME,
+ RecentAppOpsAccess.ANDROID_SYSTEM_PACKAGE_NAME,
Process.SYSTEM_UID,
AppOpsManager.OP_FINE_LOCATION,
ONE_MIN_AGO);
@@ -110,12 +143,12 @@ public class RecentLocationAccessesTest {
{ONE_MIN_AGO, TWENTY_THREE_HOURS_AGO, TWO_DAYS_AGO, ONE_MIN_AGO};
List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
appOps.add(androidSystemPackageOps);
- when(mAppOpsManager.getPackagesForOps(RecentLocationAccesses.LOCATION_OPS)).thenReturn(
+ when(mAppOpsManager.getPackagesForOps(RecentAppOpsAccess.LOCATION_OPS)).thenReturn(
appOps);
mockTestApplicationInfos(
- Process.SYSTEM_UID, RecentLocationAccesses.ANDROID_SYSTEM_PACKAGE_NAME);
+ Process.SYSTEM_UID, RecentAppOpsAccess.ANDROID_SYSTEM_PACKAGE_NAME);
- List<RecentLocationAccesses.Access> requests = mRecentLocationAccesses.getAppList(true);
+ List<RecentAppOpsAccess.Access> requests = mRecentAppOpsAccess.getAppList(true);
// Android OS shouldn't show up in the list of apps.
assertThat(requests).hasSize(2);
// Make sure apps are ordered by recency
@@ -159,7 +192,7 @@ public class RecentLocationAccessesTest {
// Slot for background access timestamp.
final LongSparseArray<AppOpsManager.NoteOpEvent> accessEvents = new LongSparseArray<>();
accessEvents.put(AppOpsManager.makeKey(AppOpsManager.UID_STATE_BACKGROUND,
- AppOpsManager.OP_FLAG_SELF), new AppOpsManager.NoteOpEvent(time, -1, null));
+ AppOpsManager.OP_FLAG_SELF), new AppOpsManager.NoteOpEvent(time, -1, null));
return new OpEntry(op, AppOpsManager.MODE_ALLOWED, Collections.singletonMap(null,
new AppOpsManager.AttributedOpEntry(op, false, accessEvents, null)));
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
index 4f11fb1f782f..6caf7624e1bc 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
@@ -151,4 +151,14 @@ public class PowerAllowlistBackendTest {
assertThat(mPowerAllowlistBackend.isSysAllowlisted(PACKAGE_TWO)).isFalse();
assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE)).isFalse();
}
+
+ @Test
+ public void testIsPowerSaveWhitelistExceptIdleApp() throws Exception {
+ doReturn(true).when(mDeviceIdleService)
+ .isPowerSaveWhitelistExceptIdleApp(PACKAGE_ONE);
+
+ mPowerAllowlistBackend.refreshList();
+
+ assertThat(mPowerAllowlistBackend.isAllowlistedExceptIdle(PACKAGE_ONE)).isTrue();
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
index 9e3312ae2ddf..29549d9a7fa7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
@@ -55,6 +55,7 @@ public class IllustrationPreferenceTest {
@Mock
private ViewGroup mRootView;
private Uri mImageUri;
+ private ImageView mBackgroundView;
private LottieAnimationView mAnimationView;
private IllustrationPreference mPreference;
private PreferenceViewHolder mViewHolder;
@@ -66,6 +67,7 @@ public class IllustrationPreferenceTest {
MockitoAnnotations.initMocks(this);
mImageUri = new Uri.Builder().build();
+ mBackgroundView = new ImageView(mContext);
mAnimationView = spy(new LottieAnimationView(mContext));
mMiddleGroundLayout = new FrameLayout(mContext);
final FrameLayout illustrationFrame = new FrameLayout(mContext);
@@ -73,6 +75,7 @@ public class IllustrationPreferenceTest {
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
doReturn(mMiddleGroundLayout).when(mRootView).findViewById(R.id.middleground_layout);
+ doReturn(mBackgroundView).when(mRootView).findViewById(R.id.background_view);
doReturn(mAnimationView).when(mRootView).findViewById(R.id.lottie_view);
doReturn(illustrationFrame).when(mRootView).findViewById(R.id.illustration_frame);
mViewHolder = spy(PreferenceViewHolder.createInstanceForTests(mRootView));
@@ -155,4 +158,32 @@ public class IllustrationPreferenceTest {
verify(mAnimationView).setFailureListener(any());
}
+
+ @Test
+ public void setMaxHeight_smallerThanRestrictedHeight_matchResult() {
+ final int restrictedHeight =
+ mContext.getResources().getDimensionPixelSize(
+ R.dimen.settingslib_illustration_height);
+ final int maxHeight = restrictedHeight - 200;
+
+ mPreference.setMaxHeight(maxHeight);
+ mPreference.onBindViewHolder(mViewHolder);
+
+ assertThat(mBackgroundView.getMaxHeight()).isEqualTo(maxHeight);
+ assertThat(mAnimationView.getMaxHeight()).isEqualTo(maxHeight);
+ }
+
+ @Test
+ public void setMaxHeight_largerThanRestrictedHeight_specificHeight() {
+ final int restrictedHeight =
+ mContext.getResources().getDimensionPixelSize(
+ R.dimen.settingslib_illustration_height);
+ final int maxHeight = restrictedHeight + 200;
+
+ mPreference.setMaxHeight(maxHeight);
+ mPreference.onBindViewHolder(mViewHolder);
+
+ assertThat(mBackgroundView.getMaxHeight()).isEqualTo(restrictedHeight);
+ assertThat(mAnimationView.getMaxHeight()).isEqualTo(restrictedHeight);
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index a46d28b273e5..db33c3f2c9cf 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -76,6 +76,8 @@ public class GlobalSettings {
Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED,
Settings.Global.DEVICE_CONFIG_SYNC_DISABLED,
Settings.Global.POWER_BUTTON_LONG_PRESS,
+ Settings.Global.AUTOMATIC_POWER_SAVE_MODE,
+ Settings.Global.ADVANCED_BATTERY_USAGE_AMOUNT,
Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 84c5febcb5a2..d0448ef63793 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -141,6 +141,8 @@ public class GlobalSettingsValidators {
/* last= */Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT));
VALIDATORS.put(Global.DISABLE_WINDOW_BLURS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.DEVICE_CONFIG_SYNC_DISABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.AUTOMATIC_POWER_SAVE_MODE, ANY_INTEGER_VALIDATOR);
+ VALIDATORS.put(Global.ADVANCED_BATTERY_USAGE_AMOUNT, PERCENTAGE_INTEGER_VALIDATOR);
VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS_DURATION_MS, NONE_NEGATIVE_LONG_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 6d7fb027ee99..7aeacdc0cf71 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -34,7 +34,9 @@ import static android.provider.settings.validators.SettingsValidators.TILE_LIST_
import static android.provider.settings.validators.SettingsValidators.TTS_LIST_VALIDATOR;
import android.provider.Settings.Secure;
+import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import java.util.Map;
@@ -276,7 +278,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.ACCESSIBILITY_BUTTON_MODE,
new InclusiveIntegerRangeValidator(
Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR,
- Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU));
+ Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE));
VALIDATORS.put(Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
new DiscreteValueValidator(new String[] {"0", "1"}));
VALIDATORS.put(Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
@@ -287,5 +289,32 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.NOTIFICATION_BUBBLES, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.DEVICE_STATE_ROTATION_LOCK, value -> {
+ if (TextUtils.isEmpty(value)) {
+ return true;
+ }
+ String[] intValues = value.split(":");
+ if (intValues.length % 2 != 0) {
+ return false;
+ }
+ InclusiveIntegerRangeValidator enumValidator =
+ new InclusiveIntegerRangeValidator(
+ Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED,
+ Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
+ ArraySet<String> keys = new ArraySet<>();
+ for (int i = 0; i < intValues.length - 1; ) {
+ String entryKey = intValues[i++];
+ String entryValue = intValues[i++];
+ if (!NON_NEGATIVE_INTEGER_VALIDATOR.validate(entryKey)
+ || !enumValidator.validate(entryValue)) {
+ return false;
+ }
+ // If the same device state key was specified more than once, this is invalid
+ if (!keys.add(entryKey)) {
+ return false;
+ }
+ }
+ return true;
+ });
}
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 7db73c697ea7..4a896e7f7042 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -137,7 +137,6 @@ public class SettingsBackupTest {
Settings.Global.AUTOFILL_LOGGING_LEVEL,
Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE,
Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
- Settings.Global.AUTOMATIC_POWER_SAVE_MODE,
Settings.Global.AVERAGE_TIME_TO_DISCHARGE,
Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME,
@@ -592,8 +591,7 @@ public class SettingsBackupTest {
Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
Settings.Global.CACHED_APPS_FREEZER_ENABLED,
Settings.Global.APP_INTEGRITY_VERIFICATION_TIMEOUT,
- Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
- Settings.Global.ADVANCED_BATTERY_USAGE_AMOUNT);
+ Settings.Global.KEY_CHORD_POWER_VOLUME_UP);
private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
newHashSet(
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index d051290cb4b4..721b432099f5 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -50,13 +50,37 @@ java_library {
srcs: ["src/com/android/systemui/EventLogTags.logtags"],
}
+filegroup {
+ name: "ReleaseJavaFiles",
+ srcs: [
+ "src-release/**/*.kt",
+ "src-release/**/*.java",
+ ],
+}
+
+filegroup {
+ name: "DebugJavaFiles",
+ srcs: [
+ "src-debug/**/*.kt",
+ "src-debug/**/*.java",
+ ],
+}
+
android_library {
name: "SystemUI-core",
srcs: [
"src/**/*.kt",
"src/**/*.java",
"src/**/I*.aidl",
+ "src-release/**/*.kt",
+ "src-release/**/*.java",
],
+ product_variables: {
+ debuggable: {
+ srcs: [":DebugJavaFiles"],
+ exclude_srcs: [":ReleaseJavaFiles"],
+ },
+ },
resource_dirs: [
"res-product",
"res-keyguard",
@@ -92,6 +116,7 @@ android_library {
"iconloader_base",
"SystemUI-tags",
"SystemUI-proto",
+ "monet",
"dagger2",
"jsr330",
"lottie",
@@ -142,6 +167,8 @@ android_library {
"src/**/*.kt",
"src/**/*.java",
"src/**/I*.aidl",
+ "src-release/**/*.kt",
+ "src-release/**/*.java",
],
static_libs: [
"SystemUIAnimationLib",
@@ -179,6 +206,7 @@ android_library {
"mockito-target-extended-minus-junit4",
"testables",
"truth-prebuilt",
+ "monet",
"dagger2",
"jsr330",
"WindowManager-Shell",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 9de1c5ea1a3d..58e3d398553c 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -183,6 +183,9 @@
<permission android:name="com.android.systemui.permission.PLUGIN"
android:protectionLevel="signature" />
+ <permission android:name="com.android.systemui.permission.FLAGS"
+ android:protectionLevel="signature" />
+
<!-- Adding Quick Settings tiles -->
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 0d18b8dea284..e509777633e7 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -22,6 +22,9 @@
},
{
"exclude-annotation": "android.platform.test.annotations.Postsubmit"
+ },
+ {
+ "exclude-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly"
}
]
},
@@ -82,6 +85,9 @@
},
{
"exclude-annotation": "android.platform.helpers.Staging"
+ },
+ {
+ "exclude-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly"
}
]
}
@@ -101,6 +107,9 @@
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly"
}
]
}
@@ -117,5 +126,24 @@
}
]
}
+ ],
+ "large-screen-postsubmit": [
+ {
+ "name": "PlatformScenarioTests",
+ "options" : [
+ {
+ "include-filter": "android.platform.test.scenario.sysui"
+ },
+ {
+ "include-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
]
}
diff --git a/packages/SystemUI/animation/res/anim/launch_host_dialog_enter.xml b/packages/SystemUI/animation/res/anim/launch_host_dialog_enter.xml
new file mode 100644
index 000000000000..c6b87d38f7da
--- /dev/null
+++ b/packages/SystemUI/animation/res/anim/launch_host_dialog_enter.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<!-- The enter animation of the host dialog is a translation of 0px that lasts 500ms so that the -->
+<!-- host dialog is directly visible but the dim background still takes 500ms to fade in. -->
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fromXDelta="0"
+ android:toXDelta="0"
+ android:duration="500" /> \ No newline at end of file
diff --git a/packages/SystemUI/animation/res/anim/launch_host_dialog_exit.xml b/packages/SystemUI/animation/res/anim/launch_host_dialog_exit.xml
new file mode 100644
index 000000000000..a0f441eaeed4
--- /dev/null
+++ b/packages/SystemUI/animation/res/anim/launch_host_dialog_exit.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<alpha
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:interpolator/decelerate_cubic"
+ android:duration="150"
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0" /> \ No newline at end of file
diff --git a/packages/SystemUI/animation/res/values/ids.xml b/packages/SystemUI/animation/res/values/ids.xml
new file mode 100644
index 000000000000..ef60a248f79a
--- /dev/null
+++ b/packages/SystemUI/animation/res/values/ids.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources>
+ <item type="id" name="launch_animation_running"/>
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/animation/res/values/styles.xml b/packages/SystemUI/animation/res/values/styles.xml
new file mode 100644
index 000000000000..ad06c9192bc3
--- /dev/null
+++ b/packages/SystemUI/animation/res/values/styles.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<resources>
+ <style name="HostDialogTheme">
+ <item name="android:windowAnimationStyle">@style/Animation.HostDialog</item>
+ <item name="android:windowIsFloating">false</item>
+ <item name="android:backgroundDimEnabled">true</item>
+ <item name="android:navigationBarColor">@android:color/transparent</item>
+ </style>
+
+ <style name="Animation.HostDialog" parent="@android:style/Animation">
+ <item name="android:windowEnterAnimation">@anim/launch_host_dialog_enter</item>
+ <item name="android:windowExitAnimation">@anim/launch_host_dialog_exit</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index a50efd731cf6..702060338359 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -1,24 +1,31 @@
+/*
+ * 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.systemui.animation
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.ValueAnimator
import android.app.ActivityManager
import android.app.ActivityTaskManager
-import android.app.AppGlobals
import android.app.PendingIntent
import android.app.TaskInfo
-import android.content.Context
import android.graphics.Matrix
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.graphics.RectF
-import android.graphics.drawable.GradientDrawable
import android.os.Looper
import android.os.RemoteException
import android.util.Log
-import android.util.MathUtils
import android.view.IRemoteAnimationFinishedCallback
import android.view.IRemoteAnimationRunner
import android.view.RemoteAnimationAdapter
@@ -27,7 +34,6 @@ import android.view.SyncRtSurfaceTransactionApplier
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
-import android.view.animation.AnimationUtils
import android.view.animation.PathInterpolator
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
@@ -39,53 +45,23 @@ private const val TAG = "ActivityLaunchAnimator"
* A class that allows activities to be started in a seamless way from a view that is transforming
* nicely into the starting window.
*/
-class ActivityLaunchAnimator(
- private val callback: Callback,
- context: Context
-) {
+class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) {
companion object {
- const val ANIMATION_DURATION = 500L
- private const val ANIMATION_DURATION_FADE_OUT_CONTENT = 150L
- private const val ANIMATION_DURATION_FADE_IN_WINDOW = 183L
- private const val ANIMATION_DELAY_FADE_IN_WINDOW = ANIMATION_DURATION_FADE_OUT_CONTENT
private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
private const val ANIMATION_DELAY_NAV_FADE_IN =
- ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN
+ LaunchAnimator.ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN
private const val LAUNCH_TIMEOUT = 1000L
- @JvmField val CONTENT_FADE_OUT_INTERPOLATOR = PathInterpolator(0f, 0f, 0.2f, 1f)
- private val WINDOW_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0.6f, 1f)
private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f)
private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
-
- private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
-
- /**
- * Given the [linearProgress] of a launch animation, return the linear progress of the
- * sub-animation starting [delay] ms after the launch animation and that lasts [duration].
- */
- @JvmStatic
- fun getProgress(linearProgress: Float, delay: Long, duration: Long): Float {
- return MathUtils.constrain(
- (linearProgress * ANIMATION_DURATION - delay) / duration,
- 0.0f,
- 1.0f
- )
- }
}
- private val packageManager = AppGlobals.getPackageManager()
-
- /** The interpolator used for the width, height, Y position and corner radius. */
- private val animationInterpolator = AnimationUtils.loadInterpolator(context,
- R.interpolator.launch_animation_interpolator_y)
-
- /** The interpolator used for the X position. */
- private val animationInterpolatorX = AnimationUtils.loadInterpolator(context,
- R.interpolator.launch_animation_interpolator_x)
-
- private val cornerRadii = FloatArray(8)
+ /**
+ * The callback of this animator. This should be set before any call to
+ * [start(Pending)IntentWithAnimation].
+ */
+ var callback: Callback? = null
/**
* Start an intent and animate the opening window. The intent will be started by running
@@ -100,6 +76,10 @@ class ActivityLaunchAnimator(
* If possible, you should pass the [packageName] of the intent that will be started so that
* trampoline activity launches will also be animated.
*
+ * If the device is currently locked, the user will have to unlock it before the intent is
+ * started unless [showOverLockscreen] is true. In that case, the activity will be started
+ * directly over the lockscreen.
+ *
* This method will throw any exception thrown by [intentStarter].
*/
@JvmOverloads
@@ -107,25 +87,28 @@ class ActivityLaunchAnimator(
controller: Controller?,
animate: Boolean = true,
packageName: String? = null,
+ showOverLockscreen: Boolean = false,
intentStarter: (RemoteAnimationAdapter?) -> Int
) {
if (controller == null || !animate) {
- Log.d(TAG, "Starting intent with no animation")
+ Log.i(TAG, "Starting intent with no animation")
intentStarter(null)
controller?.callOnIntentStartedOnMainThread(willAnimate = false)
return
}
- Log.d(TAG, "Starting intent with a launch animation")
+ val callback = this.callback ?: throw IllegalStateException(
+ "ActivityLaunchAnimator.callback must be set before using this animator")
val runner = Runner(controller)
- val isOnKeyguard = callback.isOnKeyguard()
+ val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen
- // Pass the RemoteAnimationAdapter to the intent starter only if we are not on the keyguard.
- val animationAdapter = if (!isOnKeyguard) {
+ // Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the
+ // keyguard with the animation
+ val animationAdapter = if (!hideKeyguardWithAnimation) {
RemoteAnimationAdapter(
- runner,
- ANIMATION_DURATION,
- ANIMATION_DURATION - 150 /* statusBarTransitionDelay */
+ runner,
+ LaunchAnimator.ANIMATION_DURATION,
+ LaunchAnimator.ANIMATION_DURATION - 150 /* statusBarTransitionDelay */
)
} else {
null
@@ -147,11 +130,13 @@ class ActivityLaunchAnimator(
// Only animate if the app is not already on top and will be opened, unless we are on the
// keyguard.
val willAnimate =
- launchResult == ActivityManager.START_TASK_TO_FRONT ||
- launchResult == ActivityManager.START_SUCCESS ||
- (launchResult == ActivityManager.START_DELIVERED_TO_TOP && isOnKeyguard)
+ launchResult == ActivityManager.START_TASK_TO_FRONT ||
+ launchResult == ActivityManager.START_SUCCESS ||
+ (launchResult == ActivityManager.START_DELIVERED_TO_TOP &&
+ hideKeyguardWithAnimation)
- Log.d(TAG, "launchResult=$launchResult willAnimate=$willAnimate isOnKeyguard=$isOnKeyguard")
+ Log.i(TAG, "launchResult=$launchResult willAnimate=$willAnimate " +
+ "hideKeyguardWithAnimation=$hideKeyguardWithAnimation")
controller.callOnIntentStartedOnMainThread(willAnimate)
// If we expect an animation, post a timeout to cancel it in case the remote animation is
@@ -160,7 +145,7 @@ class ActivityLaunchAnimator(
runner.postTimeout()
// Hide the keyguard using the launch animation instead of the default unlock animation.
- if (isOnKeyguard) {
+ if (hideKeyguardWithAnimation) {
callback.hideKeyguardWithAnimation(runner)
}
}
@@ -229,7 +214,7 @@ class ActivityLaunchAnimator(
*
* Note that all callbacks (onXXX methods) are all called on the main thread.
*/
- interface Controller {
+ interface Controller : LaunchAnimator.Controller {
companion object {
/**
* Return a [Controller] that will animate and expand [view] into the opening window.
@@ -254,53 +239,12 @@ class ActivityLaunchAnimator(
}
/**
- * The container in which the view that started the intent will be animating together with
- * the opening window.
- *
- * This will be used to:
- * - Get the associated [Context].
- * - Compute whether we are expanding fully above the current window.
- * - Apply surface transactions in sync with RenderThread.
- *
- * This container can be changed to force this [Controller] to animate the expanding view
- * inside a different location, for instance to ensure correct layering during the
- * animation.
- */
- var launchContainer: ViewGroup
-
- /**
- * Return the [State] of the view that will be animated. We will animate from this state to
- * the final window state.
- *
- * Note: This state will be mutated and passed to [onLaunchAnimationProgress] during the
- * animation.
- */
- fun createAnimatorState(): State
-
- /**
* The intent was started. If [willAnimate] is false, nothing else will happen and the
* animation will not be started.
*/
fun onIntentStarted(willAnimate: Boolean) {}
/**
- * The animation started. This is typically used to initialize any additional resource
- * needed for the animation. [isExpandingFullyAbove] will be true if the window is expanding
- * fully above the [root view][getRootView].
- */
- fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {}
-
- /** The animation made progress and the expandable view [state] should be updated. */
- fun onLaunchAnimationProgress(state: State, progress: Float, linearProgress: Float) {}
-
- /**
- * The animation ended. This will be called *if and only if* [onLaunchAnimationStart] was
- * called previously. This is typically used to clean up the resources initialized when the
- * animation was started.
- */
- fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {}
-
- /**
* The animation was cancelled. Note that [onLaunchAnimationEnd] will still be called after
* this if the animation was already started, i.e. if [onLaunchAnimationStart] was called
* before the cancellation.
@@ -308,66 +252,11 @@ class ActivityLaunchAnimator(
fun onLaunchAnimationCancelled() {}
}
- /** The state of an expandable view during an [ActivityLaunchAnimator] animation. */
- open class State(
- /** The position of the view in screen space coordinates. */
- var top: Int,
- var bottom: Int,
- var left: Int,
- var right: Int,
-
- var topCornerRadius: Float = 0f,
- var bottomCornerRadius: Float = 0f
- ) {
- private val startTop = top
- private val startBottom = bottom
- private val startLeft = left
- private val startRight = right
- private val startWidth = width
- private val startHeight = height
- val startCenterX = centerX
- val startCenterY = centerY
-
- val width: Int
- get() = right - left
-
- val height: Int
- get() = bottom - top
-
- open val topChange: Int
- get() = top - startTop
-
- open val bottomChange: Int
- get() = bottom - startBottom
-
- val leftChange: Int
- get() = left - startLeft
-
- val rightChange: Int
- get() = right - startRight
-
- val widthRatio: Float
- get() = width.toFloat() / startWidth
-
- val heightRatio: Float
- get() = height.toFloat() / startHeight
-
- val centerX: Float
- get() = left + width / 2f
-
- val centerY: Float
- get() = top + height / 2f
-
- /** Whether the expanded view should be visible or hidden. */
- var visible: Boolean = true
- }
-
@VisibleForTesting
inner class Runner(private val controller: Controller) : IRemoteAnimationRunner.Stub() {
private val launchContainer = controller.launchContainer
private val context = launchContainer.context
private val transactionApplier = SyncRtSurfaceTransactionApplier(launchContainer)
- private var animator: ValueAnimator? = null
private val matrix = Matrix()
private val invertMatrix = Matrix()
@@ -375,6 +264,7 @@ class ActivityLaunchAnimator(
private var windowCropF = RectF()
private var timedOut = false
private var cancelled = false
+ private var animation: LaunchAnimator.Animation? = null
// A timeout to cancel the remote animation if it is not started within X milliseconds after
// the intent was started.
@@ -424,13 +314,16 @@ class ActivityLaunchAnimator(
nonApps: Array<out RemoteAnimationTarget>?,
iCallback: IRemoteAnimationFinishedCallback?
) {
- Log.d(TAG, "Remote animation started")
+ if (LaunchAnimator.DEBUG) {
+ Log.d(TAG, "Remote animation started")
+ }
+
val window = apps?.firstOrNull {
it.mode == RemoteAnimationTarget.MODE_OPENING
}
if (window == null) {
- Log.d(TAG, "Aborting the animation as no window is opening")
+ Log.i(TAG, "Aborting the animation as no window is opening")
removeTimeout()
iCallback?.invoke()
controller.onLaunchAnimationCancelled()
@@ -441,130 +334,63 @@ class ActivityLaunchAnimator(
it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
}
- // Start state.
- val state = controller.createAnimatorState()
-
- val startTop = state.top
- val startBottom = state.bottom
- val startLeft = state.left
- val startRight = state.right
- val startXCenter = (startLeft + startRight) / 2f
- val startWidth = startRight - startLeft
-
- val startTopCornerRadius = state.topCornerRadius
- val startBottomCornerRadius = state.bottomCornerRadius
-
- // End state.
val windowBounds = window.screenSpaceBounds
- val endTop = windowBounds.top
- val endBottom = windowBounds.bottom
- val endLeft = windowBounds.left
- val endRight = windowBounds.right
- val endXCenter = (endLeft + endRight) / 2f
- val endWidth = endRight - endLeft
-
- // TODO(b/184121838): Ensure that we are launching on the same screen.
- val rootViewLocation = launchContainer.locationOnScreen
- val isExpandingFullyAbove = endTop <= rootViewLocation[1] &&
- endBottom >= rootViewLocation[1] + launchContainer.height &&
- endLeft <= rootViewLocation[0] &&
- endRight >= rootViewLocation[0] + launchContainer.width
-
- // TODO(b/184121838): We should somehow get the top and bottom radius of the window.
+ val endState = LaunchAnimator.State(
+ top = windowBounds.top,
+ bottom = windowBounds.bottom,
+ left = windowBounds.left,
+ right = windowBounds.right
+ )
+ val callback = this@ActivityLaunchAnimator.callback!!
+ val windowBackgroundColor = callback.getBackgroundColor(window.taskInfo)
+
+ // TODO(b/184121838): We should somehow get the top and bottom radius of the window
+ // instead of recomputing isExpandingFullyAbove here.
+ val isExpandingFullyAbove =
+ launchAnimator.isExpandingFullyAbove(controller.launchContainer, endState)
val endRadius = if (isExpandingFullyAbove) {
// Most of the time, expanding fully above the root view means expanding in full
// screen.
- ScreenDecorationsUtils.getWindowCornerRadius(context.resources)
+ ScreenDecorationsUtils.getWindowCornerRadius(context)
} else {
// This usually means we are in split screen mode, so 2 out of 4 corners will have
// a radius of 0.
0f
}
+ endState.topCornerRadius = endRadius
+ endState.bottomCornerRadius = endRadius
- // We add an extra layer with the same color as the app splash screen background color,
- // which is usually the same color of the app background. We first fade in this layer
- // to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
- // launch container and reveal the opening window.
- val windowBackgroundColor = callback.getBackgroundColor(window.taskInfo)
- val windowBackgroundLayer = GradientDrawable().apply {
- setColor(windowBackgroundColor)
- alpha = 0
- }
-
- // Update state.
- val animator = ValueAnimator.ofFloat(0f, 1f)
- this.animator = animator
- animator.duration = ANIMATION_DURATION
- animator.interpolator = Interpolators.LINEAR
-
- val launchContainerOverlay = launchContainer.overlay
- animator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
- Log.d(TAG, "Animation started")
+ // We animate the opening window and delegate the view expansion to [this.controller].
+ val delegate = this.controller
+ val controller = object : LaunchAnimator.Controller by delegate {
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
callback.setBlursDisabledForAppLaunch(true)
- controller.onLaunchAnimationStart(isExpandingFullyAbove)
-
- // Add the drawable to the launch container overlay. Overlays always draw
- // drawables after views, so we know that it will be drawn above any view added
- // by the controller.
- launchContainerOverlay.add(windowBackgroundLayer)
+ delegate.onLaunchAnimationStart(isExpandingFullyAbove)
}
- override fun onAnimationEnd(animation: Animator?) {
- Log.d(TAG, "Animation ended")
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
callback.setBlursDisabledForAppLaunch(false)
iCallback?.invoke()
- controller.onLaunchAnimationEnd(isExpandingFullyAbove)
- launchContainerOverlay.remove(windowBackgroundLayer)
+ delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
}
- })
- animator.addUpdateListener { animation ->
- if (cancelled) {
- return@addUpdateListener
+ override fun onLaunchAnimationProgress(
+ state: LaunchAnimator.State,
+ progress: Float,
+ linearProgress: Float
+ ) {
+ applyStateToWindow(window, state)
+ navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
+ delegate.onLaunchAnimationProgress(state, progress, linearProgress)
}
-
- val linearProgress = animation.animatedFraction
- val progress = animationInterpolator.getInterpolation(linearProgress)
- val xProgress = animationInterpolatorX.getInterpolation(linearProgress)
- val xCenter = MathUtils.lerp(startXCenter, endXCenter, xProgress)
- val halfWidth = lerp(startWidth, endWidth, progress) / 2
-
- state.top = lerp(startTop, endTop, progress).roundToInt()
- state.bottom = lerp(startBottom, endBottom, progress).roundToInt()
- state.left = (xCenter - halfWidth).roundToInt()
- state.right = (xCenter + halfWidth).roundToInt()
-
- state.topCornerRadius = MathUtils.lerp(startTopCornerRadius, endRadius, progress)
- state.bottomCornerRadius =
- MathUtils.lerp(startBottomCornerRadius, endRadius, progress)
-
- // The expanding view can/should be hidden once it is completely coverred by the
- // windowBackgroundLayer.
- state.visible =
- getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) < 1
-
- applyStateToWindow(window, state)
- applyStateToWindowBackgroundLayer(windowBackgroundLayer, state, linearProgress)
- navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
-
- // If we started expanding the view, we make it 1 pixel smaller on all sides to
- // avoid artefacts on the corners caused by anti-aliasing of the view background and
- // the window background layer.
- if (state.top != startTop && state.left != startLeft &&
- state.bottom != startBottom && state.right != startRight) {
- state.top += 1
- state.left += 1
- state.right -= 1
- state.bottom -= 1
- }
- controller.onLaunchAnimationProgress(state, progress, linearProgress)
}
- animator.start()
+ // We draw a hole when the additional layer is fading out to reveal the opening window.
+ animation = launchAnimator.startAnimation(
+ controller, endState, windowBackgroundColor, drawHole = true)
}
- private fun applyStateToWindow(window: RemoteAnimationTarget, state: State) {
+ private fun applyStateToWindow(window: RemoteAnimationTarget, state: LaunchAnimator.State) {
val screenBounds = window.screenSpaceBounds
val centerX = (screenBounds.left + screenBounds.right) / 2f
val centerY = (screenBounds.top + screenBounds.bottom) / 2f
@@ -618,48 +444,13 @@ class ActivityLaunchAnimator(
transactionApplier.scheduleApply(params)
}
- private fun applyStateToWindowBackgroundLayer(
- drawable: GradientDrawable,
- state: State,
- linearProgress: Float
- ) {
- // Update position.
- drawable.setBounds(state.left, state.top, state.right, state.bottom)
-
- // Update radius.
- cornerRadii[0] = state.topCornerRadius
- cornerRadii[1] = state.topCornerRadius
- cornerRadii[2] = state.topCornerRadius
- cornerRadii[3] = state.topCornerRadius
- cornerRadii[4] = state.bottomCornerRadius
- cornerRadii[5] = state.bottomCornerRadius
- cornerRadii[6] = state.bottomCornerRadius
- cornerRadii[7] = state.bottomCornerRadius
- drawable.cornerRadii = cornerRadii
-
- // We first fade in the background layer to hide the expanding view, then fade it out
- // with SRC mode to draw a hole punch in the status bar and reveal the opening window.
- val fadeInProgress = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT)
- if (fadeInProgress < 1) {
- val alpha = CONTENT_FADE_OUT_INTERPOLATOR.getInterpolation(fadeInProgress)
- drawable.alpha = (alpha * 0xFF).roundToInt()
- drawable.setXfermode(null)
- } else {
- val fadeOutProgress = getProgress(linearProgress,
- ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW)
- val alpha = 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(fadeOutProgress)
- drawable.alpha = (alpha * 0xFF).roundToInt()
- drawable.setXfermode(SRC_MODE)
- }
- }
-
private fun applyStateToNavigationBar(
navigationBar: RemoteAnimationTarget,
- state: State,
+ state: LaunchAnimator.State,
linearProgress: Float
) {
- val fadeInProgress = getProgress(linearProgress, ANIMATION_DELAY_NAV_FADE_IN,
- ANIMATION_DURATION_NAV_FADE_OUT)
+ val fadeInProgress = LaunchAnimator.getProgress(linearProgress,
+ ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_DURATION_NAV_FADE_OUT)
val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash)
if (fadeInProgress > 0) {
@@ -668,13 +459,13 @@ class ActivityLaunchAnimator(
0f, (state.top - navigationBar.sourceContainerBounds.top).toFloat())
windowCrop.set(state.left, 0, state.right, state.height)
params
- .withAlpha(NAV_FADE_IN_INTERPOLATOR.getInterpolation(fadeInProgress))
- .withMatrix(matrix)
- .withWindowCrop(windowCrop)
- .withVisibility(true)
+ .withAlpha(NAV_FADE_IN_INTERPOLATOR.getInterpolation(fadeInProgress))
+ .withMatrix(matrix)
+ .withWindowCrop(windowCrop)
+ .withVisibility(true)
} else {
- val fadeOutProgress = getProgress(linearProgress, 0,
- ANIMATION_DURATION_NAV_FADE_OUT)
+ val fadeOutProgress = LaunchAnimator.getProgress(linearProgress, 0,
+ ANIMATION_DURATION_NAV_FADE_OUT)
params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress))
}
@@ -686,7 +477,7 @@ class ActivityLaunchAnimator(
return
}
- Log.d(TAG, "Remote animation timed out")
+ Log.i(TAG, "Remote animation timed out")
timedOut = true
controller.onLaunchAnimationCancelled()
}
@@ -696,11 +487,11 @@ class ActivityLaunchAnimator(
return
}
- Log.d(TAG, "Remote animation was cancelled")
+ Log.i(TAG, "Remote animation was cancelled")
cancelled = true
removeTimeout()
context.mainExecutor.execute {
- animator?.cancel()
+ animation?.cancel()
controller.onLaunchAnimationCancelled()
}
}
@@ -712,9 +503,5 @@ class ActivityLaunchAnimator(
e.printStackTrace()
}
}
-
- private fun lerp(start: Int, stop: Int, amount: Float): Float {
- return MathUtils.lerp(start.toFloat(), stop.toFloat(), amount)
- }
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
index d4be25382395..258ca6bdf79b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.systemui.animation
/**
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
new file mode 100644
index 000000000000..669a054eaa2a
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -0,0 +1,651 @@
+/*
+ * 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.systemui.animation
+
+import android.app.Dialog
+import android.content.Context
+import android.graphics.Color
+import android.os.Looper
+import android.util.Log
+import android.view.GhostView
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewTreeObserver.OnPreDrawListener
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+import android.view.WindowManagerPolicyConstants
+import android.widget.FrameLayout
+
+private const val TAG = "DialogLaunchAnimator"
+
+/**
+ * A class that allows dialogs to be started in a seamless way from a view that is transforming
+ * nicely into the starting dialog.
+ *
+ * Important: Don't forget to call [DialogLaunchAnimator.onDozeAmountChanged] when the doze amount
+ * changes to gracefully handle dialogs fading out when the device is dozing.
+ */
+class DialogLaunchAnimator(
+ private val context: Context,
+ private val launchAnimator: LaunchAnimator,
+ private val hostDialogProvider: HostDialogProvider
+) {
+ private companion object {
+ private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.launch_animation_running
+ }
+
+ // TODO(b/201264644): Remove this set.
+ private val currentAnimations = hashSetOf<DialogLaunchAnimation>()
+
+ /**
+ * Show [dialog] by expanding it from [view].
+ *
+ * Caveats: When calling this function, the dialog content view will actually be stolen and
+ * attached to a different dialog (and thus a different window) which means that the actual
+ * dialog window will never be drawn. Moreover, unless [dialog] is a [ListenableDialog], you
+ * must call dismiss(), hide() and show() on the [Dialog] returned by this function to actually
+ * dismiss, hide or show the dialog.
+ */
+ fun showFromView(dialog: Dialog, view: View): Dialog {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ throw IllegalStateException(
+ "showFromView must be called from the main thread and dialog must be created in " +
+ "the main thread")
+ }
+
+ // Make sure we don't run the launch animation from the same view twice at the same time.
+ if (view.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) {
+ Log.e(TAG, "Not running dialog launch animation as there is already one running")
+ dialog.show()
+ return dialog
+ }
+
+ view.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
+
+ val launchAnimation = DialogLaunchAnimation(
+ context, launchAnimator, hostDialogProvider, view,
+ onDialogDismissed = { currentAnimations.remove(it) }, originalDialog = dialog)
+ val hostDialog = launchAnimation.hostDialog
+ currentAnimations.add(launchAnimation)
+
+ // If the dialog is dismissed/hidden/shown, then we should actually dismiss/hide/show the
+ // host dialog.
+ if (dialog is ListenableDialog) {
+ dialog.addListener(object : DialogListener {
+ override fun onDismiss() {
+ dialog.removeListener(this)
+ hostDialog.dismiss()
+ }
+
+ override fun onHide() {
+ if (launchAnimation.ignoreNextCallToHide) {
+ launchAnimation.ignoreNextCallToHide = false
+ return
+ }
+
+ hostDialog.hide()
+ }
+
+ override fun onShow() {
+ hostDialog.show()
+
+ // We don't actually want to show the original dialog, so hide it.
+ launchAnimation.ignoreNextCallToHide = true
+ dialog.hide()
+ }
+ })
+ }
+
+ launchAnimation.start()
+ return hostDialog
+ }
+
+ /** Notify the current doze amount, to ensure that dialogs fade out when dozing. */
+ // TODO(b/193634619): Replace this by some mandatory constructor parameter to make sure that we
+ // don't forget to call this when the doze amount changes.
+ fun onDozeAmountChanged(amount: Float) {
+ currentAnimations.forEach { it.onDozeAmountChanged(amount) }
+ }
+
+ /**
+ * Ensure that all dialogs currently shown won't animate into their touch surface when
+ * dismissed.
+ *
+ * This is a temporary API meant to be called right before we both dismiss a dialog and start
+ * an activity, which currently does not look good if we animate the dialog into the touch
+ * surface at the same time as the activity starts.
+ *
+ * TODO(b/193634619): Remove this function and animate dialog into opening activity instead.
+ */
+ fun disableAllCurrentDialogsExitAnimations() {
+ currentAnimations.forEach { it.exitAnimationDisabled = true }
+ }
+}
+
+interface HostDialogProvider {
+ /**
+ * Create a host dialog that will be used to host a launch animation. This host dialog must:
+ * 1. call [onCreateCallback] in its onCreate() method, e.g. right after calling
+ * super.onCreate().
+ * 2. call [dismissOverride] instead of doing any dismissing logic. The actual dismissing
+ * logic should instead be done inside the lambda passed to [dismissOverride], which will
+ * be called after the exit animation.
+ *
+ * See SystemUIHostDialogProvider for an example of implementation.
+ */
+ fun createHostDialog(
+ context: Context,
+ theme: Int,
+ onCreateCallback: () -> Unit,
+ dismissOverride: (() -> Unit) -> Unit
+ ): Dialog
+}
+
+/** A dialog to/from which we can add/remove listeners. */
+interface ListenableDialog {
+ /** Add [listener] to the listeners. */
+ fun addListener(listener: DialogListener)
+
+ /** Remove [listener] from the listeners. */
+ fun removeListener(listener: DialogListener)
+}
+
+interface DialogListener {
+ /** Called when this dialog dismiss() is called. */
+ fun onDismiss()
+
+ /** Called when this dialog hide() is called. */
+ fun onHide()
+
+ /** Called when this dialog show() is called. */
+ fun onShow()
+}
+
+private class DialogLaunchAnimation(
+ private val context: Context,
+ private val launchAnimator: LaunchAnimator,
+ hostDialogProvider: HostDialogProvider,
+
+ /** The view that triggered the dialog after being tapped. */
+ private val touchSurface: View,
+
+ /**
+ * A callback that will be called with this [DialogLaunchAnimation] after the dialog was
+ * dismissed and the exit animation is done.
+ */
+ private val onDialogDismissed: (DialogLaunchAnimation) -> Unit,
+
+ /** The original dialog whose content will be shown and animate in/out in [hostDialog]. */
+ private val originalDialog: Dialog
+) {
+ /**
+ * The fullscreen dialog to which we will add the content view [originalDialogView] of
+ * [originalDialog].
+ */
+ val hostDialog = hostDialogProvider.createHostDialog(
+ context, R.style.HostDialogTheme, this::onHostDialogCreated, this::onHostDialogDismissed)
+
+ /** The root content view of [hostDialog]. */
+ private val hostDialogRoot = FrameLayout(context)
+
+ /**
+ * The content view of [originalDialog], which will be stolen from that dialog and added to
+ * [hostDialogRoot].
+ */
+ private var originalDialogView: View? = null
+
+ /**
+ * The background color of [originalDialogView], taking into consideration the [originalDialog]
+ * window background color.
+ */
+ private var originalDialogBackgroundColor = Color.BLACK
+
+ /**
+ * Whether we are currently launching/showing the dialog by animating it from [touchSurface].
+ */
+ private var isLaunching = true
+
+ /** Whether we are currently dismissing/hiding the dialog by animating into [touchSurface]. */
+ private var isDismissing = false
+
+ private var dismissRequested = false
+ var ignoreNextCallToHide = false
+ var exitAnimationDisabled = false
+
+ private var isTouchSurfaceGhostDrawn = false
+ private var isOriginalDialogViewLaidOut = false
+
+ fun start() {
+ // Show the host (fullscreen) dialog, to which we will add the stolen dialog view.
+ hostDialog.show()
+
+ // Steal the dialog view. We do that by showing it but preventing it from drawing, then
+ // hiding it as soon as its content is available.
+ stealOriginalDialogContentView(then = this::showDialogFromView)
+ }
+
+ private fun onHostDialogCreated() {
+ // Make the dialog fullscreen with a transparent background.
+ hostDialog.setContentView(
+ hostDialogRoot,
+ ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ )
+
+ val window = hostDialog.window
+ ?: throw IllegalStateException("There is no window associated to the host dialog")
+ window.setBackgroundDrawableResource(android.R.color.transparent)
+ window.setLayout(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.MATCH_PARENT
+ )
+
+ // If we are using gesture navigation, then we can overlay the navigation/task bars with
+ // the host dialog.
+ val navigationMode = context.resources.getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode)
+ if (navigationMode == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL) {
+ window.attributes.fitInsetsTypes = window.attributes.fitInsetsTypes and
+ WindowInsets.Type.navigationBars().inv()
+ window.addFlags(FLAG_LAYOUT_IN_SCREEN or FLAG_LAYOUT_INSET_DECOR)
+ window.setDecorFitsSystemWindows(false)
+ }
+
+ // Disable the dim. We will enable it once we start the animation.
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+
+ // Add a temporary touch surface ghost as soon as the window is ready to draw. This
+ // temporary ghost will be drawn together with the touch surface, but in the host dialog
+ // window. Once it is drawn, we will make the touch surface invisible, and then start the
+ // animation. We do all this synchronization to avoid flicker that would occur if we made
+ // the touch surface invisible too early (before its ghost is drawn), leading to one or more
+ // frames with a hole instead of the touch surface (or its ghost).
+ hostDialogRoot.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ hostDialogRoot.viewTreeObserver.removeOnPreDrawListener(this)
+ addTemporaryTouchSurfaceGhost()
+ return true
+ }
+ })
+ hostDialogRoot.invalidate()
+ }
+
+ private fun addTemporaryTouchSurfaceGhost() {
+ // Create a ghost of the touch surface (which will make the touch surface invisible) and add
+ // it to the host dialog. We will wait for this ghost to be drawn before starting the
+ // animation.
+ val ghost = GhostView.addGhost(touchSurface, hostDialogRoot)
+
+ // The ghost of the touch surface was just created, so the touch surface was made invisible.
+ // We make it visible again until the ghost is actually drawn.
+ touchSurface.visibility = View.VISIBLE
+
+ // Wait for the ghost to be drawn before continuing.
+ ghost.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ ghost.viewTreeObserver.removeOnPreDrawListener(this)
+ onTouchSurfaceGhostDrawn()
+ return true
+ }
+ })
+ ghost.invalidate()
+ }
+
+ private fun onTouchSurfaceGhostDrawn() {
+ // Make the touch surface invisible and make sure that it stays invisible as long as the
+ // dialog is shown or animating.
+ touchSurface.visibility = View.INVISIBLE
+ if (touchSurface is LaunchableView) {
+ touchSurface.setShouldBlockVisibilityChanges(true)
+ }
+
+ // Add a pre draw listener to (maybe) start the animation once the touch surface is
+ // actually invisible.
+ touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ touchSurface.viewTreeObserver.removeOnPreDrawListener(this)
+ isTouchSurfaceGhostDrawn = true
+ maybeStartLaunchAnimation()
+ return true
+ }
+ })
+ touchSurface.invalidate()
+ }
+
+ /** Get the content view of [originalDialog] and pass it to [then]. */
+ private fun stealOriginalDialogContentView(then: (View) -> Unit) {
+ // The original dialog content view will be attached to android.R.id.content when the dialog
+ // is shown, so we show the dialog and add an observer to get the view but also prevents the
+ // original dialog from being drawn.
+ val androidContent = originalDialog.findViewById<ViewGroup>(android.R.id.content)
+ ?: throw IllegalStateException("Dialog does not have any android.R.id.content view")
+
+ androidContent.viewTreeObserver.addOnPreDrawListener(
+ object : OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ if (androidContent.childCount == 1) {
+ androidContent.viewTreeObserver.removeOnPreDrawListener(this)
+
+ // Hide the animated dialog. Because of the dialog listener set up
+ // earlier, this would also hide the host dialog, but in this case we
+ // need to keep the host dialog visible.
+ ignoreNextCallToHide = true
+ originalDialog.hide()
+
+ then(androidContent.getChildAt(0))
+ return false
+ }
+
+ // Never draw the original dialog content.
+ return false
+ }
+ })
+ originalDialog.show()
+ }
+
+ private fun showDialogFromView(dialogView: View) {
+ // Save the dialog view for later as we will need it for the close animation.
+ this.originalDialogView = dialogView
+
+ // Close the dialog when clicking outside of it.
+ hostDialogRoot.setOnClickListener { hostDialog.dismiss() }
+ dialogView.isClickable = true
+
+ // Set the background of the window dialog to the dialog itself.
+ // TODO(b/193634619): Support dialog windows without background.
+ // TODO(b/193634619): Support dialog whose background comes from the content view instead of
+ // the window.
+ val typedArray =
+ originalDialog.context.obtainStyledAttributes(com.android.internal.R.styleable.Window)
+ val backgroundRes =
+ typedArray.getResourceId(com.android.internal.R.styleable.Window_windowBackground, 0)
+ typedArray.recycle()
+ if (backgroundRes == 0) {
+ throw IllegalStateException("Dialogs with no backgrounds on window are not supported")
+ }
+
+ dialogView.setBackgroundResource(backgroundRes)
+ originalDialogBackgroundColor =
+ GhostedViewLaunchAnimatorController.findGradientDrawable(dialogView.background!!)
+ ?.color
+ ?.defaultColor ?: Color.BLACK
+
+ // Add the dialog view to the host (fullscreen) dialog and make it invisible to make sure
+ // it's not drawn yet.
+ (dialogView.parent as? ViewGroup)?.removeView(dialogView)
+ hostDialogRoot.addView(
+ dialogView,
+
+ // We give it the size of its original dialog window.
+ FrameLayout.LayoutParams(
+ originalDialog.window.attributes.width,
+ originalDialog.window.attributes.height,
+ Gravity.CENTER
+ )
+ )
+ dialogView.visibility = View.INVISIBLE
+
+ // Start the animation when the dialog is laid out in the center of the host dialog.
+ dialogView.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
+ override fun onLayoutChange(
+ view: View,
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int,
+ oldLeft: Int,
+ oldTop: Int,
+ oldRight: Int,
+ oldBottom: Int
+ ) {
+ dialogView.removeOnLayoutChangeListener(this)
+
+ isOriginalDialogViewLaidOut = true
+ maybeStartLaunchAnimation()
+ }
+ })
+ }
+
+ private fun maybeStartLaunchAnimation() {
+ if (!isTouchSurfaceGhostDrawn || !isOriginalDialogViewLaidOut) {
+ return
+ }
+
+ // Show the background dim.
+ hostDialog.window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+
+ startAnimation(
+ isLaunching = true,
+ onLaunchAnimationStart = {
+ // Remove the temporary ghost. Another ghost (that ghosts only the touch surface
+ // content, and not its background) will be added right after this and will be
+ // animated.
+ GhostView.removeGhost(touchSurface)
+ },
+ onLaunchAnimationEnd = {
+ touchSurface.setTag(R.id.launch_animation_running, null)
+
+ // We hide the touch surface when the dialog is showing. We will make this
+ // view visible again when dismissing the dialog.
+ touchSurface.visibility = View.INVISIBLE
+
+ isLaunching = false
+
+ // dismiss was called during the animation, dismiss again now to actually
+ // dismiss.
+ if (dismissRequested) {
+ hostDialog.dismiss()
+ }
+ }
+ )
+ }
+
+ private fun onHostDialogDismissed(actualDismiss: () -> Unit) {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ context.mainExecutor.execute { onHostDialogDismissed(actualDismiss) }
+ return
+ }
+
+ // TODO(b/193634619): Support interrupting the launch animation in the middle.
+ if (isLaunching) {
+ dismissRequested = true
+ return
+ }
+
+ if (isDismissing) {
+ return
+ }
+
+ isDismissing = true
+ hideDialogIntoView { instantDismiss: Boolean ->
+ if (instantDismiss) {
+ originalDialog.hide()
+ hostDialog.hide()
+ }
+
+ originalDialog.dismiss()
+ actualDismiss()
+ }
+ }
+
+ /**
+ * Hide the dialog into the touch surface and call [dismissDialogs] when the animation is done
+ * (passing instantDismiss=true) or if it's skipped (passing instantDismiss=false) to actually
+ * dismiss the dialogs.
+ */
+ private fun hideDialogIntoView(dismissDialogs: (Boolean) -> Unit) {
+ if (!shouldAnimateDialogIntoView()) {
+ Log.i(TAG, "Skipping animation of dialog into the touch surface")
+
+ // Make sure we allow the touch surface to change its visibility again.
+ if (touchSurface is LaunchableView) {
+ touchSurface.setShouldBlockVisibilityChanges(false)
+ }
+
+ // If the view is invisible it's probably because of us, so we make it visible again.
+ if (touchSurface.visibility == View.INVISIBLE) {
+ touchSurface.visibility = View.VISIBLE
+ }
+
+ dismissDialogs(false /* instantDismiss */)
+ onDialogDismissed(this@DialogLaunchAnimation)
+ return
+ }
+
+ startAnimation(
+ isLaunching = false,
+ onLaunchAnimationStart = {
+ // Remove the dim background as soon as we start the animation.
+ hostDialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ },
+ onLaunchAnimationEnd = {
+ // Make sure we allow the touch surface to change its visibility again.
+ if (touchSurface is LaunchableView) {
+ touchSurface.setShouldBlockVisibilityChanges(false)
+ }
+
+ touchSurface.visibility = View.VISIBLE
+ originalDialogView!!.visibility = View.INVISIBLE
+
+ // The animated ghost was just removed. We create a temporary ghost that will be
+ // removed only once we draw the touch surface, to avoid flickering that would
+ // happen when removing the ghost too early (before the touch surface is drawn).
+ GhostView.addGhost(touchSurface, hostDialogRoot)
+
+ touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ touchSurface.viewTreeObserver.removeOnPreDrawListener(this)
+
+ // Now that the touch surface was drawn, we can remove the temporary ghost
+ // and instantly dismiss the dialog.
+ GhostView.removeGhost(touchSurface)
+ dismissDialogs(true /* instantDismiss */)
+ onDialogDismissed(this@DialogLaunchAnimation)
+
+ return true
+ }
+ })
+ touchSurface.invalidate()
+ }
+ )
+ }
+
+ private fun startAnimation(
+ isLaunching: Boolean,
+ onLaunchAnimationStart: () -> Unit = {},
+ onLaunchAnimationEnd: () -> Unit = {}
+ ) {
+ val dialogView = this.originalDialogView!!
+
+ // Create 2 ghost controllers to animate both the dialog and the touch surface in the host
+ // dialog.
+ val startView = if (isLaunching) touchSurface else dialogView
+ val endView = if (isLaunching) dialogView else touchSurface
+ val startViewController = GhostedViewLaunchAnimatorController(startView)
+ val endViewController = GhostedViewLaunchAnimatorController(endView)
+ startViewController.launchContainer = hostDialogRoot
+ endViewController.launchContainer = hostDialogRoot
+
+ val endState = endViewController.createAnimatorState()
+ val controller = object : LaunchAnimator.Controller {
+ override var launchContainer: ViewGroup
+ get() = startViewController.launchContainer
+ set(value) {
+ startViewController.launchContainer = value
+ endViewController.launchContainer = value
+ }
+
+ override fun createAnimatorState(): LaunchAnimator.State {
+ return startViewController.createAnimatorState()
+ }
+
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ // During launch, onLaunchAnimationStart will be used to remove the temporary touch
+ // surface ghost so it is important to call this before calling
+ // onLaunchAnimationStart on the controller (which will create its own ghost).
+ onLaunchAnimationStart()
+
+ startViewController.onLaunchAnimationStart(isExpandingFullyAbove)
+ endViewController.onLaunchAnimationStart(isExpandingFullyAbove)
+ }
+
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ startViewController.onLaunchAnimationEnd(isExpandingFullyAbove)
+ endViewController.onLaunchAnimationEnd(isExpandingFullyAbove)
+
+ onLaunchAnimationEnd()
+ }
+
+ override fun onLaunchAnimationProgress(
+ state: LaunchAnimator.State,
+ progress: Float,
+ linearProgress: Float
+ ) {
+ startViewController.onLaunchAnimationProgress(state, progress, linearProgress)
+
+ // The end view is visible only iff the starting view is not visible.
+ state.visible = !state.visible
+ endViewController.onLaunchAnimationProgress(state, progress, linearProgress)
+
+ // If the dialog content is complex, its dimension might change during the launch
+ // animation. The animation end position might also change during the exit
+ // animation, for instance when locking the phone when the dialog is open. Therefore
+ // we update the end state to the new position/size. Usually the dialog dimension or
+ // position will change in the early frames, so changing the end state shouldn't
+ // really be noticeable.
+ endViewController.fillGhostedViewState(endState)
+ }
+ }
+
+ launchAnimator.startAnimation(controller, endState, originalDialogBackgroundColor)
+ }
+
+ private fun shouldAnimateDialogIntoView(): Boolean {
+ if (exitAnimationDisabled) {
+ return false
+ }
+
+ // The touch surface should be invisible by now, if it's not then something else changed its
+ // visibility and we probably don't want to run the animation.
+ if (touchSurface.visibility != View.INVISIBLE) {
+ return false
+ }
+
+ // If the touch surface is not attached or one of its ancestors is not visible, then we
+ // don't run the animation either.
+ if (!touchSurface.isAttachedToWindow) {
+ return false
+ }
+
+ return (touchSurface.parent as? View)?.isShown ?: true
+ }
+
+ internal fun onDozeAmountChanged(amount: Float) {
+ val alpha = Interpolators.ALPHA_OUT.getInterpolation(1 - amount)
+ val decorView = this.hostDialog.window?.decorView ?: return
+ if (decorView.hasOverlappingRendering() && alpha > 0.0f &&
+ alpha < 1.0f && decorView.layerType != View.LAYER_TYPE_HARDWARE) {
+ decorView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ }
+ decorView.alpha = alpha
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index b4ffb3f6cf4e..3ccf5e4fbdd0 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -1,7 +1,24 @@
+/*
+ * 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.systemui.animation
import android.graphics.Canvas
import android.graphics.ColorFilter
+import android.graphics.Insets
import android.graphics.Matrix
import android.graphics.PixelFormat
import android.graphics.Rect
@@ -42,6 +59,7 @@ open class GhostedViewLaunchAnimatorController(
override var launchContainer = ghostedView.rootView as ViewGroup
private val launchContainerOverlay: ViewGroupOverlay
get() = launchContainer.overlay
+ private val launchContainerLocation = IntArray(2)
/** The ghost view that is drawn and animated instead of the ghosted view. */
private var ghostView: GhostView? = null
@@ -59,8 +77,12 @@ open class GhostedViewLaunchAnimatorController(
* [backgroundView].
*/
private var backgroundDrawable: WrappedDrawable? = null
+ private val backgroundInsets by lazy { getBackground()?.opticalInsets ?: Insets.NONE }
private var startBackgroundAlpha: Int = 0xFF
+ private val ghostedViewLocation = IntArray(2)
+ private val ghostedViewState = LaunchAnimator.State()
+
/**
* Return the background of the [ghostedView]. This background will be used to draw the
* background of the background view that is expanding up to the final animation position. This
@@ -103,16 +125,24 @@ open class GhostedViewLaunchAnimatorController(
return gradient.cornerRadii?.get(CORNER_RADIUS_BOTTOM_INDEX) ?: gradient.cornerRadius
}
- override fun createAnimatorState(): ActivityLaunchAnimator.State {
- val location = ghostedView.locationOnScreen
- return ActivityLaunchAnimator.State(
- top = location[1],
- bottom = location[1] + ghostedView.height,
- left = location[0],
- right = location[0] + ghostedView.width,
+ override fun createAnimatorState(): LaunchAnimator.State {
+ val state = LaunchAnimator.State(
topCornerRadius = getCurrentTopCornerRadius(),
bottomCornerRadius = getCurrentBottomCornerRadius()
)
+ fillGhostedViewState(state)
+ return state
+ }
+
+ fun fillGhostedViewState(state: LaunchAnimator.State) {
+ // For the animation we are interested in the area that has a non transparent background,
+ // so we have to take the optical insets into account.
+ ghostedView.getLocationOnScreen(ghostedViewLocation)
+ val insets = backgroundInsets
+ state.top = ghostedViewLocation[1] + insets.top
+ state.bottom = ghostedViewLocation[1] + ghostedView.height - insets.bottom
+ state.left = ghostedViewLocation[0] + insets.left
+ state.right = ghostedViewLocation[0] + ghostedView.width - insets.right
}
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -144,7 +174,7 @@ open class GhostedViewLaunchAnimatorController(
}
override fun onLaunchAnimationProgress(
- state: ActivityLaunchAnimator.State,
+ state: LaunchAnimator.State,
progress: Float,
linearProgress: Float
) {
@@ -162,19 +192,52 @@ open class GhostedViewLaunchAnimatorController(
return
}
- val scale = min(state.widthRatio, state.heightRatio)
- ghostViewMatrix.setValues(initialGhostViewMatrixValues)
- ghostViewMatrix.postScale(scale, scale, state.startCenterX, state.startCenterY)
+ // The ghost and backgrounds views were made invisible earlier. That can for instance happen
+ // when animating a dialog into a view.
+ if (ghostView.visibility == View.INVISIBLE) {
+ ghostView.visibility = View.VISIBLE
+ backgroundView.visibility = View.VISIBLE
+ }
+
+ fillGhostedViewState(ghostedViewState)
+ val leftChange = state.left - ghostedViewState.left
+ val rightChange = state.right - ghostedViewState.right
+ val topChange = state.top - ghostedViewState.top
+ val bottomChange = state.bottom - ghostedViewState.bottom
+
+ val widthRatio = state.width.toFloat() / ghostedViewState.width
+ val heightRatio = state.height.toFloat() / ghostedViewState.height
+ val scale = min(widthRatio, heightRatio)
+
+ if (ghostedView.parent is ViewGroup) {
+ // Recalculate the matrix in case the ghosted view moved. We ensure that the ghosted
+ // view is still attached to a ViewGroup, otherwise calculateMatrix will throw.
+ GhostView.calculateMatrix(ghostedView, launchContainer, ghostViewMatrix)
+ }
+
+ launchContainer.getLocationOnScreen(launchContainerLocation)
+ ghostViewMatrix.postScale(
+ scale, scale,
+ ghostedViewState.centerX - launchContainerLocation[0],
+ ghostedViewState.centerY - launchContainerLocation[1]
+ )
ghostViewMatrix.postTranslate(
- (state.leftChange + state.rightChange) / 2f,
- (state.topChange + state.bottomChange) / 2f
+ (leftChange + rightChange) / 2f,
+ (topChange + bottomChange) / 2f
)
ghostView.animationMatrix = ghostViewMatrix
- backgroundView.top = state.top
- backgroundView.bottom = state.bottom
- backgroundView.left = state.left
- backgroundView.right = state.right
+ // We need to take into account the background insets for the background position.
+ val insets = backgroundInsets
+ val topWithInsets = state.top - insets.top
+ val leftWithInsets = state.left - insets.left
+ val rightWithInsets = state.right + insets.right
+ val bottomWithInsets = state.bottom + insets.bottom
+
+ backgroundView.top = topWithInsets - launchContainerLocation[1]
+ backgroundView.bottom = bottomWithInsets - launchContainerLocation[1]
+ backgroundView.left = leftWithInsets - launchContainerLocation[0]
+ backgroundView.right = rightWithInsets - launchContainerLocation[0]
val backgroundDrawable = backgroundDrawable!!
backgroundDrawable.wrapped?.let {
@@ -207,7 +270,7 @@ open class GhostedViewLaunchAnimatorController(
* [drawable] is a [LayerDrawable], this will return the first layer that is a
* [GradientDrawable].
*/
- private fun findGradientDrawable(drawable: Drawable): GradientDrawable? {
+ fun findGradientDrawable(drawable: Drawable): GradientDrawable? {
if (drawable is GradientDrawable) {
return drawable
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
index 659b9fee8656..80634832acd9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
@@ -16,6 +16,7 @@
package com.android.systemui.animation;
+import android.graphics.Path;
import android.util.MathUtils;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
@@ -29,7 +30,97 @@ import android.view.animation.PathInterpolator;
* Utility class to receive interpolators from
*/
public class Interpolators {
- public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
+ /*
+ * ============================================================================================
+ * Emphasized interpolators.
+ * ============================================================================================
+ */
+
+ /**
+ * The default emphasized interpolator. Used for hero / emphasized movement of content.
+ */
+ public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
+
+ /**
+ * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
+ * is disappearing e.g. when moving off screen.
+ */
+ public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
+ 0.3f, 0f, 0.8f, 0.15f);
+
+ /**
+ * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
+ * is appearing e.g. when coming from off screen
+ */
+ public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
+ 0.05f, 0.7f, 0.1f, 1f);
+
+
+ /*
+ * ============================================================================================
+ * Standard interpolators.
+ * ============================================================================================
+ */
+
+ /**
+ * The standard interpolator that should be used on every normal animation
+ */
+ public static final Interpolator STANDARD = new PathInterpolator(
+ 0.2f, 0f, 0f, 1f);
+
+ /**
+ * The standard accelerating interpolator that should be used on every regular movement of
+ * content that is disappearing e.g. when moving off screen.
+ */
+ public static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(
+ 0.3f, 0f, 1f, 1f);
+
+ /**
+ * The standard decelerating interpolator that should be used on every regular movement of
+ * content that is appearing e.g. when coming from off screen.
+ */
+ public static final Interpolator STANDARD_DECELERATE = new PathInterpolator(
+ 0f, 0f, 0f, 1f);
+
+ /*
+ * ============================================================================================
+ * Legacy
+ * ============================================================================================
+ */
+
+ /**
+ * The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN.
+ */
+ public static final Interpolator LEGACY = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
+ /**
+ * The default legacy accelerating interpolator as defined in Material 1.
+ * Also known as FAST_OUT_LINEAR_IN.
+ */
+ public static final Interpolator LEGACY_ACCELERATE = new PathInterpolator(0.4f, 0f, 1f, 1f);
+
+ /**
+ * The default legacy decelerating interpolator as defined in Material 1.
+ * Also known as LINEAR_OUT_SLOW_IN.
+ */
+ public static final Interpolator LEGACY_DECELERATE = new PathInterpolator(0f, 0f, 0.2f, 1f);
+
+ /**
+ * Linear interpolator. Often used if the interpolator is for different properties who need
+ * different interpolations.
+ */
+ public static final Interpolator LINEAR = new LinearInterpolator();
+
+ /*
+ * ============================================================================================
+ * Custom interpolators
+ * ============================================================================================
+ */
+
+ public static final Interpolator FAST_OUT_SLOW_IN = LEGACY;
+ public static final Interpolator FAST_OUT_LINEAR_IN = LEGACY_ACCELERATE;
+ public static final Interpolator LINEAR_OUT_SLOW_IN = LEGACY_DECELERATE;
/**
* Like {@link #FAST_OUT_SLOW_IN}, but used in case the animation is played in reverse (i.e. t
@@ -37,12 +128,9 @@ public class Interpolators {
*/
public static final Interpolator FAST_OUT_SLOW_IN_REVERSE =
new PathInterpolator(0.8f, 0f, 0.6f, 1f);
- public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
- public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
public static final Interpolator SLOW_OUT_LINEAR_IN = new PathInterpolator(0.8f, 0f, 1f, 1f);
public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
- public static final Interpolator LINEAR = new LinearInterpolator();
public static final Interpolator ACCELERATE = new AccelerateInterpolator();
public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
@@ -72,6 +160,12 @@ public class Interpolators {
public static final Interpolator TOUCH_RESPONSE_REVERSE =
new PathInterpolator(0.9f, 0f, 0.7f, 1f);
+ /*
+ * ============================================================================================
+ * Functions / Utilities
+ * ============================================================================================
+ */
+
/**
* Calculate the amount of overshoot using an exponential falloff function with desired
* properties, where the overshoot smoothly transitions at the 1.0f boundary into the
@@ -103,23 +197,13 @@ public class Interpolators {
return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress)));
}
- /**
- * Interpolate alpha for notifications background scrim during shade expansion.
- * @param fraction Shade expansion fraction
- * @param forNotification If we want the alpha of the notification shade or the scrim.
- */
- public static float getNotificationScrimAlpha(float fraction, boolean forNotification) {
- if (forNotification) {
- fraction = MathUtils.constrainedMap(0f, 1f, 0.3f, 1f, fraction);
- } else {
- fraction = MathUtils.constrainedMap(0f, 1f, 0f, 0.5f, fraction);
- }
- fraction = fraction * 1.2f - 0.2f;
- if (fraction <= 0) {
- return 0;
- } else {
- final float oneMinusFrac = 1f - fraction;
- return (float) (1f - 0.5f * (1f - Math.cos(3.14159f * oneMinusFrac * oneMinusFrac)));
- }
+ // Create the default emphasized interpolator
+ private static PathInterpolator createEmphasizedInterpolator() {
+ Path path = new Path();
+ // Doing the same as fast_out_extra_slow_in
+ path.moveTo(0f, 0f);
+ path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
+ path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
+ return new PathInterpolator(path);
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
new file mode 100644
index 000000000000..3bf6c5ebd091
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
@@ -0,0 +1,355 @@
+/*
+ * 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.systemui.animation
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.drawable.GradientDrawable
+import android.util.Log
+import android.util.MathUtils
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.AnimationUtils
+import android.view.animation.PathInterpolator
+import kotlin.math.roundToInt
+
+private const val TAG = "LaunchAnimator"
+
+/** A base class to animate a window launch (activity or dialog) from a view . */
+class LaunchAnimator @JvmOverloads constructor(
+ context: Context,
+ private val isForTesting: Boolean = false
+) {
+ companion object {
+ internal const val DEBUG = false
+ const val ANIMATION_DURATION = 500L
+ private const val ANIMATION_DURATION_FADE_OUT_CONTENT = 150L
+ private const val ANIMATION_DURATION_FADE_IN_WINDOW = 183L
+ private const val ANIMATION_DELAY_FADE_IN_WINDOW = ANIMATION_DURATION_FADE_OUT_CONTENT
+
+ private val WINDOW_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0.6f, 1f)
+ private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
+
+ /**
+ * Given the [linearProgress] of a launch animation, return the linear progress of the
+ * sub-animation starting [delay] ms after the launch animation and that lasts [duration].
+ */
+ @JvmStatic
+ fun getProgress(linearProgress: Float, delay: Long, duration: Long): Float {
+ return MathUtils.constrain(
+ (linearProgress * ANIMATION_DURATION - delay) / duration,
+ 0.0f,
+ 1.0f
+ )
+ }
+ }
+
+ /** The interpolator used for the width, height, Y position and corner radius. */
+ private val animationInterpolator = AnimationUtils.loadInterpolator(context,
+ R.interpolator.launch_animation_interpolator_y)
+
+ /** The interpolator used for the X position. */
+ private val animationInterpolatorX = AnimationUtils.loadInterpolator(context,
+ R.interpolator.launch_animation_interpolator_x)
+
+ private val launchContainerLocation = IntArray(2)
+ private val cornerRadii = FloatArray(8)
+
+ /**
+ * A controller that takes care of applying the animation to an expanding view.
+ *
+ * Note that all callbacks (onXXX methods) are all called on the main thread.
+ */
+ interface Controller {
+ /**
+ * The container in which the view that started the animation will be animating together
+ * with the opening window.
+ *
+ * This will be used to:
+ * - Get the associated [Context].
+ * - Compute whether we are expanding fully above the launch container.
+ * - Apply surface transactions in sync with RenderThread when animating an activity
+ * launch.
+ *
+ * This container can be changed to force this [Controller] to animate the expanding view
+ * inside a different location, for instance to ensure correct layering during the
+ * animation.
+ */
+ var launchContainer: ViewGroup
+
+ /**
+ * Return the [State] of the view that will be animated. We will animate from this state to
+ * the final window state.
+ *
+ * Note: This state will be mutated and passed to [onLaunchAnimationProgress] during the
+ * animation.
+ */
+ fun createAnimatorState(): State
+
+ /**
+ * The animation started. This is typically used to initialize any additional resource
+ * needed for the animation. [isExpandingFullyAbove] will be true if the window is expanding
+ * fully above the [launchContainer].
+ */
+ fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {}
+
+ /** The animation made progress and the expandable view [state] should be updated. */
+ fun onLaunchAnimationProgress(state: State, progress: Float, linearProgress: Float) {}
+
+ /**
+ * The animation ended. This will be called *if and only if* [onLaunchAnimationStart] was
+ * called previously. This is typically used to clean up the resources initialized when the
+ * animation was started.
+ */
+ fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {}
+ }
+
+ /** The state of an expandable view during a [LaunchAnimator] animation. */
+ open class State(
+ /** The position of the view in screen space coordinates. */
+ var top: Int = 0,
+ var bottom: Int = 0,
+ var left: Int = 0,
+ var right: Int = 0,
+
+ var topCornerRadius: Float = 0f,
+ var bottomCornerRadius: Float = 0f
+ ) {
+ private val startTop = top
+
+ val width: Int
+ get() = right - left
+
+ val height: Int
+ get() = bottom - top
+
+ open val topChange: Int
+ get() = top - startTop
+
+ val centerX: Float
+ get() = left + width / 2f
+
+ val centerY: Float
+ get() = top + height / 2f
+
+ /** Whether the expanding view should be visible or hidden. */
+ var visible: Boolean = true
+ }
+
+ interface Animation {
+ /** Cancel the animation. */
+ fun cancel()
+ }
+
+ /**
+ * Start a launch animation controlled by [controller] towards [endState]. An intermediary
+ * layer with [windowBackgroundColor] will fade in then fade out above the expanding view, and
+ * should be the same background color as the opening (or closing) window. If [drawHole] is
+ * true, then this intermediary layer will be drawn with SRC blending mode while it fades out.
+ *
+ * TODO(b/184121838): Remove [drawHole] and instead make the StatusBar draw this hole instead.
+ */
+ fun startAnimation(
+ controller: Controller,
+ endState: State,
+ windowBackgroundColor: Int,
+ drawHole: Boolean = false
+ ): Animation {
+ val state = controller.createAnimatorState()
+
+ // Start state.
+ val startTop = state.top
+ val startBottom = state.bottom
+ val startLeft = state.left
+ val startRight = state.right
+ val startCenterX = (startLeft + startRight) / 2f
+ val startWidth = startRight - startLeft
+ val startTopCornerRadius = state.topCornerRadius
+ val startBottomCornerRadius = state.bottomCornerRadius
+
+ // End state.
+ var endTop = endState.top
+ var endBottom = endState.bottom
+ var endLeft = endState.left
+ var endRight = endState.right
+ var endCenterX = (endLeft + endRight) / 2f
+ var endWidth = endRight - endLeft
+ val endTopCornerRadius = endState.topCornerRadius
+ val endBottomCornerRadius = endState.bottomCornerRadius
+
+ fun maybeUpdateEndState() {
+ if (endTop != endState.top || endBottom != endState.bottom ||
+ endLeft != endState.left || endRight != endState.right) {
+ endTop = endState.top
+ endBottom = endState.bottom
+ endLeft = endState.left
+ endRight = endState.right
+ endCenterX = (endLeft + endRight) / 2f
+ endWidth = endRight - endLeft
+ }
+ }
+
+ val launchContainer = controller.launchContainer
+ val isExpandingFullyAbove = isExpandingFullyAbove(launchContainer, endState)
+
+ // We add an extra layer with the same color as the dialog/app splash screen background
+ // color, which is usually the same color of the app background. We first fade in this layer
+ // to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
+ // launch container and reveal the opening window.
+ val windowBackgroundLayer = GradientDrawable().apply {
+ setColor(windowBackgroundColor)
+ alpha = 0
+ }
+
+ // Update state.
+ val animator = ValueAnimator.ofFloat(0f, 1f)
+ animator.duration = if (isForTesting) 0 else ANIMATION_DURATION
+ animator.interpolator = Interpolators.LINEAR
+
+ val launchContainerOverlay = launchContainer.overlay
+ var cancelled = false
+ animator.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
+ if (DEBUG) {
+ Log.d(TAG, "Animation started")
+ }
+ controller.onLaunchAnimationStart(isExpandingFullyAbove)
+
+ // Add the drawable to the launch container overlay. Overlays always draw
+ // drawables after views, so we know that it will be drawn above any view added
+ // by the controller.
+ launchContainerOverlay.add(windowBackgroundLayer)
+ }
+
+ override fun onAnimationEnd(animation: Animator?) {
+ if (DEBUG) {
+ Log.d(TAG, "Animation ended")
+ }
+ controller.onLaunchAnimationEnd(isExpandingFullyAbove)
+ launchContainerOverlay.remove(windowBackgroundLayer)
+ }
+ })
+
+ animator.addUpdateListener { animation ->
+ if (cancelled) {
+ // TODO(b/184121838): Cancel the animator directly instead of just skipping the
+ // update.
+ return@addUpdateListener
+ }
+
+ maybeUpdateEndState()
+
+ // TODO(b/184121838): Use reverse interpolators to get the same path/arc as the non
+ // reversed animation.
+ val linearProgress = animation.animatedFraction
+ val progress = animationInterpolator.getInterpolation(linearProgress)
+ val xProgress = animationInterpolatorX.getInterpolation(linearProgress)
+
+ val xCenter = MathUtils.lerp(startCenterX, endCenterX, xProgress)
+ val halfWidth = MathUtils.lerp(startWidth, endWidth, progress) / 2f
+
+ state.top = MathUtils.lerp(startTop, endTop, progress).roundToInt()
+ state.bottom = MathUtils.lerp(startBottom, endBottom, progress).roundToInt()
+ state.left = (xCenter - halfWidth).roundToInt()
+ state.right = (xCenter + halfWidth).roundToInt()
+
+ state.topCornerRadius =
+ MathUtils.lerp(startTopCornerRadius, endTopCornerRadius, progress)
+ state.bottomCornerRadius =
+ MathUtils.lerp(startBottomCornerRadius, endBottomCornerRadius, progress)
+
+ // The expanding view can/should be hidden once it is completely covered by the opening
+ // window.
+ state.visible = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) < 1
+
+ applyStateToWindowBackgroundLayer(
+ windowBackgroundLayer,
+ state,
+ linearProgress,
+ launchContainer,
+ drawHole
+ )
+ controller.onLaunchAnimationProgress(state, progress, linearProgress)
+ }
+
+ animator.start()
+ return object : Animation {
+ override fun cancel() {
+ cancelled = true
+ animator.cancel()
+ }
+ }
+ }
+
+ /** Return whether we are expanding fully above the [launchContainer]. */
+ internal fun isExpandingFullyAbove(launchContainer: View, endState: State): Boolean {
+ launchContainer.getLocationOnScreen(launchContainerLocation)
+ return endState.top <= launchContainerLocation[1] &&
+ endState.bottom >= launchContainerLocation[1] + launchContainer.height &&
+ endState.left <= launchContainerLocation[0] &&
+ endState.right >= launchContainerLocation[0] + launchContainer.width
+ }
+
+ private fun applyStateToWindowBackgroundLayer(
+ drawable: GradientDrawable,
+ state: State,
+ linearProgress: Float,
+ launchContainer: View,
+ drawHole: Boolean
+ ) {
+ // Update position.
+ launchContainer.getLocationOnScreen(launchContainerLocation)
+ drawable.setBounds(
+ state.left - launchContainerLocation[0],
+ state.top - launchContainerLocation[1],
+ state.right - launchContainerLocation[0],
+ state.bottom - launchContainerLocation[1]
+ )
+
+ // Update radius.
+ cornerRadii[0] = state.topCornerRadius
+ cornerRadii[1] = state.topCornerRadius
+ cornerRadii[2] = state.topCornerRadius
+ cornerRadii[3] = state.topCornerRadius
+ cornerRadii[4] = state.bottomCornerRadius
+ cornerRadii[5] = state.bottomCornerRadius
+ cornerRadii[6] = state.bottomCornerRadius
+ cornerRadii[7] = state.bottomCornerRadius
+ drawable.cornerRadii = cornerRadii
+
+ // We first fade in the background layer to hide the expanding view, then fade it out
+ // with SRC mode to draw a hole punch in the status bar and reveal the opening window.
+ val fadeInProgress = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT)
+ if (fadeInProgress < 1) {
+ val alpha = Interpolators.LINEAR_OUT_SLOW_IN.getInterpolation(fadeInProgress)
+ drawable.alpha = (alpha * 0xFF).roundToInt()
+ } else {
+ val fadeOutProgress = getProgress(
+ linearProgress, ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW)
+ val alpha = 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(fadeOutProgress)
+ drawable.alpha = (alpha * 0xFF).roundToInt()
+
+ if (drawHole) {
+ drawable.setXfermode(SRC_MODE)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
new file mode 100644
index 000000000000..80a3eb839940
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.systemui.animation
+
+/** A view that can expand/launch into an app or a dialog. */
+interface LaunchableView {
+ /**
+ * Set whether this view should block/prevent all visibility changes. This ensures that this
+ * view remains invisible during the launch animation given that it is ghosted and already drawn
+ * somewhere else.
+ *
+ * Note that when this is set to true, both the [normal][android.view.View.setVisibility] and
+ * [transition][android.view.View.setTransitionVisibility] visibility changes must be blocked.
+ */
+ fun setShouldBlockVisibilityChanges(block: Boolean)
+} \ No newline at end of file
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt
new file mode 100644
index 000000000000..0ee2bfea55c5
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt
@@ -0,0 +1,37 @@
+package com.android.systemui.animation
+
+import android.util.MathUtils
+
+object ShadeInterpolation {
+
+ /**
+ * Interpolate alpha for notification background scrim during shade expansion.
+ * @param fraction Shade expansion fraction
+ */
+ @JvmStatic
+ fun getNotificationScrimAlpha(fraction: Float): Float {
+ val mappedFraction = MathUtils.constrainedMap(0f, 1f, 0f, 0.5f, fraction)
+ return interpolateEaseInOut(mappedFraction)
+ }
+
+ /**
+ * Interpolate alpha for shade content during shade expansion.
+ * @param fraction Shade expansion fraction
+ */
+ @JvmStatic
+ fun getContentAlpha(fraction: Float): Float {
+ val mappedFraction = MathUtils.constrainedMap(0f, 1f, 0.3f, 1f, fraction)
+ return interpolateEaseInOut(mappedFraction)
+ }
+
+ private fun interpolateEaseInOut(fraction: Float): Float {
+ val mappedFraction = fraction * 1.2f - 0.2f
+ return if (mappedFraction <= 0) {
+ 0f
+ } else {
+ val oneMinusFrac = 1f - mappedFraction
+ (1f - 0.5f * (1f - Math.cos((3.14159f * oneMinusFrac * oneMinusFrac).toDouble())))
+ .toFloat()
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/docs/keyguard.md b/packages/SystemUI/docs/keyguard.md
new file mode 100644
index 000000000000..5e7bc1c8cad2
--- /dev/null
+++ b/packages/SystemUI/docs/keyguard.md
@@ -0,0 +1,46 @@
+# Keyguard (aka Lockscreen)
+
+Keyguard is responsible for:
+
+1. Handling authentication to allow the user to unlock the device, via biometrics or [KeyguardBouncer][1]
+2. Displaying informational content such as the time, notifications, and smartspace
+3. Always-on Display (AOD)
+
+Keyguard is the first screen available when turning on the device, as long as the user has not specified a security method of NONE.
+
+## Critical User Journeys
+
+The journeys below generally refer to Keyguard's portion of the overall flow, especially regarding use of the power button. Power button key interpretation (short press, long press, very long press, multi press) is done in [PhoneWindowManager][4], with calls to [PowerManagerService][2] to sleep or wake up, if needed.
+
+### Power On - AOD enabled or disabled
+
+Begins with the device in low power mode, with the display active for [AOD][3] or inactive. [PowerManagerService][2] can be directed to wake up on various user-configurable signals, such as lift to wake, screen taps, among others. [AOD][2], whether visibly enabled or not, handles these signals to transition AOD to full Lockscreen content. See more in [AOD][3].
+
+### Power Off
+
+An indication to power off the device most likely comes from one of two signals: the user presses the power button or the screen timeout has passed. This may [lock the device](#How-the-device-locks)
+
+#### On Lockscreen
+
+#### On Lockscreen, occluded by an activity
+
+#### Device unlocked, Keyguard has gone away
+
+### Pulsing (Incoming notifications while dozing)
+
+### How the device locks
+
+More coming
+* Screen timeout
+* Smart lock
+* Device policy
+* Power button instantly locks setting
+* Lock timeout after screen timeout setting
+
+
+[1]: /frameworks/base/packages/SystemUI/docs/keyguard/bouncer.md
+[2]: /frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
+[3]: /frameworks/base/packages/SystemUI/docs/keyguard/aod.md
+[4]: /frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
+
+
diff --git a/packages/SystemUI/docs/keyguard/aod.md b/packages/SystemUI/docs/keyguard/aod.md
new file mode 100644
index 000000000000..6d76ed55174a
--- /dev/null
+++ b/packages/SystemUI/docs/keyguard/aod.md
@@ -0,0 +1 @@
+# Always-on Display (AOD)
diff --git a/packages/SystemUI/docs/keyguard/bouncer.md b/packages/SystemUI/docs/keyguard/bouncer.md
new file mode 100644
index 000000000000..51f851608a34
--- /dev/null
+++ b/packages/SystemUI/docs/keyguard/bouncer.md
@@ -0,0 +1,18 @@
+# Bouncer
+
+[KeyguardBouncer][1] is the component responsible for displaying the security method set by the user (password, PIN, pattern) as well as SIM-related security methods, allowing the user to unlock the device or SIM.
+
+## Components
+
+The bouncer contains a hierarchy of controllers/views to render the user's security method and to manage the authentication attempts.
+
+1. [KeyguardBouncer][1] - Entrypoint for managing the bouncer visibility.
+ 1. [KeyguardHostViewController][2] - Intercepts media keys. Can most likely be merged with the next item.
+ 1. [KeyguardSecurityContainerController][3] - Manages unlock attempt responses, one-handed use
+ 1. [KeyguardSecurityViewFlipperController][4] - Based upon the [KeyguardSecurityModel#SecurityMode][5], will instantiate the required view and controller. PIN, Pattern, etc.
+
+[1]: /frameworks/base/packages/SystemUI/com/android/systemui/statusbar/phone/KeyguardBouncer
+[2]: /frameworks/base/packages/SystemUI/com/android/keyguard/KeyguardHostViewController
+[3]: /frameworks/base/packages/SystemUI/com/android/keyguard/KeyguardSecurityContainerController
+[4]: /frameworks/base/packages/SystemUI/com/android/keyguard/KeyguardSecurityViewFlipperController
+[5]: /frameworks/base/packages/SystemUI/com/android/keyguard/KeyguardSecurityModel
diff --git a/packages/SystemUI/monet/Android.bp b/packages/SystemUI/monet/Android.bp
new file mode 100644
index 000000000000..507ea25083e1
--- /dev/null
+++ b/packages/SystemUI/monet/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+ name: "monet",
+ platform_apis: true,
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.core_core",
+ ],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+}
diff --git a/packages/SystemUI/monet/AndroidManifest.xml b/packages/SystemUI/monet/AndroidManifest.xml
new file mode 100644
index 000000000000..1fab52877847
--- /dev/null
+++ b/packages/SystemUI/monet/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?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.systemui.monet">
+</manifest>
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
new file mode 100644
index 000000000000..1844288796cc
--- /dev/null
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -0,0 +1,261 @@
+/*
+ * 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.systemui.monet
+
+import android.annotation.ColorInt
+import android.app.WallpaperColors
+import android.graphics.Color
+import com.android.internal.graphics.ColorUtils
+import com.android.internal.graphics.cam.Cam
+import com.android.internal.graphics.cam.CamUtils.lstarFromInt
+import kotlin.math.absoluteValue
+import kotlin.math.roundToInt
+
+const val TAG = "ColorScheme"
+
+const val ACCENT1_CHROMA = 48.0f
+const val ACCENT2_CHROMA = 16.0f
+const val ACCENT3_CHROMA = 32.0f
+const val ACCENT3_HUE_SHIFT = 60.0f
+
+const val NEUTRAL1_CHROMA = 4.0f
+const val NEUTRAL2_CHROMA = 8.0f
+
+const val GOOGLE_BLUE = 0xFF1b6ef3.toInt()
+
+const val MIN_CHROMA = 5
+
+public class ColorScheme(@ColorInt seed: Int, val darkTheme: Boolean) {
+
+ val accent1: List<Int>
+ val accent2: List<Int>
+ val accent3: List<Int>
+ val neutral1: List<Int>
+ val neutral2: List<Int>
+
+ constructor(wallpaperColors: WallpaperColors, darkTheme: Boolean):
+ this(getSeedColor(wallpaperColors), darkTheme)
+
+ val allAccentColors: List<Int>
+ get() {
+ val allColors = mutableListOf<Int>()
+ allColors.addAll(accent1)
+ allColors.addAll(accent2)
+ allColors.addAll(accent3)
+ return allColors
+ }
+
+ val allNeutralColors: List<Int>
+ get() {
+ val allColors = mutableListOf<Int>()
+ allColors.addAll(neutral1)
+ allColors.addAll(neutral2)
+ return allColors
+ }
+
+ val backgroundColor
+ get() = ColorUtils.setAlphaComponent(if (darkTheme) neutral1[8] else neutral1[0], 0xFF)
+
+ val accentColor
+ get() = ColorUtils.setAlphaComponent(if (darkTheme) accent1[2] else accent1[6], 0xFF)
+
+ init {
+ val proposedSeedCam = Cam.fromInt(seed)
+ val seedArgb = if (seed == Color.TRANSPARENT) {
+ GOOGLE_BLUE
+ } else if (proposedSeedCam.chroma < 5) {
+ GOOGLE_BLUE
+ } else {
+ seed
+ }
+ val camSeed = Cam.fromInt(seedArgb)
+ val hue = camSeed.hue
+ val chroma = camSeed.chroma.coerceAtLeast(ACCENT1_CHROMA)
+ accent1 = Shades.of(hue, chroma).toList()
+ accent2 = Shades.of(hue, ACCENT2_CHROMA).toList()
+ accent3 = Shades.of(hue + ACCENT3_HUE_SHIFT, ACCENT3_CHROMA).toList()
+ neutral1 = Shades.of(hue, NEUTRAL1_CHROMA).toList()
+ neutral2 = Shades.of(hue, NEUTRAL2_CHROMA).toList()
+ }
+
+ override fun toString(): String {
+ return "ColorScheme {\n" +
+ " neutral1: ${humanReadable(neutral1)}\n" +
+ " neutral2: ${humanReadable(neutral2)}\n" +
+ " accent1: ${humanReadable(accent1)}\n" +
+ " accent2: ${humanReadable(accent2)}\n" +
+ " accent3: ${humanReadable(accent3)}\n" +
+ "}"
+ }
+
+ companion object {
+ /**
+ * Identifies a color to create a color scheme from.
+ *
+ * @param wallpaperColors Colors extracted from an image via quantization.
+ * @return ARGB int representing the color
+ */
+ @JvmStatic
+ @ColorInt
+ fun getSeedColor(wallpaperColors: WallpaperColors): Int {
+ return getSeedColors(wallpaperColors).first()
+ }
+
+ /**
+ * Filters and ranks colors from WallpaperColors.
+ *
+ * @param wallpaperColors Colors extracted from an image via quantization.
+ * @return List of ARGB ints, ordered from highest scoring to lowest.
+ */
+ @JvmStatic
+ fun getSeedColors(wallpaperColors: WallpaperColors): List<Int> {
+ val totalPopulation = wallpaperColors.allColors.values.reduce { a, b -> a + b }
+ .toDouble()
+ val totalPopulationMeaningless = (totalPopulation == 0.0)
+ if (totalPopulationMeaningless) {
+ // WallpaperColors with a population of 0 indicate the colors didn't come from
+ // quantization. Instead of scoring, trust the ordering of the provided primary
+ // secondary/tertiary colors.
+ //
+ // In this case, the colors are usually from a Live Wallpaper.
+ val distinctColors = wallpaperColors.mainColors.map {
+ it.toArgb()
+ }.distinct().filter {
+ Cam.fromInt(it).chroma >= MIN_CHROMA
+ }.toList()
+
+ if (distinctColors.isEmpty()) {
+ return listOf(GOOGLE_BLUE)
+ }
+ return distinctColors
+ }
+
+ val intToProportion = wallpaperColors.allColors.mapValues {
+ it.value.toDouble() / totalPopulation
+ }
+ val intToCam = wallpaperColors.allColors.mapValues { Cam.fromInt(it.key) }
+
+ // Get an array with 360 slots. A slot contains the percentage of colors with that hue.
+ val hueProportions = huePopulations(intToCam, intToProportion)
+ // Map each color to the percentage of the image with its hue.
+ val intToHueProportion = wallpaperColors.allColors.mapValues {
+ val cam = intToCam[it.key]!!
+ val hue = cam.hue.roundToInt()
+ var proportion = 0.0
+ for (i in hue - 15..hue + 15) {
+ proportion += hueProportions[wrapDegrees(i)]
+ }
+ proportion
+ }
+ // Remove any inappropriate seed colors. For example, low chroma colors look grayscale
+ // raising their chroma will turn them to a much louder color that may not have been
+ // in the image.
+ val filteredIntToCam = intToCam.filter {
+ val cam = it.value
+ val lstar = lstarFromInt(it.key)
+ val proportion = intToHueProportion[it.key]!!
+ cam.chroma >= MIN_CHROMA &&
+ (totalPopulationMeaningless || proportion > 0.01)
+ }
+ // Sort the colors by score, from high to low.
+ val intToScoreIntermediate = filteredIntToCam.mapValues {
+ score(it.value, intToHueProportion[it.key]!!)
+ }
+ val intToScore = intToScoreIntermediate.entries.toMutableList()
+ intToScore.sortByDescending { it.value }
+
+ // Go through the colors, from high score to low score.
+ // If the color is distinct in hue from colors picked so far, pick the color.
+ // Iteratively decrease the amount of hue distinctness required, thus ensuring we
+ // maximize difference between colors.
+ val minimumHueDistance = 15
+ val seeds = mutableListOf<Int>()
+ maximizeHueDistance@ for (i in 90 downTo minimumHueDistance step 1) {
+ seeds.clear()
+ for (entry in intToScore) {
+ val int = entry.key
+ val existingSeedNearby = seeds.find {
+ val hueA = intToCam[int]!!.hue
+ val hueB = intToCam[it]!!.hue
+ hueDiff(hueA, hueB) < i } != null
+ if (existingSeedNearby) {
+ continue
+ }
+ seeds.add(int)
+ if (seeds.size >= 4) {
+ break@maximizeHueDistance
+ }
+ }
+ }
+
+ if (seeds.isEmpty()) {
+ // Use gBlue 500 if there are 0 colors
+ seeds.add(GOOGLE_BLUE)
+ }
+
+ return seeds
+ }
+
+ private fun wrapDegrees(degrees: Int): Int {
+ return when {
+ degrees < 0 -> {
+ (degrees % 360) + 360
+ }
+ degrees >= 360 -> {
+ degrees % 360
+ }
+ else -> {
+ degrees
+ }
+ }
+ }
+
+ private fun hueDiff(a: Float, b: Float): Float {
+ return 180f - ((a - b).absoluteValue - 180f).absoluteValue
+ }
+
+ private fun humanReadable(colors: List<Int>): String {
+ return colors.joinToString { "#" + Integer.toHexString(it) }
+ }
+
+ private fun score(cam: Cam, proportion: Double): Double {
+ val proportionScore = 0.7 * 100.0 * proportion
+ val chromaScore = if (cam.chroma < ACCENT1_CHROMA) 0.1 * (cam.chroma - ACCENT1_CHROMA)
+ else 0.3 * (cam.chroma - ACCENT1_CHROMA)
+ return chromaScore + proportionScore
+ }
+
+ private fun huePopulations(
+ camByColor: Map<Int, Cam>,
+ populationByColor: Map<Int, Double>
+ ): List<Double> {
+ val huePopulation = List(size = 360, init = { 0.0 }).toMutableList()
+
+ for (entry in populationByColor.entries) {
+ val population = populationByColor[entry.key]!!
+ val cam = camByColor[entry.key]!!
+ val hue = cam.hue.roundToInt() % 360
+ if (cam.chroma <= MIN_CHROMA) {
+ continue
+ }
+ huePopulation[hue] = huePopulation[hue] + population
+ }
+
+ return huePopulation
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java b/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java
new file mode 100644
index 000000000000..aab3538e3c4c
--- /dev/null
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java
@@ -0,0 +1,69 @@
+/*
+ * 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.systemui.monet;
+
+
+import androidx.annotation.ColorInt;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
+
+
+/**
+ * Generate sets of colors that are shades of the same color
+ */
+@VisibleForTesting
+public class Shades {
+ /**
+ * Combining the ability to convert between relative luminance and perceptual luminance with
+ * contrast leads to a design system that can be based on a linear value to determine contrast,
+ * rather than a ratio.
+ *
+ * This codebase implements a design system that has that property, and as a result, we can
+ * guarantee that any shades 5 steps from each other have a contrast ratio of at least 4.5.
+ * 4.5 is the requirement for smaller text contrast in WCAG 2.1 and earlier.
+ *
+ * However, lstar 50 does _not_ have a contrast ratio >= 4.5 with lstar 100.
+ * lstar 49.6 is the smallest lstar that will lead to a contrast ratio >= 4.5 with lstar 100,
+ * and it also contrasts >= 4.5 with lstar 100.
+ */
+ public static final float MIDDLE_LSTAR = 49.6f;
+
+ /**
+ * Generate shades of a color. Ordered in lightness _descending_.
+ * <p>
+ * The first shade will be at 95% lightness, the next at 90, 80, etc. through 0.
+ *
+ * @param hue hue in CAM16 color space
+ * @param chroma chroma in CAM16 color space
+ * @return shades of a color, as argb integers. Ordered by lightness descending.
+ */
+ public static @ColorInt int[] of(float hue, float chroma) {
+ int[] shades = new int[12];
+ // At tone 90 and above, blue and yellow hues can reach a much higher chroma.
+ // To preserve a consistent appearance across all hues, use a maximum chroma of 40.
+ shades[0] = ColorUtils.CAMToColor(hue, Math.min(40f, chroma), 99);
+ shades[1] = ColorUtils.CAMToColor(hue, Math.min(40f, chroma), 95);
+ for (int i = 2; i < 12; i++) {
+ float lStar = (i == 6) ? MIDDLE_LSTAR : 100 - 10 * (i - 1);
+ if (lStar >= 90) {
+ chroma = Math.min(40f, chroma);
+ }
+ shades[i] = ColorUtils.CAMToColor(hue, chroma, lStar);
+ }
+ return shades;
+ }
+}
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index 989010e8730c..a16f5cd5d930 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -123,18 +123,18 @@ public interface BcSmartspaceDataPlugin extends Plugin {
/** Interface for launching Intents, which can differ on the lockscreen */
interface IntentStarter {
- default void startFromAction(SmartspaceAction action, View v) {
+ default void startFromAction(SmartspaceAction action, View v, boolean showOnLockscreen) {
if (action.getIntent() != null) {
- startIntent(v, action.getIntent());
+ startIntent(v, action.getIntent(), showOnLockscreen);
} else if (action.getPendingIntent() != null) {
- startPendingIntent(action.getPendingIntent());
+ startPendingIntent(action.getPendingIntent(), showOnLockscreen);
}
}
/** Start the intent */
- void startIntent(View v, Intent i);
+ void startIntent(View v, Intent i, boolean showOnLockscreen);
/** Start the PendingIntent */
- void startPendingIntent(PendingIntent pi);
+ void startPendingIntent(PendingIntent pi, boolean showOnLockscreen);
}
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flag.kt
new file mode 100644
index 000000000000..68834bc2aa23
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flag.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.systemui.flags
+
+interface Flag<T> {
+ val id: Int
+ val default: T
+}
+
+data class BooleanFlag @JvmOverloads constructor(
+ override val id: Int,
+ override val default: Boolean = false
+) : Flag<Boolean>
+
+data class StringFlag @JvmOverloads constructor(
+ override val id: Int,
+ override val default: String = ""
+) : Flag<String>
+
+data class IntFlag @JvmOverloads constructor(
+ override val id: Int,
+ override val default: Int = 0
+) : Flag<Int>
+
+data class LongFlag @JvmOverloads constructor(
+ override val id: Int,
+ override val default: Long = 0
+) : Flag<Long>
+
+data class FloatFlag @JvmOverloads constructor(
+ override val id: Int,
+ override val default: Float = 0f
+) : Flag<Float>
+
+data class DoubleFlag @JvmOverloads constructor(
+ override val id: Int,
+ override val default: Double = 0.0
+) : Flag<Double> \ No newline at end of file
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
new file mode 100644
index 000000000000..3761d42ae98c
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.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.systemui.flags;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * List of {@link Flag} objects for use in SystemUI.
+ *
+ * Flag Ids are integers.
+ * Ids must be unique. This is enforced in a unit test.
+ * Ids need not be sequential. Flags can "claim" a chunk of ids for flags in related featurs with
+ * a comment. This is purely for organizational purposes.
+ *
+ * On public release builds, flags will always return their default value. There is no way to
+ * change their value on release builds.
+ *
+ * See {@link FeatureFlagManager} for instructions on flipping the flags via adb.
+ */
+public class Flags {
+ public static final BooleanFlag TEAMFOOD = new BooleanFlag(1, false);
+
+ /***************************************/
+ // 100 - notification
+ public static final BooleanFlag NEW_NOTIFICATION_PIPELINE =
+ new BooleanFlag(100, true);
+
+ public static final BooleanFlag NEW_NOTIFICATION_PIPELINE_RENDERING =
+ new BooleanFlag(101, false);
+
+ public static final BooleanFlag NOTIFICATION_UPDATES =
+ new BooleanFlag(102, true);
+
+
+ /***************************************/
+ // 200 - keyguard/lockscreen
+ public static final BooleanFlag KEYGUARD_LAYOUT =
+ new BooleanFlag(200, true);
+
+ public static final BooleanFlag LOCKSCREEN_ANIMATIONS =
+ new BooleanFlag(201, true);
+
+ public static final BooleanFlag NEW_UNLOCK_SWIPE_ANIMATION =
+ new BooleanFlag(202, true);
+
+ /***************************************/
+ // 300 - power menu
+ public static final BooleanFlag POWER_MENU_LITE =
+ new BooleanFlag(300, true);
+
+ /***************************************/
+ // 400 - smartspace
+ public static final BooleanFlag SMARTSPACE_DEDUPING =
+ new BooleanFlag(400, true);
+
+ public static final BooleanFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
+ new BooleanFlag(401, false);
+
+ /***************************************/
+ // 500 - quick settings
+ public static final BooleanFlag NEW_USER_SWITCHER =
+ new BooleanFlag(500, true);
+
+ /***************************************/
+ // 600- status bar
+ public static final BooleanFlag COMBINED_STATUS_BAR_SIGNAL_ICONS =
+ new BooleanFlag(501, false);
+
+ /***************************************/
+ // 700 - dialer/calls
+ public static final BooleanFlag ONGOING_CALL_STATUS_BAR_CHIP =
+ new BooleanFlag(600, true);
+
+ public static final BooleanFlag ONGOING_CALL_IN_IMMERSIVE =
+ new BooleanFlag(601, true);
+
+ public static final BooleanFlag ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP =
+ new BooleanFlag(602, true);
+
+ // Pay no attention to the reflection behind the curtain.
+ // ========================== Curtain ==========================
+ // | |
+ // | . . . . . . . . . . . . . . . . . . . |
+ private static Map<Integer, Flag<?>> sFlagMap;
+ static Map<Integer, Flag<?>> collectFlags() {
+ if (sFlagMap != null) {
+ return sFlagMap;
+ }
+ Map<Integer, Flag<?>> flags = new HashMap<>();
+
+ Field[] fields = Flags.class.getFields();
+
+ for (Field field : fields) {
+ Class<?> t = field.getType();
+ if (Flag.class.isAssignableFrom(t)) {
+ try {
+ Flag<?> flag = (Flag<?>) field.get(null);
+ flags.put(flag.getId(), flag);
+ } catch (IllegalAccessException e) {
+ // no-op
+ }
+ }
+ }
+
+ sFlagMap = flags;
+
+ return sFlagMap;
+ }
+ // | . . . . . . . . . . . . . . . . . . . |
+ // | |
+ // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/
+
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index 7c81325d685f..6d088f090bcd 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -60,8 +60,16 @@ public interface ActivityStarter {
*/
void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags);
void startActivity(Intent intent, boolean dismissShade);
+
+ default void startActivity(Intent intent, boolean dismissShade,
+ @Nullable ActivityLaunchAnimator.Controller animationController) {
+ startActivity(intent, dismissShade, animationController,
+ false /* showOverLockscreenWhenLocked */);
+ }
+
void startActivity(Intent intent, boolean dismissShade,
- @Nullable ActivityLaunchAnimator.Controller animationController);
+ @Nullable ActivityLaunchAnimator.Controller animationController,
+ boolean showOverLockscreenWhenLocked);
void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade);
void startActivity(Intent intent, boolean dismissShade, Callback callback);
void postStartActivityDismissingKeyguard(Intent intent, int delay);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index 0424382a1b88..b83ea4acd26f 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -16,7 +16,6 @@ package com.android.systemui.plugins.qs;
import android.view.View;
import android.view.View.OnClickListener;
-import android.view.ViewGroup;
import com.android.systemui.plugins.FragmentBase;
import com.android.systemui.plugins.annotations.DependsOn;
@@ -53,10 +52,20 @@ public interface QS extends FragmentBase {
boolean isShowingDetail();
void closeDetail();
void animateHeaderSlidingOut();
- void setQsExpansion(float qsExpansionFraction, float headerTranslation);
+
+ /**
+ * Asks QS to update its presentation, according to {@code NotificationPanelViewController}.
+ * @param qsExpansionFraction How much each UI element in QS should be expanded (QQS to QS.)
+ * @param panelExpansionFraction Whats the expansion of the whole shade.
+ * @param headerTranslation How much we should vertically translate QS.
+ * @param squishinessFraction Fraction that affects tile height. 0 when collapsed,
+ * 1 when expanded.
+ */
+ void setQsExpansion(float qsExpansionFraction, float panelExpansionFraction,
+ float headerTranslation, float squishinessFraction);
void setHeaderListening(boolean listening);
void notifyCustomizeChanged();
- void setContainer(ViewGroup container);
+ void setContainerController(QSContainerController controller);
void setExpandClickListener(OnClickListener onClickListener);
View getHeader();
@@ -76,13 +85,13 @@ public interface QS extends FragmentBase {
/**
* If QS should translate as we pull it down, or if it should be static.
*/
- void setTranslateWhileExpanding(boolean shouldTranslate);
+ void setInSplitShade(boolean shouldTranslate);
/**
* Set the amount of pixels we have currently dragged down if we're transitioning to the full
* shade. 0.0f means we're not transitioning yet.
*/
- default void setTransitionToFullShadeAmount(float pxAmount, boolean animated) {}
+ default void setTransitionToFullShadeAmount(float pxAmount, float progress) {}
/**
* A rounded corner clipping that makes QS feel as if it were behind everything.
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt
new file mode 100644
index 000000000000..8bf982d360a1
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt
@@ -0,0 +1,9 @@
+package com.android.systemui.plugins.qs
+
+interface QSContainerController {
+ fun setCustomizerAnimating(animating: Boolean)
+
+ fun setCustomizerShowing(showing: Boolean)
+
+ fun setDetailShowing(showing: Boolean)
+} \ No newline at end of file
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
index 883f4de1149c..94fdbae83253 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
@@ -183,12 +183,6 @@ public interface NotificationMenuRowPlugin extends Plugin {
public boolean canBeDismissed();
/**
- * Informs the menu whether dismiss gestures are left-to-right or right-to-left.
- */
- default void setDismissRtl(boolean dismissRtl) {
- }
-
- /**
* Determines whether the menu should remain open given its current state, or snap closed.
* @return true if the menu should remain open, false otherwise.
*/
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
index 6c5c4ef94921..9829918a0302 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
@@ -109,9 +109,8 @@ public interface StatusBarStateController {
* Callback to be notified when the fullscreen or immersive state changes.
*
* @param isFullscreen if any of the system bar is hidden by the focused window.
- * @param isImmersive if the navigation bar can stay hidden when the display gets tapped.
*/
- default void onFullscreenStateChanged(boolean isFullscreen, boolean isImmersive) {}
+ default void onFullscreenStateChanged(boolean isFullscreen) {}
/**
* Callback to be notified when the pulsing state changes
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 4adb54686a89..61147281da6f 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -37,9 +37,6 @@
-keep class com.android.systemui.fragments.FragmentService$FragmentCreator {
*;
}
--keep class com.android.systemui.util.InjectionInflationController$ViewInstanceCreator {
- *;
-}
-keep class androidx.core.app.CoreComponentFactory
-keep public class * extends com.android.systemui.SystemUI {
diff --git a/packages/SystemUI/res-keyguard/color/numpad_key_color_secondary.xml b/packages/SystemUI/res-keyguard/color/numpad_key_color_secondary.xml
new file mode 100644
index 000000000000..08c66a24348c
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/color/numpad_key_color_secondary.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentSecondaryVariant"/>
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/face_auth_wallpaper.png b/packages/SystemUI/res-keyguard/drawable/face_auth_wallpaper.png
deleted file mode 100644
index b907f4eaf362..000000000000
--- a/packages/SystemUI/res-keyguard/drawable/face_auth_wallpaper.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/anim/fp_to_unlock.xml b/packages/SystemUI/res-keyguard/drawable/fp_to_unlock.xml
index a5f75b6726c8..b93ccc6ac106 100644
--- a/packages/SystemUI/res/anim/fp_to_unlock.xml
+++ b/packages/SystemUI/res-keyguard/drawable/fp_to_unlock.xml
@@ -19,15 +19,15 @@
<group android:name="_R_G">
<group android:name="_R_G_L_1_G_N_7_T_0" android:translateX="-27" android:translateY="-17.5">
<group android:name="_R_G_L_1_G" android:translateX="30.75" android:translateY="25.75">
- <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " />
- <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " />
- <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " />
- <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " />
+ <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " />
+ <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " />
+ <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " />
+ <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " />
</group>
</group>
<group android:name="_R_G_L_0_G_N_7_T_0" android:translateX="-27" android:translateY="-17.5">
<group android:name="_R_G_L_0_G" android:translateX="47.357" android:translateY="53.25" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866">
- <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#b7f29f" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c " />
+ <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c " />
</group>
</group>
</group>
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml b/packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml
index dd35dd91f5d6..1eec8204a7f2 100644
--- a/packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml
+++ b/packages/SystemUI/res-keyguard/drawable/ic_backspace_24dp.xml
@@ -20,6 +20,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?android:attr/colorBackground"
+ android:fillColor="?android:attr/textColorPrimaryInverse"
android:pathData="M9,15.59L12.59,12L9,8.41L10.41,7L14,10.59L17.59,7L19,8.41L15.41,12L19,15.59L17.59,17L14,13.41L10.41,17L9,15.59zM21,6H8l-4.5,6L8,18h13V6M21,4c1.1,0 2,0.9 2,2v12c0,1.1 -0.9,2 -2,2H8c-0.63,0 -1.22,-0.3 -1.6,-0.8L1,12l5.4,-7.2C6.78,4.3 7.37,4 8,4H21L21,4z"/>
</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_fingerprint.xml b/packages/SystemUI/res-keyguard/drawable/ic_fingerprint.xml
new file mode 100644
index 000000000000..2063d21bb5d6
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_fingerprint.xml
@@ -0,0 +1,48 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="65dp"
+ android:width="46dp"
+ android:viewportHeight="65"
+ android:viewportWidth="46">
+ <group android:name="_R_G_L_0_G" android:translateX="3.75" android:translateY="8.25">
+ <path
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2.5"
+ android:pathData="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " />
+ <path
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2.5"
+ android:pathData="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " />
+ <path
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2.5"
+ android:pathData="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " />
+ <path
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2.5"
+ android:pathData="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " />
+ </group>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_keyboard_tab_36dp.xml b/packages/SystemUI/res-keyguard/drawable/ic_keyboard_tab_36dp.xml
index b844515f1088..2ad5e54eb92a 100644
--- a/packages/SystemUI/res-keyguard/drawable/ic_keyboard_tab_36dp.xml
+++ b/packages/SystemUI/res-keyguard/drawable/ic_keyboard_tab_36dp.xml
@@ -19,7 +19,8 @@
android:viewportHeight="36"
android:viewportWidth="36"
android:width="36sp">
- <path android:fillColor="?android:attr/colorBackground"
+
+ <path android:fillColor="?android:attr/textColorPrimaryInverse"
android:pathData="M17.59,13.41L21.17,17H7v2h14.17l-3.59,3.59L19,24l6,-6l-6,-6L17.59,
13.41zM26,12v12h2V12H26z"/>
</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_lock.xml b/packages/SystemUI/res-keyguard/drawable/ic_lock.xml
new file mode 100644
index 000000000000..14a8d0bdf8e8
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_lock.xml
@@ -0,0 +1,95 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="65dp"
+ android:width="46dp"
+ android:viewportHeight="65"
+ android:viewportWidth="46">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_2_G_N_10_N_11_T_0"
+ android:translateX="-27.5"
+ android:translateY="-17.5">
+ <group android:name="_R_G_L_2_G_N_10_T_1"
+ android:translateX="50.25"
+ android:translateY="61">
+ <group android:name="_R_G_L_2_G_N_10_T_0"
+ android:translateX="-13.75"
+ android:translateY="-7.5">
+ <group android:name="_R_G_L_2_G"
+ android:translateX="-0.375"
+ android:translateY="-22.375">
+ <path android:name="_R_G_L_2_G_D_0_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="1"
+ android:pathData=" M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c "/>
+ </group>
+ </group>
+ </group>
+ </group>
+ <group android:name="_R_G_L_1_G_N_10_N_11_T_0"
+ android:translateX="-27.5"
+ android:translateY="-17.5">
+ <group android:name="_R_G_L_1_G_N_10_T_1"
+ android:translateX="50.25"
+ android:translateY="61">
+ <group android:name="_R_G_L_1_G_N_10_T_0"
+ android:translateX="-13.75"
+ android:translateY="-7.5">
+ <group android:name="_R_G_L_1_G"
+ android:translateX="5"
+ android:translateY="-22.5">
+ <path android:name="_R_G_L_1_G_D_0_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="1"
+ android:pathData=" M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 "/>
+ </group>
+ </group>
+ </group>
+ </group>
+ <group android:name="_R_G_L_0_G_N_10_N_11_T_0"
+ android:translateX="-27.5"
+ android:translateY="-17.5">
+ <group android:name="_R_G_L_0_G_N_10_T_1"
+ android:translateX="50.25"
+ android:translateY="61">
+ <group android:name="_R_G_L_0_G_N_10_T_0"
+ android:translateX="-13.75"
+ android:translateY="-7.5">
+ <group android:name="_R_G_L_0_G"
+ android:translateX="11"
+ android:translateY="-0.25"
+ android:pivotX="2.75"
+ android:pivotY="2.75"
+ android:scaleX="1"
+ android:scaleY="1">
+ <path android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillColor="#FF000000"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/>
+ </group>
+ </group>
+ </group>
+ </group>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_lock_aod.xml b/packages/SystemUI/res-keyguard/drawable/ic_lock_aod.xml
new file mode 100644
index 000000000000..cdae306cfc26
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_lock_aod.xml
@@ -0,0 +1,41 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="65dp"
+ android:width="46dp"
+ android:viewportHeight="65"
+ android:viewportWidth="46">
+ <group android:name="_R_G_L_2_G" android:translateX="23" android:translateY="32.125">
+ <path
+ android:fillColor="#FF000000"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " />
+ <path
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="1.5"
+ android:pathData=" M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " />
+ <path
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="1.5"
+ android:pathData=" M4.38 -2.62 C4.38,-2.62 4.38,-7.1 4.38,-7.1 C4.38,-9.46 2.42,-11.37 0,-11.37 C-2.42,-11.37 -4.37,-9.46 -4.37,-7.1 C-4.37,-7.1 -4.37,-2.62 -4.37,-2.62 " />
+ </group>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_unlocked.xml b/packages/SystemUI/res-keyguard/drawable/ic_unlocked.xml
new file mode 100644
index 000000000000..54242781cd73
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_unlocked.xml
@@ -0,0 +1,48 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="65dp"
+ android:width="46dp"
+ android:viewportHeight="65"
+ android:viewportWidth="46">
+ <group android:translateX="8.625"
+ android:translateY="13.625">
+ <path
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2.5"
+ android:pathData="M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c "/>
+ </group>
+ <group android:translateX="14"
+ android:translateY="13.5">
+ <path
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2.5"
+ android:pathData="M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 "/>
+ </group>
+ <group android:translateX="20"
+ android:translateY="35.75">
+ <path
+ android:fillColor="#FF000000"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/lock_aod_to_ls.xml b/packages/SystemUI/res-keyguard/drawable/lock_aod_to_ls.xml
new file mode 100644
index 000000000000..d35f69589c39
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/lock_aod_to_ls.xml
@@ -0,0 +1,151 @@
+<?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.
+-->
+<animated-vector xmlns:aapt="http://schemas.android.com/aapt"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <aapt:attr name="android:drawable">
+ <vector android:height="65dp"
+ android:width="46dp"
+ android:viewportHeight="65"
+ android:viewportWidth="46">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_2_G"
+ android:translateX="23"
+ android:translateY="32.125">
+ <path android:name="_R_G_L_2_G_D_0_P_0"
+ android:fillColor="#FF000000"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c "/>
+ </group>
+ <group android:name="_R_G_L_1_G"
+ android:translateX="23"
+ android:translateY="32.125">
+ <path android:name="_R_G_L_1_G_D_0_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="1.5"
+ android:strokeAlpha="1"
+ android:pathData=" M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c "/>
+ </group>
+ <group android:name="_R_G_L_0_G"
+ android:translateX="23"
+ android:translateY="32.125">
+ <path android:name="_R_G_L_0_G_D_0_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="1.5"
+ android:strokeAlpha="1"
+ android:pathData=" M4.38 -2.62 C4.38,-2.62 4.38,-7.1 4.38,-7.1 C4.38,-9.46 2.42,-11.37 0,-11.37 C-2.42,-11.37 -4.37,-9.46 -4.37,-7.1 C-4.37,-7.1 -4.37,-2.62 -4.37,-2.62 "/>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_2_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c "
+ android:valueTo="M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.372,0 0.203,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="strokeWidth"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="1.5"
+ android:valueTo="2"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.386,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c "
+ android:valueTo="M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.372,0 0.203,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="strokeWidth"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="1.5"
+ android:valueTo="2"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.386,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="M4.38 -2.62 C4.38,-2.62 4.38,-7.1 4.38,-7.1 C4.38,-9.46 2.42,-11.37 0,-11.37 C-2.42,-11.37 -4.37,-9.46 -4.37,-7.1 C-4.37,-7.1 -4.37,-2.62 -4.37,-2.62 "
+ android:valueTo="M5.88 -3.87 C5.88,-3.87 5.88,-10.2 5.88,-10.2 C5.88,-13.54 3.08,-16.25 -0.37,-16.25 C-3.83,-16.25 -6.62,-13.54 -6.62,-10.2 C-6.62,-10.2 -6.62,-3.87 -6.62,-3.87 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.372,0 0.203,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="translateX"
+ android:duration="517"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/lock_ls_to_aod.xml b/packages/SystemUI/res-keyguard/drawable/lock_ls_to_aod.xml
new file mode 100644
index 000000000000..8a728ee7b46a
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/lock_ls_to_aod.xml
@@ -0,0 +1,151 @@
+<?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.
+-->
+<animated-vector xmlns:aapt="http://schemas.android.com/aapt"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <aapt:attr name="android:drawable">
+ <vector android:height="65dp"
+ android:width="46dp"
+ android:viewportHeight="65"
+ android:viewportWidth="46">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_2_G"
+ android:translateX="23"
+ android:translateY="32.125">
+ <path android:name="_R_G_L_2_G_D_0_P_0"
+ android:fillColor="#FF000000"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c "/>
+ </group>
+ <group android:name="_R_G_L_1_G"
+ android:translateX="23"
+ android:translateY="32.125">
+ <path android:name="_R_G_L_1_G_D_0_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="1"
+ android:pathData=" M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c "/>
+ </group>
+ <group android:name="_R_G_L_0_G"
+ android:translateX="23"
+ android:translateY="32.125">
+ <path android:name="_R_G_L_0_G_D_0_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="1"
+ android:pathData=" M5.88 -3.87 C5.88,-3.87 5.88,-10.2 5.88,-10.2 C5.88,-13.54 3.08,-16.25 -0.37,-16.25 C-3.83,-16.25 -6.62,-13.54 -6.62,-10.2 C-6.62,-10.2 -6.62,-3.87 -6.62,-3.87 "/>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_2_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c "
+ android:valueTo="M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="strokeWidth"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="2"
+ android:valueTo="1.5"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.38,0 0.274,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c "
+ android:valueTo="M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="strokeWidth"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="2"
+ android:valueTo="1.5"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.38,0 0.274,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="333"
+ android:startOffset="0"
+ android:valueFrom="M5.88 -3.87 C5.88,-3.87 5.88,-10.2 5.88,-10.2 C5.88,-13.54 3.08,-16.25 -0.37,-16.25 C-3.83,-16.25 -6.62,-13.54 -6.62,-10.2 C-6.62,-10.2 -6.62,-3.87 -6.62,-3.87 "
+ android:valueTo="M4.38 -2.62 C4.38,-2.62 4.38,-7.1 4.38,-7.1 C4.38,-9.46 2.42,-11.37 0,-11.37 C-2.42,-11.37 -4.37,-9.46 -4.37,-7.1 C-4.37,-7.1 -4.37,-2.62 -4.37,-2.62 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="translateX"
+ android:duration="517"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
diff --git a/packages/SystemUI/res/anim/lock_to_unlock.xml b/packages/SystemUI/res-keyguard/drawable/lock_to_unlock.xml
index 76f7a05866d9..ab7e9d9e582b 100644
--- a/packages/SystemUI/res/anim/lock_to_unlock.xml
+++ b/packages/SystemUI/res-keyguard/drawable/lock_to_unlock.xml
@@ -21,7 +21,7 @@
<group android:name="_R_G_L_2_G_N_10_T_1" android:translateX="50.25" android:translateY="61">
<group android:name="_R_G_L_2_G_N_10_T_0" android:translateX="-13.75" android:translateY="-7.5">
<group android:name="_R_G_L_2_G" android:translateX="-0.375" android:translateY="-22.375">
- <path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c " />
+ <path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c " />
</group>
</group>
</group>
@@ -30,7 +30,7 @@
<group android:name="_R_G_L_1_G_N_10_T_1" android:translateX="50.25" android:translateY="61">
<group android:name="_R_G_L_1_G_N_10_T_0" android:translateX="-13.75" android:translateY="-7.5">
<group android:name="_R_G_L_1_G" android:translateX="5" android:translateY="-22.5">
- <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 " />
+ <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 " />
</group>
</group>
</group>
@@ -39,7 +39,7 @@
<group android:name="_R_G_L_0_G_N_10_T_1" android:translateX="50.25" android:translateY="61">
<group android:name="_R_G_L_0_G_N_10_T_0" android:translateX="-13.75" android:translateY="-7.5">
<group android:name="_R_G_L_0_G" android:translateX="11" android:translateY="-0.25" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1" android:scaleY="1">
- <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#b7f29f" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c " />
+ <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c " />
</group>
</group>
</group>
diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
new file mode 100644
index 000000000000..c58e2e3266d0
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
@@ -0,0 +1,82 @@
+<?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
+ -->
+<animated-selector
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <!--
+ State corresponds with the following icons:
+ state_first => lock icon
+ state_middle => fingerprint icon
+ state_last => unlocked icon
+
+ state_single
+ = true => AOD
+ = false => LS
+ -->
+
+ <item
+ android:id="@+id/locked"
+ android:drawable="@drawable/ic_lock"
+ android:state_first="true"
+ android:state_single="false"/>
+
+ <item
+ android:id="@+id/locked_fp"
+ android:state_middle="true"
+ android:state_single="false"
+ android:drawable="@drawable/ic_kg_fingerprint" />
+
+ <item
+ android:id="@+id/unlocked"
+ android:state_last="true"
+ android:state_single="false"
+ android:drawable="@drawable/ic_unlocked" />
+
+ <item
+ android:id="@+id/locked_aod"
+ android:state_first="true"
+ android:state_single="true"
+ android:drawable="@drawable/ic_lock_aod" />
+
+ <item
+ android:id="@+id/no_icon"
+ android:drawable="@color/transparent" />
+
+ <transition
+ android:fromId="@id/locked"
+ android:toId="@id/unlocked"
+ android:drawable="@drawable/lock_to_unlock" />
+
+ <transition
+ android:fromId="@id/locked_fp"
+ android:toId="@id/unlocked"
+ android:drawable="@drawable/fp_to_unlock" />
+
+ <transition
+ android:fromId="@id/unlocked"
+ android:toId="@id/locked_fp"
+ android:drawable="@drawable/unlock_to_fp" />
+
+ <transition
+ android:fromId="@id/locked_aod"
+ android:toId="@id/locked"
+ android:drawable="@drawable/lock_aod_to_ls" />
+
+ <transition
+ android:fromId="@id/locked"
+ android:toId="@id/locked_aod"
+ android:drawable="@drawable/lock_ls_to_aod" />
+</animated-selector>
diff --git a/packages/SystemUI/res-keyguard/drawable/unlock_to_fp.xml b/packages/SystemUI/res-keyguard/drawable/unlock_to_fp.xml
new file mode 100644
index 000000000000..620c71a73121
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/unlock_to_fp.xml
@@ -0,0 +1,298 @@
+<?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
+ -->
+<animated-vector xmlns:aapt="http://schemas.android.com/aapt"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <aapt:attr name="android:drawable">
+ <vector android:height="65dp"
+ android:width="46dp"
+ android:viewportHeight="65"
+ android:viewportWidth="46">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_1_G"
+ android:translateX="3.75"
+ android:translateY="8.25">
+ <path android:name="_R_G_L_1_G_D_0_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="1"
+ android:pathData=" M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "/>
+ <path android:name="_R_G_L_1_G_D_1_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="0"
+ android:pathData=" M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "/>
+ <path android:name="_R_G_L_1_G_D_2_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="1"
+ android:pathData=" M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "/>
+ <path android:name="_R_G_L_1_G_D_3_P_0"
+ android:strokeColor="#FF000000"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2"
+ android:strokeAlpha="1"
+ android:pathData=" M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "/>
+ </group>
+ <group android:name="_R_G_L_0_G"
+ android:translateX="20.357"
+ android:translateY="35.75"
+ android:pivotX="2.75"
+ android:pivotY="2.75"
+ android:scaleX="1"
+ android:scaleY="1">
+ <path android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillColor="#FF000000"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="183"
+ android:startOffset="0"
+ android:valueFrom="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "
+ android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData"
+ android:duration="133"
+ android:startOffset="183"
+ android:valueFrom="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "
+ android:valueTo="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="strokeAlpha"
+ android:duration="183"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="strokeAlpha"
+ android:duration="33"
+ android:startOffset="183"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="183"
+ android:startOffset="0"
+ android:valueFrom="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "
+ android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData"
+ android:duration="133"
+ android:startOffset="183"
+ android:valueFrom="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "
+ android:valueTo="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="183"
+ android:startOffset="0"
+ android:valueFrom="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "
+ android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData"
+ android:duration="133"
+ android:startOffset="183"
+ android:valueFrom="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "
+ android:valueTo="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_3_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData"
+ android:duration="150"
+ android:startOffset="0"
+ android:valueFrom="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "
+ android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.261,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData"
+ android:duration="33"
+ android:startOffset="150"
+ android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "
+ android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.123,0 0.261,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData"
+ android:duration="133"
+ android:startOffset="183"
+ android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "
+ android:valueTo="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.123,0 0.15,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="fillAlpha"
+ android:duration="200"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="fillAlpha"
+ android:duration="17"
+ android:startOffset="200"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="scaleX"
+ android:duration="183"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.596,0 0.018,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY"
+ android:duration="183"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.596,0 0.018,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleX"
+ android:duration="67"
+ android:startOffset="183"
+ android:valueFrom="1"
+ android:valueTo="1.4186600000000003"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.596,0 0.018,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY"
+ android:duration="67"
+ android:startOffset="183"
+ android:valueFrom="1"
+ android:valueTo="1.4186600000000003"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.596,0 0.018,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="translateX"
+ android:duration="433"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
new file mode 100644
index 000000000000..dfc3e63a4e2b
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright 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.
+-->
+
+<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc -->
+<com.android.systemui.qs.FooterActionsView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:gravity="center_vertical">
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@android:id/edit"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+ android:layout_weight="1"
+ android:background="@drawable/qs_footer_action_chip_background"
+ android:clickable="true"
+ android:clipToPadding="false"
+ android:contentDescription="@string/accessibility_quick_settings_edit"
+ android:focusable="true"
+ android:padding="@dimen/qs_footer_icon_padding"
+ android:src="@*android:drawable/ic_mode_edit"
+ android:tint="?android:attr/textColorPrimary" />
+
+ <com.android.systemui.statusbar.phone.MultiUserSwitch
+ android:id="@+id/multi_user_switch"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+ android:layout_weight="1"
+ android:background="@drawable/qs_footer_action_chip_background"
+ android:focusable="true">
+
+ <ImageView
+ android:id="@+id/multi_user_avatar"
+ android:layout_width="@dimen/multi_user_avatar_expanded_size"
+ android:layout_height="@dimen/multi_user_avatar_expanded_size"
+ android:layout_gravity="center"
+ android:scaleType="centerInside" />
+ </com.android.systemui.statusbar.phone.MultiUserSwitch>
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/pm_lite"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+ android:layout_weight="1"
+ android:background="@drawable/qs_footer_action_chip_background"
+ android:clickable="true"
+ android:clipToPadding="false"
+ android:focusable="true"
+ android:padding="@dimen/qs_footer_icon_padding"
+ android:src="@*android:drawable/ic_lock_power_off"
+ android:contentDescription="@string/accessibility_quick_settings_power_menu"
+ android:tint="?android:attr/textColorPrimary" />
+
+ <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+ android:id="@+id/settings_button_container"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:background="@drawable/qs_footer_action_chip_background"
+ android:layout_weight="1"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+
+ <com.android.systemui.statusbar.phone.SettingsButton
+ android:id="@+id/settings_button"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:layout_gravity="center"
+ android:contentDescription="@string/accessibility_quick_settings_settings"
+ android:background="@drawable/qs_footer_action_chip_background_borderless"
+ android:padding="@dimen/qs_footer_icon_padding"
+ android:scaleType="centerInside"
+ android:src="@drawable/ic_settings"
+ android:tint="?android:attr/textColorPrimary" />
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/tuner_icon"
+ android:layout_width="8dp"
+ android:layout_height="8dp"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_marginBottom="@dimen/qs_footer_icon_padding"
+ android:src="@drawable/tuner"
+ android:tint="?android:attr/textColorTertiary"
+ android:visibility="invisible" />
+
+ </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+
+</com.android.systemui.qs.FooterActionsView> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 28c61663bd4d..87a9825af1cb 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -51,7 +51,7 @@
android:id="@+id/lockscreen_clock_view_large"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_below="@id/keyguard_status_area"
+ android:layout_below="@id/keyguard_slice_view"
android:visibility="gone">
<com.android.keyguard.AnimatableClockView
android:id="@+id/animatable_clock_view_large"
@@ -68,19 +68,28 @@
lockScreenWeight="400"
/>
</FrameLayout>
- <include layout="@layout/keyguard_status_area"
+
+ <!-- Not quite optimal but needed to translate these items as a group. The
+ NotificationIconContainer has its own logic for translation. -->
+ <LinearLayout
android:id="@+id/keyguard_status_area"
+ android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
- android:layout_below="@id/lockscreen_clock_view" />
+ android:layout_below="@id/lockscreen_clock_view">
- <com.android.systemui.statusbar.phone.NotificationIconContainer
- android:id="@+id/left_aligned_notification_icon_container"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_shelf_height"
- android:layout_below="@id/keyguard_status_area"
- android:paddingStart="@dimen/below_clock_padding_start_icons"
- android:visibility="invisible"
- />
+ <include layout="@layout/keyguard_slice_view"
+ android:id="@+id/keyguard_slice_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <com.android.systemui.statusbar.phone.NotificationIconContainer
+ android:id="@+id/left_aligned_notification_icon_container"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_shelf_height"
+ android:paddingStart="@dimen/below_clock_padding_start_icons"
+ android:visibility="invisible"
+ />
+ </LinearLayout>
</com.android.keyguard.KeyguardClockSwitch>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index ce63082868bb..f613a195ea67 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -27,49 +27,44 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false"
androidprv:layout_maxWidth="@dimen/keyguard_security_width"
- androidprv:layout_maxHeight="@dimen/keyguard_security_height"
- android:gravity="center_horizontal">
+ android:clipChildren="false"
+ android:clipToPadding="false">
- <FrameLayout
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/pattern_container"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false">
-
- <LinearLayout
- android:id="@+id/pattern_container"
- android:layout_height="wrap_content"
+ android:layout_height="0dp"
+ android:layout_marginBottom="8dp"
+ android:layout_weight="1"
+ android:layoutDirection="ltr">
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/pattern_top_guideline"
android:layout_width="wrap_content"
- android:orientation="vertical"
- android:layout_gravity="center_horizontal|bottom"
- android:clipChildren="false"
- android:clipToPadding="false">
+ android:layout_height="wrap_content"
+ androidprv:layout_constraintGuide_percent="0"
+ android:orientation="horizontal" />
- <com.android.internal.widget.LockPatternView
- android:id="@+id/lockPatternView"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:layout_marginEnd="8dip"
- android:layout_marginBottom="4dip"
- android:layout_marginStart="8dip"
- android:layout_gravity="center_horizontal"
- android:gravity="center"
- android:clipChildren="false"
- android:clipToPadding="false" />
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPatternView"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ androidprv:layout_constraintTop_toBottomOf="@id/pattern_top_guideline"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintLeft_toLeftOf="parent"
+ androidprv:layout_constraintRight_toRightOf="parent"
+ androidprv:layout_constraintDimensionRatio="1.0"
+ androidprv:layout_constraintVertical_bias="1.0"
+ />
+ </androidx.constraintlayout.widget.ConstraintLayout>
- <include layout="@layout/keyguard_eca"
- android:id="@+id/keyguard_selector_fade_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_gravity="bottom|center_horizontal"
- android:layout_marginTop="@dimen/keyguard_eca_top_margin"
- android:gravity="center_horizontal" />
- </LinearLayout>
- </FrameLayout>
+ <include layout="@layout/keyguard_eca"
+ android:id="@+id/keyguard_selector_fade_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:gravity="center_horizontal" />
</com.android.keyguard.KeyguardPatternView>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index 02cb2bcfad81..a946318cb313 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -20,171 +20,174 @@
<com.android.keyguard.KeyguardPINView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/keyguard_pin_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
androidprv:layout_maxWidth="@dimen/keyguard_security_width"
android:orientation="vertical"
>
- <LinearLayout
- android:id="@+id/pin_container"
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/pin_container"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_marginBottom="8dp"
+ android:layout_weight="1"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <!-- Set this to be just above key1. It would be better to introduce a barrier above
+ key1/key2/key3, then place this View above that. Sadly, that doesn't work (the Barrier
+ drops to the bottom of the page, and key1/2/3 all shoot up to the top-left). In any
+ case, the Flow should ensure that key1/2/3 all have the same top, so this should be
+ fine. -->
+ <com.android.keyguard.AlphaOptimizedRelativeLayout
+ android:id="@+id/row0"
android:layout_width="match_parent"
- android:layout_height="0dp"
- android:orientation="vertical"
- android:layout_weight="1"
- android:layoutDirection="ltr"
- android:layout_marginBottom="8dp"
- >
- <Space
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- />
- <com.android.keyguard.AlphaOptimizedRelativeLayout
- android:id="@+id/row0"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
- >
+ android:layout_height="wrap_content"
+ android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:layout_constraintEnd_toEndOf="parent"
+ androidprv:layout_constraintStart_toStartOf="parent"
+
+ androidprv:layout_constraintTop_toTopOf="parent"
+ androidprv:layout_constraintBottom_toTopOf="@id/key1"
+ androidprv:layout_constraintVertical_bias="0.0">
+
<com.android.keyguard.PasswordTextView
- android:id="@+id/pinEntry"
- android:layout_width="@dimen/keyguard_security_width"
- android:layout_height="@dimen/keyguard_password_height"
- style="@style/Widget.TextView.Password"
- android:layout_centerHorizontal="true"
- android:layout_marginRight="72dp"
- androidprv:scaledTextSize="@integer/scaled_password_text_size"
- android:contentDescription="@string/keyguard_accessibility_pin_area"
- />
+ android:id="@+id/pinEntry"
+ style="@style/Widget.TextView.Password"
+ android:layout_width="@dimen/keyguard_security_width"
+ android:layout_height="@dimen/keyguard_password_height"
+ android:layout_centerHorizontal="true"
+ android:layout_marginRight="72dp"
+ android:contentDescription="@string/keyguard_accessibility_pin_area"
+ androidprv:scaledTextSize="@integer/scaled_password_text_size" />
</com.android.keyguard.AlphaOptimizedRelativeLayout>
- <LinearLayout
- android:id="@+id/row1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_gravity="center_horizontal"
- android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
- >
- <com.android.keyguard.NumPadKey
- android:id="@+id/key1"
- android:layout_width="@dimen/num_pad_key_width"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/num_pad_key_margin_end"
- androidprv:textView="@+id/pinEntry"
- androidprv:digit="1"
- />
- <com.android.keyguard.NumPadKey
- android:id="@+id/key2"
- android:layout_width="@dimen/num_pad_key_width"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/num_pad_key_margin_end"
- androidprv:textView="@+id/pinEntry"
- androidprv:digit="2"
- />
- <com.android.keyguard.NumPadKey
- android:id="@+id/key3"
- android:layout_width="@dimen/num_pad_key_width"
- android:layout_height="match_parent"
- androidprv:textView="@+id/pinEntry"
- androidprv:digit="3"
- />
- </LinearLayout>
- <LinearLayout
- android:id="@+id/row2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_gravity="center_horizontal"
- android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
- >
- <com.android.keyguard.NumPadKey
- android:id="@+id/key4"
- android:layout_width="@dimen/num_pad_key_width"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/num_pad_key_margin_end"
- androidprv:textView="@+id/pinEntry"
- androidprv:digit="4"
- />
- <com.android.keyguard.NumPadKey
- android:id="@+id/key5"
- android:layout_width="@dimen/num_pad_key_width"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/num_pad_key_margin_end"
- androidprv:textView="@+id/pinEntry"
- androidprv:digit="5"
- />
- <com.android.keyguard.NumPadKey
- android:id="@+id/key6"
- android:layout_width="@dimen/num_pad_key_width"
- android:layout_height="match_parent"
- androidprv:textView="@+id/pinEntry"
- androidprv:digit="6"
- />
- </LinearLayout>
- <LinearLayout
- android:id="@+id/row3"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_gravity="center_horizontal"
- android:layout_marginBottom="@dimen/num_pad_row_margin_bottom"
- >
- <com.android.keyguard.NumPadKey
- android:id="@+id/key7"
- android:layout_width="@dimen/num_pad_key_width"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/num_pad_key_margin_end"
- androidprv:textView="@+id/pinEntry"
- androidprv:digit="7"
- />
- <com.android.keyguard.NumPadKey
- android:id="@+id/key8"
- android:layout_width="@dimen/num_pad_key_width"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/num_pad_key_margin_end"
- androidprv:textView="@+id/pinEntry"
- androidprv:digit="8"
- />
- <com.android.keyguard.NumPadKey
- android:id="@+id/key9"
- android:layout_width="@dimen/num_pad_key_width"
- android:layout_height="match_parent"
- androidprv:textView="@+id/pinEntry"
- androidprv:digit="9"
- />
- </LinearLayout>
- <LinearLayout
- android:id="@+id/row4"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_gravity="center_horizontal"
- >
- <com.android.keyguard.NumPadButton
- android:id="@+id/delete_button"
- android:layout_width="@dimen/num_pad_key_width"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/num_pad_key_margin_end"
- android:contentDescription="@string/keyboardview_keycode_delete"
- style="@style/NumPadKey.Delete"
- />
- <com.android.keyguard.NumPadKey
- android:id="@+id/key0"
- android:layout_width="@dimen/num_pad_key_width"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/num_pad_key_margin_end"
- androidprv:textView="@+id/pinEntry"
- androidprv:digit="0"
- />
- <com.android.keyguard.NumPadButton
- android:id="@+id/key_enter"
- android:layout_width="@dimen/num_pad_key_width"
- android:layout_height="match_parent"
- style="@style/NumPadKey.Enter"
- android:contentDescription="@string/keyboardview_keycode_enter"
- />
- </LinearLayout>
- </LinearLayout>
+
+ <!-- Guideline used to place the top row of keys relative to the screen height. This will be
+ updated in KeyguardPINView to reduce the height of the PIN pad. -->
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/pin_pad_top_guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ androidprv:layout_constraintGuide_percent="0"
+ android:orientation="horizontal" />
+
+ <androidx.constraintlayout.helper.widget.Flow
+ android:id="@+id/flow1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:orientation="horizontal"
+
+ androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+
+ androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+
+ androidprv:flow_horizontalStyle="packed"
+ androidprv:flow_maxElementsWrap="3"
+
+ androidprv:flow_verticalBias="1.0"
+ androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:flow_verticalStyle="packed"
+
+ androidprv:flow_wrapMode="aligned"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintEnd_toEndOf="parent"
+ androidprv:layout_constraintStart_toStartOf="parent"
+ androidprv:layout_constraintTop_toBottomOf="@id/pin_pad_top_guideline" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ androidprv:digit="1"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key2"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ androidprv:digit="2"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key3"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ androidprv:digit="3"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key4"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ androidprv:digit="4"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key5"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ androidprv:digit="5"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key6"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ androidprv:digit="6"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key7"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ androidprv:digit="7"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key8"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ androidprv:digit="8"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key9"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ androidprv:digit="9"
+ androidprv:textView="@+id/pinEntry" />
+
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/delete_button"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ style="@style/NumPadKey.Delete"
+ android:contentDescription="@string/keyboardview_keycode_delete"
+ />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key0"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ androidprv:digit="0"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/key_enter"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ style="@style/NumPadKey.Enter"
+ android:contentDescription="@string/keyboardview_keycode_enter"
+ />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+
+
<include layout="@layout/keyguard_eca"
android:id="@+id/keyguard_selector_fade_container"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml
index 95eb5c1d5f1a..1863d1112947 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml
@@ -22,11 +22,10 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
+ android:layout_gravity="start"
android:clipToPadding="false"
android:orientation="vertical"
- android:paddingStart="@dimen/below_clock_padding_start"
- android:layout_centerHorizontal="true">
+ android:paddingStart="@dimen/below_clock_padding_start">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
@@ -42,6 +41,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:gravity="center"
+ android:gravity="start"
/>
</com.android.keyguard.KeyguardSliceView>
diff --git a/packages/SystemUI/res-keyguard/values-land/donottranslate.xml b/packages/SystemUI/res-keyguard/values-land/donottranslate.xml
new file mode 100644
index 000000000000..9912b699507e
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/values-land/donottranslate.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="num_pad_key_ratio">1.51</string>
+</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml
index c34012dc85a8..17765b51c325 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml
@@ -21,4 +21,8 @@
<!-- Overload default clock widget parameters -->
<dimen name="widget_big_font_size">88dp</dimen>
+
+ <dimen name="qs_header_system_icons_area_height">0dp</dimen>
+ <dimen name="qs_panel_padding_top">@dimen/qqs_layout_margin_top</dimen>
+
</resources> \ No newline at end of file
diff --git a/packages/overlays/NoCutoutOverlay/res/values-is/strings.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/donottranslate.xml
index 229c113bbdb3..1a52e93df8ae 100644
--- a/packages/overlays/NoCutoutOverlay/res/values-is/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/donottranslate.xml
@@ -1,5 +1,5 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?>
+<!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,9 +13,9 @@
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Fela"</string>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Don't use the smaller PIN pad keys if we have the screen space to support it. -->
+ <string name="num_pad_key_ratio">1.0</string>
</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-vi/strings.xml b/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml
index fc83d257fdba..e09bf7e37ed0 100644
--- a/packages/overlays/NoCutoutOverlay/res/values-vi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml
@@ -1,5 +1,5 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?>
+<!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,9 +13,8 @@
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
- -->
+ -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Ẩn"</string>
+<resources>
+ <bool name="can_use_one_handed_bouncer">true</bool>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 7e3c87b24f07..e854b027e074 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -98,4 +98,17 @@
<dimen name="below_clock_padding_start">32dp</dimen>
<dimen name="below_clock_padding_end">16dp</dimen>
<dimen name="below_clock_padding_start_icons">28dp</dimen>
+
+ <!-- Proportion of the screen height to use to set the maximum height of the bouncer to when
+ the device is in the DEVICE_POSTURE_HALF_OPENED posture, for the PIN/pattern entry. 0 will
+ allow it to use the whole screen space, 0.6 will allow it to use just under half of the
+ screen. -->
+ <item name="half_opened_bouncer_height_ratio" type="dimen" format="float">0.0</item>
+
+ <!-- The actual amount of translation that is applied to the bouncer when it animates from one
+ side of the screen to the other in one-handed mode. Note that it will always translate from
+ the side of the screen to the other (it will "jump" closer to the destination while the
+ opacity is zero), but this controls how much motion will actually be applied to it while
+ animating. Larger values will cause it to move "faster" while fading out/in. -->
+ <dimen name="one_handed_bouncer_move_animation_translation">120dp</dimen>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/donottranslate.xml b/packages/SystemUI/res-keyguard/values/donottranslate.xml
index 1934457b4bc6..052d329ee63c 100644
--- a/packages/SystemUI/res-keyguard/values/donottranslate.xml
+++ b/packages/SystemUI/res-keyguard/values/donottranslate.xml
@@ -29,4 +29,6 @@
<!-- Skeleton string format for displaying the time in 24-hour format. -->
<string name="clock_24hr_format">Hm</string>
+
+ <string name="num_pad_key_ratio">1</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 098b7e8dedbe..6e89fb0169ab 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -17,7 +17,7 @@
*/
-->
-<resources>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Keyguard PIN pad styles -->
<style name="Keyguard.TextView" parent="@android:style/Widget.DeviceDefault.TextView">
<item name="android:textSize">@dimen/kg_status_line_font_size</item>
@@ -37,6 +37,10 @@
<item name="android:colorControlNormal">@null</item>
<item name="android:colorControlHighlight">?android:attr/colorAccent</item>
<item name="android:background">@drawable/num_pad_key_background</item>
+
+ <!-- Default values for NumPadKey used in a ConstraintLayout. -->
+ <item name="layout_constraintDimensionRatio">@string/num_pad_key_ratio</item>
+ <item name="layout_constraintWidth_max">@dimen/num_pad_key_width</item>
</style>
<style name="Widget.TextView.NumPadKey.Digit"
parent="@android:style/Widget.DeviceDefault.TextView">
@@ -54,12 +58,12 @@
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="NumPadKey.Delete">
- <item name="android:colorControlNormal">?android:attr/textColorSecondary</item>
+ <item name="android:colorControlNormal">@color/numpad_key_color_secondary</item>
<item name="android:src">@drawable/ic_backspace_24dp</item>
</style>
<style name="NumPadKey.Enter">
- <item name="android:colorControlNormal">?android:attr/textColorSecondary</item>
- <item name="android:src">@drawable/ic_keyboard_tab_36dp</item>
+ <item name="android:colorControlNormal">@color/numpad_key_color_secondary</item>
+ <item name="android:src">@drawable/ic_keyboard_tab_36dp</item>
</style>
<style name="Widget.TextView.NumPadKey.Klondike"
parent="@android:style/Widget.DeviceDefault.TextView">
diff --git a/packages/SystemUI/res/color/docked_divider_background.xml b/packages/SystemUI/res/color/docked_divider_background.xml
new file mode 100644
index 000000000000..2ab8ecdec936
--- /dev/null
+++ b/packages/SystemUI/res/color/docked_divider_background.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral1_500" android:lStar="35" />
+</selector>
diff --git a/packages/SystemUI/res/color/prv_color_surface.xml b/packages/SystemUI/res/color/prv_color_surface.xml
new file mode 100644
index 000000000000..b9d016c46ba0
--- /dev/null
+++ b/packages/SystemUI/res/color/prv_color_surface.xml
@@ -0,0 +1,20 @@
+<?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.
+ -->
+<selector xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="?androidprv:attr/colorSurface" />
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/color/prv_text_color_on_accent.xml b/packages/SystemUI/res/color/prv_text_color_on_accent.xml
new file mode 100644
index 000000000000..9f44acadb420
--- /dev/null
+++ b/packages/SystemUI/res/color/prv_text_color_on_accent.xml
@@ -0,0 +1,20 @@
+<?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.
+ -->
+<selector xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="?androidprv:attr/textColorOnAccent" />
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_fingerprint.xml b/packages/SystemUI/res/drawable/ic_kg_fingerprint.xml
index 91d72a7902c0..91d72a7902c0 100644
--- a/packages/SystemUI/res/drawable/ic_fingerprint.xml
+++ b/packages/SystemUI/res/drawable/ic_kg_fingerprint.xml
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ca/strings.xml b/packages/SystemUI/res/drawable/ic_qs_drag_handle.xml
index e0a577e5290f..9a69b33fd591 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_drag_handle.xml
@@ -1,5 +1,4 @@
-<?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");
@@ -12,10 +11,14 @@
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Renderitza les aplicacions per sota de l\'àrea de retallada"</string>
-</resources>
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="36dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="?android:attr/textColorPrimary"
+ android:pathData="M5.41,7.59L4,9l8,8 8,-8 -1.41,-1.41L12,14.17" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/internet_dialog_background.xml b/packages/SystemUI/res/drawable/internet_dialog_background.xml
deleted file mode 100644
index 3ceb0f6ac06a..000000000000
--- a/packages/SystemUI/res/drawable/internet_dialog_background.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<inset xmlns:android="http://schemas.android.com/apk/res/android">
- <shape android:shape="rectangle">
- <corners android:radius="8dp" />
- <solid android:color="?android:attr/colorBackground" />
- </shape>
-</inset>
diff --git a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
index 1535e7276a6f..3a08a7111d9a 100644
--- a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
+++ b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
@@ -22,11 +22,12 @@
android:color="?android:attr/textColorPrimary">
<item>
<shape
- android:shape="oval">
+ android:shape="rectangle">
<solid android:color="?androidprv:attr/colorSurface"/>
<size
android:width="@dimen/keyguard_affordance_width"
android:height="@dimen/keyguard_affordance_height"/>
+ <corners android:radius="@dimen/keyguard_affordance_fixed_radius"/>
</shape>
</item>
</ripple>
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_background.xml
deleted file mode 100644
index 3ceb0f6ac06a..000000000000
--- a/packages/SystemUI/res/drawable/media_output_dialog_background.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<inset xmlns:android="http://schemas.android.com/apk/res/android">
- <shape android:shape="rectangle">
- <corners android:radius="8dp" />
- <solid android:color="?android:attr/colorBackground" />
- </shape>
-</inset>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-be/strings.xml b/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml
index 0e5c8bcb55a2..363a022efdac 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-be/strings.xml
+++ b/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml
@@ -1,5 +1,4 @@
-<?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");
@@ -13,9 +12,18 @@
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Візуалізацыя праграм ніжэй месца выраза"</string>
-</resources>
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <stroke
+ android:color="?androidprv:attr/colorAccentPrimaryVariant"
+ android:width="1dp"/>
+ <corners android:radius="20dp"/>
+ <padding
+ android:left="16dp"
+ android:right="16dp"
+ android:top="8dp"
+ android:bottom="8dp" />
+ <solid android:color="@android:color/transparent" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
new file mode 100644
index 000000000000..1a128dfe8b10
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
@@ -0,0 +1,39 @@
+<?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.
+ -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:insetTop="@dimen/qs_dialog_button_vertical_inset"
+ android:insetBottom="@dimen/qs_dialog_button_vertical_inset">
+ <ripple android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="@android:color/white"/>
+ <corners android:radius="?android:attr/buttonCornerRadius"/>
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="?android:attr/buttonCornerRadius"/>
+ <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+ <padding android:left="@dimen/qs_dialog_button_horizontal_padding"
+ android:top="@dimen/qs_dialog_button_vertical_padding"
+ android:right="@dimen/qs_dialog_button_horizontal_padding"
+ android:bottom="@dimen/qs_dialog_button_vertical_padding"/>
+ </shape>
+ </item>
+ </ripple>
+</inset>
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
new file mode 100644
index 000000000000..467c20f3ffcd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
@@ -0,0 +1,42 @@
+<?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.
+ -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:insetTop="@dimen/qs_dialog_button_vertical_inset"
+ android:insetBottom="@dimen/qs_dialog_button_vertical_inset">
+ <ripple android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="@android:color/white"/>
+ <corners android:radius="?android:attr/buttonCornerRadius"/>
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="?android:attr/buttonCornerRadius"/>
+ <solid android:color="@android:color/transparent"/>
+ <stroke android:color="?androidprv:attr/colorAccentPrimary"
+ android:width="1dp"
+ />
+ <padding android:left="@dimen/qs_dialog_button_horizontal_padding"
+ android:top="@dimen/qs_dialog_button_vertical_padding"
+ android:right="@dimen/qs_dialog_button_horizontal_padding"
+ android:bottom="@dimen/qs_dialog_button_vertical_padding"/>
+ </shape>
+ </item>
+ </ripple>
+</inset>
diff --git a/packages/SystemUI/res/drawable/rounded_corner_bottom_secondary.xml b/packages/SystemUI/res/drawable/rounded_corner_bottom_secondary.xml
new file mode 100644
index 000000000000..5cc8d6ae470e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/rounded_corner_bottom_secondary.xml
@@ -0,0 +1,16 @@
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- Overlay this resource to change rounded_corners_bottom -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/rounded_secondary"/>
diff --git a/packages/SystemUI/res/drawable/rounded_corner_top_secondary.xml b/packages/SystemUI/res/drawable/rounded_corner_top_secondary.xml
new file mode 100644
index 000000000000..724e3ef40d43
--- /dev/null
+++ b/packages/SystemUI/res/drawable/rounded_corner_top_secondary.xml
@@ -0,0 +1,16 @@
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- Overlay this resource to change rounded_corners_top -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/rounded_secondary"/>
diff --git a/packages/SystemUI/res/drawable/rounded_secondary.xml b/packages/SystemUI/res/drawable/rounded_secondary.xml
new file mode 100644
index 000000000000..eb72fa16cb6d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/rounded_secondary.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="8dp"
+ android:height="8dp"
+ android:viewportWidth="8"
+ android:viewportHeight="8">
+
+ <path
+ android:fillColor="#000000"
+ android:pathData="M8,0H0v8C0,3.6,3.6,0,8,0z" />
+
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
index 3761a40ddbaa..c415ecd4f0a8 100644
--- a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
@@ -21,7 +21,4 @@
<path
android:fillColor="@android:color/white"
android:pathData="M20,6h-4V4c0-1.1-0.9-2-2-2h-4C8.9,2,8,2.9,8,4v2H4C2.9,6,2,6.9,2,8l0,11c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8 C22,6.9,21.1,6,20,6z M10,4h4v2h-4V4z M20,19H4V8h16V19z" />
- <path
- android:fillColor="@android:color/white"
- android:pathData="M 12 12 C 12.8284271247 12 13.5 12.6715728753 13.5 13.5 C 13.5 14.3284271247 12.8284271247 15 12 15 C 11.1715728753 15 10.5 14.3284271247 10.5 13.5 C 10.5 12.6715728753 11.1715728753 12 12 12 Z" />
</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_screenshot_static.xml b/packages/SystemUI/res/layout/global_screenshot_static.xml
index 6a9254cad8f4..21c5ab04eb9a 100644
--- a/packages/SystemUI/res/layout/global_screenshot_static.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_static.xml
@@ -130,13 +130,4 @@
app:layout_constraintStart_toStartOf="@id/global_screenshot_preview"
app:layout_constraintTop_toTopOf="@id/global_screenshot_preview"
android:elevation="@dimen/screenshot_preview_elevation"/>
- <View
- android:id="@+id/screenshot_transition_view"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:visibility="invisible"
- app:layout_constraintStart_toStartOf="@id/global_screenshot_preview"
- app:layout_constraintTop_toTopOf="@id/global_screenshot_preview"
- app:layout_constraintEnd_toEndOf="@id/global_screenshot_preview"
- app:layout_constraintBottom_toBottomOf="@id/global_screenshot_preview"/>
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index c88e95f07f12..86e2661f9534 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -19,9 +19,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/internet_connectivity_dialog"
- android:layout_width="@dimen/internet_dialog_list_max_width"
- android:layout_height="@dimen/internet_dialog_list_max_height"
- android:background="@drawable/internet_dialog_rounded_top_corner_background"
+ android:layout_width="@dimen/large_dialog_width"
+ android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
@@ -330,7 +329,8 @@
android:layout_height="wrap_content"
android:paddingBottom="4dp"
android:clickable="false"
- android:focusable="false">
+ android:focusable="false"
+ android:visibility="gone">
<LinearLayout
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml
index d996cee4b39e..cd6bc11bacf3 100644
--- a/packages/SystemUI/res/layout/media_output_dialog.xml
+++ b/packages/SystemUI/res/layout/media_output_dialog.xml
@@ -18,47 +18,50 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/media_output_dialog"
- android:layout_width="match_parent"
+ android:layout_width="@dimen/large_dialog_width"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="94dp"
+ android:layout_height="96dp"
android:gravity="start|center_vertical"
android:paddingStart="16dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/header_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingEnd="@dimen/media_output_dialog_header_icon_padding"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
android:importantForAccessibility="no"/>
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginEnd="16dp"
+ android:layout_height="match_parent"
+ android:paddingStart="16dp"
+ android:paddingTop="20dp"
+ android:paddingBottom="24dp"
+ android:paddingEnd="24dp"
android:orientation="vertical">
<TextView
android:id="@+id/header_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
+ android:gravity="center_vertical"
android:maxLines="1"
android:textColor="?android:attr/textColorPrimary"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
android:textSize="20sp"/>
-
<TextView
android:id="@+id/header_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:gravity="center_vertical"
android:ellipsize="end"
android:maxLines="1"
+ android:textColor="?android:attr/textColorTertiary"
android:fontFamily="roboto-regular"
- android:textSize="14sp"/>
-
+ android:textSize="16sp"/>
</LinearLayout>
</LinearLayout>
@@ -81,21 +84,21 @@
android:overScrollMode="never"/>
</LinearLayout>
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?android:attr/listDivider"/>
-
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:layout_marginStart="24dp"
+ android:layout_marginBottom="18dp"
+ android:layout_marginEnd="24dp"
android:orientation="horizontal">
<Button
android:id="@+id/stop"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ style="@style/MediaOutputRoundedOutlinedButton"
android:layout_width="wrap_content"
- android:layout_height="64dp"
+ android:layout_height="36dp"
+ android:minWidth="0dp"
android:text="@string/keyboard_key_media_stop"
android:visibility="gone"/>
@@ -106,10 +109,10 @@
<Button
android:id="@+id/done"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ style="@style/MediaOutputRoundedOutlinedButton"
android:layout_width="wrap_content"
- android:layout_height="64dp"
- android:layout_marginEnd="0dp"
+ android:layout_height="36dp"
+ android:minWidth="0dp"
android:text="@string/inline_done_button"/>
</LinearLayout>
</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_output_list_item.xml b/packages/SystemUI/res/layout/media_output_list_item.xml
index 16c03e124794..a5a7efa24f47 100644
--- a/packages/SystemUI/res/layout/media_output_list_item.xml
+++ b/packages/SystemUI/res/layout/media_output_list_item.xml
@@ -23,17 +23,20 @@
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
- android:layout_height="64dp">
+ android:layout_height="88dp"
+ android:paddingTop="24dp"
+ android:paddingBottom="16dp"
+ android:paddingStart="24dp"
+ android:paddingEnd="8dp">
<FrameLayout
- android:layout_width="36dp"
- android:layout_height="36dp"
- android:layout_gravity="center_vertical|start"
- android:layout_marginStart="16dp">
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical|start">
<ImageView
android:id="@+id/title_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
android:layout_gravity="center"/>
</FrameLayout>
@@ -42,49 +45,69 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
- android:layout_marginStart="68dp"
+ android:layout_marginStart="64dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:attr/textColorPrimary"
- android:textSize="14sp"/>
+ android:textSize="16sp"/>
<RelativeLayout
android:id="@+id/two_line_layout"
android:layout_width="wrap_content"
android:layout_height="48dp"
- android:layout_marginStart="52dp"
- android:layout_marginEnd="69dp"
- android:layout_marginTop="10dp">
+ android:layout_marginStart="48dp">
<TextView
android:id="@+id/two_line_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
- android:layout_marginEnd="15dp"
+ android:layout_marginEnd="48dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:attr/textColorPrimary"
- android:textSize="14sp"/>
+ android:textSize="16sp"/>
<TextView
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="15dp"
- android:layout_marginBottom="7dp"
+ android:layout_marginTop="4dp"
android:layout_alignParentBottom="true"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:attr/textColorSecondary"
- android:textSize="12sp"
+ android:textSize="14sp"
android:fontFamily="roboto-regular"
android:visibility="gone"/>
<SeekBar
android:id="@+id/volume_seekbar"
+ android:layout_marginTop="16dp"
+ android:layout_marginEnd="8dp"
style="@*android:style/Widget.DeviceDefault.SeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"/>
+ <ImageView
+ android:id="@+id/add_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="right"
+ android:layout_marginEnd="24dp"
+ android:layout_alignParentRight="true"
+ android:src="@drawable/ic_add"
+ android:tint="?android:attr/colorAccent"
+ />
+ <CheckBox
+ android:id="@+id/check_box"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="right"
+ android:layout_marginEnd="24dp"
+ android:layout_alignParentRight="true"
+ android:button="@drawable/ic_check_box"
+ android:visibility="gone"
+ />
</RelativeLayout>
<ProgressBar
@@ -92,47 +115,17 @@
style="@*android:style/Widget.Material.ProgressBar.Horizontal"
android:layout_width="258dp"
android:layout_height="18dp"
- android:layout_marginStart="68dp"
- android:layout_marginTop="40dp"
+ android:layout_marginStart="64dp"
+ android:layout_marginTop="28dp"
android:indeterminate="true"
android:indeterminateOnly="true"
android:visibility="gone"/>
-
- <View
- android:id="@+id/end_divider"
- android:layout_width="1dp"
- android:layout_height="36dp"
- android:layout_marginEnd="68dp"
- android:layout_gravity="right|center_vertical"
- android:background="?android:attr/listDivider"
- android:visibility="gone"/>
-
- <ImageView
- android:id="@+id/add_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_gravity="right|center_vertical"
- android:layout_marginEnd="24dp"
- android:src="@drawable/ic_add"
- android:tint="?android:attr/colorAccent"
- android:visibility="gone"/>
-
- <CheckBox
- android:id="@+id/check_box"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_gravity="right|center_vertical"
- android:layout_marginEnd="24dp"
- android:button="@drawable/ic_check_box"
- android:visibility="gone"/>
</FrameLayout>
<View
android:id="@+id/bottom_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
- android:layout_marginTop="12dp"
- android:layout_marginBottom="12dp"
android:layout_gravity="bottom"
android:background="?android:attr/listDivider"
android:visibility="gone"/>
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index 566cd25e86a5..b546a9cbe90e 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -134,6 +134,7 @@
android:background="@drawable/qs_media_light_source"
android:forceHasOverlappingRendering="false">
<LinearLayout
+ android:id="@+id/media_seamless_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="@dimen/qs_seamless_height"
diff --git a/packages/SystemUI/res/layout/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_call_chip.xml
index 5389d9bbcc97..c949ba0db171 100644
--- a/packages/SystemUI/res/layout/ongoing_call_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_call_chip.xml
@@ -21,6 +21,7 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="5dp"
>
<LinearLayout
android:id="@+id/ongoing_call_chip_background"
diff --git a/packages/SystemUI/res/layout/qs_customize_panel_content.xml b/packages/SystemUI/res/layout/qs_customize_panel_content.xml
index 8ca1b8e85634..3be99939ba0f 100644
--- a/packages/SystemUI/res/layout/qs_customize_panel_content.xml
+++ b/packages/SystemUI/res/layout/qs_customize_panel_content.xml
@@ -19,7 +19,7 @@
<View
android:id="@+id/customizer_transparent_view"
android:layout_width="match_parent"
- android:layout_height="@*android:dimen/quick_qs_offset_height"
+ android:layout_height="@dimen/qs_header_system_icons_area_height"
android:background="@android:color/transparent" />
<com.android.keyguard.AlphaOptimizedLinearLayout
diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
index 59e1a755d7d2..78655c03dd73 100644
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ b/packages/SystemUI/res/layout/qs_detail.xml
@@ -20,9 +20,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/qs_detail_background"
+ android:layout_marginTop="@dimen/qs_detail_margin_top"
android:clickable="true"
android:orientation="vertical"
- android:layout_marginTop="@*android:dimen/quick_qs_offset_height"
android:paddingBottom="8dp"
android:visibility="invisible"
android:elevation="4dp"
diff --git a/packages/SystemUI/res/layout/qs_detail_header.xml b/packages/SystemUI/res/layout/qs_detail_header.xml
index da80633f944b..d1ab054dd335 100644
--- a/packages/SystemUI/res/layout/qs_detail_header.xml
+++ b/packages/SystemUI/res/layout/qs_detail_header.xml
@@ -28,7 +28,7 @@
<com.android.systemui.ResizingSpace
android:layout_width="match_parent"
- android:layout_height="@dimen/qs_detail_margin_top" />
+ android:layout_height="@dimen/qs_detail_header_margin_top" />
<LinearLayout
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 317dbc09eae6..e70084b80308 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -68,93 +68,9 @@
</LinearLayout>
- <LinearLayout
- android:id="@+id/qs_footer_actions_container"
- android:layout_width="match_parent"
- android:layout_height="48dp"
- android:gravity="center_vertical">
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@android:id/edit"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
- android:layout_weight="1"
- android:background="@drawable/qs_footer_action_chip_background"
- android:clickable="true"
- android:clipToPadding="false"
- android:contentDescription="@string/accessibility_quick_settings_edit"
- android:focusable="true"
- android:padding="@dimen/qs_footer_icon_padding"
- android:src="@*android:drawable/ic_mode_edit"
- android:tint="?android:attr/textColorPrimary" />
-
- <com.android.systemui.statusbar.phone.MultiUserSwitch
- android:id="@+id/multi_user_switch"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
- android:layout_weight="1"
- android:background="@drawable/qs_footer_action_chip_background"
- android:focusable="true">
-
- <ImageView
- android:id="@+id/multi_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_expanded_size"
- android:layout_height="@dimen/multi_user_avatar_expanded_size"
- android:layout_gravity="center"
- android:scaleType="centerInside" />
- </com.android.systemui.statusbar.phone.MultiUserSwitch>
+ <include layout="@layout/footer_actions"
+ android:id="@+id/qs_footer_actions"/>
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/pm_lite"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
- android:layout_weight="1"
- android:background="@drawable/qs_footer_action_chip_background"
- android:clickable="true"
- android:clipToPadding="false"
- android:focusable="true"
- android:padding="@dimen/qs_footer_icon_padding"
- android:src="@*android:drawable/ic_lock_power_off"
- android:contentDescription="@string/accessibility_quick_settings_power_menu"
- android:tint="?android:attr/textColorPrimary" />
-
- <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
- android:id="@+id/settings_button_container"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="@drawable/qs_footer_action_chip_background"
- android:layout_weight="1"
- android:clipChildren="false"
- android:clipToPadding="false">
-
- <com.android.systemui.statusbar.phone.SettingsButton
- android:id="@+id/settings_button"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_gravity="center"
- android:contentDescription="@string/accessibility_quick_settings_settings"
- android:background="@drawable/qs_footer_action_chip_background_borderless"
- android:padding="@dimen/qs_footer_icon_padding"
- android:scaleType="centerInside"
- android:src="@drawable/ic_settings"
- android:tint="?android:attr/textColorPrimary" />
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/tuner_icon"
- android:layout_width="8dp"
- android:layout_height="8dp"
- android:layout_gravity="center_horizontal|bottom"
- android:layout_marginBottom="@dimen/qs_footer_icon_padding"
- android:src="@drawable/tuner"
- android:tint="?android:attr/textColorTertiary"
- android:visibility="invisible" />
-
- </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
-
- </LinearLayout>
</LinearLayout>
</com.android.systemui.qs.QSFooterView>
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 4c6418ace3fc..2ac03c262edb 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -13,13 +13,13 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.systemui.qs.QSContainerImpl
- xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.systemui.qs.QSContainerImpl xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/quick_settings_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
- android:clipChildren="false" >
+ android:clipChildren="false">
<com.android.systemui.qs.NonInterceptingScrollView
android:id="@+id/expanded_qs_scroll_view"
@@ -40,15 +40,33 @@
android:accessibilityTraversalBefore="@android:id/edit"
android:clipToPadding="false"
android:clipChildren="false">
+
<include layout="@layout/qs_footer_impl" />
</com.android.systemui.qs.QSPanel>
</com.android.systemui.qs.NonInterceptingScrollView>
<include layout="@layout/quick_status_bar_expanded_header" />
- <include android:id="@+id/qs_detail" layout="@layout/qs_detail" />
+ <include
+ android:id="@+id/qs_detail"
+ layout="@layout/qs_detail" />
- <include android:id="@+id/qs_customize" layout="@layout/qs_customize_panel"
+ <include
+ android:id="@+id/qs_customize"
+ layout="@layout/qs_customize_panel"
android:visibility="gone" />
+ <ImageView
+ android:id="@+id/qs_drag_handle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="24dp"
+ android:elevation="4dp"
+ android:importantForAccessibility="no"
+ android:scaleType="center"
+ android:src="@drawable/ic_qs_drag_handle"
+ android:tint="@color/qs_detail_button_white"
+ tools:ignore="UseAppTint" />
+
</com.android.systemui.qs.QSContainerImpl>
diff --git a/packages/SystemUI/res/layout/qs_user_dialog_content.xml b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
new file mode 100644
index 000000000000..543b7d77243b
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
@@ -0,0 +1,93 @@
+<?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.
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:sysui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="24dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ >
+ <TextView
+ android:id="@+id/title"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:textAlignment="center"
+ android:text="@string/qs_user_switch_dialog_title"
+ android:textAppearance="@style/TextAppearance.QSDialog.Title"
+ android:layout_marginBottom="32dp"
+ sysui:layout_constraintTop_toTopOf="parent"
+ sysui:layout_constraintStart_toStartOf="parent"
+ sysui:layout_constraintEnd_toEndOf="parent"
+ sysui:layout_constraintBottom_toTopOf="@id/grid"
+ />
+
+ <com.android.systemui.qs.PseudoGridView
+ android:id="@+id/grid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="28dp"
+ sysui:verticalSpacing="4dp"
+ sysui:horizontalSpacing="4dp"
+ sysui:fixedChildWidth="80dp"
+ sysui:layout_constraintTop_toBottomOf="@id/title"
+ sysui:layout_constraintStart_toStartOf="parent"
+ sysui:layout_constraintEnd_toEndOf="parent"
+ sysui:layout_constraintBottom_toTopOf="@id/barrier"
+ />
+
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/barrier"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ sysui:barrierDirection="top"
+ sysui:constraint_referenced_ids="settings,done"
+ />
+
+ <Button
+ android:id="@+id/settings"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:text="@string/quick_settings_more_user_settings"
+ sysui:layout_constraintTop_toBottomOf="@id/barrier"
+ sysui:layout_constraintBottom_toBottomOf="parent"
+ sysui:layout_constraintStart_toStartOf="parent"
+ sysui:layout_constraintEnd_toStartOf="@id/done"
+ sysui:layout_constraintHorizontal_chainStyle="spread_inside"
+ style="@style/Widget.QSDialog.Button.BorderButton"
+ />
+
+ <Button
+ android:id="@+id/done"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:text="@string/quick_settings_done"
+ sysui:layout_constraintTop_toBottomOf="@id/barrier"
+ sysui:layout_constraintBottom_toBottomOf="parent"
+ sysui:layout_constraintStart_toEndOf="@id/settings"
+ sysui:layout_constraintEnd_toEndOf="parent"
+ style="@style/Widget.QSDialog.Button"
+ />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
index 42a7c895ff84..542a1c9d22bd 100644
--- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml
+++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
@@ -21,7 +21,7 @@
android:layout_height="@*android:dimen/quick_qs_offset_height"
android:clipChildren="false"
android:clipToPadding="false"
- android:minHeight="48dp"
+ android:minHeight="@dimen/qs_header_row_min_height"
android:clickable="false"
android:focusable="true"
android:theme="@style/Theme.SystemUI.QuickSettings.Header">
@@ -39,7 +39,7 @@
android:id="@+id/clock"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:minHeight="48dp"
+ android:minHeight="@dimen/qs_header_row_min_height"
android:gravity="center_vertical|start"
android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
@@ -64,7 +64,7 @@
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
- android:minHeight="48dp"
+ android:minHeight="@dimen/qs_header_row_min_height"
android:minWidth="48dp"
android:layout_marginStart="8dp"
android:layout_gravity="end|center_vertical"
@@ -97,7 +97,7 @@
android:layout_height="match_parent"
android:paddingEnd="@dimen/signal_cluster_battery_padding" />
- <com.android.systemui.BatteryMeterView
+ <com.android.systemui.battery.BatteryMeterView
android:id="@+id/batteryRemainingIcon"
android:layout_height="match_parent"
android:layout_width="0dp"
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index f3b8b0bfaf36..10a2f4c625c6 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -32,7 +32,7 @@
android:paddingStart="0dp"
android:elevation="4dp" >
- <!-- Date and privacy. Only visible in QS -->
+ <!-- Date and privacy. Only visible in QS when not in split shade -->
<include layout="@layout/quick_status_bar_header_date_privacy"/>
<RelativeLayout
@@ -42,21 +42,32 @@
android:layout_gravity="top"
android:clipChildren="false"
android:clipToPadding="false">
- <!-- Time, icons and Carrier (only in QS) -->
- <include layout="@layout/quick_qs_status_icons"/>
+ <!-- Time, icons and Carrier (only in QS when not in split shade) -->
+ <include layout="@layout/quick_qs_status_icons"/>
- <com.android.systemui.qs.QuickQSPanel
- android:id="@+id/quick_qs_panel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/quick_qs_status_icons"
- android:layout_marginTop="@dimen/qqs_layout_margin_top"
- android:accessibilityTraversalAfter="@id/quick_qs_status_icons"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:focusable="true"
- android:paddingBottom="24dp"
- android:importantForAccessibility="yes" />
+ <com.android.systemui.qs.QuickQSPanel
+ android:id="@+id/quick_qs_panel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/quick_qs_status_icons"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:accessibilityTraversalAfter="@id/quick_qs_status_icons"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:focusable="true"
+ android:paddingBottom="24dp"
+ android:importantForAccessibility="yes">
+
+ <include
+ layout="@layout/footer_actions"
+ android:id="@+id/qqs_footer_actions"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:layout_marginStart="@dimen/qs_footer_margin"
+ android:layout_marginEnd="@dimen/qs_footer_margin"
+ />
+ </com.android.systemui.qs.QuickQSPanel>
</RelativeLayout>
</com.android.systemui.qs.QuickStatusBarHeader>
diff --git a/packages/SystemUI/res/layout/rotate_suggestion.xml b/packages/SystemUI/res/layout/rotate_suggestion.xml
index 194d2e063e97..2fb775cbc9be 100644
--- a/packages/SystemUI/res/layout/rotate_suggestion.xml
+++ b/packages/SystemUI/res/layout/rotate_suggestion.xml
@@ -14,16 +14,17 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-
-<com.android.systemui.navigationbar.buttons.KeyButtonView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/rotate_suggestion"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_weight="0"
- android:scaleType="center"
- android:visibility="invisible"
- android:contentDescription="@string/accessibility_rotate_button"
- android:paddingStart="@dimen/navigation_key_padding"
- android:paddingEnd="@dimen/navigation_key_padding"
-/> \ No newline at end of file
+ >
+ <com.android.systemui.shared.rotation.FloatingRotationButtonView
+ android:id="@+id/rotate_suggestion"
+ android:layout_width="@dimen/floating_rotation_button_diameter"
+ android:layout_height="@dimen/floating_rotation_button_diameter"
+ android:paddingStart="@dimen/navigation_key_padding"
+ android:paddingEnd="@dimen/navigation_key_padding"
+ android:layout_gravity="bottom|left"
+ android:scaleType="center"
+ android:visibility="invisible" />
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/rounded_corners_bottom.xml b/packages/SystemUI/res/layout/rounded_corners_bottom.xml
index 720e47b1908c..f91ab6f4980a 100644
--- a/packages/SystemUI/res/layout/rounded_corners_bottom.xml
+++ b/packages/SystemUI/res/layout/rounded_corners_bottom.xml
@@ -31,8 +31,7 @@
android:id="@+id/privacy_dot_left_container"
android:layout_height="@dimen/status_bar_height"
android:layout_width="wrap_content"
- android:layout_marginTop="@dimen/status_bar_padding_top"
- android:layout_marginLeft="0dp"
+ android:paddingTop="@dimen/status_bar_padding_top"
android:layout_gravity="left|bottom"
android:visibility="invisible" >
<ImageView
@@ -51,12 +50,12 @@
android:tint="#ff000000"
android:layout_gravity="right|bottom"
android:src="@drawable/rounded_corner_bottom"/>
+
<FrameLayout
android:id="@+id/privacy_dot_right_container"
android:layout_height="@dimen/status_bar_height"
android:layout_width="wrap_content"
- android:layout_marginTop="@dimen/status_bar_padding_top"
- android:layout_marginRight="0dp"
+ android:paddingTop="@dimen/status_bar_padding_top"
android:layout_gravity="right|bottom"
android:visibility="invisible" >
<ImageView
diff --git a/packages/SystemUI/res/layout/rounded_corners_top.xml b/packages/SystemUI/res/layout/rounded_corners_top.xml
index 6abe406e0ea6..819a9a4e9b02 100644
--- a/packages/SystemUI/res/layout/rounded_corners_top.xml
+++ b/packages/SystemUI/res/layout/rounded_corners_top.xml
@@ -29,10 +29,9 @@
<FrameLayout
android:id="@+id/privacy_dot_left_container"
- android:layout_height="@*android:dimen/status_bar_height_portrait"
+ android:layout_height="@dimen/status_bar_height"
android:layout_width="wrap_content"
- android:layout_marginTop="@dimen/status_bar_padding_top"
- android:layout_marginLeft="0dp"
+ android:paddingTop="@dimen/status_bar_padding_top"
android:layout_gravity="left|top"
android:visibility="invisible" >
<ImageView
@@ -54,10 +53,9 @@
<FrameLayout
android:id="@+id/privacy_dot_right_container"
- android:layout_height="@*android:dimen/status_bar_height_portrait"
+ android:layout_height="@dimen/status_bar_height"
android:layout_width="wrap_content"
- android:layout_marginTop="@dimen/status_bar_padding_top"
- android:layout_marginRight="0dp"
+ android:paddingTop="@dimen/status_bar_padding_top"
android:layout_gravity="right|top"
android:visibility="invisible" >
<ImageView
@@ -67,8 +65,6 @@
android:layout_gravity="center_vertical|left"
android:src="@drawable/system_animation_ongoing_dot"
android:visibility="visible" />
-
</FrameLayout>
-
</com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/res/layout/sidefps_view.xml b/packages/SystemUI/res/layout/sidefps_view.xml
index 397763531eb9..921f78830981 100644
--- a/packages/SystemUI/res/layout/sidefps_view.xml
+++ b/packages/SystemUI/res/layout/sidefps_view.xml
@@ -14,11 +14,13 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.biometrics.SidefpsView
+<com.airbnb.lottie.LottieAnimationView
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto"
- android:id="@+id/sidefps_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:contentDescription="@string/accessibility_fingerprint_label">
-</com.android.systemui.biometrics.SidefpsView>
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/sidefps_animation"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:lottie_autoPlay="true"
+ app:lottie_loop="true"
+ app:lottie_rawRes="@raw/sfps_pulse"
+ android:contentDescription="@string/accessibility_fingerprint_label"/>
diff --git a/packages/SystemUI/res/layout/split_shade_header.xml b/packages/SystemUI/res/layout/split_shade_header.xml
new file mode 100644
index 000000000000..f2c5b7bd491c
--- /dev/null
+++ b/packages/SystemUI/res/layout/split_shade_header.xml
@@ -0,0 +1,89 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/split_shade_status_bar"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/split_shade_header_height"
+ android:minHeight="@dimen/split_shade_header_min_height"
+ android:clickable="false"
+ android:focusable="true"
+ android:paddingLeft="@dimen/qs_panel_padding"
+ android:paddingRight="@dimen/qs_panel_padding"
+ android:visibility="gone"
+ android:theme="@style/Theme.SystemUI.QuickSettings.Header">
+
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="48dp"
+ android:minHeight="@dimen/split_shade_header_min_height"
+ android:gravity="start|center_vertical"
+ android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
+ android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.QS.Status" />
+
+ <com.android.systemui.statusbar.policy.DateView
+ android:id="@+id/date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|center_vertical"
+ android:gravity="center_vertical"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.QS.Status"
+ systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
+
+ <FrameLayout
+ android:id="@+id/rightLayout"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="end">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="end|center_vertical">
+
+ <include
+ android:id="@+id/carrier_group"
+ layout="@layout/qs_carrier_group"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="end|center_vertical"
+ android:layout_marginStart="8dp"
+ android:focusable="false"
+ android:minHeight="@dimen/split_shade_header_min_height"
+ android:minWidth="48dp" />
+
+ <com.android.systemui.statusbar.phone.StatusIconContainer
+ android:id="@+id/statusIcons"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingEnd="@dimen/signal_cluster_battery_padding" />
+
+ <com.android.systemui.battery.BatteryMeterView
+ android:id="@+id/batteryRemainingIcon"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ systemui:textAppearance="@style/TextAppearance.QS.Status" />
+ </LinearLayout>
+ </FrameLayout>
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 8b787323ccb6..cc1af873ce2b 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -25,12 +25,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent">
- <FrameLayout
- android:id="@+id/big_clock_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone" />
-
<ViewStub
android:id="@+id/keyguard_qs_user_switch_stub"
android:layout="@layout/keyguard_qs_user_switch"
@@ -71,18 +65,6 @@
android:layout_gravity="center"
android:scaleType="centerCrop"/>
- <!-- Fingerprint -->
- <!-- AOD dashed fingerprint icon with moving dashes -->
- <com.airbnb.lottie.LottieAnimationView
- android:id="@+id/lock_udfps_aod_fp"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:padding="@dimen/lock_icon_padding"
- android:layout_gravity="center"
- android:scaleType="centerCrop"
- systemui:lottie_autoPlay="false"
- systemui:lottie_loop="true"
- systemui:lottie_rawRes="@raw/udfps_aod_fp"/>
</com.android.keyguard.LockIconView>
<com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
@@ -93,11 +75,13 @@
android:clipToPadding="false"
android:clipChildren="false">
+ <include layout="@layout/split_shade_header"/>
+
<include
layout="@layout/keyguard_status_view"
android:visibility="gone"/>
- <include layout="@layout/dock_info_overlay" />
+ <include layout="@layout/dock_info_overlay"/>
<FrameLayout
android:id="@+id/qs_frame"
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 71e8fc9991ed..b28cb2f6f483 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -80,9 +80,11 @@
/>
<!-- Keyguard messages -->
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ <LinearLayout
+ android:id="@+id/keyguard_message_area_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
android:layout_marginTop="@dimen/status_bar_height"
android:layout_gravity="top|center_horizontal"
android:gravity="center_horizontal">
@@ -96,7 +98,11 @@
android:singleLine="true"
android:ellipsize="marquee"
android:focusable="true" />
- </FrameLayout>
+ <FrameLayout android:id="@+id/keyguard_bouncer_container"
+ android:layout_height="0dp"
+ android:layout_width="match_parent"
+ android:layout_weight="1" />
+ </LinearLayout>
<com.android.systemui.biometrics.AuthRippleView
android:id="@+id/auth_ripple"
diff --git a/packages/SystemUI/res/layout/system_icons.xml b/packages/SystemUI/res/layout/system_icons.xml
index 818d1d77177b..6d5c7d40a5f8 100644
--- a/packages/SystemUI/res/layout/system_icons.xml
+++ b/packages/SystemUI/res/layout/system_icons.xml
@@ -29,7 +29,7 @@
android:gravity="center_vertical"
android:orientation="horizontal"/>
- <com.android.systemui.BatteryMeterView android:id="@+id/battery"
+ <com.android.systemui.battery.BatteryMeterView android:id="@+id/battery"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:clipToPadding="false"
diff --git a/packages/SystemUI/res/layout/text_toast.xml b/packages/SystemUI/res/layout/text_toast.xml
index 49b182addd05..a3fe8efa48bf 100644
--- a/packages/SystemUI/res/layout/text_toast.xml
+++ b/packages/SystemUI/res/layout/text_toast.xml
@@ -45,6 +45,5 @@
android:maxLines="2"
android:paddingTop="12dp"
android:paddingBottom="12dp"
- android:lineHeight="20sp"
android:textAppearance="@*android:style/TextAppearance.Toast"/>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_change_panel.xml b/packages/SystemUI/res/layout/udfps_aod_lock_icon.xml
index bc9c203b299a..f5bfa49e4b32 100644
--- a/packages/SystemUI/res/layout/global_actions_change_panel.xml
+++ b/packages/SystemUI/res/layout/udfps_aod_lock_icon.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2021 The Android Open Source Project
~
@@ -14,18 +13,15 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<LinearLayout
+<com.airbnb.lottie.LottieAnimationView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
- <TextView
- android:id="@+id/global_actions_change_message"
- android:layout_width="wrap_content"
- android:visibility="gone"
- android:layout_height="wrap_content"
- android:text="@string/global_actions_change_description" />
- <ImageView
- android:id="@+id/global_actions_change_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-</LinearLayout> \ No newline at end of file
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/lock_udfps_aod_fp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/lock_icon_padding"
+ android:layout_gravity="center"
+ android:scaleType="centerCrop"
+ systemui:lottie_autoPlay="false"
+ systemui:lottie_loop="true"
+ systemui:lottie_rawRes="@raw/udfps_aod_fp"/> \ No newline at end of file
diff --git a/packages/SystemUI/res/raw/sfps_pulse.json b/packages/SystemUI/res/raw/sfps_pulse.json
new file mode 100644
index 000000000000..c4903a2857a1
--- /dev/null
+++ b/packages/SystemUI/res/raw/sfps_pulse.json
@@ -0,0 +1 @@
+{"v":"5.7.6","fr":60,"ip":0,"op":300,"w":42,"h":80,"nm":"Fingerprint Pulse Motion","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":0,"s":[28,40,0],"to":[0.751,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":30,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":60,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":90,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":120,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":150,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":180,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":210,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":240,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":270,"s":[32.503,40,0],"to":[0,0,0],"ti":[0.751,0,0]},{"t":300,"s":[28,40,0]}],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1.566,0],[-3.929,-5.503],[-2.751,-6.68],[3.929,0],[-2.751,6.68],[-3.929,5.503]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.218,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue600","cl":"blue600","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[41.878,40,0],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[55,55],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 2","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[28.253,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[20]},{"t":30,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":-30,"s":[55,55]},{"t":30,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-30,"op":30,"st":-30,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[20]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":0,"s":[55,55]},{"t":60,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":70,"s":[20]},{"t":90,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":30,"s":[55,55]},{"t":90,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":90,"st":30,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":100,"s":[20]},{"t":120,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":60,"s":[55,55]},{"t":120,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":120,"st":60,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":130,"s":[20]},{"t":150,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":90,"s":[55,55]},{"t":150,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":90,"op":150,"st":90,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[20]},{"t":180,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":120,"s":[55,55]},{"t":180,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":120,"op":180,"st":120,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":190,"s":[20]},{"t":210,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":150,"s":[55,55]},{"t":210,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":150,"op":210,"st":150,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":220,"s":[20]},{"t":240,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":180,"s":[55,55]},{"t":240,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":240,"st":180,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":250,"s":[20]},{"t":270,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":210,"s":[55,55]},{"t":270,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":210,"op":270,"st":210,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":280,"s":[20]},{"t":300,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":240,"s":[55,55]},{"t":300,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":300,"st":240,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":310,"s":[20]},{"t":330,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":270,"s":[55,55]},{"t":330,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":270,"op":330,"st":270,"bm":0}],"markers":[]} \ No newline at end of file
diff --git a/packages/SystemUI/res/raw/sfps_pulse_landscape.json b/packages/SystemUI/res/raw/sfps_pulse_landscape.json
new file mode 100644
index 000000000000..8c91762d7286
--- /dev/null
+++ b/packages/SystemUI/res/raw/sfps_pulse_landscape.json
@@ -0,0 +1 @@
+{"v":"5.7.6","fr":60,"ip":0,"op":300,"w":80,"h":42,"nm":"Fingerprint Pulse Motion Portrait","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":0,"s":[40,14,0],"to":[0,-0.751,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":30,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":60,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":90,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":120,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":150,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":180,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":210,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":240,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":270,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,-0.751,0]},{"t":300,"s":[40,14,0]}],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1.566,0],[-3.929,-5.503],[-2.751,-6.68],[3.929,0],[-2.751,6.68],[-3.929,5.503]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.218,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue600","cl":"blue600","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,0.122,0],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[55,55],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 2","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[28.253,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[20]},{"t":30,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":-30,"s":[55,55]},{"t":30,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-30,"op":30,"st":-30,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[20]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":0,"s":[55,55]},{"t":60,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":70,"s":[20]},{"t":90,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":30,"s":[55,55]},{"t":90,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":90,"st":30,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":100,"s":[20]},{"t":120,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":60,"s":[55,55]},{"t":120,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":120,"st":60,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":130,"s":[20]},{"t":150,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":90,"s":[55,55]},{"t":150,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":90,"op":150,"st":90,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[20]},{"t":180,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":120,"s":[55,55]},{"t":180,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":120,"op":180,"st":120,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":190,"s":[20]},{"t":210,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":150,"s":[55,55]},{"t":210,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":150,"op":210,"st":150,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":220,"s":[20]},{"t":240,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":180,"s":[55,55]},{"t":240,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":240,"st":180,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":250,"s":[20]},{"t":270,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":210,"s":[55,55]},{"t":270,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":210,"op":270,"st":210,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":280,"s":[20]},{"t":300,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":240,"s":[55,55]},{"t":300,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":300,"st":240,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":310,"s":[20]},{"t":330,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":270,"s":[55,55]},{"t":330,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":270,"op":330,"st":270,"bm":0}],"markers":[]} \ No newline at end of file
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 3e35addcb236..083e3e48f514 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Kragkennisgewingkontroles"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Aan"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Af"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Aan – gesiggegrond"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Met kragkennisgewingkontroles kan jy \'n belangrikheidvlak van 0 tot 5 vir \'n program se kennisgewings stel. \n\n"<b>"Vlak 5"</b>" \n- Wys aan die bokant van die kennisgewinglys \n- Laat volskermonderbreking toe \n- Wys altyd opspringkennisgewings \n\n"<b>"Vlak 4"</b>" \n- Verhoed volskermonderbreking \n- Wys altyd opspringkennisgewings \n\n"<b>"Vlak 3"</b>" \n- Verhoed volskermonderbreking \n- Verhoed opspringkennisgewings \n\n"<b>"Vlak 2"</b>" \n- Verhoed volskermonderbreking \n- Verhoed opspringkennisgewings \n- Moet nooit \'n klank maak of vibreer nie \n\n"<b>"Vlak 1"</b>" \n- Verhoed volskermonderbreking \n- Verhoed opspringkennisgewings \n- Moet nooit \'n klank maak of vibreer nie \n- Versteek van sluitskerm en statusbalk \n- Wys aan die onderkant van die kennisgewinglys \n\n"<b>"Vlak 0"</b>" \n- Blokkeer alle kennisgewings van die program af"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Kennisgewings"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Jy sal nie meer hierdie kennisgewings sien nie"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Hervat"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Instellings"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> deur <xliff:g id="ARTIST_NAME">%2$s</xliff:g> speel tans vanaf <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> van <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Speel"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Maak <xliff:g id="APP_LABEL">%1$s</xliff:g> oop"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Speel <xliff:g id="SONG_NAME">%1$s</xliff:g> deur <xliff:g id="ARTIST_NAME">%2$s</xliff:g> vanaf <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Groep"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 toestel gekies"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> toestelle gekies"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ontkoppel)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ontkoppel)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Kon nie koppel nie. Probeer weer."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Bind nuwe toestel saam"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Bounommer"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Sien alles"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Ontkoppel Ethernet om netwerke te wissel"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Om toestelervaring te verbeter, kan programme en dienste steeds enige tyd na wi‑fi-netwerke soek, selfs wanneer wi‑fi af is. Jy kan dit in Wi-fi-opsporing-instellings verander. "<annotation id="link">"Verander"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kies gebruiker"</string>
</resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 7ccaefed97f0..e68a6f579497 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"የኃይል ማሳወቂያ መቆጣጠሪያዎች"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"በርቷል"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"ጠፍቷል"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"በርቷል - መልክ ላይ የተመሠረተ"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"በኃይል ማሳወቂያ መቆጣጠሪያዎች አማካኝነት የአንድ መተግበሪያ ማሳወቂያዎች የአስፈላጊነት ደረጃ ከ0 እስከ 5 ድረስ ማዘጋጀት ይችላሉ። \n\n"<b>"ደረጃ 5"</b>" \n- በማሳወቂያ ዝርዝሩ አናት ላይ አሳይ \n- የሙሉ ማያ ገጽ ማቋረጥን ፍቀድ \n- ሁልጊዜ አጮልቀው ይመልከቱ \n\n"<b>"ደረጃ 4"</b>" \n- የሙሉ ማያ ገጽ ማቋረጥን ከልክል \n- ሁልጊዜ አጮልቀው ይመልከቱ \n\n"<b>"ደረጃ 3"</b>" \n- የሙሉ ማያ ገጽ ማቋረጥን ከልክል \n- በፍጹም አጮልቀው አይምልከቱ \n\n"<b>"ደረጃ 2"</b>" \n- የሙሉ ማያ ገጽ ማቋረጥን ይከልክሉ \n- በፍጹም አጮልቀው አይመልከቱ \n- ድምፅ እና ንዝረትን በፍጹም አይኑር \n\n"<b>"ደረጃ 1"</b>" \n- የሙሉ ማያ ገጽ ማቋረጥን ይከልክሉ \n- በፍጹም አጮልቀው አይመልከቱ \n- ድምፅ ወይም ንዝረትን በፍጹም አያደርጉ \n- ከመቆለፊያ ገጽ እና የሁኔታ አሞሌ ይደብቁ \n- በማሳወቂያ ዝርዝር ግርጌ ላይ አሳይ \n\n"<b>"ደረጃ 0"</b>" \n- ሁሉንም የመተግበሪያው ማሳወቂያዎች ያግዱ"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"ማሳወቂያዎች"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"እነዚህን ማሳወቂያዎችን ከእንግዲህ አይመለከቷቸውም"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"ከቆመበት ቀጥል"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ቅንብሮች"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> በ<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ከ<xliff:g id="APP_LABEL">%3$s</xliff:g> እየተጫወተ ነው"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> ከ<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"አጫውት"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ክፈት"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> በ<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ከ<xliff:g id="APP_LABEL">%3$s</xliff:g> ያጫውቱ"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"ቡድን"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 መሣሪያ ተመርጧል"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> መሣሪያዎች ተመርጠዋል"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ግንኙነት ተቋርጧል)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ተቋርጧል)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ማገናኘት አልተቻለም። እንደገና ይሞክሩ።"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"አዲስ መሣሪያ ያጣምሩ"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"የግንብ ቁጥር"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"ሁሉንም ይመልከቱ"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"አውታረ መረቦችን ለመቀየር፣ የኢተርኔት ግንኙነት ያቋርጡ"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"የመሣሪያ ተሞክሮን ለማሻሻል፣ መተግበሪያዎች እና አገልግሎቶች አሁንም በማንኛውም ጊዜ የWi-Fi አውታረ መረቦችን መቃኘት ይችላሉ፣ Wi-Fi ጠፍቶ ቢሆንም እንኳ። ይህንን በ Wi‑Fi ቅኝት ቅንብሮች ውስጥ መቀየር ይችላሉ። "<annotation id="link">"ቀይር"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ተጠቃሚን ይምረጡ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 272fa2b0b04a..60e1f06ba470 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -722,7 +722,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"عناصر التحكم في إشعارات التشغيل"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"تشغيل"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"إيقاف"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"تفعيل - استنادًا للوجه"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"باستخدام عناصر التحكم في إشعار التشغيل، يمكنك ضبط مستوى الأهمية من 0 إلى 5 لإشعارات التطبيق. \n\n"<b>"المستوى 5"</b>" \n- العرض أعلى قائمة الإشعارات \n- يسمح بمقاطعة ملء الشاشة \n- الظهور الخاطف دائمًا \n\n"<b>"المستوى 4"</b>" \n- منع مقاطعة ملء الشاشة \n- الظهور الخاطف دائمًا \n\n"<b>"المستوى 3"</b>" \n- منع مقاطعة ملء الشاشة \n- عدم الظهور الخاطف أبدًا \n\n"<b>"المستوى 2"</b>" \n- منع مقاطعة ملء الشاشة \n- عدم الظهور الخاطف أبدًا \n- عدم إصدار أصوات واهتزاز \n\n"<b>"المستوى 1"</b>" \n- منع مقاطعة ملء الشاشة \n- عدم الظهور الخاطف أبدًا \n- عدم إصدار أصوات أو اهتزاز أبدًا \n- الإخفاء من شاشة القفل وشريط الحالة \n- العرض أسفل قائمة الإشعارات \n\n"<b>"المستوى 0"</b>" \n- حظر جميع الإشعارات من التطبيق"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"الإشعارات"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"لن تتلقى هذه الإشعارات بعد الآن."</string>
@@ -1115,6 +1114,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"استئناف التشغيل"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"الإعدادات"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"يتم تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> للفنان <xliff:g id="ARTIST_NAME">%2$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> من إجمالي <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"تشغيل"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"فتح <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> للفنان <xliff:g id="ARTIST_NAME">%2$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1135,7 +1135,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"مجموعة"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"تم اختيار جهاز واحد."</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"تم اختيار <xliff:g id="COUNT">%1$d</xliff:g> جهاز."</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (غير متّصل)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(غير متّصل)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"تعذّر الاتصال. يُرجى إعادة المحاولة."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"إقران جهاز جديد"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"رقم الإصدار"</string>
@@ -1204,4 +1204,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"عرض الكل"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"للتبديل بين الشبكات، يجب فصل إيثرنت."</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"‏لتحسين تجربتك على الجهاز، يظل بإمكان التطبيقات والخدمات البحث عن شبكات Wi‑Fi في أي وقت، حتى عند إيقاف شبكة Wi‑Fi. وبإمكانك تغيير هذا الخيار في إعدادات البحث عن شبكات Wi-Fi. "<annotation id="link">"تغيير"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"اختيار المستخدم"</string>
</resources>
diff --git a/packages/SystemUI/res/values-as-land/strings.xml b/packages/SystemUI/res/values-as-land/strings.xml
index d5bf35a44b71..6fa43ccfe720 100644
--- a/packages/SystemUI/res/values-as-land/strings.xml
+++ b/packages/SystemUI/res/values-as-land/strings.xml
@@ -19,5 +19,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="toast_rotation_locked" msgid="4914046305911646988">"স্ক্ৰীণখন এতিয়া লেণ্ডস্কেপ স্ক্ৰীণৰ দিশত লক কৰা অৱস্থাত আছে"</string>
+ <string name="toast_rotation_locked" msgid="4914046305911646988">"স্ক্ৰীনখন এতিয়া লেণ্ডস্কে\'প স্ক্ৰীনৰ দিশত লক কৰা অৱস্থাত আছে"</string>
</resources>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 33a8661c2b56..fc53a1082f80 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"জাননী নিয়ন্ত্ৰণৰ অধিক কৰ্তৃত্ব"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"অন"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"অফ"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"অন আছে - মুখাৱয়ব ভিত্তিক"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"জাননী নিয়ন্ত্ৰণৰ অধিক কৰ্তৃত্বৰ সৈতে আপুনি এটা এপৰ জাননীৰ গুৰুত্বৰ স্তৰ ০ৰ পৰা ৫লৈ ছেট কৰিব পাৰে।\n\n"<b>"স্তৰ ৫"</b>" \n- জাননী তালিকাৰ একেবাৰে ওপৰত দেখুৱাওক \n- সম্পূৰ্ণ স্ক্ৰীনত থাকোঁতে ব্যাঘাত জন্মাবলৈ অনুমতি দিয়ক\n- সদায় ভুমুকি মাৰিবলৈ দিয়ক\n\n"<b>"স্তৰ ৪"</b>" \n- সম্পূৰ্ণ স্ক্ৰীনত থাকোঁতে ব্যাঘাত জন্মাবলৈ নিদিব\n- সদায় ভুমুকি মাৰিবলৈ দিয়ক\n\n"<b>"স্তৰ ৩"</b>" \n- সম্পূৰ্ণ স্ক্ৰীনত থাকোঁতে ব্যাঘাত জন্মাবলৈ নিদিব\n- কেতিয়াও ভুমুকি মাৰিবলৈ নিদিব\n\n"<b>"স্তৰ ২"</b>" \n- সম্পূর্ণ স্ক্ৰীনত থাকোঁতে ব্যাঘাত জন্মাবলৈ নিদিব \n- কেতিয়াও ভুমুকি মাৰিবলৈ নিদিব\n- কেতিয়াও শব্দ আৰু কম্পন কৰিবলৈ নিদিব\n\n"<b>" স্তৰ ১"</b>" \n- সম্পূৰ্ণ স্ক্ৰীনত থাকোঁতে ব্যাঘাত জন্মাবলৈ নিদিব\n- কেতিয়াও ভুমুকি মাৰিবলৈ নিদিব\n-কেতিয়াও শব্দ আৰু কম্পন কৰিবলৈ নিদিব \n- লক স্ক্ৰীন আৰু স্থিতি দণ্ডৰ পৰা লুকুৱাই ৰাখক \n- জাননী তালিকাৰ একেবাৰে তলত দেখুৱাওক\n\n"<b>"স্তৰ ০"</b>" \n- এই এপৰ আটাইবোৰ জাননী অৱৰোধ কৰক"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"জাননীসমূহ"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"আপোনাক এই জাননীসমূহ আৰু দেখুওৱা নহ’ব"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"পুনৰ আৰম্ভ কৰক"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ছেটিং"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ত <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ৰ <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ হৈ আছে"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>ৰ <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"প্লে’ কৰক"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> খোলক"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ত <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ৰ <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ কৰক"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"গোট"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"১ টা ডিভাইচ বাছনি কৰা হৈছে"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> টা ডিভাইচ বাছনি কৰা হৈছে"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (সংযোগ বিচ্ছিন্ন হৈছে)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(সংযোগ বিচ্ছিন্ন কৰা হৈছে)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"সংযোগ কৰিব পৰা নগ’ল। পুনৰ চেষ্টা কৰক।"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"নতুন ডিভাইচ পেয়াৰ কৰক"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"বিল্ডৰ নম্বৰ"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"আটাইবোৰ চাওক"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"নেটৱৰ্ক সলনি কৰিবলৈ ইথাৰনেটৰ পৰা সংযোগ বিচ্ছিন্ন কৰক"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"ডিভাইচ ব্যৱহাৰৰ অভিজ্ঞতা উন্নত কৰিবলৈ ৱাই-ফাই অফ থকা অৱস্থাতো এপ্ আৰু সেৱাসমূহে ৱাই-ফাই নেটৱৰ্কবোৰ স্কেন কৰিব পাৰে। আপুনি ৱাই-ফাই স্কেনিঙৰ ছেটিঙত এইটো সলনি কৰিব পাৰে। "<annotation id="link">"সলনি কৰক"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ব্যৱহাৰকাৰী বাছনি কৰক"</string>
</resources>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 89dca12e9b8d..1f1997fdaf6b 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Enerji bildiriş nəzarəti"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Aktiv"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Deaktiv"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Aktiv - Üz əsaslı"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Enerji bildiriş nəzarəti ilə, tətbiq bildirişləri üçün əhəmiyyət səviyyəsini 0-dan 5-ə kimi ayarlaya bilərsiniz. \n\n"<b>"Səviyyə 5"</b>" \n- Bildiriş siyahısının yuxarı hissəsində göstərin \n- Tam ekran kəsintisinə icazə verin \n- Hər zaman izləyin \n\n"<b>"Səviyyə 4"</b>" \n- Tam ekran kəsintisinin qarşısını alın \n- Hər zaman izləyin \n\n"<b>"Level 3"</b>" \n- Tam ekran kəsintisinin qarşısını alın \n- Heç vaxt izləməyin \n\n"<b>"Level 2"</b>" \n- Tam ekran kəsintisinin qarşısını alın \n- Heç vaxt izləməyin \n- Heç vaxt səsliyə və ya vibrasiyaya qoymayın \n\n"<b>"Səviyyə 1"</b>" \n- Prevent full screen interruption \n- Heç vaxt izləməyin \n- Heç vaxt səsliyə və ya vibrasiyaya qoymayın \n- Ekran kilidi və ya status panelindən gizlədin \n- Bildiriş siyahısının yuxarı hissəsində göstərin \n\n"<b>"Səviyyə 0"</b>" \n- Bütün bildirişləri tətbiqdən blok edin"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Bildirişlər"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Artıq bu bildirişləri görməyəcəkəsiniz"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Davam edin"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Ayarlar"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> tərəfindən <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%3$s</xliff:g> tətbiqindən oxudulur"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Oxudun"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> tətbiqini açın"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> tərəfindən <xliff:g id="SONG_NAME">%1$s</xliff:g> mahnısını <xliff:g id="APP_LABEL">%3$s</xliff:g> tətbiqindən oxudun"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Qrup"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 cihaz seçilib"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> cihaz seçilib"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (bağlantı kəsilib)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(bağlantı kəsildi)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Qoşulmaq alınmadı. Yenə cəhd edin."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Cihaz əlavə edin"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Montaj nömrəsi"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Hamısına baxın"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Şəbəkəni dəyişmək üçün etherneti ayırın"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Cihaz təcrübəsini yaxşılaşdırmaq üçün Wi-Fi deaktiv olduqda belə, tətbiqlər və xidmətlər Wi-Fi şəbəkəsini axtara biləcək. Bunu Wi-Fi axtarışı ayarlarında dəyişə bilərsiniz. "<annotation id="link">"Dəyişin"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"İstifadəçi seçin"</string>
</resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index b6a539090ec0..ee82b070f473 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -713,7 +713,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Napredne kontrole za obaveštenja"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Uključeno"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Isključeno"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Uključeno – na osnovu lica"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Pomoću naprednih kontrola za obaveštenja možete da podesite nivo važnosti od 0. do 5. za obaveštenja aplikacije. \n\n"<b>"5. nivo"</b>" \n– Prikazuju se u vrhu liste obaveštenja \n- Dozvoli prekid režima celog ekrana \n– Uvek zaviruj \n\n"<b>"4. nivo"</b>" \n– Spreči prekid režima celog ekrana \n– Uvek zaviruj \n\n"<b>"3. nivo"</b>" \n– Spreči prekid režima celog ekrana \n– Nikada ne zaviruj \n\n"<b>"2. nivo"</b>" \n– Spreči prekid režima celog ekrana \n– Nikada ne zaviruj \n– Nikada ne proizvodi zvuk ili vibraciju \n\n"<b>"1. nivo"</b>" \n– Spreči prekid režima celog ekrana \n– Nikada ne zaviruj \n– Nikada ne proizvodi zvuk ili vibraciju \n– Sakrij na zaključanom ekranu i statusnoj traci \n– Prikazuju se u dnu liste obaveštenja \n\n"<b>"0. nivo"</b>" \n– Blokiraj sva obaveštenja iz aplikacije"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Obaveštenja"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Više nećete videti ova obaveštenja"</string>
@@ -1097,6 +1096,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Podešavanja"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se pušta iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> od <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Pusti"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvorite <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1117,7 +1117,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Izabran je 1 uređaj"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Izabranih uređaja: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (veza je prekinuta)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(veza je prekinuta)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Povezivanje nije uspelo. Probajte ponovo."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Upari novi uređaj"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Broj verzije"</string>
@@ -1186,4 +1186,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Pogledajte sve"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Da biste promenili mrežu, prekinite eternet vezu"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Radi boljeg doživljaja uređaja, aplikacije i usluge i dalje mogu da traže WiFi mreže u bilo kom trenutku, čak i kada je WiFi isključen. To možete da promenite u podešavanjima WiFi skeniranja. "<annotation id="link">"Promenite"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Izaberite korisnika"</string>
</resources>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 8aa994f610d0..58dbe0e180db 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -716,7 +716,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Пашыранае кіраванне апавяшчэннямі"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Уключана"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Выключана"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Уключана – З улікам паставы галавы"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"З дапамогай пашыранага кіравання апавяшчэннямі вы можаце задаваць узровень важнасці апавяшчэнняў праграмы ад 0 да 5. \n\n"<b>"Узровень 5"</b>" \n- Паказваць уверсе спіса апавяшчэнняў \n- Дазваляць перапыняць рэжым поўнага экрана \n- Заўсёды дазваляць кароткі паказ \n\n"<b>"Узровень 4"</b>" \n- Забараняць перапыняць рэжым поўнага экрана \n- Заўсёды дазваляць кароткі паказ \n\n"<b>"Узровень 3"</b>" \n- Забараняць перапыняць рэжым поўнага экрана \n- Ніколі не дазваляць кароткі паказ \n\n"<b>"Узровень 2"</b>" \n- Забараняць перапыняць рэжым поўнага экрана \n- Ніколі не дазваляць кароткі паказ \n- Ніколі не прайграваць гук і не вібрыраваць \n\n"<b>"Узровень 1"</b>" \n- Забараняць перапыняць рэжым поўнага экрана \n- Ніколі не дазваляць кароткі паказ \n- Ніколі не прайграваць гук і не вібрыраваць \n- Хаваць з экрана блакіроўкі і панэлі стану \n- Паказваць унізе спіса апавяшчэнняў \n\n"<b>"Узровень 0"</b>" \n- Блакіраваць усе апавяшчэнні ад праграмы"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Апавяшчэнні"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Вы больш не будзеце бачыць гэтыя апавяшчэнні"</string>
@@ -1103,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Узнавіць"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Налады"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"У праграме \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\" прайграецца кампазіцыя \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\", выканаўца – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> з <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Прайграць"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Адкрыйце праграму \"<xliff:g id="APP_LABEL">%1$s</xliff:g>\""</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Прайграйце кампазіцыю \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (выканаўца – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) з дапамогай праграмы \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\""</string>
@@ -1123,7 +1123,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Выбрана 1 прылада"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Выбрана прылад: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (адключана)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(адключана)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Не ўдалося падключыцца. Паўтарыце спробу."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Спалучыць з новай прыладай"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Нумар зборкі"</string>
@@ -1192,4 +1192,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Паказаць усе"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Каб падключыцца да сетак, выключыце Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Каб палепшыць працу прылады, вы можаце дазволіць праграмам і сэрвісам шукаць сеткі Wi-Fi, нават калі Wi‑Fi выключаны. Змяніць гэты рэжым можна ў наладах пошуку сетак Wi-Fi. "<annotation id="link">"Змяніць"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Выбар карыстальніка"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index e9c222ca1cc1..8968dab785fe 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Контроли за известията"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Вкл."</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Изкл."</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Вкл. – въз основа на лицето"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"С помощта на контролите за известията можете да зададете ниво на важност от 0 до 5 за известията от дадено приложение. \n\n"<b>"Ниво 5"</b>" \n– Показване най-горе в списъка с известия. \n– Разрешаване на прекъсването на цял екран. \n– Известията винаги се показват мимолетно. \n\n"<b>"Ниво 4"</b>" \n– Предотвратяване на прекъсването на цял екран. \n– Известията винаги се показват мимолетно. \n\n"<b>"Ниво 3"</b>" \n– Предотвратяване на прекъсването на цял екран. \n– Известията никога не се показват мимолетно. \n\n"<b>"Ниво 2"</b>" \n– Предотвратяване на прекъсването на цял екран. \n– Известията никога не се показват мимолетно. \n– Без издаване на звуков сигнал и вибриране. \n\n"<b>"Ниво 1"</b>" \n– Предотвратяване на прекъсването на цял екран. \n– Известията никога не се показват мимолетно. \n– Без издаване на звуков сигнал и вибриране. \n– Скриване от заключения екран и лентата на състоянието. \n– Показване най-долу в списъка с известия. \n\n"<b>"Ниво 0"</b>" \n– Блокиране на всички известия от приложението."</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Известия"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Вече няма да виждате тези известия"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Възобновяване"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Настройки"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="ARTIST_NAME">%2$s</xliff:g> се възпроизвежда от <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> от <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Google Play"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Отваряне на <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пускане на <xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="ARTIST_NAME">%2$s</xliff:g> от <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 избрано устройство"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> избрани устройства"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (връзката е прекратена)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(връзката е прекратена)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Неуспешно свързване. Опитайте отново."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Сдвояване на ново устройство"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Номер на компилацията"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Вижте всички"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"За да превключите мрежите, прекъснете връзката с Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"С цел подобряване на практическата работа с устройството приложенията и услугите пак могат да сканират за Wi‑Fi мрежи по всяко време дори когато функцията за Wi‑Fi e изключена. Можете да промените съответното поведение от настройките за сканиране за Wi‑Fi. "<annotation id="link">"Промяна"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Избор на потребител"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 3f4746b2c8c5..3478b4d90431 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"পাওয়ার বিজ্ঞপ্তির নিয়ন্ত্রণগুলি"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"চালু আছে"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"বন্ধ আছে"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"চালু আছে - মুখের হিসেবে"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"পাওয়ার বিজ্ঞপ্তির নিয়ন্ত্রণগুলি ব্যহবার করে, আপনি কোনও অ্যাপ্লিকেশনের বিজ্ঞপ্তির জন্য ০ থেকে ৫ পর্যন্ত একটি গুরুত্বের লেভেলকে সেট করতে পারবেন৷ \n\n"<b>"লেভেল ৫"</b>" \n- বিজ্ঞপ্তি তালিকার শীর্ষে দেখায় \n- পূর্ণ স্ক্রিনের বাধাকে অনুমতি দেয় \n- সর্বদা স্ক্রিনে উপস্থিত হয় \n\n"<b>"লেভেল ৪"</b>" \n- পূর্ণ স্ক্রিনের বাধাকে আটকায় \n- সর্বদা স্ক্রিনে উপস্থিত হয় \n\n"<b>"লেভেল ৩"</b>" \n- পূর্ণ স্ক্রিনের বাধাকে আটকায় \n- কখনওই স্ক্রিনে উপস্থিত হয় না \n\n"<b>"লেভেল ২"</b>" \n- পূর্ণ স্ক্রিনের বাধাকে আটকায় \n- কখনওই স্ক্রিনে উপস্থিত হয় না \n- কখনওই শব্দ এবং কম্পন করে না \n\n"<b>"লেভেল ১"</b>" \n- পূর্ণ স্ক্রিনের বাধাকে আটকায় \n- কখনওই স্ক্রিনে উপস্থিত হয় না \n- কখনওই শব্দ এবং কম্পন করে না \n- লক স্ক্রিন এবং স্ট্যাটাস বার থেকে লুকায় \n- বিজ্ঞপ্তি তালিকার নীচের দিকে দেখায় \n\n"<b>"লেভেল ০"</b>" \n- অ্যাপ্লিকেশন থেকে সমস্ত বিজ্ঞপ্তিকে অবরূদ্ধ করে"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"বিজ্ঞপ্তি"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"এই বিজ্ঞপ্তিগুলি আপনাকে আর দেখানো হবে না"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"আবার চালু করুন"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"সেটিংস"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-এর <xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%3$s</xliff:g> অ্যাপে চলছে"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>টির মধ্যে <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>টি"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"চালান"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> অ্যাপ খুলুন"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-এর <xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%3$s</xliff:g> অ্যাপে চালান"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"গ্রুপ"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"১টি ডিভাইস বেছে নেওয়া হয়েছে"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g>টি ডিভাইস বেছে নেওয়া হয়েছে"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (কানেক্ট করা নেই)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ডিসকানেক্ট হয়ে গেছে)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"কানেক্ট করা যায়নি। আবার চেষ্টা করুন।"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"নতুন ডিভাইস পেয়ার করুন"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"বিল্ড নম্বর"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"সবকটি দেখুন"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"নেটওয়ার্ক বদলাতে ইথারনেট ডিসকানেক্ট করুন"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"ডিভাইস সংক্রান্ত অভিজ্ঞতা আরও ভাল করতে, অ্যাপ ও পরিষেবা যেকোনও সময় আপনার ওয়াই-ফাই নেটওয়ার্ক স্ক্যান করতে পারবে, এমনকি ডিভাইসের ওয়াই-ফাই বন্ধ করা থাকলেও। ওয়াই-ফাই স্ক্যানিং সেটিংস থেকে আপনি এটি পরিবর্তন করতে পারবেন। "<annotation id="link">"পরিবর্তন করুন"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ব্যবহারকারী বেছে নিন"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index fdb862a980fc..84e806f17b3d 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -713,7 +713,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Kontrole obavještenja o napajanju"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Uključeno"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Isključeno"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Uključeno – na osnovu lica"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Uz kontrolu obavještenja o napajanju, možete postaviti nivo značaja obavještenja iz aplikacije, i to od nivoa 0 do 5. \n\n"<b>"Nivo 5"</b>" \n- Prikaži na vrhu liste obavještenja \n- Dopusti prekid prikaza cijelog ekrana \n- Uvijek izviruj \n\n"<b>"Nvio 4"</b>" \n- Spriječi prekid prikaza cijelog ekrana \n- Uvijek izviruj \n\n"<b>"Nivo 3"</b>" \n- Spriječi prekid prikaza cijelog ekrana \n- Nikad ne izviruj \n\n"<b>"Nivo 2"</b>" \n- Spriječi prekid prikaza cijelog ekrana \n- Nikad ne izviruj \n- Nikada ne puštaj zvuk ili vibraciju \n\n"<b>"Nivo 1"</b>" \n- Spriječi prekid prikaza cijelog ekrana \n- Nikada ne izviruj \n- Nikada ne puštaj zvuk ili vibraciju \n- Sakrij sa ekrana za zaključavanje i statusne trake \n- Prikaži na dnu liste obavještenja \n\n"<b>"Nivo 0"</b>" \n- Blokiraj sva obavještenja iz aplikacije"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Obavještenja"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Nećete više vidjeti ova obavještenja"</string>
@@ -1097,6 +1096,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Postavke"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Pjesma <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se reproducira pomoću aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> od <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Pokrenite"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvorite aplikaciju <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproducirajte pjesmu <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> pomoću aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1117,7 +1117,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Odabran je 1 uređaj"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Broj odabranih uređaja: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (veza je prekinuta)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(veza je prekinuta)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Povezivanje nije uspjelo. Pokušajte ponovo."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Uparite novi uređaj"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Broj verzije"</string>
@@ -1186,4 +1186,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Prikaži sve"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Da promijenite mrežu, isključite ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Radi poboljšanja iskustva s uređajem aplikacije i usluge i dalje mogu bilo kada skenirati WiFi mreže, čak i kada je WiFi isključen. Ovo možete promijeniti u Postavkama skeniranja WiFi mreže. "<annotation id="link">"Promijeni"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Odaberite korisnika"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 72964cb134d1..8dedb28b23eb 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Controls millorats per a notificacions"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Activat"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Desactivat"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Activat: basat en cares"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Amb els controls de notificació millorats, pots establir un nivell d\'importància d\'entre 0 i 5 per a les notificacions d\'una aplicació. \n\n"<b>"Nivell 5"</b>" \n- Mostra les notificacions a la part superior de la llista \n- Permet la interrupció de la pantalla completa \n- Permet sempre la previsualització \n\n"<b>"Nivell 4"</b>" \n- No permet la interrupció de la pantalla completa \n- Permet sempre la previsualització \n\n"<b>"Nivell 3"</b>" \n- No permet la interrupció de la pantalla completa \n- No permet mai la previsualització \n\n"<b>"Nivell 2"</b>" \n- No permet la interrupció de la pantalla completa \n- No permet mai la previsualització \n- Les notificacions no poden emetre sons ni vibracions \n\n"<b>"Nivell 1"</b>" \n- No permet la interrupció de la pantalla completa \n- No permet mai la previsualització \n- No activa mai el so ni la vibració \n- Amaga les notificacions de la pantalla de bloqueig i de la barra d\'estat \n- Mostra les notificacions a la part inferior de la llista \n\n"<b>"Nivell 0"</b>" \n- Bloqueja totes les notificacions de l\'aplicació"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notificacions"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Ja no veuràs aquestes notificacions"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Reprèn"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Configuració"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) s\'està reproduint des de l\'aplicació <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reprodueix"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Obre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reprodueix <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) des de l\'aplicació <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositiu seleccionat"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"S\'han seleccionat <xliff:g id="COUNT">%1$d</xliff:g> dispositius"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desconnectat)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(desconnectat)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"No s\'ha pogut connectar. Torna-ho a provar."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Vincula un dispositiu nou"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilació"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Mostra-ho tot"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Per canviar de xarxa, desconnecta la connexió Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Per millorar l\'experiència del dispositiu, les aplicacions i els serveis poden cercar xarxes Wi‑Fi en qualsevol moment, fins i tot quan la Wi‑Fi estigui desactivada. Pots canviar aquesta opció a la configuració de cerca de xarxes Wi‑Fi. "<annotation id="link">"Canvia-la"</annotation>"."</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecciona un usuari"</string>
</resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 23fc8a52184e..fbef25aa7800 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -716,7 +716,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Rozšířené ovládací prvky oznámení"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Zapnuto"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Vypnuto"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Zapnuto – podle obličeje"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Rozšířené ovládací prvky oznámení umožňují nastavit úroveň důležitosti oznámení aplikace od 0 do 5. \n\n"<b>"Úroveň 5"</b>" \n– Zobrazit na začátku seznamu oznámení \n– Povolit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 4"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 3"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n\n"<b>"Úroveň 2"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat žádný zvukový signál ani nevibrovat \n\n"<b>"Úroveň 1"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat zvukový signál ani nevibrovat \n– Skrýt na obrazovce uzamčení a stavového řádku \n– Zobrazovat na konci seznamu oznámení \n\n"<b>";Úroveň 0"</b>" \n– Blokovat všechna oznámení z aplikace"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Oznámení"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Tato oznámení již nebudete dostávat"</string>
@@ -1103,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Pokračovat"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavení"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Skladba <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> hrajte z aplikace <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> z <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Přehrát"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otevřít aplikaci <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Přehrát skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikace <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1123,7 +1123,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Je vybráno 1 zařízení"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Vybraná zařízení: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (odpojeno)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(odpojeno)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Spojení se nezdařilo. Zkuste to znovu."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Spárovat nové zařízení"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Číslo sestavení"</string>
@@ -1192,4 +1192,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Zobrazit vše"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Pokud chcete přepnout sítě, odpojte ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Za účelem lepšího fungování zařízení mohou aplikace a služby vyhledávat sítě Wi-Fi, i když je připojení Wi-Fi vypnuté. Toto chování můžete změnit v nastavení vyhledávání Wi-Fi. "<annotation id="link">"Změnit"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Zvolte uživatele"</string>
</resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 85d976fad13a..72771df230a3 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Kontrolelementer til notifikation om strøm"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Til"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Fra"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Til – ansigtsbaseret"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Med kontrolelementer til notifikationer om strøm kan du konfigurere et vigtighedsniveau fra 0 til 5 for en apps notifikationer. \n\n"<b>"Niveau 5"</b>\n"- Vis øverst på listen over notifikationer \n- Tillad afbrydelse af fuld skærm \n- Se altid smugkig \n\n"<b>"Niveau 4"</b>\n"- Ingen afbrydelse af fuld skærm \n- Se altid smugkig \n\n"<b>"Niveau 3"</b>\n"- Ingen afbrydelse af fuld skærm \n- Se aldrig smugkig \n\n"<b>"Niveau 2"</b>\n"- Ingen afbrydelse af fuld skærm \n Se aldrig smugkig \n- Ingen lyd og vibration \n\n"<b>"Niveau 1"</b>\n"- Ingen afbrydelse af fuld skærm \n- Se aldrig smugkig \n- Ingen lyd eller vibration \n- Skjul fra låseskærm og statusbjælke \n- Vis nederst på listen over notifikationer \n\n"<b>"Niveau 0"</b>\n"- Bloker alle notifikationer fra appen."</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notifikationer"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Du får ikke længere vist disse notifikationer"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Genoptag"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Indstillinger"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> af <xliff:g id="ARTIST_NAME">%2$s</xliff:g> afspilles via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> af <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Afspil"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Åbn <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Afspil <xliff:g id="SONG_NAME">%1$s</xliff:g> af <xliff:g id="ARTIST_NAME">%2$s</xliff:g> via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Der er valgt 1 enhed"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Der er valgt <xliff:g id="COUNT">%1$d</xliff:g> enhed"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ingen forbindelse)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(afbrudt)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Der kunne ikke oprettes forbindelse. Prøv igen."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Par ny enhed"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Buildnummer"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Se alle"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Afbryd ethernetforbindelsen for at skifte netværk"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"For at forbedre brugeroplevelsen på enheden kan apps og tjenester stadig til enhver tid scanne efter Wi‑Fi-netværk, også selvom Wi‑Fi er deaktiveret. Du kan ændre dette i indstillingerne for Wi-Fi-scanning. "<annotation id="link">"Skift indstilling"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Vælg bruger"</string>
</resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index e929104cc3e6..933de430bdb3 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Erweiterte Benachrichtigungseinstellungen"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"An"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Aus"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"An – gesichtsbasiert"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Mit den erweiterten Benachrichtigungseinstellungen kannst du für App-Benachrichtigungen eine Wichtigkeitsstufe von 0 bis 5 festlegen. \n\n"<b>"Stufe 5"</b>" \n- Auf der Benachrichtigungsleiste ganz oben anzeigen \n- Vollbildunterbrechung zulassen \n- Immer kurz einblenden \n\n"<b>"Stufe 4"</b>" \n- Keine Vollbildunterbrechung \n- Immer kurz einblenden \n\n"<b>"Stufe 3"</b>" \n- Keine Vollbildunterbrechung \n- Nie kurz einblenden \n\n"<b>"Stufe 2"</b>" \n- Keine Vollbildunterbrechung \n- Nie kurz einblenden \n- Weder Ton noch Vibration \n\n"<b>"Stufe 1"</b>" \n- Keine Vollbildunterbrechung \n- Nie kurz einblenden \n- Weder Ton noch Vibration \n- Auf Sperrbildschirm und Statusleiste verbergen \n- Auf der Benachrichtigungsleiste ganz unten anzeigen \n\n"<b>"Stufe 0"</b>" \n- Alle Benachrichtigungen der App sperren"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Benachrichtigungen"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Du erhältst diese Benachrichtigungen nicht mehr"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Fortsetzen"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Einstellungen"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> von <xliff:g id="ARTIST_NAME">%2$s</xliff:g> wird gerade über <xliff:g id="APP_LABEL">%3$s</xliff:g> wiedergegeben"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> von <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Wiedergeben"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> öffnen"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> von <xliff:g id="ARTIST_NAME">%2$s</xliff:g> über <xliff:g id="APP_LABEL">%3$s</xliff:g> wiedergeben"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Ein Gerät ausgewählt"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> Geräte ausgewählt"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (nicht verbunden)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(nicht verbunden)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Verbindung nicht möglich. Versuch es noch einmal."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Neues Gerät koppeln"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Build-Nummer"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Alle ansehen"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Trenne das Ethernetkabel, um das Netzwerk zu wechseln"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Zur Verbesserung der Gerätenutzung können Apps und Dienste weiter nach WLANs suchen, auch wenn die WLAN-Funktion deaktiviert ist. Dies lässt sich in den Einstellungen für die WLAN-Suche ändern. "<annotation id="link">"Ändern"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Nutzer auswählen"</string>
</resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 5930f4c67952..bd197608cd07 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Στοιχεία ελέγχου ειδοποίησης ισχύος"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Ενεργό"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Ανενεργή"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Ενεργό - Βάσει προσώπου"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Με τα στοιχεία ελέγχου ειδοποίησης ισχύος, μπορείτε να ορίσετε ένα επίπεδο βαρύτητας από 0 έως 5 για τις ειδοποιήσεις μιας εφαρμογής. \n\n"<b>"Επίπεδο 5"</b>" \n- Εμφάνιση στην κορυφή της λίστας ειδοποιήσεων \n- Να επιτρέπεται η διακοπή πλήρους οθόνης \n- Να γίνεται πάντα σύντομη προβολή \n\n"<b>"Επίπεδο 4"</b>" \n- Αποτροπή διακοπής πλήρους οθόνης \n- Να γίνεται πάντα σύντομη προβολή \n\n"<b>"Επίπεδο 3"</b>" \n- Αποτροπή διακοπής πλήρους οθόνης \n- Να μην γίνεται ποτέ σύντομη προβολή \n\n"<b>"Επίπεδο 2"</b>" \n- Αποτροπή διακοπής πλήρους οθόνης \n- Να μην γίνεται ποτέ σύντομη προβολή \n- Να μην χρησιμοποιείται ποτέ ήχος και δόνηση \n\n"<b>"Επίπεδο 1"</b>" \n- Αποτροπή διακοπής πλήρους οθόνης \n- Να μην γίνεται ποτέ σύντομη προβολή \n- Να μην χρησιμοποιείται ποτέ ήχος και δόνηση \n- Απόκρυψη από την οθόνη κλειδώματος και τη γραμμή κατάστασης \n- Εμφάνιση στο κάτω μέρος της λίστας ειδοποιήσεων \n\n"<b>"Επίπεδο 0"</b>" \n- Αποκλεισμός όλων των ειδοποιήσεων από την εφαρμογή"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Ειδοποιήσεις"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Δεν θα βλέπετε πλέον αυτές τις ειδοποιήσεις"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Συνέχιση"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Ρυθμίσεις"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Γίνεται αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> από <xliff:g id="ARTIST_NAME">%2$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> από <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Άνοιγμα της εφαρμογής <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> από <xliff:g id="ARTIST_NAME">%2$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Ομάδα"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Επιλέχτηκε 1 συσκευή"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Επιλέχτηκαν <xliff:g id="COUNT">%1$d</xliff:g> συσκευές"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (αποσυνδέθηκε)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(αποσυνδέθηκε)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Δεν ήταν δυνατή η σύνδεση. Δοκιμάστε ξανά."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Σύζευξη νέας συσκευής"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Αριθμός έκδοσης"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Εμφάνιση όλων"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Για εναλλαγή δικτύων, αποσυνδέστε το ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Για βελτίωση της εμπειρίας στη συσκευή, οι εφαρμογές και οι υπηρεσίες μπορούν ακόμα να εκτελούν σάρωση για δίκτυα Wi‑Fi ανά πάσα στιγμή, ακόμα και όταν το Wi‑Fi είναι απενεργοποιημένο. Μπορείτε να αλλάξετε αυτήν τη ρύθμιση στις ρυθμίσεις της Σάρωσης Wi‑Fi. "<annotation id="link">"Αλλαγή"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Επιλογή χρήστη"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 96b50bd08e74..8f8f195a2a71 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Power notification controls"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"On"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Off"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"On – Face-based"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications. \n\n"<b>"Level 5"</b>" \n- Show at the top of the notification list \n- Allow full screen interruption \n- Always peek \n\n"<b>"Level 4"</b>" \n- Prevent full screen interruption \n- Always peek \n\n"<b>"Level 3"</b>" \n- Prevent full screen interruption \n- Never peek \n\n"<b>"Level 2"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound and vibration \n\n"<b>"Level 1"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound or vibrate \n- Hide from lock screen and status bar \n- Show at the bottom of the notification list \n\n"<b>"Level 0"</b>" \n- Block all notifications from the app"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notifications"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"You won\'t see these notifications anymore"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> devices selected"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (disconnected)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(disconnected)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Couldn\'t connect. Try again."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Pair new device"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"See all"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"To improve device experience, apps and services can still scan for Wi‑Fi networks at any time, even when Wi‑Fi is off. You can change this in Wi‑Fi scanning settings. "<annotation id="link">"Change"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 5361b9fd4082..5532dcb84da0 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Power notification controls"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"On"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Off"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"On – Face-based"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications. \n\n"<b>"Level 5"</b>" \n- Show at the top of the notification list \n- Allow full screen interruption \n- Always peek \n\n"<b>"Level 4"</b>" \n- Prevent full screen interruption \n- Always peek \n\n"<b>"Level 3"</b>" \n- Prevent full screen interruption \n- Never peek \n\n"<b>"Level 2"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound and vibration \n\n"<b>"Level 1"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound or vibrate \n- Hide from lock screen and status bar \n- Show at the bottom of the notification list \n\n"<b>"Level 0"</b>" \n- Block all notifications from the app"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notifications"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"You won\'t see these notifications anymore"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> devices selected"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (disconnected)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(disconnected)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Couldn\'t connect. Try again."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Pair new device"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"See all"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"To improve device experience, apps and services can still scan for Wi‑Fi networks at any time, even when Wi‑Fi is off. You can change this in Wi‑Fi scanning settings. "<annotation id="link">"Change"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 96b50bd08e74..8f8f195a2a71 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Power notification controls"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"On"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Off"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"On – Face-based"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications. \n\n"<b>"Level 5"</b>" \n- Show at the top of the notification list \n- Allow full screen interruption \n- Always peek \n\n"<b>"Level 4"</b>" \n- Prevent full screen interruption \n- Always peek \n\n"<b>"Level 3"</b>" \n- Prevent full screen interruption \n- Never peek \n\n"<b>"Level 2"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound and vibration \n\n"<b>"Level 1"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound or vibrate \n- Hide from lock screen and status bar \n- Show at the bottom of the notification list \n\n"<b>"Level 0"</b>" \n- Block all notifications from the app"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notifications"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"You won\'t see these notifications anymore"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> devices selected"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (disconnected)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(disconnected)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Couldn\'t connect. Try again."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Pair new device"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"See all"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"To improve device experience, apps and services can still scan for Wi‑Fi networks at any time, even when Wi‑Fi is off. You can change this in Wi‑Fi scanning settings. "<annotation id="link">"Change"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 96b50bd08e74..8f8f195a2a71 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Power notification controls"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"On"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Off"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"On – Face-based"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications. \n\n"<b>"Level 5"</b>" \n- Show at the top of the notification list \n- Allow full screen interruption \n- Always peek \n\n"<b>"Level 4"</b>" \n- Prevent full screen interruption \n- Always peek \n\n"<b>"Level 3"</b>" \n- Prevent full screen interruption \n- Never peek \n\n"<b>"Level 2"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound and vibration \n\n"<b>"Level 1"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound or vibrate \n- Hide from lock screen and status bar \n- Show at the bottom of the notification list \n\n"<b>"Level 0"</b>" \n- Block all notifications from the app"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notifications"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"You won\'t see these notifications anymore"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> devices selected"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (disconnected)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(disconnected)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Couldn\'t connect. Try again."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Pair new device"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"See all"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"To improve device experience, apps and services can still scan for Wi‑Fi networks at any time, even when Wi‑Fi is off. You can change this in Wi‑Fi scanning settings. "<annotation id="link">"Change"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 78d60f605934..1b538a7bf9ce 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‎‏‎‎‎‎‏‏‎‏‎‎‎‎‏‎‎‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‏‏‏‎‎‏‏‎Power notification controls‎‏‎‎‏‎"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎‏‏‏‏‎‏‎‏‎‏‎‎‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‏‏‏‎‎‎‏‎‏‎‏‎‏‏‎‎‎‏‏‎‏‏‏‏‎‏‏‎On‎‏‎‎‏‎"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‎‏‎‎‏‏‏‏‎‏‎‏‎‏‎‏‏‎‎‎‎‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‏‏‏‏‎‏‎Off‎‏‎‎‏‎"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‎‏‏‏‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎On - Face-based‎‏‎‎‏‎"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‏‏‏‎‏‎‎‎‎‎‏‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‎With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications. ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎"<b>"‎‏‎‎‏‏‏‎Level 5‎‏‎‎‏‏‎"</b>"‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Show at the top of the notification list ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Allow full screen interruption ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Always peek ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎"<b>"‎‏‎‎‏‏‏‎Level 4‎‏‎‎‏‏‎"</b>"‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Prevent full screen interruption ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Always peek ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎"<b>"‎‏‎‎‏‏‏‎Level 3‎‏‎‎‏‏‎"</b>"‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Prevent full screen interruption ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Never peek ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎"<b>"‎‏‎‎‏‏‏‎Level 2‎‏‎‎‏‏‎"</b>"‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Prevent full screen interruption ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Never peek ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Never make sound and vibration ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎"<b>"‎‏‎‎‏‏‏‎Level 1‎‏‎‎‏‏‎"</b>"‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Prevent full screen interruption ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Never peek ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Never make sound or vibrate ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Hide from lock screen and status bar ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Show at the bottom of the notification list ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎"<b>"‎‏‎‎‏‏‏‎Level 0‎‏‎‎‏‏‎"</b>"‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎- Block all notifications from the app‎‏‎‎‏‎"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‎‏‏‎‎‏‎‏‎‎‎‏‎‏‎‎‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‎Notifications‎‏‎‎‏‎"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‎‏‎‎‏‏‏‎‎‎‎‏‎‏‎‎‏‏‎‏‎‏‏‏‏‏‎‎‏‎‏‏‏‏‎‎‎‎‎‎‏‎‏‎‎‎‏‎You won\'t see these notifications anymore‎‏‎‎‏‎"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‎‏‎‏‎Resume‎‏‎‎‏‎"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‎‏‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‏‏‎‎‎‎Settings‎‏‎‎‏‎"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‎‏‏‎‎‏‏‎‎‏‎‏‎‎‎‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="SONG_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ by ‎‏‎‎‏‏‎<xliff:g id="ARTIST_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ is playing from ‎‏‎‎‏‏‎<xliff:g id="APP_LABEL">%3$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎‎‎‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ of ‎‏‎‎‏‏‎<xliff:g id="TOTAL_TIME">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‏‏‏‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‏‏‎‏‎‏‏‎‏‎‎‎‏‏‏‎‎‎‏‏‏‏‏‏‎Play‎‏‎‎‏‎"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‏‎‎‏‏‎‏‏‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‏‎‎‏‏‏‎‏‎‏‎‎‏‏‎‎‎Open ‎‏‎‎‏‏‎<xliff:g id="APP_LABEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‏‏‎‏‏‎‏‏‎‎‎‏‏‎‎‎‏‎‏‏‎‏‏‎‏‏‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎‏‏‎‎‎‏‏‎Play ‎‏‎‎‏‏‎<xliff:g id="SONG_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ by ‎‏‎‎‏‏‎<xliff:g id="ARTIST_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ from ‎‏‎‎‏‏‎<xliff:g id="APP_LABEL">%3$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‎‎Group‎‏‎‎‏‎"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‎‎‏‏‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‎‏‏‎‎‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎1 device selected‎‏‎‎‏‎"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‎‎‏‏‏‎‎‎‎‏‏‎‏‏‎‏‎‎‎‏‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="COUNT">%1$d</xliff:g>‎‏‎‎‏‏‏‎ devices selected‎‏‎‎‏‎"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‏‏‏‎‏‎‏‎‏‎‏‏‎‏‏‏‎‎‏‎‎‎‏‏‏‎‎‎‎‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‏‎‎‏‎‏‏‏‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ (disconnected)‎‏‎‎‏‎"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‏‏‎‏‏‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‎‎‏‎(disconnected)‎‏‎‎‏‎"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‏‎‎‎‎‏‎‎‎‏‎‏‏‎‏‏‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‏‎‎‏‎‎‎‏‏‎‏‎‏‎‏‎‏‏‎‎‎‎‏‎‎Couldn\'t connect. Try again.‎‏‎‎‏‎"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‏‎‏‎‏‏‎‎‏‏‎‎‎‏‎‏‎‎‏‎‎‏‏‏‎‏‏‎‏‎‎‏‏‎‎Pair new device‎‏‎‎‏‎"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‎‏‎‎‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‎‏‎‎‏‎‎Build number‎‏‎‎‏‎"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‏‎‎‎‎‎‎‎‎‎‎‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‎‏‎‎See all‎‏‎‎‏‎"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‎‎‎‏‎‎‎‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‎‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‏‎‎‏‏‎To switch networks, disconnect ethernet‎‏‎‎‏‎"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‏‏‎‏‎‏‏‏‎‎‎‏‎‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎‎To improve device experience, apps and services can still scan for Wi‑Fi networks at any time, even when Wi‑Fi is off. You can change this in Wi‑Fi scanning settings. ‎‏‎‎‏‏‎"<annotation id="link">"‎‏‎‎‏‏‏‎Change‎‏‎‎‏‏‎"</annotation>"‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‎‏‎‏‏‏‏‏‎‎‎‎‏‏‏‎‏‎‎‎‏‏‎‎‎‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‎‎Select user‎‏‎‎‏‎"</string>
</resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index b66f6e42ce84..08d0a0523b6d 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Controles de activación de notificaciones"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Activado"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Desactivado"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Activa - En función del rostro"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Con los controles de activación de notificaciones, puedes establecer un nivel de importancia para las notificaciones de una app. \n\n"<b>"Nivel 5"</b>" \n- Mostrar en la parte superior de la lista de notificaciones. \n- Permitir interrupción en la pantalla completa. \n- Mostrar siempre. \n\n"<b>"Nivel 4"</b>" \n- No permitir interrupción en la pantalla completa. \n- Mostrar siempre. \n\n"<b>"Nivel 3"</b>" \n- No permitir interrupción en la pantalla completa. \n- No mostrar. \n\n"<b>"Nivel 2"</b>" \n- No permitir interrupción en la pantalla completa. \n- No mostrar. \n- No sonar ni vibrar. \n\n"<b>"Nivel 1"</b>" \n- No permitir interrupción en la pantalla completa. \n- No mostrar. \n- No sonar ni vibrar. \n- Ocultar de la pantalla bloqueada y la barra de estado. \n- Mostrar al final de la lista de notificaciones. \n\n"<b>"Nivel 0"</b>" \n- Bloquear todas las notificaciones de la app."</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notificaciones"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Ya no verás estas notificaciones"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Reanudar"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Configuración"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Se está reproduciendo <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproducir"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Se seleccionó 1 dispositivo"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Se seleccionaron <xliff:g id="COUNT">%1$d</xliff:g> dispositivos"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desconectado)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(desconectado)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"No se pudo establecer la conexión. Vuelve a intentarlo."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Vincular dispositivo nuevo"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilación"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Ver todo"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para cambiar de red, desconéctate de Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Para mejorar la experiencia con el dispositivo, las apps y los servicios pueden seguir buscando redes Wi-Fi en cualquier momento, incluso cuando la conexión Wi-Fi esté desactivada. Puedes cambiar este parámetro en la configuración de búsqueda de Wi-Fi. "<annotation id="link">"Cambiar"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string>
</resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 30a6d09ea4d3..eda51baac58f 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Controles de energía de las notificaciones"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Activado"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Desactivado"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Activado: basado en caras"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Los controles de energía de las notificaciones permiten establecer un nivel de importancia de 0 a 5 para las notificaciones de las aplicaciones. \n\n"<b>"Nivel 5"</b>" \n- Mostrar en la parte superior de la lista de notificaciones \n- Permitir interrumpir en el modo de pantalla completa \n- Mostrar siempre \n\n"<b>"Nivel 4"</b>" \n- Evitar interrumpir en el modo de pantalla completa \n- Mostrar siempre \n\n"<b>"Nivel 3"</b>" \n- Evitar interrumpir en el modo de pantalla completa \n- No mostrar nunca \n\n"<b>"Nivel 2"</b>" \n- Evitar interrumpir en el modo de pantalla completa\n- No mostrar nunca \n- No emitir sonido ni vibrar nunca \n\n"<b>"Nivel 1"</b>" \n- Evitar interrumpir en el modo de pantalla completa \n- No mostrar nunca \n- No emitir sonido ni vibrar nunca \n- Ocultar de la pantalla de bloqueo y de la barra de estado \n- Mostrar en la parte inferior de la lista de notificaciones \n\n"<b>"Nivel 0"</b>" \n- Bloquear todas las notificaciones de la aplicación"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notificaciones"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"No volverás a ver estas notificaciones"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Reanudar"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Ajustes"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Se está reproduciendo <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproducir"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Poner <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo seleccionado"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> dispositivos seleccionados"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desconectado)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(desconectado)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"No se ha podido conectar. Inténtalo de nuevo."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Emparejar nuevo dispositivo"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilación"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Ver todo"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para cambiar de red, desconecta el cable Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Para mejorar la experiencia con el dispositivo, las aplicaciones y los servicios podrán buscar redes Wi-Fi en cualquier momento, aunque la conexión Wi-Fi esté desactivada. Puedes cambiarlo en los ajustes de búsqueda de redes Wi-Fi. "<annotation id="link">"Cambiar"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string>
</resources>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index f0e01d057417..e67462791bbd 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Toite märguannete juhtnupud"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Sees"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Väljas"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Sees – näopõhine"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Toite märguannete juhtnuppudega saate määrata rakenduse märguannete tähtsuse taseme vahemikus 0–5. \n\n"<b>"5. tase"</b>" \n- Kuva märguannete loendi ülaosas\n- Luba täisekraanil häirimine \n- Kuva alati ekraani servas \n\n"<b>"4. tase"</b>" \n- Keela täisekraanil häirimine \n- Kuva alati ekraani servas \n\n"<b>"3. tase"</b>" \n- Keela täisekraanil häirimine \n- Ära kunagi kuva ekraani servas \n\n"<b>"2. tase"</b>" \n- Keela täisekraanil häirimine \n- Ära kunagi kuva ekraani servas \n- Ära kunagi helise ega vibreeri \n\n"<b>"1. tase"</b>" \n- Keela täisekraanil häirimine \n- Ära kunagi kuva ekraani servas \n- Ära kunagi helise ega vibreeri \n- Peida lukustuskuval ja olekuribal \n- Kuva märguannete loendi allosas \n\n"<b>"Tase 0"</b>" \n- Blokeeri kõik rakenduse märguanded"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Märguanded"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Te ei näe enam neid märguandeid"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Jätka"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Seaded"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> esitajalt <xliff:g id="ARTIST_NAME">%2$s</xliff:g> esitatakse rakenduses <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Esitamine"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Rakenduse <xliff:g id="APP_LABEL">%1$s</xliff:g> avamine"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Esita lugu <xliff:g id="SONG_NAME">%1$s</xliff:g> esitajalt <xliff:g id="ARTIST_NAME">%2$s</xliff:g> rakenduses <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupp"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 seade on valitud"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> seadet on valitud"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (pole ühendatud)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ühendus on katkestatud)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Ühenduse loomine ebaõnnestus. Proovige uuesti."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Uue seadme sidumine"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Järgunumber"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Kuva kõik"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Võrkude vahetamiseks katkestage Etherneti-ühendus"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Seadme kasutuskogemuse parandamiseks võivad rakendused ja teenused siiski alati otsida WiFi-võrke isegi siis, kui WiFi on väljas. Seda saab muuta WiFi-skannimise seadetes. "<annotation id="link">"Muuda"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kasutaja valimine"</string>
</resources>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 27418388ae8b..5f3a2863e1b1 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Bateria-mailaren arabera jakinarazpenak kontrolatzeko aukerak"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Aktibatuta"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Desaktibatuta"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Aktibatuta: aurpegian oinarrituta"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Bateria-mailaren arabera jakinarazpenak kontrolatzeko aukerekin, 0 eta 5 bitarteko garrantzi-mailetan sailka ditzakezu aplikazioen jakinarazpenak. \n\n"<b>"5. maila"</b>" \n- Erakutsi jakinarazpenen zerrendaren goialdean. \n- Baimendu etetea pantaila osoko moduan zaudenean. \n- Agerrarazi beti jakinarazpenak. \n\n"<b>"4. maila"</b>" \n- Galarazi etetea pantaila osoko moduan zaudenean. \n- Agerrarazi beti jakinarazpenak. \n\n"<b>"3. maila"</b>" \n- Galarazi etetea pantaila osoko moduan zaudenean. \n- Ez agerrarazi jakinarazpenik inoiz. \n\n"<b>"2. maila"</b>" \n- Galarazi etetea pantaila osoko moduan zaudenean. \n- Ez agerrarazi jakinarazpenik inoiz. \n- Ez egin soinurik edo dardararik inoiz. \n\n"<b>"1. maila"</b>" \n- Galarazi etetea pantaila osoko moduan zaudenean. \n- Ez agerrarazi jakinarazpenik inoiz. \n- Ez egin soinurik edo dardararik inoiz. \n- Ezkutatu pantaila blokeatutik eta egoera-barratik. \n- Erakutsi jakinarazpenen zerrendaren behealdean. \n\n"<b>"0. maila"</b>" \n- Blokeatu aplikazioaren jakinarazpen guztiak."</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Jakinarazpenak"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Aurrerantzean ez duzu ikusiko horrelako jakinarazpenik"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Berrekin"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Ezarpenak"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) ari da erreproduzitzen <xliff:g id="APP_LABEL">%3$s</xliff:g> bidez"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Erreproduzitu"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Ireki <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Erreproduzitu <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> bidez"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Taldea"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 gailu hautatu da"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> gailu hautatu dira"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (deskonektatuta)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(deskonektatuta)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Ezin izan da konektatu. Saiatu berriro."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Parekatu beste gailu batekin"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Konpilazio-zenbakia"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Ikusi guztiak"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Sarea aldatzeko, deskonektatu Ethernet-a"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Gailuaren funtzionamendua hobetzeko, aplikazioek eta zerbitzuek wifi-sareak bilatzen jarraituko dute, baita wifi-konexioa desaktibatuta dagoenean ere. Aukera hori aldatzeko, joan wifi-sareen bilaketaren ezarpenetara. "<annotation id="link">"Aldatu"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Hautatu erabiltzaile bat"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 91d3090fd97d..6cd43497a14b 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"کنترل‌های قدرتمند اعلان"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"روشن"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"خاموش"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"روشن - براساس چهره"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"با کنترل‌های قدرتمند اعلان می‌توانید سطح اهمیت اعلان‌های هر برنامه را از ۰ تا ۵ تعیین کنید. \n\n"<b>"سطح ۵"</b>" \n- در صدر فهرست اعلان‌ها نشان داده می‌شود \n- وقفه برای نمایش تمام‌صفحه مجاز است \n- همیشه اجمالی نشان داده می‌شود \n\n"<b>"سطح ۴"</b>" \n- وقفه برای نمایش تمام‌صفحه مجاز نیست \n- همیشه اجمالی نشان داده می‌شود \n\n"<b>"سطح ۳"</b>" \n- وقفه برای نمایش تمام‌صفحه مجاز نیست \n- هیچ‌وقت اجمالی نشان داده نمی‌شود \n\n"<b>"سطح ۲"</b>" \n- وقفه برای نمایش تمام‌صفحه مجاز نیست \n- هیچ‌وقت اجمالی نشان داده نمی‌شود \n- هیچ‌وقت صدا و لرزش ایجاد نمی‌کند \n\n"<b>"سطح ۱"</b>" \n- نمایش تمام صفحه مجاز نیست \n- هیچ‌وقت اجمالی نشان داده نمی‌شود \n- هیچ‌وقت صدا یا لرزش ایجاد نمی‌کند \n- در صفحه قفل و نوار وضعیت پنهان است \n- در پایین فهرست اعلان‌ها نشان داده می‌شود \n\n"<b>"سطح ۰"</b>" \n- همه اعلان‌های این برنامه مسدود است"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"اعلان‌ها"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"دیگر این اعلان‌ها را نخواهید دید"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"ازسرگیری"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"تنظیمات"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> از <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ازطریق <xliff:g id="APP_LABEL">%3$s</xliff:g> پخش می‌شود"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> از <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"پخش"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"باز کردن <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> از <xliff:g id="ARTIST_NAME">%2$s</xliff:g> را ازطریق <xliff:g id="APP_LABEL">%3$s</xliff:g> پخش کنید"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"گروه"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"۱ دستگاه انتخاب شد"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> دستگاه انتخاب شد"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (اتصال قطع شد)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(اتصال قطع شد)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"متصل نشد. دوباره امتحان کنید."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"مرتبط کردن دستگاه جدید"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"شماره ساخت"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"مشاهده همه"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"برای تغییر شبکه، اترنت را قطع کنید"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"‏برای بهبود تجربه استفاده از دستگاه، برنامه‌ها و سرویس‌ها همچنان می‌توانند در هر زمانی شبکه‌های Wi-Fi را اسکن کنند؛ حتی وقتی که Wi-Fi خاموش باشد. می‌توانید این مورد را در تنظیمات اسکن کردن Wi‑Fi تغییر دهید. "<annotation id="link">"تغییر"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"انتخاب کاربر"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index bb5396d24ce4..c0dc8a070ec1 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Ilmoitusten tehohallinta"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Päällä"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Pois päältä"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Päällä – kasvojen perusteella"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Ilmoitusten tehohallinnan avulla voit määrittää sovelluksen ilmoituksille tärkeystason väliltä 0–5. \n\n"<b>"Taso 5"</b>" \n– Ilmoitukset näytetään ilmoitusluettelon yläosassa \n– Näkyminen koko näytön tilassa sallitaan \n– Ilmoitukset kurkistavat aina näytölle\n\n"<b>"Taso 4"</b>" \n– Näkyminen koko näytön tilassa estetään \n– Ilmoitukset kurkistavat aina näytölle \n\n"<b>"Taso 3"</b>" \n– Näkyminen koko näytön tilassa estetään \n– Ei kurkistamista \n\n"<b>"Taso 2"</b>" \n– Näkyminen koko näytön tilassa estetään \n– Ei kurkistamista \n– Ei ääniä eikä värinää \n\n"<b>"Taso 1"</b>" \n– Näkyminen koko näytön tilassa estetään \n– Ei kurkistamista \n– Ei ääniä eikä värinää \n– Ilmoitukset piilotetaan lukitusnäytöltä ja tilapalkista \n– Ilmoitukset näytetään ilmoitusluettelon alaosassa \n\n"<b>"Taso 0"</b>" \n– Kaikki sovelluksen ilmoitukset estetään"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Ilmoitukset"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Et näe näitä ilmoituksia enää"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Jatka"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Asetukset"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> soittaa nyt tätä: <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>)"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Toista"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Avaa <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Soita <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) sovelluksessa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Ryhmä"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 laite valittu"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> laitetta valittu"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (yhteys katkaistu)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(yhteys katkaistu)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Ei yhteyttä. Yritä uudelleen."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Muodosta uusi laitepari"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Koontiversion numero"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Näytä kaikki"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Irrota Ethernet-johto, jos haluat vaihtaa verkkoa"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Laitteen käyttökokemuksen parantamiseksi sovellukset ja palvelut voivat hakea Wi-Fi-verkkoja myös silloin, kun Wi-Fi on pois päältä. Voit muuttaa asetusta Wi-Fi-haun asetuksissa. "<annotation id="link">"Muuta"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Valitse käyttäjä"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index b885249a4e91..675c30c6449a 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Réglages avancés des notifications"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Activé"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Désactivé"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Activé : en fonction du visage"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Avec les réglages avancés des notifications, vous pouvez définir un degré d\'importance de 0 à 5 pour les notifications d\'une application. \n\n"<b>"Niveau 5"</b>" \n- Afficher dans le haut de la liste des notifications \n- Autoriser les interruptions en mode plein écran \n- Toujours afficher les aperçus \n\n"<b>"Niveau 4"</b>" \n- Empêcher les interruptions en mode plein écran \n- Toujours afficher les aperçus \n\n"<b>"Niveau 3"</b>" \n- Empêcher les interruptions en mode plein écran \n- Ne jamais afficher les aperçus \n\n"<b>"Niveau 2"</b>" \n- Empêcher les interruptions en mode plein écran \n- Ne jamais afficher les aperçus \n- Ne pas autoriser les sons et les vibrations \n\n"<b>"Niveau 1"</b>" \n- Empêcher les interruptions en mode plein écran \n- Ne jamais afficher les aperçus \n- Ne pas autoriser les sons et les vibrations \n- Masquer de l\'écran de verrouillage et de la barre d\'état status bar \n- Afficher dans le bas de la liste des notifications \n\n"<b>"Level 0"</b>" \n- Bloquer toutes les notifications de l\'application"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notifications"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Vous ne verrez plus ces notifications"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Reprendre"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Paramètres"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> est en cours de lecteur à partir de <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Faire jouer"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Ouvrez <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Lecture de <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> à partir de <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Groupe"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Un appareil sélectionné"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> appareil sélectionné"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (déconnecté)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(déconnecté)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Impossible de se connecter. Réessayez."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Associer un autre appareil"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Numéro de version"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Tout afficher"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Pour changer de réseau, débranchez le câble Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Pour améliorer l\'expérience de l\'appareil, les applications et les services peuvent quand même rechercher des réseaux Wi-Fi en tout temps, même lorsque le Wi-Fi est désactivé. Vous pouvez modifier vos préférences dans les paramètres de recherche de réseaux Wi-Fi. "<annotation id="link">"Modifier"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Sélect. utilisateur"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index ce6cd9591e93..e3d6ef9fa5d1 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Commandes de gestion des notifications"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Activé"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Désactivé"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Active - En fonction du visage"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Grâce aux commandes de gestion des notifications, vous pouvez définir le niveau d\'importance (compris entre 0 et 5) des notifications d\'une application. \n\n"<b>"Niveau 5"</b>" \n- Afficher en haut de la liste des notifications \n- Autoriser l\'interruption en plein écran \n- Toujours en aperçu \n\n"<b>"Niveau 4"</b>" \n- Empêcher l\'interruption en plein écran \n- Toujours en aperçu \n\n"<b>"Niveau 3"</b>" \n- Empêcher l\'interruption en plein écran \n- Jamais en aperçu \n\n"<b>"Niveau 2"</b>" \n- Empêcher l\'interruption en plein écran \n- Jamais en aperçu \n- Ne jamais émettre de signal sonore ni déclencher le vibreur \n\n"<b>"Niveau 1"</b>" \n- Empêcher l\'interruption en plein écran \n- Jamais en aperçu \n- Ne jamais émettre de signal sonore ni déclencher le vibreur \n- Masquer les notifications dans l\'écran de verrouillage et la barre d\'état \n- Afficher au bas de la liste des notifications \n\n"<b>"Niveau 0"</b>" \n- Bloquer toutes les notifications de l\'application"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notifications"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Vous ne recevrez plus ces notifications"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Reprendre"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Paramètres"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> est en cours de lecture depuis <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> sur <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Lire"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Ouvre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mets <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> depuis <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Groupe"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 appareil sélectionné"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> appareils sélectionnés"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (déconnecté)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(déconnecté)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Impossible de se connecter. Réessayez."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Associer un nouvel appareil"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Numéro de build"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Tout afficher"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Pour changer de réseau, déconnectez l\'Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Pour améliorer l\'expérience sur l\'appareil, les applis et les services peuvent continuer de rechercher les réseaux Wi-Fi, même si le Wi-Fi est désactivé. Vous pouvez modifier cela dans les paramètres de recherche Wi-Fi. "<annotation id="link">"Modifier"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Choisir utilisateur"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fr/tiles_states_strings.xml b/packages/SystemUI/res/values-fr/tiles_states_strings.xml
index 5898e0c52068..e66169dff505 100644
--- a/packages/SystemUI/res/values-fr/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-fr/tiles_states_strings.xml
@@ -64,7 +64,7 @@
<string-array name="tile_states_rotation">
<item msgid="4578491772376121579">"Indisponible"</item>
<item msgid="5776427577477729185">"Désactivé"</item>
- <item msgid="7105052717007227415">"Activée"</item>
+ <item msgid="7105052717007227415">"Activé"</item>
</string-array>
<string-array name="tile_states_bt">
<item msgid="5330252067413512277">"Indisponible"</item>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 9da7d6fbac0a..e12796a58e90 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Controis de notificacións mellorados"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Activado"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Desactivado"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Activada: baseada na cara"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Cos controis de notificacións mellorados, podes asignarlles un nivel de importancia comprendido entre 0 e 5 ás notificacións dunha aplicación determinada. \n\n"<b>"Nivel 5"</b>" \n- Mostrar na parte superior da lista de notificacións. \n- Permitir interrupcións no modo de pantalla completa. \n- Mostrar sempre. \n\n"<b>"Nivel 4"</b>" \n- Impedir interrupcións no modo de pantalla completa. \n- Mostrar sempre. \n\n"<b>"Nivel 3"</b>" \n- Impedir interrupcións no modo de pantalla completa. \n- Non mostrar nunca. \n\n"<b>"Nivel 2"</b>" \n- Impedir interrupcións no modo de pantalla completa. \n- Non mostrar nunca. \n- Non soar nin vibrar nunca. \n\n"<b>"Nivel 1"</b>" \n- Impedir interrupcións no modo de pantalla completa. \n- Non mostrar nunca. \n- Non soar nin vibrar nunca. \n- Ocultar na pantalla de bloqueo e na barra de estado. \n- Mostrar na parte inferior da lista de notificacións. \n\n"<b>"Nivel 0"</b>" \n- Bloquear todas as notificacións da aplicación."</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notificacións"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Deixarás de ver estas notificacións"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Configuración"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Estase reproducindo <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproducir"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Seleccionouse 1 dispositivo"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Seleccionáronse <xliff:g id="COUNT">%1$d</xliff:g> dispositivos"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (dispositivo desconectado)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(desconectado)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Non se puido establecer a conexión. Téntao de novo."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Vincular dispositivo novo"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilación"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Ver todo"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para cambiar de rede, desconecta a Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Para mellorar a experiencia que ofrece o dispositivo, as aplicacións e os servizos poden seguir buscando redes wifi en calquera momento, aínda que esta conexión estea desactivada. Podes cambiar esta opción na configuración da función Busca de redes wifi. "<annotation id="link">"Cambiar"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string>
</resources>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 79bb64f0599b..e81fedb8181c 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"પાવર સૂચના નિયંત્રણો"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"ચાલુ"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"બંધ"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ચાલુ છે - ચહેરા આધારિત રોટેશન"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"પાવર સૂચના નિયંત્રણો સાથે, તમે ઍપની સૂચનાઓ માટે 0 થી 5 સુધીના મહત્વના સ્તરને સેટ કરી શકો છો. \n\n"<b>"સ્તર 5"</b>" \n- સૂચના સૂચિની ટોચ પર બતાવો \n- પૂર્ણ સ્ક્રીન અવરોધની મંજૂરી આપો \n- હંમેશાં ત્વરિત દૃષ્ટિ કરો \n\n"<b>"સ્તર 4"</b>" \n- પૂર્ણ સ્ક્રીન અવરોધ અટકાવો \n- હંમેશાં ત્વરિત દૃષ્ટિ કરો \n\n"<b>"સ્તર 3"</b>" \n- પૂર્ણ સ્ક્રીન અવરોધ અટકાવો \n- ક્યારેય ત્વરિત દૃષ્ટિ કરશો નહીં \n\n"<b>"સ્તર 2"</b>" \n- પૂર્ણ સ્ક્રીન અવરોધ અટકાવો \n- ક્યારેય ત્વરિત દૃષ્ટિ કરશો નહીં \n- ક્યારેય અવાજ અથવા વાઇબ્રેટ કરશો નહીં \n\n"<b>"સ્તર 1"</b>" \n- પૂર્ણ સ્ક્રીન અવરોધની મંજૂરી આપો \n- ક્યારેય ત્વરિત દૃષ્ટિ કરશો નહીં \n- ક્યારેય અવાજ અથવા વાઇબ્રેટ કરશો નહીં \n- લૉક સ્ક્રીન અને સ્ટેટસ બારથી છુપાવો \n- સૂચના સૂચિના તળિયા પર બતાવો \n\n"<b>"સ્તર 0"</b>" \n- ઍપની તમામ સૂચનાઓને બ્લૉક કરો"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"નોટિફિકેશન"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"તમને હવેથી આ નોટિફિકેશન દેખાશે નહીં"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"ફરી શરૂ કરો"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"સેટિંગ"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> પર <xliff:g id="ARTIST_NAME">%2$s</xliff:g>નું <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચાલી રહ્યું છે"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>માંથી <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ચલાવો"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ખોલો"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> પર <xliff:g id="ARTIST_NAME">%2$s</xliff:g>નું <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચલાવો"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"ગ્રૂપ"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ડિવાઇસ પસંદ કર્યું"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> ડિવાઇસ પસંદ કર્યા"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ડિસ્કનેક્ટ થયેલું)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ડિસ્કનેક્ટ કરેલું)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"કનેક્ટ કરી શકાયું નહીં. ફરી પ્રયાસ કરો."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"નવા ડિવાઇસ સાથે જોડાણ કરો"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"બિલ્ડ નંબર"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"બધા જુઓ"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"બીજા નેટવર્ક પર જવા માટે, ઇથરનેટ ડિસ્કનેક્ટ કરો"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"ડિવાઇસના અનુભવને બહેતર બનાવવા માટે, વાઇ-ફાઇ બંધ હોય ત્યારે પણ ઍપ અને સેવાઓ કોઈપણ સમયે વાઇ-ફાઇ નેટવર્ક સ્કૅન કરી શકે છે. તમે વાઇ-ફાઇ સ્કૅનિંગના સેટિંગમાં જઈને આને બદલી શકો છો. "<annotation id="link">"બદલો"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"વપરાશકર્તા પસંદ કરો"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 429bbccaf797..d344f3e5cd96 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"पावर सूचना नियंत्रण"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"चालू"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"बंद"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"चालू है - चेहरे की गतिविधि के हिसाब से कैमरे को घुमाने की सुविधा"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"पावर सूचना नियंत्रण के ज़रिये, आप किसी ऐप की सूचना को उसकी अहमियत के हिसाब से 0 से 5 के लेवल पर सेट कर सकते हैं.\n\n"<b>"लेवल 5"</b>" \n- सूचना सूची में सबसे ऊपर दिखाएं \n- पूरे स्क्रीन को ढंकने की अनुमति दें \n- लगातार देखते रहें \n\n"<b>" लेवल 4"</b>" \n- पूरे स्क्रीन को ढंकें \n- लगातार देखते रहें \n\n"<b>"लेवल 3"</b>" \n- पूरे स्क्रीन को ढंकने से रोकें \n-कभी भी न देखें \n\n"<b>"लेवल 2"</b>" \n- पूरे स्क्रीन को ढंकने से रोकें \n- कभी भी देखें \n- कभी भी आवाज़ या कंपन (वाइब्रेशन) न करें \n\n"<b>"लेवल 1"</b>" \n- पूरे स्क्रीन को ढंकने से रोकें \n- कभी भी न देखें \n- कभी भी आवाज़ या कंपन (वाइब्रेशन) न करें \n- लॉक स्क्रीन और स्टेटस बार से छिपाएं \n- सूचना सूची के नीचे दिखाएं \n\n"<b>"लेवल 0"</b>" \n- ऐप्लिकेशन की सभी सूचनाएं रोक दें"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"सूचनाएं"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"अब आपको ये सूचनाएं दिखाई नहीं देंगी"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"फिर से शुरू करें"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिंग"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> पर, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> का <xliff:g id="SONG_NAME">%1$s</xliff:g> चल रहा है"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> में से <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"चलाएं"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> खोलें"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> पर, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> का <xliff:g id="SONG_NAME">%1$s</xliff:g> चलाएं"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"ग्रुप"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"एक डिवाइस चुना गया"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> डिवाइस चुने गए"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (डिसकनेक्ट किया गया)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(डिसकनेक्ट हो गया)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"कनेक्ट नहीं किया जा सका. फिर से कोशिश करें."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"नया डिवाइस जोड़ें"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"बिल्ड नंबर"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"सभी देखें"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"नेटवर्क बदलने के लिए, पहले ईथरनेट को डिसकनेक्ट करें"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"डिवाइस इस्तेमाल करने के अनुभव काे बेहतर बनाने के लिए, ऐप्लिकेशन और सेवाओं की मदद से, किसी भी समय वाई-फ़ाई नेटवर्क स्कैन किए जा सकते हैं. ऐसा वाई-फ़ाई बंद होने पर भी किया जा सकता है. वाई-फ़ाई स्कैनिंग की सेटिंग में जाकर, इसे बदला जा सकता है. "<annotation id="link">"बदलें"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"उपयोगकर्ता चुनें"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 3dd504003a6e..59179e627eb5 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -713,7 +713,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Napredne kontrole obavijesti"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Uključeno"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Isključeno"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Uključeno – na temelju lica"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Napredne kontrole obavijesti omogućuju vam da postavite razinu važnosti za obavijesti aplikacije od 0 do 5. \n\n"<b>"Razina 5"</b>" \n– prikaži na vrhu popisa obavijesti \n– dopusti prekide prikaza na cijelom zaslonu \n– uvijek dopusti brzi pregled \n\n"<b>"Razina 4"</b>" \n– onemogući prekid prikaza na cijelom zaslonu \n– uvijek dopusti brzi pregled \n\n"<b>"Razina 3"</b>" \n– onemogući prekid prikaza na cijelom zaslonu \n– nikad ne dopusti brzi pregled\n\n"<b>"Razina 2"</b>" \n– onemogući prekid prikaza na cijelom zaslonu \n– nikad ne dopusti brzi pregled \n– nikad ne emitiraj zvuk ni vibraciju \n\n"<b>"Razina 1"</b>" \n– onemogući prekid prikaza na cijelom zaslonu \n– nikad ne dopusti brzi pregled \n– nikad ne emitiraj zvuk ni vibraciju \n– ne prikazuj na zaključanom zaslonu i traci statusa \n– prikaži na dnu popisa obavijesti \n\n"<b>"Razina 0"</b>" \n– blokiraj sve obavijesti aplikacije"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Obavijesti"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Te vam se obavijesti više neće prikazivati"</string>
@@ -1097,6 +1096,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Postavke"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> reproducira se putem aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> od <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reprodukcija"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvori <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> putem aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1117,7 +1117,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Odabran je jedan uređaj"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Odabrano uređaja: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (nije povezano)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(nije povezano)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Povezivanje nije bilo moguće. Pokušajte ponovo."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Uparite novi uređaj"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Broj međuverzije"</string>
@@ -1186,4 +1186,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Prikaži sve"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Da biste se prebacili na drugu mrežu, odspojite Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Da bi se poboljšao doživljaj uređaja, aplikacije i usluge i dalje mogu tražiti Wi-Fi mreže u bilo kojem trenutku, čak i kada je Wi-Fi isključen. To možete promijeniti u postavkama traženja Wi-Fija. "<annotation id="link">"Promijeni"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Odabir korisnika"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 26c34f80117f..f1b694ee9342 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Teljes körű értesítésvezérlők"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Bekapcsolva"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Kikapcsolva"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Be: Arcalapú"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Az értesítési beállítások révén 0-tól 5-ig állíthatja be a fontossági szintet az alkalmazás értesítéseinél. \n\n"<b>"5. szint"</b>" \n– Megjelenítés az értesítési lista tetején \n– Teljes képernyő megszakításának engedélyezése \n– Mindig felugrik \n\n"<b>"4. szint"</b>" \n– Teljes képernyő megszakításának megakadályozása \n– Mindig felugrik \n\n"<b>"3. szint"</b>" \n– Teljes képernyő megszakításának megakadályozása \n– Soha nem ugrik fel \n\n"<b>"2. szint"</b>" \n– Teljes képernyő megszakításának megakadályozása \n– Soha nem ugrik fel \n– Soha nincs hangjelzés és rezgés \n\n"<b>"1. szint"</b>" \n– Teljes képernyő megszakításának megakadályozása \n– Soha nem ugrik fel \n– Soha nincs hangjelzés vagy rezgés \n– Elrejtés a lezárási képernyőről és az állapotsávról \n– Megjelenítés az értesítési lista alján \n\n"<b>"0. szint"</b>" \n– Az alkalmazás összes értesítésének letiltása"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Értesítések"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Többé nem jelennek meg ezek az értesítések"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Folytatás"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Beállítások"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> <xliff:g id="SONG_NAME">%1$s</xliff:g> című száma hallható itt: <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>/<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Játék"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> megnyitása"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> <xliff:g id="SONG_NAME">%1$s</xliff:g> című számának lejátszása innen: <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Csoport"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 eszköz kiválasztva"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> eszköz kiválasztva"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (leválasztva)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(leválasztva)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Sikertelen csatlakozás. Próbálja újra."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Új eszköz párosítása"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Buildszám"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Megtekintés"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Hálózatváltáshoz válassza le az ethernetet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Az eszközhasználati élmény javítása érdekében az alkalmazások és a szolgáltatások továbbra is bármikor kereshetnek Wi-Fi-hálózatokat, még akkor is, ha a Wi-Fi ki van kapcsolva. A funkciót a „Wi-Fi scanning settings” (Wi-Fi-keresési beállítások) részben módosíthatja. "<annotation id="link">"Módosítás"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Felhasználóválasztás"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 133587000926..7e62a823329c 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Ծանուցումների ընդլայնված կառավարում"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Միացված է"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Անջատված է"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Միաց․ – Դիմաճանաչման հիման վրա"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Ծանուցումների ընդլայնված կառավարման օգնությամբ կարող եք յուրաքանչյուր հավելվածի ծանուցումների համար նշանակել կարևորության աստիճան՝ 0-5 սահմաններում: \n\n"<b>"5-րդ աստիճան"</b>" \n- Ցուցադրել ծանուցումների ցանկի վերևում \n- Թույլատրել լիաէկրան ընդհատումները \n- Միշտ ցուցադրել կարճ ծանուցումները \n\n"<b>"4-րդ աստիճան"</b>" \n- Արգելել լիաէկրան ընդհատումները \n- Միշտ ցուցադրել կարճ ծանուցումները \n\n"<b>"3-րդ աստիճան"</b>" \n- Արգելել լիաէկրան ընդհատումները \n- Արգելել կարճ ծանուցումների ցուցադրումը \n\n"<b>"2-րդ աստիճան"</b>" \n- Արգելել լիաէկրան ընդհատումները \n- Արգելել կարճ ծանուցումների ցուցադրումը \n- Անջատել ձայնը և թրթռումը \n\n"<b>"1-ին աստիճան"</b>" \n- Արգելել լիաէկրան ընդհատումները \n- Արգելել կարճ ծանուցումների ցուցադրումը \n- Անջատել ձայնը և թրթռումը \n- Չցուցադրել կողպէկրանում և կարգավիճակի գոտում \n- Ցուցադրել ծանուցումների ցանկի ներքևում \n\n"<b>"0-րդ աստիճան"</b>\n"- Արգելափակել հավելվածի բոլոր ծանուցումները"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Ծանուցումներ"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Այլևս չեք ստանա նման ծանուցումներ"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Շարունակել"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Կարգավորումներ"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Այժմ նվագարկվում է <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="ARTIST_NAME">%2$s</xliff:g>-ի կատարմամբ <xliff:g id="APP_LABEL">%3$s</xliff:g> հավելվածից"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>՝ <xliff:g id="TOTAL_TIME">%2$s</xliff:g>-ից"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Նվագարկել"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Բացեք <xliff:g id="APP_LABEL">%1$s</xliff:g> հավելվածը"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Նվագարկել <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="ARTIST_NAME">%2$s</xliff:g>-ի կատարմամբ <xliff:g id="APP_LABEL">%3$s</xliff:g> հավելվածից"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Խումբ"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Ընտրված է 1 սարք"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Ընտրված է <xliff:g id="COUNT">%1$d</xliff:g> սարք"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (անջատված է)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(անջատված է)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Չհաջողվեց միանալ։ Նորից փորձեք։"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Նոր սարքի զուգակցում"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Կառուցման համարը"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Տեսնել բոլորը"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Մի ցանցից մյուսին անցնելու համար անջատեք Ethernet-ը"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Սարքի աշխատանքը բարելավելու համար հավելվածներն ու ծառայությունները կորոնեն Wi‑Fi ցանցեր, նույնիսկ երբ Wi‑Fi-ն անջատված է։ Այս պարամետրը կարող եք փոխել Wi‑Fi ցանցերի որոնման կարգավորումներում։ "<annotation id="link">"Փոխել"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Ընտրեք օգտատեր"</string>
</resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 83b6371f4505..fcb81ba450de 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Kontrol notifikasi daya"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Aktif"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Nonaktif"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Aktif - Berbasis deteksi wajah"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Dengan kontrol notifikasi daya, Anda dapt menyetel level kepentingan notifikasi aplikasi dari 0 sampai 5. \n\n"<b>"Level 5"</b>" \n- Muncul di atas daftar notifikasi \n- Izinkan interupsi layar penuh \n- Selalu intip pesan \n\n"<b>"Level 4"</b>" \n- Jangan interupsi layar penuh \n- Selalu intip pesan \n\n"<b>"Level 3"</b>" \n- Jangan interupsi layar penuh \n- Tak pernah intip pesan \n\n"<b>"Level 2"</b>" \n- Jangan interupsi layar penuh \n- Tak pernah intip pesan \n- Tanpa suara dan getaran \n\n"<b>"Level 1"</b>" \n- Jangan interupsi layar penuh \n- Tak pernah intip pesan \n- Tanpa suara atau getaran \n- Sembunyikan dari layar kunci dan bilah status \n- Muncul di bawah daftar notifikasi \n\n"<b>"Level 0"</b>" \n- Blokir semua notifikasi dari aplikasi"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notifikasi"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Anda tidak akan melihat notifikasi ini lagi"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Lanjutkan"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Setelan"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> sedang diputar dari <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> dari <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Putar"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Buka <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Putar <xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> dari <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 perangkat dipilih"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> perangkat dipilih"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (terputus)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(terputus)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Tidak dapat terhubung. Coba lagi."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Sambungkan perangkat baru"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Nomor build"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Lihat semua"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Untuk beralih jaringan, lepaskan kabel ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Agar pengalaman perangkat menjadi lebih baik, aplikasi dan layanan tetap dapat memindai jaringan Wi-Fi kapan saja, bahkan saat Wi-Fi nonaktif. Anda dapat mengubahnya di setelan pemindaian Wi-Fi. "<annotation id="link">"Ubah"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pilih pengguna"</string>
</resources>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index af97d98d9f14..52d45fee0fa8 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Orkustillingar tilkynninga"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Kveikt"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Slökkt"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Kveikt – út frá andliti"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Með orkutilkynningastýringum geturðu stillt mikilvægi frá 0 upp í 5 fyrir tilkynningar forrita. \n\n"<b>"Stig 5"</b>" \n- Sýna efst á tilkynningalista \n- Leyfa truflun þegar birt er á öllum skjánum \n- Kíkja alltaf \n\n"<b>"Stig 4"</b>" \n- Hindra truflun við birtingu á öllum skjánum \n- Kíkja alltaf \n\n"<b>"Stig 3"</b>" \n- Hindra truflun við birtingu á öllum skjánum \n- Kíkja aldrei \n\n"<b>"Stig 2"</b>" \n- Hindra truflun við birtingu á öllum skjánum \n- Kíkja aldrei \n- Slökkva á hljóði og titringi \n\n"<b>"Stig 1"</b>" \n- Hindra truflun við birtingu á öllum skjánum \n- Kíkja aldrei \n- Slökkva á hljóði og titringi \n- Fela á lásskjá og stöðustiku \n- Sýna neðst á tilkynningalista \n\n"<b>"Stig 0"</b>" \n- Setja allar tilkynningar frá forriti á bannlista"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Tilkynningar"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Þú munt ekki sjá þessar tilkynningar aftur"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Halda áfram"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Stillingar"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> með <xliff:g id="ARTIST_NAME">%2$s</xliff:g> er í spilun á <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> af <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Spila"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Opna <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spila <xliff:g id="SONG_NAME">%1$s</xliff:g> með <xliff:g id="ARTIST_NAME">%2$s</xliff:g> í <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Hópur"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 tæki valið"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> tæki valin"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (aftengt)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(aftengt)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Tenging mistókst. Reyndu aftur."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Para nýtt tæki"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Útgáfunúmer smíðar"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Sjá allt"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Aftengdu ethernet til að skipta um net"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Til að bæta tækjaupplifun geta forrit og þjónustur áfram leitað að WiFi-netum hvenær sem er, jafnvel þótt slökkt sé á WiFi. Hægt er að breyta þessu í stillingum WiFi-leitar. "<annotation id="link">"Breyta"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Velja notanda"</string>
</resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 98b341d8c838..b4c936570301 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Controlli di gestione delle notifiche"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"On"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Off"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"On - Rotazione basata sul viso"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"I controlli di gestione delle notifiche ti consentono di impostare un livello di importanza compreso tra 0 e 5 per le notifiche di un\'app. \n\n"<b>"Livello 5"</b>" \n- Mostra in cima all\'elenco di notifiche \n- Consenti l\'interruzione a schermo intero \n- Visualizza sempre \n\n"<b>"Livello 4"</b>" \n- Impedisci l\'interruzione a schermo intero \n- Visualizza sempre \n\n"<b>"Livello 3"</b>" \n- Impedisci l\'interruzione a schermo intero \n- Non visualizzare mai \n\n"<b>"Livello 2"</b>" \n- Impedisci l\'interruzione a schermo intero \n- Non visualizzare mai \n- Non emettere mai suoni e vibrazioni \n\n"<b>"Livello 1"</b>" \n- Impedisci l\'interruzione a schermo intero \n- Non visualizzare mai \n- Non emettere mai suoni e vibrazioni \n- Nascondi da schermata di blocco e barra di stato \n- Mostra in fondo all\'elenco di notifiche \n\n"<b>"Livello 0"</b>" \n- Blocca tutte le notifiche dell\'app"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notifiche"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Non vedrai più queste notifiche"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Riprendi"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Impostazioni"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> di <xliff:g id="ARTIST_NAME">%2$s</xliff:g> è in riproduzione da <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> di <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Riproduci"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Apri <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Riproduci <xliff:g id="SONG_NAME">%1$s</xliff:g> di <xliff:g id="ARTIST_NAME">%2$s</xliff:g> da <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppo"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selezionato"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> dispositivi selezionati"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (disconnesso)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(disconnesso)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Impossibile connettersi. Riprova."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Accoppia nuovo dispositivo"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Numero build"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Mostra tutte"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Per cambiare rete, scollega il cavo Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Per migliorare l\'esperienza con il dispositivo, le app e i servizi possono continuare a cercare reti Wi-Fi in qualsiasi momento, anche quando la connessione Wi-Fi non è attiva. Puoi modificare questa preferenza nelle impostazioni relative alla ricerca di reti Wi-Fi. "<annotation id="link">"Cambia"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleziona utente"</string>
</resources>
diff --git a/packages/SystemUI/res/values-it/tiles_states_strings.xml b/packages/SystemUI/res/values-it/tiles_states_strings.xml
index f18536cb09a0..d14206974fd5 100644
--- a/packages/SystemUI/res/values-it/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-it/tiles_states_strings.xml
@@ -32,127 +32,127 @@
<!-- no translation found for tile_states_default:1 (7086813178962737808) -->
<!-- no translation found for tile_states_default:2 (9192445505551219506) -->
<string-array name="tile_states_internet">
- <item msgid="5499482407653291407">"Non disponibile"</item>
+ <item msgid="5499482407653291407">"Elemento non disponibile"</item>
<item msgid="3048856902433862868">"Off"</item>
<item msgid="6877982264300789870">"On"</item>
</string-array>
<string-array name="tile_states_wifi">
- <item msgid="8054147400538405410">"Non disponibile"</item>
+ <item msgid="8054147400538405410">"Elemento non disponibile"</item>
<item msgid="4293012229142257455">"Off"</item>
<item msgid="6221288736127914861">"On"</item>
</string-array>
<string-array name="tile_states_cell">
- <item msgid="1235899788959500719">"Non disponibile"</item>
+ <item msgid="1235899788959500719">"Elemento non disponibile"</item>
<item msgid="2074416252859094119">"Off"</item>
<item msgid="287997784730044767">"On"</item>
</string-array>
<string-array name="tile_states_battery">
- <item msgid="6311253873330062961">"Non disponibile"</item>
+ <item msgid="6311253873330062961">"Elemento non disponibile"</item>
<item msgid="7838121007534579872">"Off"</item>
<item msgid="1578872232501319194">"On"</item>
</string-array>
<string-array name="tile_states_dnd">
- <item msgid="467587075903158357">"Non disponibile"</item>
+ <item msgid="467587075903158357">"Elemento non disponibile"</item>
<item msgid="5376619709702103243">"Off"</item>
<item msgid="4875147066469902392">"On"</item>
</string-array>
<string-array name="tile_states_flashlight">
- <item msgid="3465257127433353857">"Non disponibile"</item>
+ <item msgid="3465257127433353857">"Elemento non disponibile"</item>
<item msgid="5044688398303285224">"Off"</item>
<item msgid="8527389108867454098">"On"</item>
</string-array>
<string-array name="tile_states_rotation">
- <item msgid="4578491772376121579">"Non disponibile"</item>
+ <item msgid="4578491772376121579">"Elemento non disponibile"</item>
<item msgid="5776427577477729185">"Off"</item>
<item msgid="7105052717007227415">"On"</item>
</string-array>
<string-array name="tile_states_bt">
- <item msgid="5330252067413512277">"Non disponibile"</item>
+ <item msgid="5330252067413512277">"Elemento non disponibile"</item>
<item msgid="5315121904534729843">"Off"</item>
<item msgid="503679232285959074">"On"</item>
</string-array>
<string-array name="tile_states_airplane">
- <item msgid="1985366811411407764">"Non disponibile"</item>
+ <item msgid="1985366811411407764">"Elemento non disponibile"</item>
<item msgid="4801037224991420996">"Off"</item>
<item msgid="1982293347302546665">"On"</item>
</string-array>
<string-array name="tile_states_location">
- <item msgid="3316542218706374405">"Non disponibile"</item>
+ <item msgid="3316542218706374405">"Elemento non disponibile"</item>
<item msgid="4813655083852587017">"Off"</item>
<item msgid="6744077414775180687">"On"</item>
</string-array>
<string-array name="tile_states_hotspot">
- <item msgid="3145597331197351214">"Non disponibile"</item>
+ <item msgid="3145597331197351214">"Elemento non disponibile"</item>
<item msgid="5715725170633593906">"Off"</item>
<item msgid="2075645297847971154">"On"</item>
</string-array>
<string-array name="tile_states_inversion">
- <item msgid="3638187931191394628">"Non disponibile"</item>
+ <item msgid="3638187931191394628">"Elemento non disponibile"</item>
<item msgid="9103697205127645916">"Off"</item>
<item msgid="8067744885820618230">"On"</item>
</string-array>
<string-array name="tile_states_saver">
- <item msgid="39714521631367660">"Non disponibile"</item>
+ <item msgid="39714521631367660">"Elemento non disponibile"</item>
<item msgid="6983679487661600728">"Off"</item>
<item msgid="7520663805910678476">"On"</item>
</string-array>
<string-array name="tile_states_dark">
- <item msgid="2762596907080603047">"Non disponibile"</item>
+ <item msgid="2762596907080603047">"Elemento non disponibile"</item>
<item msgid="400477985171353">"Off"</item>
<item msgid="630890598801118771">"On"</item>
</string-array>
<string-array name="tile_states_work">
- <item msgid="389523503690414094">"Non disponibile"</item>
+ <item msgid="389523503690414094">"Elemento non disponibile"</item>
<item msgid="8045580926543311193">"Off"</item>
<item msgid="4913460972266982499">"On"</item>
</string-array>
<string-array name="tile_states_cast">
- <item msgid="6032026038702435350">"Non disponibile"</item>
+ <item msgid="6032026038702435350">"Elemento non disponibile"</item>
<item msgid="1488620600954313499">"Off"</item>
<item msgid="588467578853244035">"On"</item>
</string-array>
<string-array name="tile_states_night">
- <item msgid="7857498964264855466">"Non disponibile"</item>
+ <item msgid="7857498964264855466">"Elemento non disponibile"</item>
<item msgid="2744885441164350155">"Off"</item>
<item msgid="151121227514952197">"On"</item>
</string-array>
<string-array name="tile_states_screenrecord">
- <item msgid="1085836626613341403">"Non disponibile"</item>
+ <item msgid="1085836626613341403">"Elemento non disponibile"</item>
<item msgid="8259411607272330225">"Off"</item>
<item msgid="578444932039713369">"On"</item>
</string-array>
<string-array name="tile_states_reverse">
- <item msgid="3574611556622963971">"Non disponibile"</item>
+ <item msgid="3574611556622963971">"Elemento non disponibile"</item>
<item msgid="8707481475312432575">"Off"</item>
<item msgid="8031106212477483874">"On"</item>
</string-array>
<string-array name="tile_states_reduce_brightness">
- <item msgid="1839836132729571766">"Non disponibile"</item>
+ <item msgid="1839836132729571766">"Elemento non disponibile"</item>
<item msgid="4572245614982283078">"Off"</item>
<item msgid="6536448410252185664">"On"</item>
</string-array>
<string-array name="tile_states_cameratoggle">
- <item msgid="6680671247180519913">"Non disponibile"</item>
+ <item msgid="6680671247180519913">"Elemento non disponibile"</item>
<item msgid="4765607635752003190">"Off"</item>
<item msgid="1697460731949649844">"On"</item>
</string-array>
<string-array name="tile_states_mictoggle">
- <item msgid="6895831614067195493">"Non disponibile"</item>
+ <item msgid="6895831614067195493">"Elemento non disponibile"</item>
<item msgid="3296179158646568218">"Off"</item>
<item msgid="8998632451221157987">"On"</item>
</string-array>
<string-array name="tile_states_controls">
- <item msgid="8199009425335668294">"Non disponibile"</item>
+ <item msgid="8199009425335668294">"Elemento non disponibile"</item>
<item msgid="4544919905196727508">"Off"</item>
<item msgid="3422023746567004609">"On"</item>
</string-array>
<string-array name="tile_states_wallet">
- <item msgid="4177615438710836341">"Non disponibile"</item>
+ <item msgid="4177615438710836341">"Elemento non disponibile"</item>
<item msgid="7571394439974244289">"Off"</item>
<item msgid="6866424167599381915">"On"</item>
</string-array>
<string-array name="tile_states_alarm">
- <item msgid="4936533380177298776">"Non disponibile"</item>
+ <item msgid="4936533380177298776">"Elemento non disponibile"</item>
<item msgid="2710157085538036590">"Off"</item>
<item msgid="7809470840976856149">"On"</item>
</string-array>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 7b021acca5b8..32be1c12428a 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -716,7 +716,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"פקדים של הודעות הפעלה"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"פועל"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"כבוי"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"פועל – מבוסס על זיהוי פנים"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"בעזרת פקדים של התראות הפעלה, אפשר להגדיר רמת חשיבות מ-0 עד 5 להתראות אפליקציה. \n\n"<b>"רמה 5"</b>" \n- הצגה בראש רשימת ההתראות \n- לאפשר הפרעה במסך מלא \n- תמיד לאפשר הצצה \n\n"<b>"רמה 4"</b>" \n- מניעת הפרעה במסך מלא \n- תמיד לאפשר הצצה \n\n"<b>"רמה 3"</b>" \n- מניעת הפרעה במסך מלא \n- אף פעם לא לאפשר הצצה \n\n"<b>"רמה 2"</b>" \n- מניעת הפרעה במסך מלא \n- אף פעם לא לאפשר הצצה \n- אף פעם לא לאפשר קול ורטט \n\n"<b>"רמה 1"</b>" \n- מניעת הפרעה במסך מלא \n- אף פעם לא לאפשר הצצה \n- אף פעם לא לאפשר קול ורטט \n- הסתרה ממסך הנעילה ומשורת הסטטוס \n- הצגה בתחתית רשימת ההתראות \n\n"<b>"רמה 0"</b>" \n- חסימת כל ההתראות מהאפליקציה"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"התראות"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"ההתראות האלה לא יוצגו לך יותר"</string>
@@ -1103,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"המשך"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"הגדרות"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> של <xliff:g id="ARTIST_NAME">%2$s</xliff:g> מופעל מ-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> מתוך <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"הפעלה"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"פתיחה של <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"הפעלת <xliff:g id="SONG_NAME">%1$s</xliff:g> של <xliff:g id="ARTIST_NAME">%2$s</xliff:g> מ-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1123,7 +1123,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"קבוצה"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"נבחר מכשיר אחד"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"נבחרו <xliff:g id="COUNT">%1$d</xliff:g> מכשירים"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (מנותק)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(מנותק)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"לא ניתן היה להתחבר. יש לנסות שוב."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"התאמה של מכשיר חדש"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"‏מספר Build"</string>
@@ -1192,4 +1192,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"הצגת הכול"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"כדי לעבור בין רשתות, צריך לנתק את האתרנט"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"‏כדי לשפר את חוויית השימוש במכשיר, אפליקציות ושירותים יוכלו לחפש רשתות Wi-Fi בכל שלב, גם כאשר ה-Wi-Fi כבוי. אפשר לשנות זאת בהגדרות של חיפוש נקודות Wi-Fi. "<annotation id="link">"שינוי"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"בחירת משתמש"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index ba3d2241152b..ee9790f19ef5 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"電源通知管理"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"ON"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"OFF"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ON - 顔ベース"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"電源通知管理では、アプリの通知の重要度をレベル 0~5 で設定できます。\n\n"<b>"レベル 5"</b>" \n- 通知リストの一番上に表示する \n- 全画面表示を許可する \n- 常にポップアップする \n\n"<b>"レベル 4"</b>" \n- 全画面表示しない \n- 常にポップアップする \n\n"<b>"レベル 3"</b>" \n- 全画面表示しない \n- ポップアップしない \n\n"<b>"レベル 2"</b>" \n- 全画面表示しない \n- ポップアップしない \n- 音やバイブレーションを使用しない \n\n"<b>"レベル 1"</b>" \n- 全画面表示しない \n- ポップアップしない \n- 音やバイブレーションを使用しない \n- ロック画面やステータスバーに表示しない \n- 通知リストの一番下に表示する \n\n"<b>"レベル 0"</b>" \n- アプリからのすべての通知をブロックする"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"通知"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"今後、この通知は表示されません"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"再開"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g>(アーティスト名: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>)が <xliff:g id="APP_LABEL">%3$s</xliff:g> で再生中"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"再生"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> を開く"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g>(アーティスト名: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>)を <xliff:g id="APP_LABEL">%3$s</xliff:g> で再生"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"グループ"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"選択したデバイス: 1 台"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"選択したデバイス: <xliff:g id="COUNT">%1$d</xliff:g> 台"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>(未接続)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(接続解除済み)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"接続できませんでした。もう一度お試しください。"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"新しいデバイスとのペア設定"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"ビルド番号"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"すべて表示"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ネットワークを変更するにはイーサネット接続を解除してください"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"デバイスの機能向上のため、アプリやサービスは、Wi-Fi が OFF の場合でも、いつでも Wi-Fi ネットワークをスキャンできます。この設定は Wi-Fi スキャンの設定で変更できます。"<annotation id="link">"変更"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ユーザーの選択"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 19dee6855adc..9d34a60ce28d 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"შეტყობინებების მართვის საშუალებები"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"ჩართული"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"გამორთული"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ჩართული — სახის მიხედვით"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"შეტყობინებების მართვის საშუალებების მეშვეობით, შეგიძლიათ განსაზღვროთ აპის შეტყობინებების მნიშვნელობის დონე 0-დან 5-მდე დიაპაზონში. \n\n"<b>"დონე 5"</b>" \n— შეტყობინებათა სიის თავში ჩვენება \n— სრულეკრანიანი რეჟიმის შეფერხების დაშვება \n— ეკრანზე ყოველთვის გამოჩენა \n\n"<b>"დონე 4"</b>" \n— სრულეკრანიანი რეჟიმის შეფერხების აღკვეთა \n— ეკრანზე ყოველთვის გამოჩენა \n\n"<b>"დონე 3"</b>" \n— სრულეკრანიანი რეჟიმის შეფერხების აღკვეთა \n— ეკრანზე გამოჩენის აღკვეთა \n\n"<b>"დონე 2"</b>" \n— სრულეკრანიანი რეჟიმის შეფერხების აღკვეთა \n— ეკრანზე გამოჩენის აღკვეთა \n— ხმისა და ვიბრაციის აღკვეთა \n\n"<b>"დონე 1"</b>" \n— სრულეკრანიანი რეჟიმის შეფერხების აღკვეთა \n— ეკრანზე გამოჩენის აღკვეთა \n— ხმისა და ვიბრაციის აღკვეთა \n— ჩაკეტილი ეკრანიდან და სტატუსის ზოლიდან დამალვა \n— შეტყობინებათა სიის ბოლოში ჩვენება \n\n"<b>"დონე 0"</b>" \n— აპის ყველა შეტყობინების დაბლოკვა"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"შეტყობინებები"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"ამ შეტყობინებებს აღარ დაინახავთ"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"გაგრძელება"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"პარამეტრები"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, უკრავს <xliff:g id="APP_LABEL">%3$s</xliff:g>-დან"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>-დან <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"დაკვრა"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"გახსენით <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"დაუკარით <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="APP_LABEL">%3$s</xliff:g>-დან"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"ჯგუფი"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"არჩეულია 1 მოწყობილობა"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"არჩეულია <xliff:g id="COUNT">%1$d</xliff:g> მოწყობილობა"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (კავშირი გაწყვეტილია)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(კავშირი გაწყვეტილია)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"დაკავშირება ვერ მოხერხდა. ცადეთ ხელახლა."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ახალი მოწყობილობის დაწყვილება"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"ანაწყობის ნომერი"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"ყველას ნახვა"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ქსელების გადასართავად, გაწყვიტეთ Ethernet-თან კავშირი"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"მოწყობილობისგან მიღებული გამოცდილების გასაუმჯობესებლად, აპებსა და სერვისებს მაინც შეუძლია სკანირება Wi‑Fi ქსელების აღმოსაჩენად, ნებისმიერ დროს, მაშინაც კი, როცა Wi‑Fi გამორთულია. ამის შეცვლა Wi-Fi სკანირების პარამეტრებში შეგიძლიათ. "<annotation id="link">"შეცვლა"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"მომხმარებლის არჩევა"</string>
</resources>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 5e3dbdc218d6..6f13d09b2169 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Қуат хабарландыруының басқару элементтері"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Қосулы"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Өшірулі"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Қосулы – бет негізінде"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Қуат хабарландыруының басқару элементтерімен қолданбаның хабарландырулары үшін 0-ден бастап 5-ке дейін маңыздылық деңгейін орнатуға болады. \n\n"<b>"5-деңгей"</b>" \n- Хабарландыру тізімінің ең басында көрсету \n- Толық экранға ашылуын рұқсат ету \n- Әрдайым қалқымалы хабарландыру түрінде көрсету \n\n"<b>"4-деңгей"</b>" \n- Толық экранға шығармау \n- Әрдайым қалқымалы хабарландыру түрінде көрсету \n\n"<b>"3-деңгей"</b>" \n- Толық экранға шығармау \n- Ешқашан қалқымалы хабарландыру түрінде көрсетпеу \n\n"<b>"2-деңгей"</b>" \n- Толық экранға шығармау \n- Ешқашан қалқымалы хабарландыру түрінде көрсетпеу \n- Ешқашан дыбыс және діріл шығармау \n\n"<b>"1-деңгей"</b>" \n- Толық экранға шығармау \n- Ешқашан қалқымалы хабарландыру түрінде көрсетпеу \n- Ешқашан дыбыс немесе діріл шығармау \n- Құлыпталған экраннан және күйін көрсету жолағынан жасыру \n- Хабарландыру тізімінің ең астында көрсету \n\n"<b>"0-деңгей"</b>" \n- Қолданбадағы барлық хабарландыруларға тыйым салу"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Хабарландырулар"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Хабарландырулар бұдан былай көрсетілмейді"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Жалғастыру"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Параметрлер"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> қолданбасында <xliff:g id="ARTIST_NAME">%2$s</xliff:g> орындайтын \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әні ойнатылуда."</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>/<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Ойнату"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> қолданбасын ашу"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> қолданбасында <xliff:g id="ARTIST_NAME">%2$s</xliff:g> орындайтын \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әнін ойнату"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Топ"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 құрылғы таңдалды."</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> құрылғы таңдалды."</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ажыратылған)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ажыратулы)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Қосылмады. Қайта қосылып көріңіз."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Жаңа құрылғымен жұптау"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Құрама нөмірі"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Барлығын көру"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Желілерді ауыстыру үшін ethernet кабелін ажыратыңыз."</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Құрылғы жұмысын жақсарту үшін қолданбалар мен қызметтер Wi-Fi байланысы өшірулі кезде де Wi-Fi желілерін іздейді. Оны Wi-Fi іздеу параметрлерінен өзгерте аласыз. "<annotation id="link">"Өзгерту"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Пайдаланушыны таңдау"</string>
</resources>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 7aee43f0018a..8a3953280d33 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"អង្គគ្រប់គ្រងការជូនដំណឹងថាមពល"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"បើក"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"បិទ"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"បើក - ផ្អែកលើមុខ"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"ជាមួយអង្គគ្រប់គ្រងការជូនដំណឹងថាមពល អ្នកអាចកំណត់កម្រិតសំខាន់ពី 0 ទៅ 5 សម្រាប់ការជូនដំណឹងរបស់កម្មវិធី។ \n\n"<b>"កម្រិត 5"</b>" \n- បង្ហាញនៅផ្នែកខាងលើបញ្ជីជូនដំណឹង \n- អនុញ្ញាតការរំខានលើអេក្រង់ពេញ \n- លោតឡើងជានិច្ច \n\n"<b>"កម្រិត 4"</b>" \n- រារាំងការរំខានលើអេក្រង់ពេញ \n- លោតឡើងជានិច្ច \n\n"<b>"កម្រិត 3"</b>" \n- រារាំងការរំខានលើអេក្រង់ពេញ \n- លោតឡើងជានិច្ច \n\n"<b>"កម្រិត 2"</b>" \n- រារាំងការរំខានលើអេក្រង់ពេញ \n- លោតឡើងជានិច្ច \n- មិនបន្លឺសំឡេង ឬញ័រ \n\n"<b>"កម្រិត 1"</b>" \n- រារាំងការរំខានលើអេក្រង់ពេញ \n- លោតឡើងជានិច្ច \n- មិនបន្លឺសំឡេង ឬញ័រ \n- លាក់ពីអេក្រង់ចាក់សោ និងរបារស្ថានភាព \n- បង្ហាញនៅផ្នែកខាងក្រោមបញ្ជីជូនដំណឹង \n\n"<b>"កម្រិត 0"</b>" \n- រារាំងការជូនដំណឹងទាំងអស់ពីកម្មវិធី"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"ការ​ជូនដំណឹង"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"អ្នក​នឹង​មិនឃើញ​ការជូនដំណឹង​ទាំងនេះ​ទៀតទេ"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"បន្ត"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ការកំណត់"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ច្រៀងដោយ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> កំពុងចាក់ពី <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> នៃ <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ចាក់"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"បើក <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"ចាក់ <xliff:g id="SONG_NAME">%1$s</xliff:g> ច្រៀងដោយ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ពី <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"ក្រុម"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"បានជ្រើសរើស​ឧបករណ៍ 1"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"បានជ្រើសរើស​ឧបករណ៍ <xliff:g id="COUNT">%1$d</xliff:g>"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (បាន​ផ្ដាច់)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(បាន​ដាច់)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"មិន​អាច​ភ្ជាប់​បាន​ទេ។ សូមព្យាយាមម្ដងទៀត។"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ផ្គូផ្គង​ឧបករណ៍ថ្មី"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"លេខ​កំណែបង្កើត"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"មើលទាំងអស់"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ដើម្បី​ប្ដូរ​បណ្ដាញ សូមផ្ដាច់​អ៊ីសឺរណិត"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"ដើម្បីធ្វើឱ្យ​បទពិសោធន៍ប្រើប្រាស់​ឧបករណ៍ប្រសើរឡើង កម្មវិធី និងសេវាកម្ម​នៅតែអាចស្កេនរក​បណ្ដាញ Wi‑Fi បានគ្រប់ពេល ទោះបីជា​នៅពេលដែលបិទ Wi‑Fi ក៏ដោយ។ អ្នកអាចប្ដូរវាបាន​នៅក្នុង​ការកំណត់​ការស្កេន Wi‑Fi។ "<annotation id="link">"ប្ដូរ"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ជ្រើសរើសអ្នកប្រើប្រាស់"</string>
</resources>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 8aa8febf4690..7d0661c30a2b 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"ಪವರ್ ಅಧಿಸೂಚನೆ ನಿಯಂತ್ರಣಗಳು"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"ಆನ್"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"ಆಫ್"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ಆನ್ ಆಗಿದೆ - ಮುಖ-ಆಧಾರಿತ"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"ಪವರ್ ಅಧಿಸೂಚನೆ ನಿಯಂತ್ರಣಗಳ ಮೂಲಕ, ನೀವು ಅಪ್ಲಿಕೇಶನ್‌ಗಳ ಅಧಿಸೂಚನೆಗಳನ್ನು 0 ರಿಂದ 5 ರವರೆಗಿನ ಹಂತಗಳ ಪ್ರಾಮುಖ್ಯತೆಯನ್ನು ಹೊಂದಿಸಬಹುದು. \n\n"<b>"ಹಂತ 5"</b>" \n- ಮೇಲಿನ ಅಧಿಸೂಚನೆ ಪಟ್ಟಿಯನ್ನು ತೋರಿಸಿ \n- ಪೂರ್ಣ ಪರದೆ ಅಡಚಣೆಯನ್ನು ಅನುಮತಿಸಿ \n- ಯಾವಾಗಲು ಇಣುಕು ನೋಟ \n\n"<b>"ಹಂತ 4"</b>" \n- ಪೂರ್ಣ ಪರದೆ ಅಡಚಣೆಯನ್ನು ತಡೆಯಿರಿ \n- ಯಾವಾಗಲು ಇಣುಕು ನೋಟ\n\n"<b>"ಹಂತ 3"</b>" \n- ಪೂರ್ಣ ಪರದೆ ಅಡಚಣೆಯನ್ನು ತಡೆಯಿರಿ \n- ಎಂದಿಗೂ ಇಣುಕು ನೋಟ ಬೇಡ \n\n"<b>"ಹಂತ 2"</b>" \n- ಪೂರ್ಣ ಪರದೆ ಅಡಚಣೆಯನ್ನು ತಡೆಯಿರಿ \n- ಎಂದಿಗೂ ಇಣುಕು ನೋಟ ಬೇಡ \n- ಶಬ್ದ ಮತ್ತು ವೈಬ್ರೇಷನ್ ಎಂದಿಗೂ ಮಾಡಬೇಡಿ \n\n"<b>"ಹಂತ 1"</b>" \n- ಪೂರ್ಣ ಪರದೆ ಅಡಚಣೆಯನ್ನು ತಡೆಯಿರಿ \n- ಎಂದಿಗೂ ಇಣುಕು ನೋಟ ಬೇಡ \n- ಶಬ್ದ ಮತ್ತು ವೈಬ್ರೇಷನ್ ಎಂದಿಗೂ ಮಾಡಬೇಡಿ \n- ಸ್ಥಿತಿ ಪಟ್ಟಿ ಮತ್ತು ಲಾಕ್ ಪರದೆಯಿಂದ ಮರೆಮಾಡಿ \n- ಕೆಳಗಿನ ಅಧಿಸೂಚನೆ ಪಟ್ಟಿಯನ್ನು ತೋರಿಸಿ \n\n"<b>"ಹಂತ 0"</b>" \n- ಅಪ್ಲಿಕೇಶನ್‌ನಿಂದ ಎಲ್ಲಾ ಅಧಿಸೂಚನೆಗಳನ್ನು ನಿರ್ಬಂಧಿಸಿ"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"ಅಧಿಸೂಚನೆಗಳು"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"ನೀವು ಈ ಅಧಿಸೂಚನೆಗಳನ್ನು ಇನ್ನು ಮುಂದೆ ನೋಡುವುದಿಲ್ಲ"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"ಪುನರಾರಂಭಿಸಿ"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ಅವರ <xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%3$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> ರಲ್ಲಿ <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ಪ್ಲೇ ಮಾಡಿ"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ಅನ್ನು ತೆರೆಯಿರಿ"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ಅವರ <xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%3$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"ಗುಂಪು"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ಸಾಧನವನ್ನು ಆಯ್ಕೆ ಮಾಡಲಾಗಿದೆ"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> ಸಾಧನಗಳನ್ನು ಆಯ್ಕೆ ಮಾಡಲಾಗಿದೆ"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾಗಿದೆ)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ಡಿಸ್‌ಕನೆಕ್ಟ್ ಆಗಿದೆ)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ಸಂಪರ್ಕಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ಹೊಸ ಸಾಧನವನ್ನು ಜೋಡಿಸಿ"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"ಬಿಲ್ಡ್ ಸಂಖ್ಯೆ"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"ಎಲ್ಲವನ್ನೂ ನೋಡಿ"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ನೆಟ್‌ವರ್ಕ್‌ಗಳನ್ನು ಬದಲಿಸಲು, ಇಥರ್ನೆಟ್ ಅನ್ನು ಡಿಸ್‌ಕನೆಕ್ಟ್ ಮಾಡಿ"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"ವೈ-ಫೈ ಆಫ್ ಇದ್ದಾಗಲೂ ಸಹ, ಸಾಧನದ ಅನುಭವವನ್ನು ಸುಧಾರಿಸಲು, ಆ್ಯಪ್‌ಗಳು ಮತ್ತು ಸೇವೆಗಳು ಯಾವಾಗ ಬೇಕಾದರೂ ಸಹ ವೈ-ಫೈ ನೆಟ್‌ವರ್ಕ್‌ಗಳಿಗಾಗಿ ಸ್ಕ್ಯಾನ್ ಮಾಡಬಹುದು. ನೀವು ಇದನ್ನು ವೈ-ಫೈ ಸ್ಕ್ಯಾನಿಂಗ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು. "<annotation id="link">"ಬದಲಿಸಿ"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ಬಳಕೆದಾರ ಆಯ್ಕೆಮಾಡಿ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 0346c45af6ea..bd775daef1e4 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"전원 알림 컨트롤"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"사용"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"사용 안함"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"켜짐 - 얼굴 기준"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"전원 알림 컨트롤을 사용하면 앱 알림 관련 중요도를 0부터 5까지로 설정할 수 있습니다. \n\n"<b>"레벨 5"</b>" \n- 알림 목록 상단에 표시 \n- 전체 화면일 경우 알림 표시 허용 \n- 항상 엿보기 표시 \n\n"<b>"레벨 4"</b>" \n- 전체 화면에 알림 표시 금지 \n- 항상 엿보기 표시 \n\n"<b>"레벨 3"</b>" \n- 전체 화면에 알림 표시 금지 \n- 엿보기 표시 안함 \n\n"<b>"레벨 2"</b>" \n- 전체 화면에 알림 표시 금지 \n- 엿보기 표시 안함 \n- 소리나 진동으로 알리지 않음 \n\n"<b>"레벨 1"</b>" \n- 전체 화면에 알림 표시 금지 \n- 엿보기 표시 안함 \n- 소리나 진동으로 알리지 않음 \n- 잠금 화면 및 상태 표시줄에서 숨김 \n- 알림 목록 하단에 표시 \n\n"<b>"레벨 0"</b>" \n- 앱의 모든 알림 차단"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"알림"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"더 이상 다음의 알림을 받지 않습니다"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"다시 시작"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"설정"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g>에서 <xliff:g id="ARTIST_NAME">%2$s</xliff:g>의 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생 중"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"재생"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> 열기"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>에서 <xliff:g id="ARTIST_NAME">%2$s</xliff:g>의 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"그룹"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"기기 1대 선택됨"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"기기 <xliff:g id="COUNT">%1$d</xliff:g>대 선택됨"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>(연결 끊김)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(연결 끊김)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"연결할 수 없습니다. 다시 시도하세요."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"새 기기와 페어링"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"빌드 번호"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"모두 보기"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"네트워크를 전환하려면 이더넷을 연결 해제하세요."</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"기기 환경을 개선하기 위해 Wi‑Fi가 꺼져 있을 때도 앱과 서비스에서 Wi‑Fi 네트워크를 검색할 수 있습니다. 이 설정은 Wi‑Fi 검색 설정에서 변경할 수 있습니다. "<annotation id="link">"변경"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"사용자 선택"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index c86f2f93da52..272ea77ec5f9 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Эскертмелерди башкаруу каражаттары"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Күйүк"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Өчүк"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Күйүк – Жүздүн негизинде"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Бул функциянын жардамы менен, ар бир колдонмо үчүн билдирменин маанилүүлүгүн 0дон 5ке чейин бааласаңыз болот. \n\n"<b>"5-деңгээл"</b>" \n- Билдирмелер тизмесинин өйдө жагында көрүнөт \n- Билдирмелер толук экранда көрүнөт \n- Калкып чыгуучу билдирмелерге уруксат берилет \n\n"<b>"4-деңгээл"</b>" \n- Билдирмелер толук экранда көрүнбөйт \n- Калкып чыгуучу билдирмелерге уруксат берилет \n\n"<b>"3-деңгээл"</b>" \n- Билдирмелер толук экранда көрүнбөйт \n- Калкып чыгуучу билдирмелерге тыюу салынат \n\n"<b>"2-деңгээл"</b>" \n- Билдирмелер толук экранда көрүнбөйт \n- Калкып чыгуучу билдирмелерге тыюу салынат \n- Эч качан үн чыкпайт же дирилдебейт \n\n"<b>"1-деңгээл"</b>" \n- Билдирмелер толук экранда көрүнбөйт \n- Калкып чыгуучу билдирмелерге тыюу салынат \n- Эч качан үн чыкпайт же дирилдебейт \n- Кулпуланган экрандан жана абал тилкесинен жашырылат \n- Билдирмелер тизмесинин ылдый жагында көрүнөт \n\n"<b>"0-деңгээл"</b>" \n- Колдонмодон алынган бардык билдирмелер бөгөттөлөт"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Билдирмелер"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Мындан ары бул билдирмелер сизге көрүнбөйт"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Улантуу"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Жөндөөлөр"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ыры (аткаруучу: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> колдонмосунан ойнотулуп жатат"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> ичинен <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Ойнотуу"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> колдонмосун ачуу"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ырын (аткаруучу: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> колдонмосунан ойнотуу"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Топ"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 түзмөк тандалды"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> түзмөк тандалды"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ажыратылды)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ажыратылды)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Байланышпай койду. Кайталоо."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Жаңы түзмөктү жупташтыруу"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Курама номери"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Баарын көрүү"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Башка тармактарга которулуу үчүн Ethernet кабелин ажыратыңыз"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Түзмөктүн колдонулушун жакшыртуу үчүн колдонмолор менен кызматтар Wi‑Fi өчүп турса да зымсыз тармактарды издей беришет. Аны Wi-Fi тармактарын издөө жөндөөлөрүнөн өзгөртө аласыз. "<annotation id="link">"Өзгөртүү"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Колдонуучуну тандоо"</string>
</resources>
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index ea456d81aa25..ac4dfd212bb8 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -28,9 +28,6 @@
<!-- The number of columns that the top level tiles span in the QuickSettings -->
<integer name="quick_settings_user_time_settings_tile_span">2</integer>
- <!-- We have only space for one notification on phone landscape layouts. -->
- <integer name="keyguard_max_notification_count">1</integer>
-
<!-- orientation of the dead zone when touches have recently occurred elsewhere on screen -->
<integer name="navigation_bar_deadzone_orientation">1</integer>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 34bf28aa741e..fc5edf3ade8f 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -32,16 +32,16 @@
-->
<dimen name="qs_customize_header_min_height">48dp</dimen>
+ <!-- In landscape the security footer is actually part of the header,
+ and needs to be as short as the header -->
<dimen name="qs_security_footer_single_line_height">@*android:dimen/quick_qs_offset_height</dimen>
<dimen name="qs_footer_padding">14dp</dimen>
- <dimen name="qs_footers_margin_bottom">0dp</dimen>
<dimen name="qs_security_footer_background_inset">12dp</dimen>
- <dimen name="qs_security_footer_corner_radius">28dp</dimen>
<dimen name="battery_detail_graph_space_top">9dp</dimen>
<dimen name="battery_detail_graph_space_bottom">9dp</dimen>
- <dimen name="qs_detail_margin_top">14dp</dimen>
+ <dimen name="qs_detail_header_margin_top">14dp</dimen>
<dimen name="volume_tool_tip_top_margin">12dp</dimen>
<dimen name="volume_row_slider_height">128dp</dimen>
diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml
index 82cba58e48fa..f3d83645a8e0 100644
--- a/packages/SystemUI/res/values-land/styles.xml
+++ b/packages/SystemUI/res/values-land/styles.xml
@@ -25,12 +25,6 @@
<item name="android:layout_gravity">center_horizontal</item>
</style>
- <style name="DockedDividerHandle">
- <item name="android:layout_gravity">center_vertical</item>
- <item name="android:layout_width">48dp</item>
- <item name="android:layout_height">96dp</item>
- </style>
-
<style name="DockedDividerMinimizedShadow">
<item name="android:layout_width">8dp</item>
<item name="android:layout_height">match_parent</item>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 969818e28d26..820dab60f6d5 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"ການຄວບຄຸມການແຈ້ງເຕືອນ"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"ເປີດ"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"ປິດ"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ເປີດ - ອ້າງອີງໃບໜ້າ"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"ດ້ວຍການຄວບຄຸມການແຈ້ງເຕືອນ, ທ່ານສາມາດຕັ້ງລະດັບຄວາມສຳຄັນຈາກ 0 ຮອດ 5 ໃຫ້ກັບການແຈ້ງເຕືອນແອັບໃດໜຶ່ງໄດ້. \n\n"<b>"ລະດັບ 5"</b>" \n- ສະແດງຢູ່ເທິງສຸດຂອງລາຍການແຈ້ງເຕືອນ \n- ອະນຸຍາດໃຫ້ຂັດຈັງຫວະຕອນເປີດເຕັມຈໍ \n- ແນມເບິ່ງທຸກເທື່ອ \n\n"<b>"ລະດັບ 4"</b>" \n- ກັນບໍ່ໃຫ້ຂັດຈັງຫວະຕອນເປີດເຕັມຈໍ \n- ແນມເບິ່ງທຸກເທື່ອ \n\n"<b>"ລະດັບ 3"</b>" \n- ກັນບໍ່ໃຫ້ຂັດຈັງຫວະຕອນເປີດເຕັມຈໍ \n- ບໍ່ແນມເບິ່ງ \n\n"<b>"ລະດັບ 2"</b>" \n- ກັນບໍ່ໃຫ້ຂັດຈັງຫວະຕອນເປີດເຕັມຈໍ \n- ບໍ່ແນມເບິ່ງ \n- ບໍ່ມີສຽງ ແລະ ບໍ່ມີການສັ່ນເຕືອນ \n\n"<b>"ລະດັບ 1"</b>" \n- ກັນບໍ່ໃຫ້ຂັດຈັງຫວະຕອນເປີດເຕັມຈໍ \n- ບໍ່ແນມເບິ່ງ \n- ບໍ່ມີສຽງ ແລະ ບໍ່ມີການສັ່ນເຕືອນ \n- ເຊື່ອງຈາກໜ້າຈໍລັອກ ແລະ ແຖບສະຖານະ \n- ສະແດງຢູ່ລຸ່ມສຸດຂອງລາຍການແຈ້ງເຕືອນ \n\n"<b>"ລະດັບ 0"</b>" \n- ປິດກັ້ນການແຈ້ງເຕືອນທັງໝົດຈາກແອັບ"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"ການແຈ້ງເຕືອນ"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"ທ່ານຈະບໍ່ໄດ້ຮັບການແຈ້ງເຕືອນເຫຼົ່ານີ້ອີກຕໍ່ໄປ"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"ສືບຕໍ່"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ການຕັ້ງຄ່າ"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ໂດຍ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ກຳລັງຫຼິ້ນຈາກ <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> ຈາກ <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ຫຼິ້ນ"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"ເປີດ <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"ຫຼິ້ນ <xliff:g id="SONG_NAME">%1$s</xliff:g> ໂດຍ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ຈາກ <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"ກຸ່ມ"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"ເລືອກ 1 ອຸປະກອນແລ້ວ"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"ເລືອກ <xliff:g id="COUNT">%1$d</xliff:g> ອຸປະກອນແລ້ວ"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ຕັດການເຊື່ອມຕໍ່ແລ້ວ)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ຕັດການເຊື່ອມຕໍ່ແລ້ວ)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້. ລອງໃໝ່."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ຈັບຄູ່ອຸປະກອນໃໝ່"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"ໝາຍເລກສ້າງ"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"ເບິ່ງທັງໝົດ"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ເພື່ອສະຫຼັບເຄືອຂ່າຍ, ໃຫ້ຕັດການເຊື່ອມຕໍ່ອີເທີເນັດກ່ອນ"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"ເພື່ອປັບປຸງປະສົບການອຸປະກອນ, ແອັບ ແລະ ບໍລິການຍັງຄົງສາມາດສະແກນຫາເຄືອຂ່າຍ Wi‑Fi ຕອນໃດກໍໄດ້, ເຖິງແມ່ນວ່າຈະປິດ Wi‑Fi ໄວ້ກໍຕາມ. ທ່ານສາມາດປ່ຽນສິ່ງນີ້ໄດ້ໃນການຕັ້ງຄ່າການສະແກນ Wi‑Fi. "<annotation id="link">"ປ່ຽນ"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ເລືອກຜູ້ໃຊ້"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index be976160951f..6a4ecfe06870 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -716,7 +716,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Galingi pranešimų valdikliai"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Įjungta"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Išjungta"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Įjungta – pagal veidą"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Naudodami pranešimų valdiklius galite nustatyti programos pranešimų svarbos lygį nuo 0 iki 5. \n\n"<b>"5 lygis"</b>" \n– Rodyti pranešimų sąrašo viršuje \n– Leisti pertraukti, kai veikia viso ekrano režimas \n– Visada rodyti pranešimus \n\n"<b>"4 lygis"</b>" \n– Neleisti pertraukti viso ekrano režimo \n– Visada rodyti pranešimus \n\n"<b>"3 lygis"</b>" \n– Neleisti pertraukti viso ekrano režimo \n– Niekada nerodyti pranešimų \n\n"<b>"2 lygis"</b>" \n– Neleisti pertraukti viso ekrano režimo \n– Niekada nerodyti pranešimų \n– Niekada neleisti garso ir nevibruoti \n\n"<b>"1 lygis"</b>" \n– Neleisti pertraukti viso ekrano režimo \n– Niekada nerodyti pranešimų \n– Niekada neleisti garso ir nevibruoti \n– Slėpti užrakinimo ekrane ir būsenos juostoje \n– Rodyti pranešimų sąrašo apačioje \n\n"<b>"0 lygis"</b>" \n– Blokuoti visus programos pranešimus"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Pranešimai"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Nebematysite šių pranešimų"</string>
@@ -1103,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Tęsti"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Nustatymai"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> – „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ leidžiama iš „<xliff:g id="APP_LABEL">%3$s</xliff:g>“"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> iš <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Leisti"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Atidaryti „<xliff:g id="APP_LABEL">%1$s</xliff:g>“"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Leisti <xliff:g id="ARTIST_NAME">%2$s</xliff:g> – „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ iš „<xliff:g id="APP_LABEL">%3$s</xliff:g>“"</string>
@@ -1123,7 +1123,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupė"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Pasirinktas 1 įrenginys"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Pasirinkta įrenginių: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"„<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“ (atjungta)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(atjungta)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nepavyko prijungti. Bandykite dar kartą."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Naujo įrenginio susiejimas"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Versijos numeris"</string>
@@ -1192,4 +1192,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Žiūrėti viską"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Norėdami perjungti tinklus, atjunkite eternetą"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Kad pagerintų įrenginio funkcijas, programos ir paslaugos vis tiek gali bet kada nuskaityti ieškodamos „Wi‑Fi“ tinklų, net jei „Wi‑Fi“ išjungtas. Tai galite pakeisti „Wi-Fi“ nuskaitymo nustatymuose. "<annotation id="link">"Pakeisti"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Naudotojo pasirinkimas"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index e4ba5df7631f..0ec38d3c4f81 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -713,7 +713,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Barošanas paziņojumu vadīklas"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Ieslēgts"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Izslēgts"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Ieslēgta — ar sejas noteikšanu"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Izmantojot barošanas paziņojumu vadīklas, varat lietotnes paziņojumiem iestatīt svarīguma līmeni (no 0 līdz 5). \n\n"<b>"5. līmenis"</b>" \n- Tiek rādīts paziņojumu saraksta augšdaļā \n- Tiek atļauta pilnekrāna režīma pārtraukšana \n- Ieskats vienmēr atļauts \n\n"<b>"4. līmenis"</b>" \n- Tiek novērsta pilnekrāna režīma pārtraukšana \n- Ieskats vienmēr atļauts \n\n"<b>"3. līmenis"</b>" \n- Tiek novērsta pilnekrāna režīma pārtraukšana \n- Ieskats nav atļauts \n\n"<b>"2. līmenis"</b>" \n- Tiek novērsta pilnekrāna režīma pārtraukšana \n- Ieskats nav atļauts \n- Nav atļautas skaņas un vibrosignāls \n\n"<b>"1. līmenis"</b>" \n- Tiek novērsta pilnekrāna režīma pārtraukšana \n- Ieskats nav atļauts \n- Nav atļautas skaņas un vibrosignāls \n- Paziņojumi tiek paslēpti bloķēšanas ekrānā un statusa joslā \n- Paziņojumi tiek rādīti paziņojumu saraksta apakšdaļā \n\n"<b>"0. līmenis"</b>" \n- Visi lietotnes paziņojumi tiek bloķēti"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Paziņojumi"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Jūs vairs neredzēsiet šos paziņojumus."</string>
@@ -1097,6 +1096,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Atsākt"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Iestatījumi"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Tiek atskaņots fails “<xliff:g id="SONG_NAME">%1$s</xliff:g>” (izpildītājs: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) no lietotnes <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> no <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Atskaņot"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Atveriet lietotni <xliff:g id="APP_LABEL">%1$s</xliff:g>."</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Atskaņojiet failu “<xliff:g id="SONG_NAME">%1$s</xliff:g>” (izpildītājs: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) no lietotnes <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
@@ -1117,7 +1117,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Atlasīta viena ierīce"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Atlasītas vairākas ierīces (kopā <xliff:g id="COUNT">%1$d</xliff:g>)"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (savienojums pārtraukts)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(savienojums pārtraukts)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nevarēja izveidot savienojumu. Mēģiniet vēlreiz."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Savienošana pārī ar jaunu ierīci"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Versijas numurs"</string>
@@ -1186,4 +1186,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Visu tīklu skatīšana"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Lai pārslēgtu tīklus, atvienojiet tīkla Ethernet vadu."</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Lai uzlabotu ierīces lietošanas iespējas, lietotnes un pakalpojumi joprojām varēs meklēt Wi‑Fi tīklus jebkurā laikā, pat ja Wi‑Fi būs izslēgts. Varat to mainīt Wi‑Fi meklēšanas iestatījumos. "<annotation id="link">"Mainīt"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Lietotāja atlase"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 721bd2b9fdbf..e0a971e86046 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Контроли за известувањата за напојување"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Вклучено"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Исклучено"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Вклучено - според лице"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Со контролите за известувањата за напојување, може да поставите ниво на важност од 0 до 5 за известувањата на која било апликација. \n\n"<b>"Ниво 5"</b>" \n- Прикажувај на врвот на списокот со известувања \n- Дозволи прекин во цел екран \n- Секогаш користи појавување \n\n"<b>"Ниво 4"</b>" \n- Спречи прекин во цел екран \n- Секогаш користи појавување \n\n"<b>"Ниво 3"</b>" \n- Спречи прекин во цел екран \n- Без појавување \n\n"<b>"Ниво 2"</b>" \n- Спречи прекин во цел екран \n- Без појавување \n- Без звук и вибрации \n\n"<b>"Ниво 1"</b>" \n- Спречи прекин во цел екран \n- Без појавување \n- Без звук и вибрации \n- Сокриј од заклучен екран и статусна лента \n- Прикажувај на дното на списокот со известувања \n\n"<b>"Ниво 0"</b>" \n- Блокирај ги сите известувања од апликацијата"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Известувања"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Веќе нема да ги гледате овие известувања"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Продолжи"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Поставки"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> од <xliff:g id="ARTIST_NAME">%2$s</xliff:g> е пуштено на <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> од <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Пушти"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Отворете <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пуштете <xliff:g id="SONG_NAME">%1$s</xliff:g> од <xliff:g id="ARTIST_NAME">%2$s</xliff:g> на <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Избран е 1 уред"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Избрани се <xliff:g id="COUNT">%1$d</xliff:g> уреди"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (исклучен)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(врската е прекината)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Не може да се поврзе. Обидете се повторно."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Спарете нов уред"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Број на верзија"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Прикажи ги сите"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"За промена на мрежата, прекинете ја врската со етернетот"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"За да се подобри доживувањето на уредот, апликациите и услугите може сѐ уште да скенираат за Wi‑Fi мрежи во секое време, дури и кога Wi‑Fi е исклучено. Може да го промените ова во поставките за „Скенирање за Wi-Fi“. "<annotation id="link">"Промени"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Изберете корисник"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index e432a1dc9ee5..6fe5815b1543 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"പവർ അറിയിപ്പ് നിയന്ത്രണങ്ങൾ"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"ഓൺ"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"ഓഫ്"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ഓണാണ് - ഫേസ് ബേസ്‌ഡ്"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"പവർ അറിയിപ്പ് നിയന്ത്രണം ഉപയോഗിച്ച്, ഒരു ആപ്പിനായുള്ള അറിയിപ്പുകൾക്ക് 0 മുതൽ 5 വരെയുള്ള പ്രാധാന്യ ലെവലുകളിലൊന്ന് നിങ്ങൾക്ക് സജ്ജമാക്കാവുന്നതാണ്. \n\n"<b>"ലെവൽ 5"</b>" \n- അറിയിപ്പ് ലിസ്റ്റിന്റെ മുകളിൽ കാണിക്കുക \n- മുഴുവൻ സ്ക്രീൻ തടസ്സം അനുവദിക്കുക \n- എല്ലായ്പ്പോഴും ദൃശ്യമാക്കുക \n\n"<b>"ലെവൽ 4"</b>" \n- മുഴുവൻ സ്ക്രീൻ തടസ്സം തടയുക \n- എല്ലായ്പ്പോഴും ദൃശ്യമാക്കുക \n\n"<b>"ലെവൽ 3"</b>" \n- മുഴുവൻ സ്ക്രീൻ തടസ്സം തടയുക \n- ഒരിക്കലും സൃശ്യമാക്കരുത് \n\n"<b>"ലെവൽ 2"</b>" \n- മുഴുവൻ സ്ക്രീൻ തടസ്സം തടയുക \n- ഒരിക്കലും ദൃശ്യമാക്കരുത് \n- ഒരിക്കലും ശബ്ദവും വൈബ്രേഷനും ഉണ്ടാക്കരുത് \n\n"<b>"ലെവൽ 1"</b>" \n- മുഴുവൻ സ്ക്രീൻ തടസ്സം തടയുക \n- ഒരിക്കലും ദൃശ്യമാക്കരുത് \n- ഒരിക്കലും ശബ്ദവും വൈബ്രേഷനും ഉണ്ടാക്കരുത് \n- ലോക്ക് സ്ക്രീനിൽ നിന്നും സ്റ്റാറ്റസ് ബാറിൽ നിന്നും മറയ്ക്കുക \n- അറിയിപ്പ് ലിസ്റ്റിന്റെ അടിയിൽ കാണിക്കുക \n\n"<b>"ലെവൽ 0"</b>" \n- ആപ്പിൽ നിന്നുള്ള എല്ലാ അറിയിപ്പുകളും ബ്ലോക്കുചെയ്യുക"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"അറിയിപ്പുകൾ"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"നിങ്ങൾ ഇനി ഈ അറിയിപ്പുകൾ കാണില്ല"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"പുനരാരംഭിക്കുക"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ക്രമീകരണം"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> എന്ന ആർട്ടിസ്റ്റിന്റെ <xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%3$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുന്നു"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>-ൽ <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"പ്ലേ ചെയ്യുക"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> തുറക്കുക"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> എന്ന ആർട്ടിസ്റ്റിന്റെ <xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%3$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുക"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"ഗ്രൂപ്പ്"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"ഒരു ഉപകരണം തിരഞ്ഞെടുത്തു"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> ഉപകരണങ്ങൾ തിരഞ്ഞെടുത്തു"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (വിച്ഛേദിച്ചു)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(വിച്ഛേദിച്ചു)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"കണക്റ്റ് ചെയ്യാനായില്ല. വീണ്ടും ശ്രമിക്കുക."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"പുതിയ ഉപകരണവുമായി ജോടിയാക്കുക"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"ബിൽഡ് നമ്പർ"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"എല്ലാം കാണുക"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"മറ്റ് നെറ്റ്‌വർക്കുകളിലേക്ക് മാറാൻ, ഇതർനെറ്റ് വിച്ഛേദിക്കുക"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"ഉപകരണ അനുഭവം മെച്ചപ്പെടുത്താൻ, വൈഫൈ ഓഫാക്കിയിരിക്കുമ്പോൾ പോലും ആപ്പുകൾക്കും സേവനങ്ങൾക്കും വൈഫൈ നെറ്റ്‌വർക്കുകൾ കണ്ടെത്താൻ ഏത് സമയത്തും സ്‌കാൻ ചെയ്യാനാകും. നിങ്ങൾക്ക് ഇത് വൈഫൈ സ്‌കാനിംഗ് ക്രമീകരണത്തിൽ മാറ്റാം. "<annotation id="link">"മാറ്റുക"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ഉപയോക്താവിനെ തിരഞ്ഞെടുക്കൂ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index cbab4da4909f..86869533af94 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Тэжээлийн мэдэгдлийн удирдлага"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Идэвхтэй"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Идэвхгүй"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Асаалттай - Царайнд суурилсан"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Тэжээлийн мэдэгдлийн удирдлагын тусламжтайгаар та апп-н мэдэгдэлд 0-5 хүртэлх ач холбогдлын түвшин тогтоох боломжтой. \n\n"<b>"5-р түвшин"</b>" \n- Мэдэгдлийн жагсаалтын хамгийн дээр харуулна \n- Бүтэн дэлгэцэд саад болно \n- Дэлгэцэд тогтмол гарч ирнэ \n\n"<b>"4-р түвшин"</b>" \n- Бүтэн дэлгэцэд саад болохоос сэргийлнэ \n- Дэлгэцэд тогтмол гарч ирнэ \n\n"<b>"3-р түвшин"</b>" \n- Бүтэн дэлгэцэд саад болохоос сэргийлнэ \n- Дэлгэцэд хэзээ ч гарч ирэхгүй \n\n"<b>"2-р түвшин"</b>" \n- Бүтэн дэлгэцэд саад болохоос сэргийлнэ \n- Дэлгэцэд хэзээ ч гарч ирэхгүй \n- Дуу болон чичиргээ хэзээ ч гаргахгүй \n\n"<b>"1-р түвшин"</b>" \n- Бүтэн дэлгэцэд саад болохоос сэргийлнэ \n- Дэлгэцэд хэзээ ч гарч ирэхгүй \n- Дуу болон чичиргээ хэзээ ч гаргахгүй \n- Түгжигдсэн дэлгэц болон статусын самбараас нууна \n- Мэдэгдлийн жагсаалтын доор харуулна \n\n"<b>"0-р түвшин"</b>" \n- Энэ апп-н бүх мэдэгдлийг блоклоно"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Мэдэгдэл"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Та эдгээр мэдэгдлийг цаашид харахгүй"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Үргэлжлүүлэх"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Тохиргоо"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> дээр тоглуулж буй <xliff:g id="ARTIST_NAME">%2$s</xliff:g>-н <xliff:g id="SONG_NAME">%1$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>-н <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Тоглуулах"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g>-г нээх"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-н <xliff:g id="SONG_NAME">%1$s</xliff:g>-г <xliff:g id="APP_LABEL">%3$s</xliff:g> дээр тоглуулах"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Бүлэг"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 төхөөрөмж сонгосон"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> төхөөрөмж сонгосон"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (салсан)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(салсан)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Холбогдож чадсангүй. Дахин оролдоно уу."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Шинэ төхөөрөмж хослуулах"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Хийцийн дугаар"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Бүгдийг харах"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Сүлжээг сэлгэхийн тулд этернэтийг салгана уу"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Төхөөрөмжийн туршлагыг сайжруулахын тулд аппууд болон үйлчилгээнүүд нь Wi-Fi сүлжээг хүссэн үедээ буюу Wi-Fi-г унтраалттай байсан ч скан хийх боломжтой хэвээр байна. Та үүнийг Wi-Fi скан хийх тохиргоонд өөрчлөх боломжтой. "<annotation id="link">"Өөрчлөх"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Хэрэглэгч сонгох"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 2e632806668f..978217ba2d99 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"पॉवर सूचना नियंत्रणे"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"सुरू"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"बंद"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"सुरू - चेहऱ्यावर आधारित"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"पॉवर सूचना नियंत्रणांच्या साहाय्याने तुम्ही अ‍ॅप सूचनांसाठी 0 ते 5 असे महत्त्व स्तर सेट करू शकता. \n\n"<b>"स्तर 5"</b>" \n- सूचना सूचीच्या शीर्षस्थानी दाखवा \n- फुल स्क्रीन व्यत्ययास अनुमती द्या \n- नेहमी डोकावून पहा \n\n"<b>"स्तर 4"</b>\n" - फुल स्क्रीन व्यत्ययास प्रतिबंधित करा \n- नेहमी डोकावून पहा \n\n"<b>"स्तर 3"</b>" \n- फुल स्क्रीन व्यत्ययास प्रतिबंधित करा \n- कधीही डोकावून पाहू नका \n\n"<b>"स्तर 2"</b>" \n- फुल स्क्रीन व्यत्ययास प्रतिबंधित करा \n- कधीही डोकावून पाहू नका \n- कधीही ध्वनी किंवा व्हायब्रेट करू नका \n\n"<b>"स्तर 1"</b>\n"- फुल स्क्रीन व्यत्ययास प्रतिबंधित करा \n- कधीही डोकावून पाहू नका \n- कधीही ध्वनी किंवा व्हायब्रेट करू नका \n- लॉक स्क्रीन आणि स्टेटस बार मधून लपवा \n- सूचना सूचीच्या तळाशी दर्शवा \n\n"<b>"स्तर 0"</b>" \n- अ‍ॅपमधील सर्व सूचना ब्लॉक करा"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"सूचना"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"आता तुम्हाला या सूचना दिसणार नाहीत"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"पुन्हा सुरू करा"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिंग्ज"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> मध्ये <xliff:g id="ARTIST_NAME">%2$s</xliff:g> चे <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले होत आहे"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> पैकी <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"प्ले करणे"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> उघडा"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> मध्ये <xliff:g id="ARTIST_NAME">%2$s</xliff:g> चे <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले करा"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"गट"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"एक डिव्हाइस निवडले"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> डिव्हाइस निवडली आहेत"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (डिस्कनेक्ट केले)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(डिस्कनेक्ट केलेले)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"कनेक्ट करू शकलो नाही. पुन्हा प्रयत्न करा."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"नवीन डिव्हाइससोबत पेअर करा"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"बिल्ड नंबर"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"सर्व पहा"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"नेटवर्क स्विच करण्यासाठी, इथरनेट केबल डिस्कनेक्ट करा"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"डिव्हाइसच्या अनुभवामध्ये सुधारणा करण्यासाठी, वाय-फाय बंद असले तरीही ॲप्स आणि सेवा या कधीही वाय-फाय नेटवर्क स्कॅन करू शकतात. तुम्ही हे वाय-फाय स्कॅनिंग सेटिंग्जमध्ये बदलू शकता. "<annotation id="link">"बदला"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"वापरकर्ता निवडा"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 7bc56237d5eb..35909d8e75f9 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Kawalan pemberitahuan berkuasa"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Hidup"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Mati"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Hidup - Berasaskan wajah"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Dengan kawalan pemberitahuan berkuasa, anda boleh menetapkan tahap kepentingan dari 0 hingga 5 untuk pemberitahuan apl. \n\n"<b>"Tahap 5"</b>" \n- Tunjukkan pada bahagian atas senarai pemberitahuan \n- Benarkan gangguan skrin penuh \n- Sentiasa intai \n\n"<b>"Tahap 4"</b>" \n- Halang gangguan skrin penuh \n- Sentiasa intai \n\n"<b>"Tahap 3"</b>" \n- Halang gangguan skrin penuh \n- Jangan intai \n\n"<b>"Tahap 2"</b>" \n- Halang gangguan skrin penuh \n- Jangan intai \n- Jangan berbunyi dan bergetar \n\n"<b>"Tahap 1"</b>" \n- Halang gangguan skrin penuh \n- Jangan intai \n- Jangan berbunyi atau bergetar \n- Sembunyikan daripada skrin kunci dan bar status \n- Tunjukkan di bahagian bawah senarai pemberitahuan \n\n"<b>"Tahap 0"</b>" \n- Sekat semua pemberitahuan daripada apl"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Pemberitahuan"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Anda tidak akan melihat pemberitahuan ini lagi"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Sambung semula"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Tetapan"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> dimainkan daripada <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> daripada <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Main"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Buka <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mainkan <xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> daripada <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Kumpulan"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 peranti dipilih"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> peranti dipilih"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (diputuskan sambungan)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(diputuskan sambungan)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Tidak boleh menyambung. Cuba lagi."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Gandingkan peranti baharu"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Nombor binaan"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Lihat semua"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Untuk menukar rangkaian, putuskan sambungan ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Untuk meningkatkan pengalaman peranti, apl dan perkhidmatan masih dapat melakukan imbasan untuk mengesan rangkaian Wi-Fi pada bila-bila masa, meskipun apabila Wi-Fi dimatikan. Anda boleh menukar tetapan ini dalam tetapan pengimbasan Wi-Fi. "<annotation id="link">"Tukar"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pilih pengguna"</string>
</resources>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 7c313982f490..ec665e920d43 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"ပါဝါအကြောင်းကြားချက် ထိန်းချုပ်မှုများ"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"ဖွင့်"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"ပိတ်"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ဖွင့် - မျက်နှာအခြေခံ"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"ပါဝါအကြောင်းကြားချက် ထိန်းချုပ်မှုများကိုအသုံးပြုပြီး အက်ပ်တစ်ခု၏ အကြောင်းကြားချက် အရေးပါမှု ၀ မှ ၅ အထိသတ်မှတ်ပေးနိုင်သည်။ \n\n"<b>"အဆင့် ၅"</b>" \n- အကြောင်းကြားချက်စာရင်း၏ ထိပ်ဆုံးတွင် ပြသည် \n- မျက်နှာပြင်အပြည့် ကြားဖြတ်ဖော်ပြခြင်းကို ခွင့်ပြုသည် \n- အမြဲတမ်း ခေတ္တပြပါမည် \n\n"<b>"အဆင့် ၄"</b>" \n- မျက်နှာပြင်အပြည့် ကြားဖြတ်ဖော်ပြခြင်း မရှိစေရန် ကာကွယ်ပေးသည် \n- အမြဲတမ်း ခေတ္တပြပါမည် \n\n"<b>"အဆင့် ၃"</b>" \n- မျက်နှာပြင်အပြည့် ကြားဖြတ်ဖော်ပြခြင်း မရှိစေရန် ကာကွယ်ပေးသည် \n- ဘယ်တော့မှ ခေတ္တပြခြင်း မရှိပါ \n\n"<b>"အဆင့် ၂"</b>" \n- မျက်နှာပြင်အပြည့် ကြားဖြတ်ဖော်ပြခြင်း မရှိစေရန် ကာကွယ်ပေးသည် \n- ဘယ်တော့မှ ခေတ္တပြခြင်း မရှိပါ \n- အသံမြည်ခြင်းနှင့် တုန်ခါခြင်းများ ဘယ်တော့မှ မပြုလုပ်ပါ \n\n"<b>"အဆင့် ၁"</b>" \n- မျက်နှာပြင်အပြည့် ကြားဖြတ်ဖော်ပြခြင်း မရှိစေရန် ကာကွယ်ပေးသည် \n- ဘယ်တော့မှ ခေတ္တပြခြင်း မရှိပါ \n- အသံမြည်ခြင်းနှင့် တုန်ခါခြင်းများ ဘယ်တော့မှ မပြုလုပ်ပါ \n- လော့ခ်ချထားသည့် မျက်နှာပြင်နှင့် အခြေအနေဘားတန်းတို့တွင် မပြပါ \n- အကြောင်းကြားချက်စာရင်း အောက်ဆုံးတွင်ပြသည် \n\n"<b>"အဆင့် ၀"</b>" \n- အက်ပ်မှ အကြောင်းကြားချက်များ အားလုံးကို ပိတ်ဆို့သည်"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"အကြောင်းကြားချက်များ"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"ဤအကြောင်းကြားချက်များကို မြင်ရတော့မည် မဟုတ်ပါ"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"ဆက်လုပ်ရန်"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ဆက်တင်များ"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ၏ <xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%3$s</xliff:g> တွင် ဖွင့်ထားသည်"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> အနက် <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ဖွင့်ခြင်း"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ကို ဖွင့်ပါ"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ၏ <xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%3$s</xliff:g> တွင် ဖွင့်ပါ"</string>
@@ -1111,7 +1111,8 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"အုပ်စု"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"စက်ပစ္စည်း ၁ ခုကို ရွေးချယ်ထားသည်"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"စက်ပစ္စည်း <xliff:g id="COUNT">%1$d</xliff:g> ခုကို ရွေးချယ်ထားသည်"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ချိတ်ဆက်မထားပါ)"</string>
+ <!-- no translation found for media_output_dialog_disconnected (7090512852817111185) -->
+ <skip />
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ချိတ်ဆက်၍ မရပါ။ ထပ်စမ်းကြည့်ပါ။"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"စက်အသစ် တွဲချိတ်ရန်"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"တည်ဆောက်မှုနံပါတ်"</string>
@@ -1180,4 +1181,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"အားလုံးကြည့်ရန်"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ကွန်ရက်ပြောင်းရန် အီသာနက်ကို ချိတ်ဆက်မှုဖြုတ်ပါ"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"စက်ပစ္စည်းကို ပိုမိုကောင်းမွန်စွာ အသုံးပြုနိုင်ရန် Wi-Fi ပိတ်ထားသည့်တိုင် အက်ပ်နှင့် ဝန်ဆောင်မှုများက Wi-Fi ကွန်ရက်များကို အချိန်မရွေး စကင်ဖတ်နိုင်သည်။ ၎င်းကို Wi-Fi ရှာဖွေခြင်း ဆက်တင်များတွင် ပြောင်းနိုင်သည်။ "<annotation id="link">"ပြောင်းရန်"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"အသုံးပြုသူ ရွေးခြင်း"</string>
</resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 628b6a99883d..0c911203bec3 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Effektive varselinnstillinger"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"På"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Av"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"På – ansiktsbasert"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Med effektive varselinnstillinger kan du angi viktighetsnivåer fra 0 til 5 for appvarsler. \n\n"<b>"Nivå 5"</b>" \n– Vis øverst på varsellisten \n– Tillat forstyrrelser ved fullskjermmodus \n– Vis alltid raskt \n\n"<b>"Nivå 4"</b>" \n– Forhindre forstyrrelser ved fullskjermmodus \n– Vis alltid raskt \n\n"<b>"Nivå 3"</b>" \n– Forhindre forstyrrelser ved fullskjermmodus \n– Vis aldri raskt \n\n"<b>"Nivå 2"</b>" \n– Forhindre forstyrrelser ved fullskjermmodus \n– Vis aldri fort \n– Tillat aldri lyder eller vibrering \n\n"<b>"Nivå 1"</b>" \n– Forhindre forstyrrelser ved fullskjermmodus \n– Vis aldri raskt \n– Tillat aldri lyder eller vibrering \n– Skjul fra låseskjermen og statusfeltet \n– Vis nederst på varsellisten \n\n"<b>"Nivå 0"</b>" \n– Blokkér alle varsler fra appen"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Varsler"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Du ser ikke disse varslene lenger"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Gjenoppta"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Innstillinger"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> av <xliff:g id="ARTIST_NAME">%2$s</xliff:g> spilles av fra <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> av <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Spill av"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Åpne <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spill av <xliff:g id="SONG_NAME">%1$s</xliff:g> av <xliff:g id="ARTIST_NAME">%2$s</xliff:g> fra <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 enhet er valgt"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> enheter er valgt"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (frakoblet)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(frakoblet)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Kunne ikke koble til. Prøv på nytt."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Koble til en ny enhet"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Delversjonsnummer"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Se alle"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"For å bytte nettverk, koble fra Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"For å forbedre brukeropplevelsen på enheten kan apper og tjenester søke etter Wi-Fi-nettverk når som helst – også når Wi-Fi er slått av. Du kan endre dette i innstillingene for Wi-Fi-skanning. "<annotation id="link">"Bytt"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Velg bruker"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index c6dfb37ce848..c9a97763acc4 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"सशक्त सूचना नियन्त्रण"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"अन छ"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"अफ"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"अन छ - अनुहारमा आधारित"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"सशक्त सूचना नियन्त्रणहरू मार्फत तपाईं अनुप्रयाेगका सूचनाहरूका लागि ० देखि ५ सम्मको महत्व सम्बन्धी स्तर सेट गर्न सक्नुहुन्छ। \n\n"<b>"स्तर ५"</b>" \n- सूचनाको सूचीको माथिल्लो भागमा देखाउने \n- पूर्ण स्क्रिनमा अवरोधका लागि अनुमति दिने \n- सधैँ चियाउने \n\n"<b>"स्तर ४"</b>" \n- पूर्ण स्क्रिनमा अवरोधलाई रोक्ने \n- सधैँ चियाउने \n\n"<b>"स्तर ३"</b>" \n- पूर्ण स्क्रिनमा अवरोधलाई रोक्ने \n- कहिल्यै नचियाउने \n\n"<b>"स्तर २"</b>" \n- पूर्ण स्क्रिनमा अवरोधलाई रोक्ने \n- कहिल्यै नचियाउने \n- कहिल्यै पनि आवाज ननिकाल्ने र कम्पन नगर्ने \n\n"<b>"स्तर १"</b>" \n- पूर्ण स्क्रिनमा अवरोध रोक्ने \n- कहिल्यै नचियाउने \n- कहिल्यै पनि आवाज ननिकाल्ने वा कम्पन नगर्ने \n- लक स्क्रिन र वस्तुस्थिति पट्टीबाट लुकाउने \n- सूचनाको सूचीको तल्लो भागमा देखाउने \n\n"<b>"स्तर ०"</b>" \n- एपका सबै सूचनाहरूलाई रोक्ने"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"सूचनाहरू"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"तपाईं अब उप्रान्त यी सूचनाहरू देख्नु हुने छैन"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"सुचारु गर्नुहोस्"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिङ"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> को <xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%3$s</xliff:g> मा बज्दै छ"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> मध्ये <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"प्ले गर्नुहोस्"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> खोल्नुहोस्"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> को <xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%3$s</xliff:g> मा बजाउनुहोस्"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"समूह"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"१ यन्त्र चयन गरियो"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> वटा यन्त्र चयन गरिए"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (डिस्कनेक्ट गरिएको)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(डिस्कनेक्ट गरिएको छ)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"कनेक्ट गर्न सकिएन। फेरि प्रयास गर्नुहोस्।"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"नयाँ डिभाइस कनेक्ट गर्नुहोस्"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"बिल्ड नम्बर"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"सबै नेटवर्क हेर्नुहोस्"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"नेटवर्क बदल्न इथरनेट डिस्कनेक्ट गर्नुहोस्"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"डिभाइस प्रयोगको अनुभवमा गुणस्तर सुधार गर्न, एप तथा सेवाहरूले अझै पनि जुनसुकै बेला (Wi‑Fi अफ भएका बेलामा पनि) Wi‑Fi नेटवर्क खोज्न सक्छन्। तपाईं यसलाई Wi‑Fi स्क्यानिङका सेटिङमा गई परिवर्तन गर्न सक्नुहुन्छ। "<annotation id="link">"बदल्नुहोस्"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"प्रयोगकर्ता चयन गर्नु…"</string>
</resources>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index ffcc3a821d36..07e28b6d7f20 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -16,7 +16,9 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog" />
+ <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog">
+ <item name="android:buttonCornerRadius">28dp</item>
+ </style>
<style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Dialog.Alert" />
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 00debe8d4112..a28a10ca461d 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Beheeropties voor meldingen met betrekking tot stroomverbruik"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Aan"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Uit"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Aan: op basis van gezicht"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Met beheeropties voor meldingen met betrekking tot stroomverbruik kun je een belangrijkheidsniveau van 0 tot 5 instellen voor de meldingen van een app. \n\n"<b>"Niveau 5"</b>" \n- Bovenaan de lijst met meldingen tonen \n- Onderbreking op volledig scherm toestaan \n- Altijd korte weergave \n\n"<b>"Niveau 4"</b>" \n- Geen onderbreking op volledig scherm \n- Altijd korte weergave \n\n"<b>"Niveau 3"</b>" \n- Geen onderbreking op volledig scherm \n- Nooit korte weergave \n\n"<b>"Niveau 2"</b>" \n- Geen onderbreking op volledig scherm \n- Nooit korte weergave \n- Nooit geluid laten horen of trillen \n\n"<b>"Niveau 1"</b>" \n- Geen onderbreking op volledig scherm \n- Nooit korte weergave \n- Nooit geluid laten horen of trillen \n- Verbergen op vergrendelscherm en statusbalk \n- Onderaan de lijst met meldingen tonen \n\n"<b>"Niveau 0"</b>" \n- Alle meldingen van de app blokkeren"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Meldingen"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Je ziet deze meldingen niet meer"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Hervatten"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Instellingen"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> van <xliff:g id="ARTIST_NAME">%2$s</xliff:g> wordt afgespeeld via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> van <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Afspelen"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> openen"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> van <xliff:g id="ARTIST_NAME">%2$s</xliff:g> afspelen via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Groep"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Eén apparaat geselecteerd"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> apparaten geselecteerd"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (verbinding verbroken)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(verbinding verbroken)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Kan geen verbinding maken. Probeer het nog eens."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Nieuw apparaat koppelen"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Build-nummer"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Alles tonen"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Verbreek de ethernetverbinding om van netwerk te wisselen"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Apps en services kunnen nog steeds op elk moment scannen op wifi-netwerken, zelfs als wifi uitstaat, om de apparaatfunctionaliteit te verbeteren. Je kunt dit aanpassen in de instellingen voor wifi-scannen. "<annotation id="link">"Wijzigen"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Gebruiker selecteren"</string>
</resources>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index a1e7013fec73..fce44d7e1592 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"ପାୱାର୍‍ ବିଜ୍ଞପ୍ତି କଣ୍ଟ୍ରୋଲ୍‌"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"ଚାଲୁ ଅଛି"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"ବନ୍ଦ"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ଚାଲୁ ଅଛି - ଫେସ-ଆଧାରିତ"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"ପାୱାର୍‍ ବିଜ୍ଞପ୍ତି କଣ୍ଟ୍ରୋଲ୍‌ରେ, ଆପଣ ଏକ ଆପ୍‍ ବିଜ୍ଞପ୍ତି ପାଇଁ 0 ରୁ 5 ଗୁରୁତ୍ୱ ସ୍ତର ସେଟ୍‍ କରିହେବେ। \n\n"<b>"ସ୍ତର 5"</b>" \n- ବିଜ୍ଞପ୍ତି ତାଲିକାର ଶୀର୍ଷରେ ଦେଖାନ୍ତୁ \n- ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନରେ ବାଧା ଦେବାକୁ ଅନୁମତି ଦିଅନ୍ତୁ \n- ସର୍ବଦା ପିକ୍‍ କରନ୍ତୁ \n\n"<b>"ସ୍ତର 4"</b>" \n- ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନରେ ବାଧା ଦେବା ବ୍ଲକ୍‌ କରନ୍ତୁ \n- ସର୍ବଦା ପିକ୍‍ କରନ୍ତୁ \n\n"<b>"ସ୍ତର 3"</b>" \n- ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନରେ ବାଧା ଦେବା ବ୍ଲକ୍‌ କରନ୍ତୁ \n- କଦାପି ପିକ୍‍ କରନ୍ତୁ ନାହିଁ \n\n"<b>"ସ୍ତର 2"</b>" \n- ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନରେ ବାଧା ଦେବା ବ୍ଲକ୍‌ କରନ୍ତୁ \n- କଦାପି ପିକ୍‍ କରନ୍ତୁ ନାହିଁ \n- କଦାପି ସାଉଣ୍ଡ ଓ ଭାଇବ୍ରେଟ୍‍ କରନ୍ତୁ ନାହିଁ \n\n"<b>"ସ୍ତର 1"</b>" \n- ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନରେ ବାଧା ଦେବା ବ୍ଲକ୍‌ କରନ୍ତୁ \n- କଦାପି ପିକ୍‍ କରନ୍ତୁ ନାହିଁ \n- କଦାପି ସାଉଣ୍ଡ ଓ ଭାଇବ୍ରେଟ୍‍ କରନ୍ତୁ ନାହିଁ \n- ଲକ୍‍ ସ୍କ୍ରୀନ୍‍ ଓ ଷ୍ଟାଟସ୍‍ ବାର୍‌ରୁ ଲୁଚାନ୍ତୁ \n- ବିଜ୍ଞପ୍ତି ତାଲିକାର ନିମ୍ନରେ ଦେଖାନ୍ତୁ \n\n"<b>"ସ୍ତର 0"</b>" \n- ଆପରୁ ସମସ୍ତ ବିଜ୍ଞପ୍ତି ବ୍ଲକ୍‌ କରନ୍ତୁ"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"ବିଜ୍ଞପ୍ତି"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"ଏହି ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ଆପଣ ଆଉ ଦେଖିବାକୁ ପାଇବେନାହିଁ।"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"ପୁଣି ଆରମ୍ଭ କରନ୍ତୁ"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ସେଟିଂସ୍"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ରୁ <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ଙ୍କ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚାଲୁଛି"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>ରୁ <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ଚଲାନ୍ତୁ"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ଖୋଲନ୍ତୁ"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ରୁ <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ଙ୍କ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚଲାନ୍ତୁ"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"ଗୋଷ୍ଠୀ"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1ଟି ଡିଭାଇସ୍ ଚୟନ କରାଯାଇଛି"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g>ଟି ଡିଭାଇସ୍ ଚୟନ କରାଯାଇଛି"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ବିଚ୍ଛିନ୍ନ କରାଯାଇଛି)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ବିଚ୍ଛିନ୍ନ କରାଯାଇଛି)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ସଂଯୋଗ କରାଯାଇପାରିଲା ନାହିଁ। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ନୂଆ ଡିଭାଇସକୁ ପେୟାର୍ କରନ୍ତୁ"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"ବିଲ୍ଡ ନମ୍ୱର"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"ସବୁ ଦେଖନ୍ତୁ"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ନେଟୱାର୍କ ସ୍ୱିଚ୍ କରିବାକୁ, ଇଥରନେଟ୍ ବିଚ୍ଛିନ୍ନ କରନ୍ତୁ"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"ଡିଭାଇସ ଅନୁଭୂତିକୁ ଉନ୍ନତ କରିବା ପାଇଁ, ୱାଇ-ଫାଇ ବନ୍ଦ ଥିଲେ ମଧ୍ୟ ଆପ ଓ ସେବାଗୁଡ଼ିକ ଏବେ ବି ଯେ କୌଣସି ସମୟରେ ୱାଇ-ଫାଇ ନେଟୱାର୍କ ପାଇଁ ସ୍କାନ କରିପାରିବ। ଆପଣ ଏହାକୁ ୱାଇ-ଫାଇ ସ୍କାନିଂ ସେଟିଂସରେ ପରିବର୍ତ୍ତନ କରିପାରିବେ। "<annotation id="link">"ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ଉପଯୋଗକର୍ତ୍ତା ଚୟନ କର"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 0c9dd95ec219..26f4641049eb 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"ਪਾਵਰ ਸੂਚਨਾ ਕੰਟਰੋਲ"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"ਚਾਲੂ"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"ਬੰਦ"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ਚਾਲੂ ਹੈ - ਚਿਹਰਾ-ਆਧਾਰਿਤ"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"ਪਾਵਰ ਸੂਚਨਾ ਕੰਟਰੋਲਾਂ ਨਾਲ, ਤੁਸੀਂ ਕਿਸੇ ਐਪ ਦੀਆਂ ਸੂਚਨਾਵਾਂ ਲਈ ਮਹੱਤਤਾ ਪੱਧਰ ਨੂੰ 0 ਤੋਂ 5 ਤੱਕ ਸੈੱਟ ਕਰ ਸਕਦੇ ਹੋ। \n\n"<b>"ਪੱਧਰ 5"</b>" \n- ਸੂਚਨਾ ਸੂਚੀ ਦੇ ਸਿਖਰ \'ਤੇ ਦਿਖਾਓ \n- ਪੂਰੀ ਸਕ੍ਰੀਨ ਰੁਕਾਵਟ ਦੀ ਆਗਿਆ ਦਿਓ \n- ਹਮੇਸ਼ਾਂ ਝਲਕ ਦਿਖਾਓ \n\n"<b>"ਪੱਧਰ 4"</b>" \n- ਪੂਰੀ ਸਕ੍ਰੀਨ ਰੁਕਾਵਟ ਨੂੰ ਰੋਕੋ \n- ਹਮੇਸ਼ਾਂ ਝਲਕ ਦਿਖਾਓ \n\n"<b>"ਪੱਧਰ 3"</b>" \n- ਪੂਰੀ ਸਕ੍ਰੀਨ ਰੁਕਾਵਟ ਨੂੰ ਰੋਕੋ \n- ਕਦੇ ਝਲਕ ਨਾ ਦਿਖਾਓ \n\n"<b>"ਪੱਧਰ 2"</b>" \n- ਪੂਰੀ ਸਕ੍ਰੀਨ ਰੁਕਾਵਟ ਨੂੰ ਰੋਕੋ \n- ਕਦੇ ਝਲਕ ਨਾ ਦਿਖਾਓ \n- ਕਦੇ ਵੀ ਧੁਨੀ ਜਾਂ ਥਰਥਰਾਹਟ ਨਾ ਕਰੋ \n\n"<b>"ਪੱਧਰ 1"</b>" \n- ਪੂਰੀ ਸਕ੍ਰੀਨ ਰੁਕਾਵਟ ਨੂੰ ਰੋਕੋ \n- ਕਦੇ ਝਲਕ ਨਾ ਦਿਖਾਓ \n- ਕਦੇ ਧੁਨੀ ਜਾਂ ਥਰਥਰਾਹਟ ਨਾ ਕਰੋ \n- ਲਾਕ ਸਕ੍ਰੀਨ ਅਤੇ ਸਥਿਤੀ ਪੱਟੀ ਤੋਂ ਲੁਕਾਓ \n- ਸੂਚਨਾ ਸੂਚੀ ਦੇ ਹੇਠਾਂ ਦਿਖਾਓ \n\n"<b>"ਪੱਧਰ 0"</b>" \n- ਐਪ ਤੋਂ ਸਾਰੀਆਂ ਸੂਚਨਾਵਾਂ ਨੂੰ ਬਲਾਕ ਕਰੋ"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"ਸੂਚਨਾਵਾਂ"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"ਤੁਹਾਨੂੰ ਹੁਣ ਇਹ ਸੂਚਨਾਵਾਂ ਦਿਖਾਈ ਨਹੀਂ ਦੇਣਗੀਆਂ"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"ਮੁੜ-ਚਾਲੂ ਕਰੋ"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ਸੈਟਿੰਗਾਂ"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ਤੋਂ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ਦਾ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚੱਲ ਰਿਹਾ ਹੈ"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> ਵਿੱਚੋਂ <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ਚਲਾਓ"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ਖੋਲ੍ਹੋ"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ਤੋਂ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ਦਾ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚਲਾਓ"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"ਗਰੁੱਪ"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ਡੀਵਾਈਸ ਨੂੰ ਚੁਣਿਆ ਗਿਆ"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> ਡੀਵਾਈਸਾਂ ਨੂੰ ਚੁਣਿਆ ਗਿਆ"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ਡਿਸਕਨੈਕਟ ਹੋਇਆ)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ਡਿਸਕਨੈਕਟ ਹੈ)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ਨਵਾਂ ਡੀਵਾਈਸ ਜੋੜਾਬੱਧ ਕਰੋ"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"ਬਿਲਡ ਨੰਬਰ"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"ਸਭ ਦੇਖੋ"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ਨੈੱਟਵਰਕਾਂ ਨੂੰ ਬਦਲਣ ਲਈ, ਈਥਰਨੈੱਟ ਨੂੰ ਡਿਸਕਨੈਕਟ ਕਰੋ"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"ਡੀਵਾਈਸ ਦੇ ਅਨੁਭਵ ਨੂੰ ਬਿਹਤਰ ਬਣਾਉਣ ਲਈ, ਐਪਾਂ ਅਤੇ ਸੇਵਾਵਾਂ ਕਿਸੇ ਵੀ ਸਮੇਂ ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕਾਂ ਲਈ ਸਕੈਨ ਕਰ ਸਕਦੀਆਂ ਹਨ, ਭਾਵੇਂ ਵਾਈ-ਫਾਈ ਬੰਦ ਹੀ ਕਿਉਂ ਨਾ ਹੋਵੇ। ਤੁਸੀਂ ਇਸ ਨੂੰ ਵਾਈ‑ਫਾਈ ਸਕੈਨਿੰਗ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ ਬਦਲ ਸਕਦੇ ਹੋ। "<annotation id="link">"ਬਦਲੋ"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ਵਰਤੋਂਕਾਰ ਚੁਣੋ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 9d7936887c6b..01debf4b52d4 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -716,7 +716,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Zaawansowane ustawienia powiadomień"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Wł."</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Wył."</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Włączono – na podstawie twarzy"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Dzięki zaawansowanym ustawieniom możesz określić poziom ważności powiadomień z aplikacji w skali od 0 do 5. \n\n"<b>"Poziom 5"</b>" \n– Pokazuj u góry listy powiadomień \n– Zezwalaj na powiadomienia na pełnym ekranie \n– Zawsze pokazuj podgląd \n\n"<b>"Poziom 4"</b>" \n– Wyłącz powiadomienia na pełnym ekranie \n– Zawsze pokazuj podgląd \n\n"<b>"Poziom 3"</b>" \n– Wyłącz powiadomienia na pełnym ekranie \n– Nigdy nie pokazuj podglądu \n\n"<b>"Poziom 2"</b>" \n– Wyłącz powiadomienia na pełnym ekranie \n– Nigdy nie pokazuj podglądu \n– NIgdy nie powiadamiaj dźwiękiem ani wibracjami \n\n"<b>"Poziom 1"</b>" \n– Wyłącz powiadomienia na pełnym ekranie \n– Nigdy nie pokazuj podglądu \n– NIgdy nie powiadamiaj dźwiękiem ani wibracjami \n– Ukrywaj na ekranie blokady i pasku stanu \n– Pokazuj u dołu listy powiadomień \n\n"<b>"Poziom 0"</b>" \n– Blokuj wszystkie powiadomienia aplikacji"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Powiadomienia"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Te powiadomienia nie będą już wyświetlane"</string>
@@ -1103,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Wznów"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Ustawienia"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Aplikacja <xliff:g id="APP_LABEL">%3$s</xliff:g> odtwarza utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>)"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> z <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Odtwórz"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otwórz aplikację <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Odtwórz utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) w aplikacji <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1123,7 +1123,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Wybrano 1 urządzenie"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Wybrane urządzenia: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (rozłączono)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(odłączono)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nie udało się połączyć. Spróbuj ponownie."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Sparuj nowe urządzenie"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Numer kompilacji"</string>
@@ -1192,4 +1192,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Pokaż wszystko"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Aby przełączać sieci, odłącz Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Aby zapewnić Ci większy komfort korzystania z urządzenia, aplikacje i usługi mogą nadal wyszukiwać sieci Wi-Fi w pobliżu nawet wtedy, gdy Wi-Fi jest wyłączone. Możesz to zmienić w ustawieniach skanowania Wi-Fi. "<annotation id="link">"Zmień"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Wybierz użytkownika"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index ac1bd1164481..37592ac524bb 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Controles de ativação/desativação de notificações"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Ativada"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Desativado"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Ativada (reconhecimento facial)"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Com controles de ativação de notificações, é possível definir o nível de importância de 0 a 5 para as notificações de um app. \n\n"<b>"Nível 5"</b>" \n- Exibir na parte superior da lista de notificações \n- Permitir interrupção em tela cheia \n- Sempre exibir \n\n"<b>"Nível 4"</b>" \n- Impedir interrupções em tela cheia \n- Sempre exibir \n\n"<b>"Nível 3"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n\n"<b>"Nível 2"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n- Nunca emitir som ou vibrar \n\n"<b>"Nível 1"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n- Nunca emitir som ou vibrar \n- Ocultar da tela de bloqueio e barra de status \n- Exibir na parte inferior da lista de notificações \n\n"<b>"Nível 0"</b>" \n- Bloquear todas as notificações do app"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notificações"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Você deixará de ver essas notificações"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Configurações"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Tocando <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Iniciar"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selecionado"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> dispositivos selecionados"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desconectado)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(sem conexão)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Não foi possível conectar. Tente novamente."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Parear novo dispositivo"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Número da versão"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Ver tudo"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para mudar de rede, desconecte o cabo Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Para melhorar a experiência no dispositivo, os apps e serviços ainda podem procurar redes Wi-Fi a qualquer momento, mesmo quando o Wi-Fi estiver desativado. Você pode mudar essa opção nas configurações de busca por Wi-Fi. "<annotation id="link">"Mudar"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecionar usuário"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 469170e1d363..8c044dfe3da9 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Controlos de notificações do consumo de energia"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Ativado"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Desativado"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Ativada – Com base no rosto"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Com os controlos de notificações do consumo de energia, pode definir um nível de importância de 0 a 5 para as notificações de aplicações. \n\n"<b>"Nível 5"</b>" \n- Mostrar no início da lista de notificações \n- Permitir a interrupção do ecrã inteiro \n- Aparecer rapidamente sempre \n\n"<b>"Nível 4"</b>" \n- Impedir a interrupção do ecrã inteiro \n- Aparecer rapidamente sempre\n\n"<b>"Nível 3"</b>" \n- Impedir a interrupção do ecrã inteiro \n- Nunca aparecer rapidamente \n\n"<b>"Nível 2"</b>" \n- Impedir a interrupção do ecrã inteiro \n- Nunca aparecer rapidamente \n- Nunca tocar nem vibrar \n\n"<b>"Nível 1"</b>" \n- Impedir a interrupção do ecrã inteiro \n- Nunca aparecer rapidamente \n- Nunca tocar nem vibrar \n- Ocultar do ecrã de bloqueio e da barra de estado \n- Mostrar no fim da lista de notificações \n\n"<b>"Nível 0"</b>" \n- Bloquear todas as notificações da app"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notificações"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Nunca mais verá estas notificações."</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Definições"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> em reprodução a partir da app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproduzir"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduzir <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> a partir da app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selecionado"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> dispositivos selecionados"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desligado)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(desligado)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Não foi possível ligar. Tente novamente."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Sincronize o novo dispositivo"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Número da compilação"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Veja tudo"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para mudar de rede, desligue a Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Para melhorar a experiência do dispositivo, as apps e os serviços podem continuar a procurar redes Wi-Fi em qualquer altura, mesmo quando o Wi-Fi está desativado. Pode alterar esta opção nas definições de procura de Wi-Fi. "<annotation id="link">"Alterar"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecione utilizador"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index ac1bd1164481..37592ac524bb 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Controles de ativação/desativação de notificações"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Ativada"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Desativado"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Ativada (reconhecimento facial)"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Com controles de ativação de notificações, é possível definir o nível de importância de 0 a 5 para as notificações de um app. \n\n"<b>"Nível 5"</b>" \n- Exibir na parte superior da lista de notificações \n- Permitir interrupção em tela cheia \n- Sempre exibir \n\n"<b>"Nível 4"</b>" \n- Impedir interrupções em tela cheia \n- Sempre exibir \n\n"<b>"Nível 3"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n\n"<b>"Nível 2"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n- Nunca emitir som ou vibrar \n\n"<b>"Nível 1"</b>" \n- Impedir interrupções em tela cheia \n- Nunca exibir \n- Nunca emitir som ou vibrar \n- Ocultar da tela de bloqueio e barra de status \n- Exibir na parte inferior da lista de notificações \n\n"<b>"Nível 0"</b>" \n- Bloquear todas as notificações do app"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notificações"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Você deixará de ver essas notificações"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Configurações"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Tocando <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Iniciar"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selecionado"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> dispositivos selecionados"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desconectado)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(sem conexão)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Não foi possível conectar. Tente novamente."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Parear novo dispositivo"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Número da versão"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Ver tudo"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para mudar de rede, desconecte o cabo Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Para melhorar a experiência no dispositivo, os apps e serviços ainda podem procurar redes Wi-Fi a qualquer momento, mesmo quando o Wi-Fi estiver desativado. Você pode mudar essa opção nas configurações de busca por Wi-Fi. "<annotation id="link">"Mudar"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecionar usuário"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index aa174d41dc00..a78167f6da8c 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -713,7 +713,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Comenzi de gestionare a notificărilor"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Activate"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Dezactivate"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Activată – În funcție de chip"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Folosind comenzile de gestionare a notificărilor, puteți să setați un nivel de importanță de la 0 la 5 pentru notificările unei aplicații. \n\n"<b>"Nivelul 5"</b>" \n– Se afișează la începutul listei de notificări \n– Se permite întreruperea pe ecranul complet \n– Se afișează întotdeauna scurt \n\n"<b>"Nivelul 4"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Se afișează întotdeauna scurt \n\n"<b>"Nivelul 3"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n\n"<b>"Nivelul 2"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n– Nu se emit sunete și nu vibrează niciodată \n\n"<b>"Nivelul 1"</b>" \n– Se împiedică întreruperea pe ecranul complet \n– Nu se afișează niciodată scurt \n– Nu se emit sunete și nu vibrează niciodată \n– Se ascunde în ecranul de blocare și în bara de stare \n– Se afișează la finalul listei de notificări \n\n"<b>"Nivelul 0"</b>" \n– Se blochează toate notificările din aplicație"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Notificări"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Nu veți mai vedea aceste notificări"</string>
@@ -1097,6 +1096,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Reia"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Setări"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se redă în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> din <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Redați"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Deschideți <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Redați <xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1117,7 +1117,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"S-a selectat un dispozitiv"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"S-au selectat <xliff:g id="COUNT">%1$d</xliff:g> dispozitive"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (s-a deconectat)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(deconectat)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nu s-a putut conecta. Reîncercați."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Asociați un nou dispozitiv"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Numărul versiunii"</string>
@@ -1186,4 +1186,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Afișează-le pe toate"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Pentru a schimba rețeaua, deconectați ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Pentru a îmbunătăți experiența cu dispozitivul, aplicațiile și serviciile pot să caute în continuare rețele Wi‑Fi chiar și atunci când conexiunea Wi-Fi este dezactivată. Puteți să schimbați acest aspect din setările pentru căutarea de rețele Wi-Fi. "<annotation id="link">"Schimbați"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Alegeți utilizatorul"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 922d390507e9..6abfc8707ac6 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -716,7 +716,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Расширенное управление уведомлениями"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Включено"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Отключено"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Включить (на основе распознавания лиц)"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"С помощью этой функции вы можете устанавливать уровень важности уведомлений от 0 до 5 для каждого приложения.\n\n"<b>"Уровень 5"</b>\n"‒ Помещать уведомления в начало списка.\n‒ Показывать полноэкранные уведомления.\n‒ Показывать всплывающие уведомления.\nУровень 4\n"<b></b>\n"‒ Не показывать полноэкранные уведомления.\n‒ Показывать всплывающие уведомления.\nУровень 3\n"<b></b>\n"‒ Не показывать полноэкранные уведомления.\n‒ Не показывать всплывающие уведомления.\nУровень 2\n"<b></b>\n"‒ Не показывать полноэкранные уведомления.\n‒ Не показывать всплывающие уведомления.\n‒ Не использовать звук и вибрацию.\nУровень 1\n"<b></b>\n"‒ Не показывать полноэкранные уведомления.\n‒ Не показывать всплывающие уведомления.\n‒ Не использовать звук и вибрацию.\n‒ Не показывать на экране блокировки и в строке состояния.\n‒ Помещать уведомления в конец списка.\nУровень 0\n"<b></b>\n"‒ Блокировать все уведомления приложения."</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Уведомления"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Эти уведомления отключены."</string>
@@ -1103,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Возобновить"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Настройки"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Воспроизводится медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (исполнитель: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) из приложения \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\"."</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> из <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Воспроизведение"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Открыть приложение \"<xliff:g id="APP_LABEL">%1$s</xliff:g>\""</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Воспроизвести медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (исполнитель: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) из приложения \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\""</string>
@@ -1123,7 +1123,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Группа"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Выбрано 1 устройство"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Выбрано устройств: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (отключено)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(нет подключения)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Не удалось подключиться. Повторите попытку."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Подключить новое устройство"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Номер сборки"</string>
@@ -1192,4 +1192,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Показать все"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Чтобы переключиться между сетями, отключите кабель Ethernet."</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Чтобы улучшать работу устройства, приложения и сервисы могут искать беспроводные сети в любое время, даже если вы отключили Wi‑Fi. Чтобы запретить это, отключите поиск сетей Wi‑Fi. "<annotation id="link">"Открыть настройки"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Выберите профиль"</string>
</resources>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index f6fd853170f6..21892c535810 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"බල දැනුම්දීම් පාලන"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"ක්‍රියාත්මකයි"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"ක්‍රියාවිරහිතයි"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ක්‍රියාත්මකයි - මුහුණ-පදනම්ව"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"බල දැනුම්දීම් පාලන සමගින්, ඔබට යෙදුමක දැනුම්දීම් සඳහා වැදගත්කම 0 සිට 5 දක්වා සැකසිය හැකිය. \n\n"<b>"5 මට්ටම"</b>" \n- දැනුම්දීම් ලැයිස්තුවේ ඉහළින්ම පෙන්වන්න \n- පූර්ණ තිර බාධාවට ඉඩ දෙන්න \n- සැම විට එබී බලන්න \n\n"<b>"4 මට්ටම"</b>" \n- පූර්ණ තිර බාධාව වළක්වන්න \n- සැම විට එබී බලන්න \n\n"<b>"3 මට්ටම"</b>" \n- පූර්ණ තිර බාධාව වළක්වන්න \n- කිසි විටක එබී නොබලන්න \n\n"<b>"2 මට්ටම"</b>" \n- පූර්ණ තිර බාධාව වළක්වන්න \n- කිසි විටක එබී නොබලන්න \n- කිසි විටක හඬ සහ කම්පනය සිදු නොකරන්න \n\n"<b>"1 මට්ටම"</b>" \n- පූර්ණ තිර බාධාව වළක්වන්න \n- කිසි විටක එබී නොබලන්න \n- කිසි විටක හඬ සහ කම්පනය සිදු නොකරන්න \n- අගුලු තිරය සහ තත්ත්ව තීරුව වෙතින් සඟවන්න \n- දැනුම්දීම් ලැයිස්තුවේ පහළින්ම පෙන්වන්න \n\n"<b>"0 මට්ටම"</b>" \n- යෙදුම වෙතින් වන සියලු දැනුම් දීම් සඟවන්න."</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"දැනුම් දීම්"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"ඔබට තවදුරටත් මෙම දැනුම්දීම් නොදකිනු ඇත"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"නැවත පටන් ගන්න"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"සැකසීම්"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>ගේ <xliff:g id="SONG_NAME">%1$s</xliff:g> ගීතය <xliff:g id="APP_LABEL">%3$s</xliff:g> වෙතින් ධාවනය වෙමින් පවතී"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>කින් <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"වාදනය කරන්න"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> විවෘත කරන්න"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>ගේ <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%3$s</xliff:g> වෙතින් වාදනය කරන්න"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"සමූහය"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"උපාංග 1ක් තෝරන ලදී"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"උපාංග <xliff:g id="COUNT">%1$d</xliff:g>ක් තෝරන ලදී"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (විසන්ධි විය)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(විසන්ධි විය)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"සම්බන්ධ වීමට නොහැකි විය. නැවත උත්සාහ කරන්න."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"නව උපාංගය යුගල කරන්න"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"නිමැවුම් අංකය"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"සියල්ල බලන්න"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ජාල මාරු කිරීමට, ඊතර්නෙට් විසන්ධි කරන්න"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"උපාංග අත්දැකීම වැඩි දියුණු කිරිමට, Wi‑Fi ක්‍රියාවිරහිත විට පවා, ඕනෑම අවස්ථාවක Wi‑Fi ජාල සඳහා ස්කෑන් කිරීමට යෙදුම් සහ සේවාවලට හැකිය. ඔබට මෙය Wi‑Fi ස්කෑන් කිරීමේ සැකසීම් තුළ වෙනස් කළ හැකිය. "<annotation id="link">"වෙනස් කරන්න"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"පරිශීලක තෝරන්න"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index c296666b6249..2f2fb378f437 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -716,7 +716,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Ovládacie prvky zobrazovania upozornení"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Zapnuté"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Vypnuté"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Zapnuté – podľa tváre"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Pomocou ovládacích prvkov zobrazovania upozornení môžete nastaviť pre upozornenia aplikácie úroveň dôležitosti od 0 do 5. \n\n"<b>"Úroveň 5"</b>" \n– Zobrazovať v hornej časti zoznamu upozornení. \n– Povoliť prerušenia na celú obrazovku. \n– Vždy zobrazovať čiastočne. \n\n"<b>"Úroveň 4"</b>" \n– Zabrániť prerušeniam na celú obrazovku. \n– Vždy zobrazovať čiastočne. \n\n"<b>"Úroveň 3"</b>" \n– Zabrániť prerušeniam na celú obrazovku. \n– Nikdy nezobrazovať čiastočne. \n\n"<b>"Úroveň 2"</b>" \n– Zabrániť prerušeniam na celú obrazovku. \n– Nikdy nezobrazovať čiastočne. \n– Nikdy nespúšťať zvuk ani vibrácie. \n\n"<b>"Úroveň 1"</b>" \n– Zabrániť prerušeniam na celú obrazovku. \n– Nikdy nezobrazovať čiastočne. \n– Nikdy nespúšťať zvuk ani vibrácie. \n– Skryť na uzamknutej obrazovke a v stavovom riadku. \n– Zobraziť v dolnej časti zoznamu upozornení. \n\n"<b>"Úroveň 0"</b>" \n– Blokovať všetky upozornenia z aplikácie."</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Upozornenia"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Tieto upozornenia sa už nebudú zobrazovať"</string>
@@ -1103,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Pokračovať"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavenia"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> sa prehráva z aplikácie <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> z <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Prehrať"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvoriť <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Prehrať skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikácie <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1123,7 +1123,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 vybrané zariadenie"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Počet vybraných zariadení: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (odpojené)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(odpojené)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nepodarilo sa pripojiť. Skúste to znova."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Spárovať nové zariadenie"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Číslo zostavy"</string>
@@ -1192,4 +1192,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Zobraziť všetko"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Ak chcete prepnúť siete, odpojte ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Aplikácie a služby môžu kedykoľvek vyhľadávať siete Wi‑Fi (a to aj vtedy, keď je pripojenie Wi‑Fi vypnuté), čím zlepšujú prostredie v zariadení. Môžete to zmeniť v nastaveniach vyhľadávania sietí Wi‑Fi. "<annotation id="link">"Zmeniť"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Vyberte používateľa"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index c2598211981e..8427534c11bb 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -716,7 +716,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Kontrolniki za pomembnost obvestil"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Vklopljeno"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Izklopljeno"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Vklopljeno – na podlagi obraza"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"S kontrolniki za pomebnost obvestila je mogoče za obvestila aplikacije nastaviti stopnjo pomembnosti od 0 do 5. \n\n"<b>"Stopnja 5"</b>" \n– Prikaz na vrhu seznama obvestil \n– Omogočanje prekinitev v celozaslonskem načinu \n– Vedno prikaži hitre predoglede \n\n"<b>"Stopnja 4"</b>" \n– Preprečevanje prekinitev v celozaslonskem načinu \n– Vedno prikaži hitre predoglede \n\n"<b>"Stopnja 3"</b>" \n– Preprečevanje prekinitev v celozaslonskem načinu \n– Nikoli ne prikaži hitrih predogledov \n\n"<b>"Stopnja 2"</b>" \n– Preprečevanje prekinitev v celozaslonskem načinu \n– Nikoli ne prikaži hitrih predogledov \n– Nikoli ne uporabi zvoka in vibriranja \n\n"<b>"Stopnja 1"</b>" \n– Preprečevanje prekinitev v celozaslonskem načinu \n– Nikoli ne prikaži hitrih predogledov \n– Nikoli ne uporabi zvoka in vibriranja \n– Skrivanje na zaklenjenem zaslonu in v vrstici stanja \n– Prikaz na dnu seznama obvestil \n\n"<b>"Stopnja 0"</b>" \n– Blokiranje vseh obvestil aplikacije"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Obvestila"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Ta obvestila ne bodo več prikazana"</string>
@@ -1103,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Nadaljuj"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavitve"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Skladba <xliff:g id="SONG_NAME">%1$s</xliff:g> izvajalca <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se predvaja iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> od <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Predvajaj"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Odpri aplikacijo <xliff:g id="APP_LABEL">%1$s</xliff:g>."</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Predvajaj skladbo <xliff:g id="SONG_NAME">%1$s</xliff:g> izvajalca <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
@@ -1123,7 +1123,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Izbrana je ena naprava"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Izbranih je toliko naprav: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (povezava prekinjena)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(povezava je prekinjena)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Povezave ni bilo mogoče vzpostaviti. Poskusite znova."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Seznanitev nove naprave"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Delovna različica"</string>
@@ -1192,4 +1192,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Prikaz vseh omrežij"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Če želite preklopiti omrežje, prekinite ethernetno povezavo."</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Za izboljšano izkušnjo pri uporabi naprave lahko aplikacije in storitve kadar koli iščejo omrežja Wi‑Fi, tudi ko je Wi‑Fi izklopljen. To lahko spremenite v nastavitvah iskanja omrežij Wi-Fi. "<annotation id="link">"Spremeni"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Izberite uporabnika"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index cfd5573e8fd3..1acc6e83ebaa 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Kontrollet e njoftimit të energjisë"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Aktiv"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Joaktiv"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Aktiv - Në bazë të fytyrës"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Me kontrollet e njoftimit të energjisë, mund të caktosh një nivel rëndësie nga 0 në 5 për njoftimet e një aplikacioni. \n\n"<b>"Niveli 5"</b>" \n- Shfaq në krye të listës së njoftimeve \n- Lejo ndërprerjen e ekranit të plotë \n- Gjithmonë shfaq shpejt \n\n"<b>"Niveli 4"</b>" \n- Parandalo ndërprerjen e ekranit të plotë \n- Gijthmonë shfaq shpejt \n\n"<b>"Niveli 3"</b>" \n- Parandalo ndërprerjen e ekranit të plotë \n- Asnjëherë mos shfaq shpejt \n\n"<b>"Niveli 2"</b>" \n- Parandalo ndërprerjen e ekranit të plotë \n- Asnjëherë mos shfaq shpejt \n- Asnjëherë mos lësho tingull dhe dridhje \n\n"<b>"Niveli 1"</b>" \n- Parandalo ndërprerjen e ekranit të plotë \n- Asnjëherë mos shfaq shpejt \n- Asnjëherë mos lësho tingull ose dridhje \n- Fshih nga ekrani i kyçjes dhe shiriti i statusit \n- Shfaq në fund të listës së njoftimeve \n\n"<b>"Niveli 0"</b>" \n- Blloko të gjitha njoftimet nga aplikacioni"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Njoftime"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Nuk do t\'i shikosh më këto njoftime"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Vazhdo"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Cilësimet"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="ARTIST_NAME">%2$s</xliff:g> po luhet nga <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> nga <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Luaj"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Hap <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Luaj <xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="ARTIST_NAME">%2$s</xliff:g> nga <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupi"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 pajisje e zgjedhur"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> pajisje të zgjedhura"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (e shkëputur)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(shkëputur)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nuk mund të lidhej. Provo sërish."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Çifto pajisjen e re"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Numri i ndërtimit"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Shiko të gjitha"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Për të ndërruar rrjetet, shkëput Ethernet-in"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Për të përmirësuar përvojën e pajisjes, aplikacionet dhe shërbimet mund të vazhdojnë të skanojnë për rrjete Wi-Fi në çdo kohë, edhe kur Wi-Fi është joaktiv. Mund ta ndryshosh këtë te cilësimet e skanimit të Wi-Fi. "<annotation id="link">"Ndrysho"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Zgjidh përdoruesin"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 42d114c33e3e..8c96a5154283 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -713,7 +713,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Напредне контроле за обавештења"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Укључено"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Искључено"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Укључено – на основу лица"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Помоћу напредних контрола за обавештења можете да подесите ниво важности од 0. до 5. за обавештења апликације. \n\n"<b>"5. ниво"</b>" \n– Приказују се у врху листе обавештења \n- Дозволи прекид режима целог екрана \n– Увек завируј \n\n"<b>"4. ниво"</b>" \n– Спречи прекид режима целог екрана \n– Увек завируј \n\n"<b>"3. ниво"</b>" \n– Спречи прекид режима целог екрана \n– Никада не завируј \n\n"<b>"2. ниво"</b>" \n– Спречи прекид режима целог екрана \n– Никада не завируј \n– Никада не производи звук или вибрацију \n\n"<b>"1. ниво"</b>" \n– Спречи прекид режима целог екрана \n– Никада не завируј \n– Никада не производи звук или вибрацију \n– Сакриј на закључаном екрану и статусној траци \n– Приказују се у дну листе обавештења \n\n"<b>"0. ниво"</b>" \n– Блокирај сва обавештења из апликације"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Обавештења"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Више нећете видети ова обавештења"</string>
@@ -1097,6 +1096,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Настави"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Подешавања"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> извођача <xliff:g id="ARTIST_NAME">%2$s</xliff:g> се пушта из апликације <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> од <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Пусти"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Отворите <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пустите <xliff:g id="SONG_NAME">%1$s</xliff:g> извођача <xliff:g id="ARTIST_NAME">%2$s</xliff:g> из апликације <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1117,7 +1117,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Изабран је 1 уређај"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Изабраних уређаја: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (веза је прекинута)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(веза је прекинута)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Повезивање није успело. Пробајте поново."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Упари нови уређај"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Број верзије"</string>
@@ -1186,4 +1186,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Погледајте све"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Да бисте променили мрежу, прекините етернет везу"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Ради бољег доживљаја уређаја, апликације и услуге и даље могу да траже WiFi мреже у било ком тренутку, чак и када је WiFi искључен. То можете да промените у подешавањима WiFi скенирања. "<annotation id="link">"Промените"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Изаберите корисника"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index fe81f468b169..8e44ba154764 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Prioritetsinställningar för aviseringar"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Av"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"På"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"På – ansiktsbaserad"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Med aviseringsinställningarna kan du ange prioritetsnivå från 0 till 5 för aviseringar från en app. \n\n"<b>"Nivå 5"</b>" \n– Visa högst upp i aviseringslistan\n– Tillåt avbrott i helskärmsläge \n– Snabbvisa alltid \n\n"<b>"Nivå 4"</b>" \n– Tillåt inte avbrott i helskärmsläge \n– Snabbvisa alltid \n\n"<b>"Nivå 3"</b>" \n- Tillåt inte avbrott i helskärmsläge \n– Snabbvisa aldrig \n\n"<b>"Nivå 2"</b>" \n– Tillåt inte avbrott i helskärmsläge \n– Snabbvisa aldrig \n– Aldrig med ljud eller vibration \n\n"<b>"Nivå 1"</b>" \n– Tillåt inte avbrott i helskärmsläge \n– Snabbvisa aldrig \n– Aldrig med ljud eller vibration \n– Visa inte på låsskärmen och i statusfältet \n– Visa längst ned i aviseringslistan \n\n"<b>"Nivå 0"</b>" \n– Blockera alla aviseringar från appen"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Aviseringar"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"De här aviseringarna visas inte längre"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Återuppta"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Inställningar"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> med <xliff:g id="ARTIST_NAME">%2$s</xliff:g> spelas upp från <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> av <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Spela upp"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Öppna <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spela upp <xliff:g id="SONG_NAME">%1$s</xliff:g> med <xliff:g id="ARTIST_NAME">%2$s</xliff:g> från <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupp"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 enhet har valts"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> enheter har valts"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (frånkopplad)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(frånkopplad)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Det gick inte att ansluta. Försök igen."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Parkoppla en ny enhet"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Versionsnummer"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Visa alla"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Koppla bort Ethernet för att växla nätverk"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"I syfte att förbättra upplevelsen med enheten kan appar och tjänster fortfarande söka efter wifi-nätverk när som helst, även om wifi har inaktiverats. "<annotation id="link">"Ändra"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Välj användare"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 6d503e249a6a..f4aefb760f60 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Udhibiti wa arifa"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Imewashwa"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Imezimwa"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Imewashwa - Inayolenga nyuso"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Ukiwa na udhibiti wa arifa, unaweza kuweka kiwango cha umuhimu wa arifa za programu kuanzia 0 hadi 5. \n\n"<b>"Kiwango cha 5"</b>" \n- Onyesha katika sehemu ya juu ya orodha ya arifa \n- Ruhusu ukatizaji wa skrini nzima \n- Ruhusu arifa za kuchungulia kila wakati\n\n"<b>"Kiwango cha 4"</b>" \n- Zuia ukatizaji wa skrini nzima\n- Ruhusu arifa za kuchungulia kila wakati \n\n"<b>"Kiwango cha 3"</b>" \n- Zuia ukatizaji wa skrini nzima\n- Usiruhusu kamwe arifa za kuchungulia\n\n"<b>"Kiwango cha 2"</b>" \n- Zuia ukatizaji wa skrini nzima\n- Usiruhusu kamwe arifa za kuchungulia \n- Usiruhusu kamwe sauti au mtetemo \n\n"<b>"Kiwango cha 1"</b>" \n- Zuia ukatizaji wa skrini nzima \n- Usiruhusu kamwe arifa za kuchungulia \n- Usiruhusu kamwe sauti na mtetemo \n- Usionyeshe skrini iliyofungwa na sehemu ya arifa \n- Onyesha katika sehemu ya chini ya orodha ya arifa \n\n"<b>"Kiwango cha 0"</b>" \n- Zuia arifa zote kutoka programu"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Arifa"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Hutaona tena arifa hizi"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Endelea"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Mipangilio"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ulioimbwa na <xliff:g id="ARTIST_NAME">%2$s</xliff:g> unacheza katika <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> kati ya <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Cheza"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Fungua <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Cheza <xliff:g id="SONG_NAME">%1$s</xliff:g> ulioimbwa na <xliff:g id="ARTIST_NAME">%2$s</xliff:g> katika <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Kikundi"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Umechagua kifaa 1"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Umechagua vifaa <xliff:g id="COUNT">%1$d</xliff:g>"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (hakijaunganishwa)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(imetenganishwa)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Imeshindwa kuunganisha. Jaribu tena."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Oanisha kifaa kipya"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Nambari ya muundo"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Angalia yote"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Ili kubadili mitandao, tenganisha ethaneti"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Ili kuboresha hali ya matumizi ya kifaa, programu na huduma bado zinaweza kutafuta mitandao ya Wi‑Fi wakati wowote, hata wakati umezima Wi‑Fi. Unaweza kubadilisha mipangilio hii katika mipangilio ya kutafuta Wi-Fi. "<annotation id="link">"Badilisha"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Chagua mtumiaji"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index e2b2e2590b23..040df865bfe5 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -18,6 +18,20 @@
<!-- Max number of columns for quick controls area -->
<integer name="controls_max_columns">2</integer>
+ <!-- The maximum number of rows in the QuickQSPanel -->
+ <integer name="quick_qs_panel_max_rows">4</integer>
+
+ <!-- The maximum number of tiles in the QuickQSPanel -->
+ <integer name="quick_qs_panel_max_tiles">8</integer>
+
<!-- Whether to use the split 2-column notification shade -->
<bool name="config_use_split_notification_shade">true</bool>
+
+ <!-- The number of columns in the QuickSettings -->
+ <integer name="quick_settings_num_columns">2</integer>
+
+ <!-- Notifications are sized to match the width of two (of 4) qs tiles in landscape. -->
+ <bool name="config_skinnyNotifsInLandscape">false</bool>
+
+ <dimen name="keyguard_indication_margin_bottom">25dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-h560dp-xhdpi/config.xml b/packages/SystemUI/res/values-sw600dp-port/config.xml
index cf2017f1eb6f..02fd25bd077c 100644
--- a/packages/SystemUI/res/values-h560dp-xhdpi/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/config.xml
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-
<!--
- ~ Copyright (C) 2014 The Android Open Source Project
+ ~ 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.
@@ -16,8 +15,13 @@
~ limitations under the License
-->
<resources>
- <!-- The maximum count of notifications on Keyguard. The rest will be collapsed in an overflow
- card. -->
- <integer name="keyguard_max_notification_count">3</integer>
-</resources>
+ <!-- The maximum number of tiles in the QuickQSPanel -->
+ <integer name="quick_qs_panel_max_tiles">6</integer>
+
+ <!-- The maximum number of rows in the QuickSettings -->
+ <integer name="quick_settings_max_rows">3</integer>
+
+ <!-- The number of columns in the QuickSettings -->
+ <integer name="quick_settings_num_columns">3</integer>
+</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
index 40838f362f5c..da2403a96afa 100644
--- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
@@ -16,6 +16,6 @@
-->
<resources>
<!-- Size of the panel of large phones on portrait. This shouldn't fill, but have some padding on the side -->
- <dimen name="notification_panel_width">416dp</dimen>
+ <dimen name="notification_panel_width">504dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 2f5e8eaa2263..45b5afa467ac 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -38,4 +38,10 @@
<!-- Max number of columns for quick controls area -->
<integer name="controls_max_columns">4</integer>
+ <!-- How many lines to show in the security footer -->
+ <integer name="qs_security_footer_maxLines">1</integer>
+
+ <!-- Determines whether to allow the nav bar handle to be forced to be opaque. -->
+ <bool name="allow_force_nav_bar_handle_opaque">false</bool>
+
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 0a34dfd71c7e..d17ec7379511 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -81,6 +81,7 @@
<dimen name="fab_margin">24dp</dimen>
<dimen name="navigation_key_width">128dp</dimen>
+
<dimen name="navigation_key_padding">25dp</dimen>
<!-- Keyboard shortcuts helper -->
@@ -96,6 +97,14 @@
<dimen name="global_actions_grid_item_layout_height">80dp</dimen>
- <!-- Internet panel related dimensions -->
- <dimen name="internet_dialog_list_max_width">624dp</dimen>
+ <!-- For large screens the security footer appears below the footer,
+ same as phones in portrait -->
+ <dimen name="qs_security_footer_single_line_height">48dp</dimen>
+ <dimen name="qs_security_footer_background_inset">0dp</dimen>
+
+ <!-- When split shade is used, this panel should be aligned to the top -->
+ <dimen name="qs_detail_margin_top">0dp</dimen>
+
+ <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
+ <dimen name="large_dialog_width">504dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/config.xml b/packages/SystemUI/res/values-sw720dp-land/config.xml
new file mode 100644
index 000000000000..e4573c651039
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-land/config.xml
@@ -0,0 +1,35 @@
+<?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
+ -->
+<resources>
+ <!-- Max number of columns for quick controls area -->
+ <integer name="controls_max_columns">2</integer>
+
+ <!-- The maximum number of rows in the QuickQSPanel -->
+ <integer name="quick_qs_panel_max_rows">4</integer>
+
+ <!-- The maximum number of tiles in the QuickQSPanel -->
+ <integer name="quick_qs_panel_max_tiles">8</integer>
+
+ <!-- Whether to use the split 2-column notification shade -->
+ <bool name="config_use_split_notification_shade">true</bool>
+
+ <!-- The number of columns in the QuickSettings -->
+ <integer name="quick_settings_num_columns">2</integer>
+
+ <!-- Notifications are sized to match the width of two (of 4) qs tiles in landscape. -->
+ <bool name="config_skinnyNotifsInLandscape">false</bool>
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-port/config.xml b/packages/SystemUI/res/values-sw720dp-port/config.xml
new file mode 100644
index 000000000000..12250861560b
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-port/config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. -->
+<resources>
+ <!-- The maximum number of tiles in the QuickQSPanel -->
+ <integer name="quick_qs_panel_max_tiles">6</integer>
+
+ <!-- The number of columns in the QuickSettings -->
+ <integer name="quick_settings_num_columns">3</integer>
+
+ <!-- The maximum number of rows in the QuickSettings -->
+ <integer name="quick_settings_max_rows">3</integer>
+
+</resources>
+
diff --git a/packages/SystemUI/res/values-sw720dp/config.xml b/packages/SystemUI/res/values-sw720dp/config.xml
index 64e2760e7778..436f8d0998f5 100644
--- a/packages/SystemUI/res/values-sw720dp/config.xml
+++ b/packages/SystemUI/res/values-sw720dp/config.xml
@@ -22,8 +22,5 @@
<resources>
<integer name="status_bar_config_maxNotificationIcons">5</integer>
- <!-- The maximum count of notifications on Keyguard. The rest will be collapsed in an overflow
- card. -->
- <integer name="keyguard_max_notification_count">5</integer>
</resources>
diff --git a/packages/SystemUI/res/values-sw900dp/dimens.xml b/packages/SystemUI/res/values-sw900dp/dimens.xml
index 2cff97692d9d..2758fb439edd 100644
--- a/packages/SystemUI/res/values-sw900dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw900dp/dimens.xml
@@ -17,15 +17,14 @@
-->
<resources>
- <dimen name="button_size">80dp</dimen>
- <dimen name="navigation_side_padding">@dimen/button_size</dimen>
- <dimen name="navigation_key_width">@dimen/button_size</dimen>
- <dimen name="navigation_extra_key_width">@dimen/button_size</dimen>
-
<!-- The maximum width of the navigation bar ripples. -->
<dimen name="key_button_ripple_max_width">76dp</dimen>
<!-- The padding around the navigation buttons -->
<dimen name="navigation_key_padding">0dp</dimen>
+ <dimen name="button_size">80dp</dimen>
+ <dimen name="navigation_side_padding">@dimen/button_size</dimen>
+ <dimen name="navigation_key_width">@dimen/button_size</dimen>
+ <dimen name="navigation_extra_key_width">@dimen/button_size</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 845a36148ef4..9b33030acd8f 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"ஆற்றல்மிக்க அறிவிப்புக் கட்டுப்பாடுகள்"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"ஆன்"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"ஆஃப்"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"ஆன் - முகம் அடிப்படையிலானது"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"ஆற்றல்மிக்க அறிவிப்புக் கட்டுப்பாடுகள் மூலம், ஆப்ஸின் அறிவிப்புகளுக்கு முக்கியத்துவ நிலையை (0-5) அமைக்கலாம். \n\n"<b>"நிலை 5"</b>" \n- அறிவிப்புப் பட்டியலின் மேலே காட்டும் \n- முழுத் திரைக் குறுக்கீட்டை அனுமதிக்கும் \n- எப்போதும் நடப்புத் திரையின் மேல் பகுதியில் அறிவிப்புகளைக் காட்டும் \n\n"<b>"நிலை 4"</b>" \n- முழுத் திரைக் குறுக்கீட்டைத் தடுக்கும் \n- எப்போதும் நடப்புத் திரையின் மேல் பகுதியில் அறிவிப்புகளைக் காட்டும் \n\n"<b>"நிலை 3"</b>" \n- முழுத் திரைக் குறுக்கீட்டைத் தடுக்கும் \n- ஒருபோதும் நடப்புத் திரையின் மேல் பகுதியில் அறிவிப்புகளைக் காட்டாது \n\n"<b>"நிலை 2"</b>" \n- முழுத் திரைக் குறுக்கீட்டைத் தடுக்கும் \n- ஒருபோதும் நடப்புத் திரையின் மேல் பகுதியில் அறிவிப்புகளைக் காட்டாது \n- ஒருபோதும் ஒலி எழுப்பாது, அதிர்வுறாது \n\n"<b>"நிலை 1"</b>" \n- முழுத் திரைக் குறுக்கீட்டைத் தடுக்கும் \n- ஒருபோதும் நடப்புத் திரையின் மேல் பகுதியில் அறிவிப்புகளைக் காட்டாது \n- ஒருபோதும் ஒலி எழுப்பாது அல்லது அதிர்வுறாது \n- லாக் ஸ்கிரீன் மற்றும் நிலைப்பட்டியிலிருந்து மறைக்கும் \n- அறிவிப்புகள் பட்டியலின் கீழே காட்டும் \n\n"<b>"நிலை 0"</b>" \n- ஆப்ஸின் எல்லா அறிவிப்புகளையும் தடுக்கும்"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"அறிவிப்புகள்"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"இந்த அறிவிப்புகளை இனி பார்க்கமாட்டீர்கள்"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"தொடர்க"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"அமைப்புகள்"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> இன் <xliff:g id="SONG_NAME">%1$s</xliff:g> பாடல் <xliff:g id="APP_LABEL">%3$s</xliff:g> ஆப்ஸில் பிளேயாகிறது"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> / <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"இயக்குதல்"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ஆப்ஸைத் திறங்கள்"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> இன் <xliff:g id="SONG_NAME">%1$s</xliff:g> பாடலை <xliff:g id="APP_LABEL">%3$s</xliff:g> ஆப்ஸில் பிளேசெய்"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"குழு"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 சாதனம் தேர்ந்தெடுக்கப்பட்டுள்ளது"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> சாதனங்கள் தேர்ந்தெடுக்கப்பட்டுள்ளன"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (இணைப்பு துண்டிக்கப்பட்டது)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(துண்டிக்கப்பட்டது)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"இணைக்க முடியவில்லை. மீண்டும் முயலவும்."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"புதிய சாதனத்தை இணைத்தல்"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"பதிப்பு எண்"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"அனைத்தையும் காட்டு"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"நெட்வொர்க்குகளை மாற்ற ஈதர்நெட் இணைப்பைத் துண்டிக்கவும்"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"சாதன அனுபவத்தை மேம்படுத்த, வைஃபை ஆஃப் செய்யப்பட்டிருந்தாலும்கூட எந்த நேரத்திலும் ஆப்ஸும் சேவைகளும் வைஃபை நெட்வொர்க்குகளைத் தேடலாம். வைஃபை ஸ்கேனிங் அமைப்புகளில் இதை மாற்றிக் கொள்ளலாம். "<annotation id="link">"மாற்று"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"பயனரைத் தேர்வுசெய்க"</string>
</resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 605c2eca5124..ca3b09e53aae 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"పవర్ నోటిఫికేషన్ నియంత్రణలు"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"ఆన్‌లో ఉన్నాయి"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"ఆఫ్‌లో ఉన్నాయి"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"\'ముఖం ఆధారం\'ను - ఆన్ చేయండి"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"పవర్ నోటిఫికేషన్ నియంత్రణలతో, మీరు యాప్ నోటిఫికేషన్‌ల కోసం ప్రాముఖ్యత స్థాయిని 0 నుండి 5 వరకు సెట్ చేయవచ్చు. \n\n"<b>"స్థాయి 5"</b>" \n- నోటిఫికేషన్ లిస్ట్‌ పైభాగంలో చూపబడతాయి \n- పూర్తి స్క్రీన్ అంతరాయం అనుమతించబడుతుంది \n- ఎల్లప్పుడూ త్వరిత వీక్షణ అందించబడుతుంది \n\n"<b>"స్థాయి 4"</b>\n"- పూర్తి స్క్రీన్ అంతరాయం నిరోధించబడుతుంది \n- ఎల్లప్పుడూ త్వరిత వీక్షణ అందించబడుతుంది \n\n"<b>"స్థాయి 3"</b>" \n- పూర్తి స్క్రీన్ అంతరాయం నిరోధించబడుతుంది \n- ఎప్పుడూ త్వరిత వీక్షణ అందించబడదు \n\n"<b>"స్థాయి 2"</b>" \n- పూర్తి స్క్రీన్ అంతరాయం నిరోధించబడుతుంది \n- ఎప్పుడూ త్వరిత వీక్షణ అందించబడదు \n- ఎప్పుడూ శబ్దం మరియు వైబ్రేషన్ చేయవు \n\n"<b>"స్థాయి 1"</b>" \n- పూర్తి స్క్రీన్ అంతరాయం నిరోధించబడుతుంది \n- ఎప్పుడూ త్వరిత వీక్షణ అందించబడదు \n- ఎప్పుడూ శబ్దం లేదా వైబ్రేట్ చేయవు \n- లాక్ స్క్రీన్ మరియు స్థితి పట్టీ నుండి దాచబడతాయి \n- నోటిఫికేషన్ లిస్ట్‌ దిగువ భాగంలో చూపబడతాయి \n\n"<b>"స్థాయి 0"</b>" \n- యాప్ నుండి అన్ని నోటిఫికేషన్‌లు బ్లాక్ చేయబడతాయి"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"నోటిఫికేషన్‌లు"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"ఇకపై మీకు ఈ నోటిఫికేషన్‌లు కనిపించవు"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"కొనసాగించండి"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"సెట్టింగ్‌లు"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> పాడిన <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%3$s</xliff:g> నుండి ప్లే అవుతోంది"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>లో <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ప్లే చేయండి"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g>ను తెరవండి"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> నుండి <xliff:g id="ARTIST_NAME">%2$s</xliff:g> పాడిన <xliff:g id="SONG_NAME">%1$s</xliff:g>‌ను ప్లే చేయండి"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"గ్రూప్"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 పరికరం ఎంచుకోబడింది"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> పరికరాలు ఎంచుకోబడ్డాయి"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (డిస్‌కనెక్ట్ అయ్యింది)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(డిస్కనెక్ట్ అయ్యింది)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"కనెక్ట్ చేయడం సాధ్యపడలేదు. మళ్లీ ట్రై చేయండి."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"కొత్త పరికరాన్ని పెయిర్ చేయండి"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"బిల్డ్ నంబర్"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"అన్నీ చూడండి"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"నెట్‌వర్క్‌లను మార్చడానికి, ఈథర్‌నెట్‌ను డిస్‌కనెక్ట్ చేయండి"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"పరికర అనుభవాన్ని మెరుగుపరచడానికి, Wi‑Fi ఆఫ్‌లో ఉన్నప్పుడు కూడా, ఏ సమయంలో అయినా ఇప్పటికీ Wi‑Fi నెట్‌వర్క్‌ల కోసం యాప్‌లు, సర్వీస్‌లు స్కాన్ చేయగలవు. మీరు దీనిని Wi‑Fi స్కానింగ్ సెట్టింగ్‌లలో మార్చవచ్చు. "<annotation id="link">"మార్చండి"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"యూజర్‌ను ఎంచుకోండి"</string>
</resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index dd315a24d4c6..340f3103225b 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"ส่วนควบคุมการแจ้งเตือนแบบเปิด/ปิด"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"เปิด"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"ปิด"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"เปิด - ตามใบหน้า"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"ส่วนควบคุมการแจ้งเตือนแบบเปิด/ปิดช่วยให้คุณตั้งค่าระดับความสำคัญสำหรับการแจ้งเตือนของแอปได้ตั้งแต่ระดับ 0-5 \n\n"<b>"ระดับ 5"</b>" \n- แสดงที่ด้านบนของรายการแจ้งเตือน \n- อนุญาตให้รบกวนแบบเต็มหน้าจอ \n- อนุญาตให้แสดงชั่วครู่ \n\n"<b>"ระดับ 4"</b>" \n- ป้องกันการรบกวนแบบเต็มหน้าจอ \n- แสดงชั่วครู่เสมอ \n\n"<b>"ระดับ 3"</b>" \n- ป้องกันการรบกวนแบบเต็มหน้าจอ \n- ไม่แสดงชั่วครู่เลย \n\n"<b>"ระดับ 2"</b>" \n- ป้องกันการรบกวนแบบเต็มหน้าจอ \n- ไม่แสดงชั่วครู่เลย \n- ไม่ส่งเสียงหรือสั่นเลย \n\n"<b>"ระดับ 1"</b>" \n- ป้องกันการรบกวนแบบเต็มหน้าจอ \n- ไม่แสดงชั่วครู่เลย \n- ไม่ส่งเสียงหรือสั่นเลย \n- ซ่อนจากหน้าจอล็อกและแถบสถานะ \n- แสดงที่ด้านล่างของรายการแจ้งเตือน \n\n"<b>"ระดับ 0"</b>" \n- บล็อกการแจ้งเตือนทั้งหมดจากแอป"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"การแจ้งเตือน"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"คุณจะไม่เห็นการแจ้งเตือนเหล่านี้อีก"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"เล่นต่อ"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"การตั้งค่า"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"กำลังเปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> ของ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> จาก <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> จาก <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"เล่น"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"เปิด <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"เปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> ของ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> จาก <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"กลุ่ม"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"เลือกอุปกรณ์ไว้ 1 รายการ"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"เลือกอุปกรณ์ไว้ <xliff:g id="COUNT">%1$d</xliff:g> รายการ"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ยกเลิกการเชื่อมต่อแล้ว)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(ยกเลิกการเชื่อมต่อแล้ว)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"เชื่อมต่อไม่ได้ ลองใหม่"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"จับคู่อุปกรณ์ใหม่"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"หมายเลขบิลด์"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"ดูทั้งหมด"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ตัดการเชื่อมต่ออีเทอร์เน็ตเพื่อสลับเครือข่าย"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"เพื่อปรับปรุงประสบการณ์การใช้อุปกรณ์ แอปและบริการต่างๆ จะยังคงสแกนหาเครือข่าย Wi‑Fi ได้ทุกเมื่อแม้ว่า Wi‑Fi จะปิดอยู่ คุณเปลี่ยนตัวเลือกนี้ได้ในการตั้งค่าการสแกนหา Wi-Fi "<annotation id="link">"เปลี่ยน"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"เลือกผู้ใช้"</string>
</resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index ee569df1bf65..266f77463816 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Mga kontrol sa notification ng power"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Naka-on"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Naka-off"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Naka-on - Batay sa mukha"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Sa pamamagitan ng mga kontrol sa notification ng power, magagawa mong itakda ang antas ng kahalagahan ng mga notification ng isang app mula 0 hanggang 5. \n\n"<b>"Antas 5"</b>" \n- Ipakita sa itaas ng listahan ng notification \n- Payagan ang pag-istorbo kapag full screen \n- Palaging sumilip \n\n"<b>"Antas 4"</b>" \n- Pigilan ang pag-istorbo kapag full screen \n- Palaging sumilip \n\n"<b>"Antas 3"</b>" \n- Pigilan ang pag-istorbo kapag full screen \n- Huwag kailanman sumilip \n\n"<b>"Antas 2"</b>" \n- Pigilan ang pag-istorbo kapag full screen \n- Huwag kailanman sumilip \n- Huwag kailanman tumunog o mag-vibrate \n\n"<b>"Antas 1"</b>" \n- Pigilan ang pag-istorbo kapag full screen \n- Huwag kailanman sumilip \n- Huwag kailanman tumunog o mag-vibrate \n- Itago sa lock screen at status bar \n- Ipakita sa ibaba ng listahan ng notification \n\n"<b>"Antas 0"</b>" \n- I-block ang lahat ng notification mula sa app"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Mga Notification"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Hindi mo na makikita ang mga notification na ito"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Ituloy"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Mga Setting"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Nagpe-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> ni/ng <xliff:g id="ARTIST_NAME">%2$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> sa <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"I-play"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Buksan ang <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"I-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> ni/ng <xliff:g id="ARTIST_NAME">%2$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 device ang napili"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> (na) device ang napili"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (nakadiskonekta)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(nadiskonekta)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Hindi makakonekta. Subukan ulit."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Magpares ng bagong device"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Numero ng build"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Tingnan lahat"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para lumipat ng network, idiskonekta ang ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Para pahusayin ang karanasan sa device, puwede pa ring mag-scan ng mga Wi-Fi network ang mga app at serbisyo anumang oras, kahit habang naka-off ang Wi‑Fi. Mababago mo ito sa mga setting ng pag-scan ng Wi-Fi. "<annotation id="link">"Baguhin"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pumili ng user"</string>
</resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 057a9e06d179..5abcb4a2550e 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Güç bildirim kontrolleri"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Açık"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Kapalı"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Açık - Yüze göre"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Güç bildirim kontrolleriyle, bir uygulamanın bildirimleri için 0 ile 5 arasında bir önem düzeyi ayarlayabilirsiniz. \n\n"<b>"5. Düzey"</b>" \n- Bildirim listesinin en üstünde gösterilsin \n- Tam ekran kesintisine izin verilsin \n- Ekranda her zaman kısaca belirsin \n\n"<b>"4. Düzey"</b>" \n- Tam ekran kesintisi engellensin \n- Ekranda her zaman kısaca belirsin \n\n"<b>"3. Düzey"</b>" \n- Tam ekran kesintisi engellensin \n- Ekranda hiçbir zaman kısaca belirmesin \n\n"<b>"2. Düzey"</b>" \n- Tam ekran kesintisi engellensin \n- Ekranda hiçbir zaman belirmesin \n- Hiçbir zaman ses çıkarmasın ve titreştirmesin \n\n"<b>"1. Düzey"</b>" \n- Tam ekran kesintisi engellensin \n- Ekranda hiçbir zaman kısaca belirmesin \n- Hiçbir zaman ses çıkarmasın veya titreştirmesin \n- Kilit ekranından ve durum çubuğundan gizlensin \n- Bildirim listesinin en altında gösterilsin \n\n"<b>"0. Düzey"</b>" \n- Uygulamadan gelen tüm bildirimler engellensin"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Bildirimler"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Bu bildirimleri artık görmeyeceksiniz"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Devam ettir"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Ayarlar"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> uygulamasından <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısı çalıyor"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Oynat"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> uygulamasını aç"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> uygulamasından <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısını çal"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 cihaz seçildi"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> cihaz seçildi"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (bağlı değil)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(bağlantı kesildi)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Bağlanılamadı. Tekrar deneyin."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Yeni cihaz eşle"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Derleme numarası"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Tümünü göster"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Ağ değiştirmek için ethernet bağlantısını kesin"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Uygulamalar ve hizmetler, cihaz deneyimini iyileştirmek için Kablosuz özelliği kapalı bile olsa kablosuz ağlar herhangi bir zamanda tarayabilir. Bunu kablosuz ağ taraması ayarlarından değiştirebilirsiniz. "<annotation id="link">"Değiştir"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kullanıcı seçin"</string>
</resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 62b85c45bf38..fd5c3d449fde 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -716,7 +716,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Елементи керування сповіщеннями"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Увімк."</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Вимк."</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Увімкнути (за обличчям)"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"За допомогою елементів керування сповіщеннями ви можете налаштувати пріоритет сповіщень додатка – від 0 до 5 рівня. \n\n"<b>"Рівень 5"</b>\n"- Показувати сповіщення вгорі списку \n- Виводити на весь екран \n- Завжди показувати короткі сповіщення \n\n"<b>"Рівень 4"</b>\n"- Не виводити на весь екран \n- Завжди показувати короткі сповіщення \n\n"<b>"Рівень 3"</b>\n"- Не виводити на весь екран \n- Не показувати короткі сповіщення \n\n"<b>"Рівень 2"</b>\n"- Не виводити на весь екран \n- Не показувати короткі сповіщення \n- Вимкнути звук і вібросигнал \n\n"<b>"Рівень 1"</b>\n"- Не виводити на весь екран \n- Не показувати короткі сповіщення \n- Вимкнути звук і вібросигнал \n- Не показувати на заблокованому екрані та в рядку стану \n- Показувати сповіщення внизу списку \n\n"<b>"Рівень 0"</b>\n"- Блокувати всі сповіщення з додатка"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Сповіщення"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Ви більше не бачитимете цих сповіщень"</string>
@@ -1103,6 +1102,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Відновити"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Налаштування"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Пісня \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\", яку виконує <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, грає в додатку <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> з <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Відтворення"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Відкрити додаток <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Увімкнути пісню \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\", яку виконує <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, у додатку <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1123,7 +1123,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Вибрано 1 пристрій"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Вибрано пристроїв: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (відключено)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(від’єднано)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Не вдалося підключитися. Повторіть спробу."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Підключити новий пристрій"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Номер складання"</string>
@@ -1192,4 +1192,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Показати все"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Щоб вибрати іншу мережу, від’єднайте кабель Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Щоб користуватися пристроєм було зручніше, додатки й сервіси можуть шукати бездротові мережі, навіть якщо Wi-Fi вимкнено. Це налаштування можна змінити в параметрах пошуку мереж Wi-Fi. "<annotation id="link">"Змінити"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Виберіть користувача"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 88be783776db..92aad208b4f6 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"پاور اطلاع کے کنٹرولز"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"آن"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"آف"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"آن - چہرے پر مبنی"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"پاور اطلاع کنٹرولز کے ساتھ آپ کسی ایپ کی اطلاعات کیلئے 0 سے 5 تک اہمیت کی سطح سیٹ کر سکتے ہیں۔ \n\n"<b>"سطح 5"</b>\n"- اطلاعات کی فہرست کے اوپر دکھائیں \n- پوری اسکرین کی مداخلت کی اجازت دیں \n- ہمیشہ جھانکنا\n\n"<b>"سطح 4"</b>\n"- پوری اسکرین کی مداخلت کو روکیں \n- ہمیشہ جھانکنا\n\n"<b>"سطح 3"</b>\n"- پوری اسکرین کی مداخلت کو روکیں \n- کبھی نہ جھانکنا \n\n"<b>"سطح 2"</b>\n"- پوری اسکرین کی مداخلت کو روکیں \n- کبھی نہ جھانکنا \n- کبھی آواز اور ارتعاش پیدا نہ کرنا \n\n"<b>" سطح 1"</b>\n"- پوری اسکرین کی مداخلت کو روکنا \n- کبھی نہ جھانکنا \n- کبھی بھی آواز یا ارتعاش پیدا نہ کرنا\n- مقفل اسکرین اور اسٹیٹس بار سے چھپانا \n - اطلاع کی فہرست کی نیچے دکھانا \n\n"<b>"سطح 0"</b>\n"- ایپ سے تمام اطلاعات مسدود کریں"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"اطلاعات"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"آپ کو یہ اطلاعات مزید دکھائی نہیں دیں گی"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"دوبارہ شروع کریں"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"ترتیبات"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> سے <xliff:g id="ARTIST_NAME">%2$s</xliff:g> کا <xliff:g id="SONG_NAME">%1$s</xliff:g> چل رہا ہے"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> از <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"چلائیں"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> کھولیں"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> سے <xliff:g id="ARTIST_NAME">%2$s</xliff:g> کا <xliff:g id="SONG_NAME">%1$s</xliff:g> چلائیں"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"گروپ"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 آلہ منتخب کیا گیا"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> آلات منتخب کیے گئے"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (غیر منسلک ہو گیا)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(غیر منسلک ہے)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"منسلک نہیں ہو سکا۔ پھر کوشش کریں۔"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"نئے آلہ کا جوڑا بنائیں"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"بلڈ نمبر"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"سبھی دیکھیں"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"نیٹ ورکس پر سوئچ کرنے کیلئے، ایتھرنیٹ غیر منسلک کریں"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"‏آلے کے تجربے کو بہتر بنانے کے لیے، Wi‑Fi کے آف ہونے پر بھی ایپس اور سروسز کسی بھی وقت Wi‑Fi نیٹ ورکس اسکین کر سکتی ہیں۔ آپ اسے Wi‑Fi اسکیننگ کی ترتیبات میں تبدیل کر سکتے ہیں۔ "<annotation id="link">"تبدیل کریں"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"صارف منتخب کریں"</string>
</resources>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index fb396a91421c..a95639a3f423 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Bildirishnomalar uchun kengaytirilgan boshqaruv"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Yoniq"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Yoqilmagan"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Yoqish - Yuz asosida"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Bildirishnomalar uchun kengaytirilgan boshqaruv yordamida ilova bildirishnomalarining muhimlik darajasini (0-5) sozlash mumkin. \n\n"<b>"5-daraja"</b>" \n- Bildirishnomani ro‘yxatning boshida ko‘rsatish \n- To‘liq ekranli bildirishnomalarni ko‘rsatish \n- Qalqib chiquvchi bildirishnomalarni ko‘rsatish \n\n"<b>"4-daraja"</b>" \n- To‘liq ekranli bildirishnomalarni ko‘rsatmaslik \n- Qalqib chiquvchi bildirishnomalarni ko‘rsatish \n\n"<b>"3-daraja"</b>" \n- To‘liq ekranli bildirishnomalarni ko‘rsatmaslik \n- Qalqib chiquvchi bildirishnomalarni ko‘rsatmaslik \n\n"<b>"2-daraja"</b>" \n- To‘liq ekranli bildirishnomalarni ko‘rsatmaslik \n- Qalqib chiquvchi bildirishnomalarni ko‘rsatmaslik \n- Ovoz va tebranishdan foydalanmaslik \n\n"<b>"1-daraja"</b>" \n- To‘liq ekranli bildirishnomalarni ko‘rsatmaslik \n- Qalqib chiquvchi bildirishnomalarni ko‘rsatmaslik \n- Ovoz va tebranishdan foydalanmaslik \n- Ekran qulfi va holat qatorida ko‘rsatmaslik \n- Bildirishnomani ro‘yxatning oxirida ko‘rsatish \n\n"<b>"0-daraja"</b>" \n- Ilovadan keladigan barcha bildirishnomalarni bloklash"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Bildirishnomalar"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Bu bildirishnomalar endi chiqmaydi"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Davom etish"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Sozlamalar"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ilovasida ijro etilmoqda: <xliff:g id="SONG_NAME">%1$s</xliff:g> – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> / <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Ijro"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ilovasini ochish"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ilovasida ijro etish: <xliff:g id="SONG_NAME">%1$s</xliff:g> – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Guruh"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ta qurilma tanlandi"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> ta qurilma tanlandi"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (uzilgan)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(uzildi)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Ulanmadi. Qayta urining."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Yangi qurilmani ulash"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Nashr raqami"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Hammasi"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Boshqa tarmoqqa almashish uchun Ethernet tarmogʻini uzing"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Qurilma ishlashini yaxshilash uchun ilova va xizmatlar hatto Wi-Fi yoqilmaganda ham istalgan vaqt Wi-Fi tarmoqlarni qidirishi mumkin. Buni taqiqlash uchun Wi-Fi tarmoqlarni qidirish funksiyasini faolsizlantiring. "<annotation id="link">"Sozlamalarni ochish"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Foydalanuvchini tanlang"</string>
</resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 7eb2a8b4eb11..9a63886fb711 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Điều khiển thông báo nguồn"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Bật"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Đang tắt"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Đang bật – Dựa trên khuôn mặt"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Với các kiểm soát thông báo nguồn, bạn có thể đặt cấp độ quan trọng từ 0 đến 5 cho các thông báo của ứng dụng. \n\n"<b>"Cấp 5"</b>" \n- Hiển thị ở đầu danh sách thông báo \n- Cho phép gián đoạn ở chế độ toàn màn hình \n- Luôn xem nhanh \n\n"<b>"Cấp 4"</b>" \n- Ngăn gián đoạn ở chế độ toàn màn hình \n- Luôn xem nhanh \n\n"<b>"Cấp 3"</b>" \n- Ngăn gián đoạn ở chế độ toàn màn hình \n- Không bao giờ xem nhanh \n\n"<b>"Cấp 2"</b>" \n- Ngăn gián đoạn ở chế độ toàn màn hình \n- Không bao giờ xem nhanh \n- Không bao giờ có âm báo và rung \n\n"<b>"Cấp 1"</b>" \n- Ngăn gián đoạn ở chế độ toàn màn hình \n- Không bao giờ xem nhanh \n- Không bao giờ có âm báo và rung \n- Ẩn khỏi màn hình khóa và thanh trạng thái \n- Hiển thị ở cuối danh sách thông báo \n\n"<b>"Cấp 0"</b>" \n- Chặn tất cả các thông báo từ ứng dụng"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Thông báo"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Bạn sẽ không thấy các thông báo này nữa"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Tiếp tục"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Cài đặt"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"Đang phát <xliff:g id="SONG_NAME">%1$s</xliff:g> của <xliff:g id="ARTIST_NAME">%2$s</xliff:g> trên <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Phát"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Mở <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Phát <xliff:g id="SONG_NAME">%1$s</xliff:g> của <xliff:g id="ARTIST_NAME">%2$s</xliff:g> trên <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Nhóm"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Đã chọn 1 thiết bị"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"Đã chọn <xliff:g id="COUNT">%1$d</xliff:g> thiết bị"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (đã ngắt kết nối)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(đã ngắt kết nối)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Không thể kết nối. Hãy thử lại."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Ghép nối thiết bị mới"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Số bản dựng"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Xem tất cả"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Để chuyển mạng, hãy rút cáp Ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Để cải thiện trải nghiệm khi dùng thiết bị, các ứng dụng và dịch vụ vẫn có thể quét tìm mạng Wi‑Fi bất cứ lúc nào, ngay cả khi Wi‑Fi tắt. Bạn có thể thay đổi chế độ này trong phần cài đặt tính năng Quét tìm Wi‑Fi. "<annotation id="link">"Thay đổi"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Chọn người dùng"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index faae83c6dd83..794b760b64b7 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"高级通知设置"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"已开启"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"已关闭"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"已开启 - 基于人脸"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"利用高级通知设置,您可以为应用通知设置从 0 级到 5 级的重要程度等级。\n\n"<b>"5 级"</b>" \n- 在通知列表顶部显示 \n- 允许全屏打扰 \n- 一律短暂显示通知 \n\n"<b>"4 级"</b>" \n- 禁止全屏打扰 \n- 一律短暂显示通知 \n\n"<b>"3 级"</b>" \n- 禁止全屏打扰 \n- 一律不短暂显示通知 \n\n"<b>"2 级"</b>" \n- 禁止全屏打扰 \n- 一律不短暂显示通知 \n- 一律不发出声音或振动 \n\n"<b>"1 级"</b>" \n- 禁止全屏打扰 \n- 一律不短暂显示通知 \n- 一律不发出声音或振动 \n- 不在锁定屏幕和状态栏中显示 \n- 在通知列表底部显示 \n\n"<b>"0 级"</b>" \n- 屏蔽应用的所有通知"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"通知"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"您将不会再看到这些通知"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"继续播放"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"设置"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"正在通过<xliff:g id="APP_LABEL">%3$s</xliff:g>播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> / <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"播放"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"打开<xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"通过<xliff:g id="APP_LABEL">%3$s</xliff:g>播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"群组"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"已选择 1 个设备"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"已选择 <xliff:g id="COUNT">%1$d</xliff:g> 个设备"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>(已断开连接)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(已断开连接)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"无法连接。请重试。"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"与新设备配对"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"版本号"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"查看全部"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"如要切换网络,请断开以太网连接"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"为了提升设备的使用体验,即使 WLAN 已关闭,应用和服务仍可以随时扫描 WLAN 网络。您可以在 WLAN 扫描设置中更改此设置。"<annotation id="link">"更改"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"选择用户"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index b5c40693865e..d460fb883da1 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"通知控制項"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"開啟"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"關閉"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"已開啟 - 根據面孔偵測"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"通知控制項讓您設定應用程式通知的重要性 (0 至 5 級)。\n\n"<b>"第 5 級"</b>" \n- 在通知清單頂部顯示 \n- 允許全螢幕騷擾 \n- 一律顯示通知 \n\n"<b>"第 4 級"</b>" \n- 阻止全螢幕騷擾 \n- 一律顯示通知 \n\n"<b>"第 3 級"</b>" \n- 阻止全螢幕騷擾 \n- 永不顯示通知 \n\n"<b>"第 2 級"</b>" \n- 阻止全螢幕騷擾 \n- 永不顯示通知 \n- 永不發出聲響和震動 \n\n"<b>"第 1 級"</b>" \n- 阻止全螢幕騷擾 \n- 永不顯示通知 \n- 永不發出聲響和震動 \n- 從上鎖畫面和狀態列中隱藏 \n- 在通知清單底部顯示 \n\n"<b>"第 0 級"</b>" \n- 封鎖所有應用程式通知"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"通知"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"您不會再看到這些通知"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"繼續播放"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"正在透過 <xliff:g id="APP_LABEL">%3$s</xliff:g> 播放 <xliff:g id="ARTIST_NAME">%2$s</xliff:g> 的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"播放"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"開啟 <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"在 <xliff:g id="APP_LABEL">%3$s</xliff:g> 播放 <xliff:g id="ARTIST_NAME">%2$s</xliff:g> 的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"群組"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"已選取 1 部裝置"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"已選取 <xliff:g id="COUNT">%1$d</xliff:g> 部裝置"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (已中斷連線)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(已中斷連線)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"無法連線,請再試一次。"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"配對新裝置"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"版本號碼"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"顯示全部"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"如要切換網絡,請中斷以太網連線"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"為改善裝置的使用體驗,應用程式和服務仍可隨時掃瞄 Wi-Fi 網絡 (即使 Wi-Fi 已關閉)。您可在 Wi-Fi 掃瞄設定中變更此設定。"<annotation id="link">"變更"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"選取使用者"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index a23ec38a6327..bbcf0e4ed2b8 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"電源通知控制項"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"開啟"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"關閉"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"已開啟 - 依臉部方向旋轉"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"只要使用電源通知控制項,你就能為應用程式通知設定從 0 到 5 的重要性等級。\n\n"<b>"等級 5"</b>" \n- 顯示在通知清單頂端 \n- 允許全螢幕通知 \n- 一律允許短暫顯示通知 \n\n"<b>"等級 4"</b>" \n- 禁止全螢幕通知 \n- 一律允許短暫顯示通知 \n\n"<b>"等級 3"</b>" \n- 禁止全螢幕通知 \n- 一律不允許短暫顯示通知 \n\n"<b>"等級 2"</b>" \n- 禁止全螢幕通知 \n- 一律不允許短暫顯示通知 \n- 一律不發出音效或震動 \n\n"<b>"等級 1"</b>" \n- 禁止全螢幕通知 \n- 一律不允許短暫顯示通知 \n- 一律不發出音效或震動 \n- 在鎖定畫面和狀態列中隱藏 \n- 顯示在通知清單底端 \n\n"<b>"等級 0"</b>" \n- 封鎖應用程式的所有通知"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"通知"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"你不會再看到這些通知"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"繼續播放"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"系統正透過「<xliff:g id="APP_LABEL">%3$s</xliff:g>」播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>,共 <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"播放"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"開啟「<xliff:g id="APP_LABEL">%1$s</xliff:g>」"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"透過「<xliff:g id="APP_LABEL">%3$s</xliff:g>」播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"群組"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"已選取 1 部裝置"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"已選取 <xliff:g id="COUNT">%1$d</xliff:g> 部裝置"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (已中斷連線)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(連線中斷)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"無法連線,請再試一次。"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"配對新裝置"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"版本號碼"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"查看全部"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"如要切換網路,請中斷乙太網路連線"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"為提升裝置的使用體驗,應用程式和服務仍可隨時掃描 Wi‑Fi 網路,即使 Wi-Fi 連線功能處於關閉狀態時亦然。你可以前往「掃描 Wi-Fi」設定進行變更。"<annotation id="link">"變更"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"選取使用者"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 983e2f751fdd..6c43d3ad374e 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -710,7 +710,6 @@
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Izilawuli zesaziso zamandla"</string>
<string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Vuliwe"</string>
<string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Valiwe"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"Vuliwe - Kususelwe kubuso"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"Ngezilawuli zesaziso zamandla, ungasetha ileveli ebalulekile kusuka ku-0 kuya ku-5 kusuka kuzaziso zohlelo lokusebenza. \n\n"<b>"Ileveli 5"</b>" \n- Ibonisa phezulu kuhlu lwesaziso \n- Vumela ukuphazamiseka kwesikrini esigcwele \n- Ukuhlola njalo \n\n"<b>"Ileveli 4"</b>" \n- Gwema ukuphazamiseka kwesikrini esigcwele \n- Ukuhlola njalo \n\n"<b>"Ileveli 3"</b>" \n- Gwema ukuphazamiseka kwesikrini esigcwele \n- Ukungahloli \n\n"<b>"Ileveli 2"</b>" \n- Gwema ukuphazamiseka kwesikrini esigcwele \n- Ukungahloli \n- Ungenzi umsindo nokudlidliza \n\n"<b>"Ileveli 1"</b>" \n- Gwema ukuphazamiseka kwesikrini esigcwele \n- Ukungahloli \n- Ungenzi umsindo noma ukudlidliza \n- Fihla kusuka kusikrini sokukhiya nebha yesimo \n- Bonisa phansi kohlu lwesaziso \n\n"<b>"Ileveli 0"</b>" \n- Vimbela zonke izaziso kusuka kuhlelo lokusebenza"</string>
<string name="notification_header_default_channel" msgid="225454696914642444">"Izaziso"</string>
<string name="notification_channel_disabled" msgid="928065923928416337">"Ngeke usabona lezi zaziso"</string>
@@ -1091,6 +1090,7 @@
<string name="controls_media_resume" msgid="1933520684481586053">"Qalisa kabusha"</string>
<string name="controls_media_settings_button" msgid="5815790345117172504">"Izilungiselelo"</string>
<string name="controls_media_playing_item_description" msgid="4531853311504359098">"I-<xliff:g id="SONG_NAME">%1$s</xliff:g> ka-<xliff:g id="ARTIST_NAME">%2$s</xliff:g> idlala kusuka ku-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
+ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> ku-<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Dlala"</string>
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Vula i-<xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Dlala i-<xliff:g id="SONG_NAME">%1$s</xliff:g> ka-<xliff:g id="ARTIST_NAME">%2$s</xliff:g> kusuka ku-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
@@ -1111,7 +1111,7 @@
<string name="media_output_dialog_group" msgid="5571251347877452212">"Iqembu"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"idivayisi ekhethiwe e-1"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"amadivayisi akhethiwe angu-<xliff:g id="COUNT">%1$d</xliff:g>"</string>
- <string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (inqamukile)"</string>
+ <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(inqamukile)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Ayikwazanga ukuxhumeka. Zama futhi."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Bhangqa idivayisi entsha"</string>
<string name="build_number_clip_data_label" msgid="3623176728412560914">"Yakha inombolo"</string>
@@ -1180,4 +1180,5 @@
<string name="see_all_networks" msgid="3773666844913168122">"Bona konke"</string>
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Ukuze ushintshe amanethiwekhi, nqamula i-ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"Ukuze kuthuthukiswe ukuzizwela kwedivayisi, ama-app namasevisi kusengakwazi ukuskena amanethiwekhi we-Wi-Fi noma kunini, ngisho noma i-Wi-Fi ivaliwe, Ungashintsha lokhu kumasethingi Wokuskena i-Wi-Fi. "<annotation id="link">"Shintsha"</annotation></string>
+ <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Khetha umsebenzisi"</string>
</resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 3121ce37490a..db699242a061 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -77,6 +77,7 @@
<attr name="numColumns" format="integer" />
<attr name="verticalSpacing" format="dimension" />
<attr name="horizontalSpacing" format="dimension" />
+ <attr name="fixedChildWidth" format="dimension" />
</declare-styleable>
<!-- Theme for icons in the status/nav bar (light/dark). background/fillColor is used for dual
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index f539b0cf68a1..03c6fddb145c 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -69,6 +69,9 @@
<!-- Shadows under the clock, date and other keyguard text fields -->
<color name="keyguard_shadow_color">#B2000000</color>
+ <!-- Color for the images in keyguard number pad buttons -->
+ <color name="keyguard_keypad_image_color">?android:attr/textColorPrimaryInverse</color>
+
<!-- Color for rounded background for activated user in keyguard user switcher -->
<color name="kg_user_switcher_activated_background_color">#26000000</color>
<!-- Icon color for user avatars in keyguard user switcher -->
@@ -149,7 +152,6 @@
<!-- Chosen so fill over background matches single tone -->
<color name="dark_mode_qs_icon_color_dual_tone_fill">#99000000</color>
- <color name="docked_divider_background">#ff000000</color>
<color name="docked_divider_handle">#ffffff</color>
<drawable name="forced_resizable_background">#59000000</drawable>
<color name="minimize_dock_shadow_start">#60000000</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d274c917c26d..d0de876a6b27 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -86,7 +86,10 @@
<bool name="config_navigation_bar_enable_auto_dim_no_visible_wallpaper">true</bool>
<!-- The maximum number of tiles in the QuickQSPanel -->
- <integer name="quick_qs_panel_max_columns">4</integer>
+ <integer name="quick_qs_panel_max_tiles">4</integer>
+
+ <!-- The maximum number of rows in the QuickQSPanel -->
+ <integer name="quick_qs_panel_max_rows">2</integer>
<!-- The number of columns in the QuickSettings -->
<integer name="quick_settings_num_columns">2</integer>
@@ -159,7 +162,7 @@
<!-- The maximum count of notifications on Keyguard. The rest will be collapsed in an overflow
card. -->
- <integer name="keyguard_max_notification_count">3</integer>
+ <integer name="keyguard_max_notification_count">-1</integer>
<!-- Defines the implementation of the velocity tracker to be used for the panel expansion. Can
be 'platform' or 'noisy' (i.e. for noisy touch screens). -->
@@ -188,6 +191,15 @@
low powered state yet. -->
<bool name="doze_single_tap_uses_prox">true</bool>
+ <!-- Doze: whether the single tap sensor uses the proximity sensor in the given posture.
+ See doze_single_tap_uses_prox for usage. -->
+ <integer-array name="doze_single_tap_uses_prox_posture_mapping">
+ <item>1</item> <!-- UNKNOWN -->
+ <item>1</item> <!-- CLOSED -->
+ <item>1</item> <!-- HALF_OPENED -->
+ <item>1</item> <!-- OPENED -->
+ </integer-array>
+
<!-- Doze: whether the long press sensor uses the proximity sensor.
If both this parameter and doze_selectively_register_prox are true, registration for the
sensor will be delayed when the device first enters dozing but the device has not entered its
@@ -208,9 +220,27 @@
always-on display) -->
<string name="doze_brightness_sensor_type" translatable="false"></string>
+ <!-- Name of a sensor per posture state that provides a low-power estimate of the desired
+ display brightness, suitable to listen to while the device is asleep (e.g. during
+ always-on display) -->
+ <string-array name="doze_brightness_sensor_name_posture_mapping" translatable="false">
+ <!-- UNKNOWN -->
+ <!-- CLOSED -->
+ <!-- HALF_OPENED -->
+ <!-- OPENED -->
+ </string-array>
+
<!-- Override value to use for proximity sensor. -->
<string name="proximity_sensor_type" translatable="false"></string>
+ <!-- Sensor type per posture state to use for proximity sensor -->
+ <string-array name="proximity_sensor_posture_mapping" translatable="false">
+ <!-- UNKNOWN -->
+ <!-- CLOSED -->
+ <!-- HALF_OPENED -->
+ <!-- OPENED -->
+ </string-array>
+
<!-- If using proximity_sensor_type, specifies a threshold value to distinguish near and
far break points. A sensor value less than this is considered "near". -->
<item name="proximity_sensor_threshold" translatable="false" format="float" type="dimen"></item>
@@ -224,6 +254,15 @@
<!-- Override value to use for proximity sensor as confirmation for proximity_sensor_type. -->
<string name="proximity_sensor_secondary_type" translatable="false"></string>
+ <!-- Sensor type per posture state to use for proximity sensor as a confirmation for
+ proximity_sensor_type. -->
+ <string-array name="proximity_sensor_secondary_posture_mapping" translatable="false">
+ <!-- UNKNOWN -->
+ <!-- CLOSED -->
+ <!-- HALF_OPENED -->
+ <!-- OPENED -->
+ </string-array>
+
<!-- If using proximity_sensor_secondary_type, specifies a threshold value to distinguish
near and far break points. A sensor value less than this is considered "near". -->
<item name="proximity_sensor_secondary_threshold" translatable="false" format="float"
@@ -608,8 +647,6 @@
<!-- Whether wallet view is shown in landscape / seascape orientations -->
<bool name="global_actions_show_landscape_wallet_view">false</bool>
- <!-- Whether global actions should show an informational message about changes in S -->
- <bool name="global_actions_show_change_info">false</bool>
<!-- Package name of the preferred system app to perform eSOS action -->
<string name="config_preferredEmergencySosPackage" translatable="false"></string>
@@ -670,4 +707,43 @@
1 - Override the setting to always bypass keyguard
2 - Override the setting to never bypass keyguard -->
<integer name="config_face_unlock_bypass_override">0</integer>
+
+ <!-- Flag to activate notification to contents feature -->
+ <bool name="config_notificationToContents">false</bool>
+
+ <!-- Respect drawable/rounded_secondary.xml intrinsic size for multiple radius corner path
+ customization for secondary display-->
+ <bool name="config_roundedCornerMultipleRadiusSecondary">false</bool>
+
+ <!-- Whether the rounded corners are multiple radius for each display in a multi-display device.
+ {@see com.android.internal.R.array#config_displayUniqueIdArray} -->
+ <array name="config_roundedCornerMultipleRadiusArray">
+ <item>@bool/config_roundedCornerMultipleRadius</item>
+ <item>@bool/config_roundedCornerMultipleRadiusSecondary</item>
+ </array>
+
+ <!-- The rounded corner drawable for each display in a multi-display device.
+ {@see com.android.internal.R.array#config_displayUniqueIdArray} -->
+ <array name="config_roundedCornerDrawableArray">
+ <item>@drawable/rounded</item>
+ <item>@drawable/rounded_secondary</item>
+ </array>
+
+ <!-- The top rounded corner drawable for each display in a multi-display device.
+ {@see com.android.internal.R.array#config_displayUniqueIdArray} -->
+ <array name="config_roundedCornerTopDrawableArray">
+ <item>@drawable/rounded_corner_top</item>
+ <item>@drawable/rounded_corner_top_secondary</item>
+ </array>
+
+ <!-- The bottom rounded corner drawable for each display in a multi-display device.
+ {@see com.android.internal.R.array#config_displayUniqueIdArray} -->
+ <array name="config_roundedCornerBottomDrawableArray">
+ <item>@drawable/rounded_corner_bottom</item>
+ <item>@drawable/rounded_corner_bottom_secondary</item>
+ </array>
+
+ <!-- Flag to enable privacy dot views, it shall be true for normal case -->
+ <bool name="config_enablePrivacyDot">true</bool>
+
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c231afc41953..e5ea3688ec0d 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -56,14 +56,6 @@
<!-- The amount by which the arrow is shifted to avoid the finger-->
<dimen name="navigation_edge_finger_offset">48dp</dimen>
- <!-- Luminance threshold to determine black/white contrast for the navigation affordances -->
- <item name="navigation_luminance_threshold" type="dimen" format="float">0.5</item>
- <!-- Luminance change threshold that allows applying new value if difference was exceeded -->
- <item name="navigation_luminance_change_threshold" type="dimen" format="float">0.05</item>
-
- <dimen name="floating_rotation_button_diameter">40dp</dimen>
- <dimen name="floating_rotation_button_min_margin">4dp</dimen>
-
<!-- Height of notification icons in the status bar -->
<dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
@@ -364,8 +356,6 @@
<!-- The width/height of the icon of a navigation button -->
<dimen name="navigation_icon_size">32dp</dimen>
- <dimen name="navigation_key_padding">0dp</dimen>
-
<!-- The width of the view containing the menu/ime navigation bar icons -->
<dimen name="navigation_extra_key_width">36dp</dimen>
@@ -401,8 +391,11 @@
<dimen name="status_bar_header_padding_bottom">48dp</dimen>
<!-- The height of the container that holds the battery and time in the quick settings header.
+ Preferred over using "@*android:dimen/quick_qs_offset_height" as system icons are not always
+ present in quick settings (e.g. in split shade) and it's useful to be able to override this
+ value in such cases.
-->
- <dimen name="qs_header_system_icons_area_height">48dp</dimen>
+ <dimen name="qs_header_system_icons_area_height">@*android:dimen/quick_qs_offset_height</dimen>
<!-- How far the quick-quick settings panel extends below the status bar -->
<dimen name="qs_quick_header_panel_height">128dp</dimen>
@@ -454,6 +447,10 @@
<!-- Width for the notification panel and related windows -->
<dimen name="match_parent">-1px</dimen>
+ <!-- Height of status bar in split shade mode - visible only on large screens -->
+ <dimen name="split_shade_header_height">@*android:dimen/quick_qs_offset_height</dimen>
+ <dimen name="split_shade_header_min_height">@dimen/qs_header_row_min_height</dimen>
+
<!-- The top margin of the panel that holds the list of notifications. -->
<dimen name="notification_panel_margin_top">0dp</dimen>
@@ -525,6 +522,19 @@
<!-- Size of the icon inside each item in the ringer selector drawer. -->
<dimen name="volume_ringer_drawer_icon_size">24dp</dimen>
+ <!-- The maximum width of the navigation bar ripples. -->
+ <dimen name="key_button_ripple_max_width">95dp</dimen>
+
+ <dimen name="rounded_corner_content_padding">0dp</dimen>
+
+ <dimen name="navigation_key_padding">0dp</dimen>
+
+ <!-- Floating rotation button -->
+ <dimen name="floating_rotation_button_diameter">40dp</dimen>
+ <dimen name="floating_rotation_button_min_margin">20dp</dimen>
+ <dimen name="floating_rotation_button_taskbar_left_margin">20dp</dimen>
+ <dimen name="floating_rotation_button_taskbar_bottom_margin">10dp</dimen>
+
<!-- Gravity for the notification panel -->
<integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top -->
@@ -603,7 +613,7 @@
<dimen name="qs_detail_item_primary_text_size">16sp</dimen>
<dimen name="qs_detail_item_secondary_text_size">14sp</dimen>
<dimen name="qs_detail_empty_text_size">14sp</dimen>
- <dimen name="qs_detail_margin_top">28dp</dimen>
+ <dimen name="qs_detail_header_margin_top">28dp</dimen>
<dimen name="qs_detail_back_margin_end">16dp</dimen>
<dimen name="qs_detail_header_text_padding">16dp</dimen>
<dimen name="qs_data_usage_text_size">14sp</dimen>
@@ -625,6 +635,7 @@
<dimen name="qs_footer_icon_size">20dp</dimen>
<dimen name="qs_header_top_padding">15dp</dimen>
<dimen name="qs_header_bottom_padding">14dp</dimen>
+ <dimen name="qs_header_row_min_height">48dp</dimen>
<dimen name="qs_footer_padding">20dp</dimen>
<dimen name="qs_security_footer_height">88dp</dimen>
@@ -653,6 +664,8 @@
<!-- Padding between subtitles and the following text in the QSFooter dialog -->
<dimen name="qs_footer_dialog_subtitle_padding">20dp</dimen>
+ <dimen name="qs_detail_margin_top">@*android:dimen/quick_qs_offset_height</dimen>
+
<dimen name="seek_bar_height">3dp</dimen>
<dimen name="seek_bar_corner_radius">3dp</dimen>
@@ -908,6 +921,7 @@
<dimen name="keyguard_affordance_fixed_height">48dp</dimen>
<dimen name="keyguard_affordance_fixed_width">48dp</dimen>
+ <dimen name="keyguard_affordance_fixed_radius">24dp</dimen>
<dimen name="keyguard_affordance_horizontal_offset">32dp</dimen>
<dimen name="keyguard_affordance_vertical_offset">32dp</dimen>
@@ -966,9 +980,6 @@
<dimen name="signal_indicator_to_icon_frame_spacing">3dp</dimen>
- <!-- The maximum width of the navigation bar ripples. -->
- <dimen name="key_button_ripple_max_width">95dp</dimen>
-
<!-- Inset shadow for FakeShadowDrawable. It is used to avoid gaps between the card
and the shadow. -->
<dimen name="fake_shadow_inset">1dp</dimen>
@@ -1137,7 +1148,7 @@
<!-- The maximum offset in either direction that elements are moved vertically to prevent
burn-in on AOD. -->
- <dimen name="burn_in_prevention_offset_y_large_clock">42dp</dimen>
+ <dimen name="burn_in_prevention_offset_y_clock">42dp</dimen>
<!-- Clock maximum font size (dp is intentional, to prevent any further scaling) -->
<dimen name="large_clock_text_size">150dp</dimen>
@@ -1158,7 +1169,6 @@
<!-- The absolute side margins of quick settings -->
<dimen name="quick_settings_bottom_margin_media">8dp</dimen>
- <dimen name="rounded_corner_content_padding">0dp</dimen>
<dimen name="nav_content_padding">0dp</dimen>
<dimen name="nav_quick_scrub_track_edge_padding">24dp</dimen>
<dimen name="nav_quick_scrub_track_thickness">10dp</dimen>
@@ -1324,6 +1334,7 @@
<dimen name="magnifier_up_down_controls_height">40dp</dimen>
<!-- The extra padding to show the whole outer border -->
<dimen name="magnifier_drag_handle_padding">3dp</dimen>
+ <dimen name="magnification_max_frame_size">300dp</dimen>
<!-- Home Controls -->
<dimen name="controls_header_side_margin">4dp</dimen>
@@ -1430,10 +1441,10 @@
<!-- Output switcher panel related dimensions -->
<dimen name="media_output_dialog_list_margin">12dp</dimen>
<dimen name="media_output_dialog_list_max_height">364dp</dimen>
- <dimen name="media_output_dialog_header_album_icon_size">52dp</dimen>
- <dimen name="media_output_dialog_header_back_icon_size">36dp</dimen>
+ <dimen name="media_output_dialog_header_album_icon_size">48dp</dimen>
+ <dimen name="media_output_dialog_header_back_icon_size">32dp</dimen>
<dimen name="media_output_dialog_header_icon_padding">16dp</dimen>
- <dimen name="media_output_dialog_icon_corner_radius">16dp</dimen>
+ <dimen name="media_output_dialog_icon_corner_radius">8dp</dimen>
<dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen>
<!-- Distance that the full shade transition takes in order for qs to fully transition to the
@@ -1550,6 +1561,13 @@
<!-- Location on the screen of the center of the fingerprint sensor. For devices with under
display fingerprint sensors, this directly corresponds to the fingerprint sensor's location.
For devices with sensors on the back of the device, this corresponds to the location on the
+ screen directly in front of the sensor.
+ By default, this is set to @null to use the horizontal center of the screen. -->
+ <dimen name="physical_fingerprint_sensor_center_screen_location_x">@null</dimen>
+
+ <!-- Location on the screen of the center of the fingerprint sensor. For devices with under
+ display fingerprint sensors, this directly corresponds to the fingerprint sensor's location.
+ For devices with sensors on the back of the device, this corresponds to the location on the
screen directly in front of the sensor. -->
<dimen name="physical_fingerprint_sensor_center_screen_location_y">610px</dimen>
@@ -1586,8 +1604,9 @@
<!-- Internet panel related dimensions -->
<dimen name="internet_dialog_list_margin">12dp</dimen>
- <dimen name="internet_dialog_list_max_height">646dp</dimen>
- <dimen name="internet_dialog_list_max_width">@dimen/match_parent</dimen>
+
+ <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
+ <dimen name="large_dialog_width">@dimen/match_parent</dimen>
<!-- Signal icon in internet dialog -->
<dimen name="signal_strength_icon_size">24dp</dimen>
@@ -1621,4 +1640,9 @@
<item name="communal_source_height_percentage" format="float" type="dimen">0.80</item>
<dimen name="drag_and_drop_icon_size">70dp</dimen>
+
+ <dimen name="qs_dialog_button_horizontal_padding">16dp</dimen>
+ <dimen name="qs_dialog_button_vertical_padding">8dp</dimen>
+ <!-- The button will be 48dp tall, but the background needs to be 36dp tall -->
+ <dimen name="qs_dialog_button_vertical_inset">6dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index efa87548af32..c598097cd5af 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -18,40 +18,12 @@
<resources>
<bool name="are_flags_overrideable">false</bool>
- <bool name="flag_notification_pipeline2">true</bool>
- <bool name="flag_notification_pipeline2_rendering">false</bool>
- <bool name="flag_notif_updates">true</bool>
-
- <bool name="flag_monet">false</bool>
-
- <!-- b/171917882 -->
- <bool name="flag_notification_twocolumn">false</bool>
-
- <!-- AOD/Lockscreen alternate layout -->
- <bool name="flag_keyguard_layout">true</bool>
+ <bool name="flag_monet">true</bool>
<!-- People Tile flag -->
<bool name="flag_conversations">false</bool>
- <!-- The new animations to/from lockscreen and AOD! -->
- <bool name="flag_lockscreen_animations">true</bool>
-
- <!-- The new swipe to unlock animation, which shows the app/launcher behind the keyguard during
- the swipe. -->
- <bool name="flag_new_unlock_swipe_animation">true</bool>
-
- <!-- The shared-element transition between lockscreen smartspace and launcher smartspace. -->
- <bool name="flag_smartspace_shared_element_transition">false</bool>
-
- <bool name="flag_pm_lite">true</bool>
-
<bool name="flag_charging_ripple">false</bool>
- <bool name="flag_ongoing_call_status_bar_chip">true</bool>
-
<bool name="flag_smartspace">false</bool>
-
- <bool name="flag_smartspace_deduping">true</bool>
-
- <bool name="flag_combined_status_bar_signal_icons">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4ad4fa9d0854..986f82f98565 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1779,8 +1779,6 @@
<string name="tuner_full_importance_settings">Power notification controls</string>
<string name="tuner_full_importance_settings_on">On</string>
<string name="tuner_full_importance_settings_off">Off</string>
- <!-- [CHAR LIMIT=NONE] Notification camera based rotation enabled description -->
- <string name="rotation_lock_camera_rotation_on">On - Face-based</string>
<string name="power_notification_controls_description">With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications.
\n\n<b>Level 5</b>
\n- Show at the top of the notification list
@@ -2832,6 +2830,8 @@
<string name="controls_media_settings_button">Settings</string>
<!-- Description for media control's playing media item, including information for the media's title, the artist, and source app [CHAR LIMIT=NONE]-->
<string name="controls_media_playing_item_description"><xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> is playing from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string>
+ <!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] -->
+ <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string>
<!-- Title for Smartspace recommendation card within media controls. The "Play" means the action to play a media [CHAR_LIMIT=10] -->
<string name="controls_media_smartspace_rec_title">Play</string>
@@ -2878,7 +2878,7 @@
<!-- Summary for media output group with the active device count [CHAR LIMIT=NONE] -->
<string name="media_output_dialog_multiple_devices"><xliff:g id="count" example="2">%1$d</xliff:g> devices selected</string>
<!-- Summary for disconnected status [CHAR LIMIT=50] -->
- <string name="media_output_dialog_disconnected"><xliff:g id="device_name" example="My device">%1$s</xliff:g> (disconnected)</string>
+ <string name="media_output_dialog_disconnected">(disconnected)</string>
<!-- Summary for connecting error message [CHAR LIMIT=NONE] -->
<string name="media_output_dialog_connect_failed">Couldn\'t connect. Try again.</string>
<!-- Title for pairing item [CHAR LIMIT=60] -->
@@ -2992,11 +2992,6 @@
<!-- Content description for a chip in the status bar showing that the user is currently on a phone call. [CHAR LIMIT=NONE] -->
<string name="ongoing_phone_call_content_description">Ongoing phone call</string>
- <!-- Placeholder for string describing changes in global actions -->
- <string name="global_actions_change_description" translatable="false"><xliff:g>%1$s</xliff:g></string>
- <!-- URL for more information about changes in global actions -->
- <string name="global_actions_change_url" translatable="false"></string>
-
<!-- Provider Model: Default title of the mobile network in the mobile layout. [CHAR LIMIT=50] -->
<string name="mobile_data_settings_title">Mobile data</string>
<!-- Provider Model: Summary text separator for preferences including a short description
@@ -3035,4 +3030,7 @@
<string name="to_switch_networks_disconnect_ethernet">To switch networks, disconnect ethernet</string>
<!-- Message to describe "Wi-Fi scan always available feature" when Wi-Fi is off and Wi-Fi scanning is on. [CHAR LIMIT=NONE] -->
<string name="wifi_scan_notify_message">To improve device experience, apps and services can still scan for Wi\u2011Fi networks at any time, even when Wi\u2011Fi is off. You can change this in Wi\u2011Fi scanning settings. <annotation id="link">Change</annotation></string>
+
+ <!-- Title for User Switch dialog. [CHAR LIMIT=20] -->
+ <string name="qs_user_switch_dialog_title">Select user</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 93d60cce2915..3f855c762273 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -331,9 +331,6 @@
<style name="Animation.ShutdownUi" parent="@android:style/Animation.Toast">
</style>
- <style name="Animation.MediaOutputDialog" parent="@android:style/Animation.InputMethod">
- </style>
-
<!-- Standard animations for hiding and showing the status bar. -->
<style name="Animation.StatusBar">
</style>
@@ -420,7 +417,12 @@
<item name="android:windowIsFloating">true</item>
</style>
- <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog" />
+ <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog">
+ <item name="android:buttonCornerRadius">28dp</item>
+ <item name="android:buttonBarPositiveButtonStyle">@style/Widget.QSDialog.Button</item>
+ <item name="android:buttonBarNegativeButtonStyle">@style/Widget.QSDialog.Button.BorderButton</item>
+ <item name="android:buttonBarNeutralButtonStyle">@style/Widget.QSDialog.Button.BorderButton</item>
+ </style>
<style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
@@ -436,10 +438,6 @@
<item name="android:windowCloseOnTouchOutside">true</item>
</style>
- <style name="Theme.SystemUI.Dialog.MediaOutput">
- <item name="android:windowBackground">@drawable/media_output_dialog_background</item>
- </style>
-
<style name="QSBorderlessButton">
<item name="android:padding">12dp</item>
<item name="android:background">@drawable/qs_btn_borderless_rect</item>
@@ -520,6 +518,10 @@
<item name="android:background">@drawable/btn_borderless_rect</item>
</style>
+ <style name="MediaOutputRoundedOutlinedButton" parent="@android:style/Widget.Material.Button">
+ <item name="android:background">@drawable/media_output_dialog_button_background</item>
+ </style>
+
<style name="TunerSettings" parent="@android:style/Theme.DeviceDefault.Settings">
<item name="android:windowActionBar">false</item>
<item name="preferenceTheme">@style/TunerPreferenceTheme</item>
@@ -936,8 +938,25 @@
<item name="actionDividerHeight">32dp</item>
</style>
- <style name="Theme.SystemUI.Dialog.Internet">
- <item name="android:windowBackground">@drawable/internet_dialog_background</item>
+ <style name="TextAppearance.QSDialog.Title" parent="Theme.SystemUI.Dialog">
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textSize">24sp</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:lineHeight">32sp</item>
+ </style>
+
+ <style name="Widget.QSDialog.Button" parent = "Theme.SystemUI.Dialog">
+ <item name="android:background">@drawable/qs_dialog_btn_filled</item>
+ <item name="android:textColor">@color/prv_text_color_on_accent</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:lineHeight">20sp</item>
+ <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:stateListAnimator">@null</item>
+ </style>
+
+ <style name="Widget.QSDialog.Button.BorderButton">
+ <item name="android:background">@drawable/qs_dialog_btn_outline</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault">
@@ -964,12 +983,15 @@
<style name="InternetDialog.Network">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">88dp</item>
+ <item name="android:layout_marginStart">@dimen/internet_dialog_network_layout_margin</item>
<item name="android:layout_marginEnd">@dimen/internet_dialog_network_layout_margin</item>
+ <item name="android:layout_gravity">center_vertical|start</item>
<item name="android:paddingStart">22dp</item>
<item name="android:paddingEnd">22dp</item>
<item name="android:orientation">horizontal</item>
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
+ <item name="android:background">?android:attr/selectableItemBackground</item>
</style>
<style name="InternetDialog.NetworkTitle">
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index b2ae2a0ca8ad..23307de519c8 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -43,11 +43,12 @@ android_library {
"src/**/*.kt",
"src/**/*.aidl",
":wm_shell-aidls",
+ ":wm_shell_util-sources",
],
-
static_libs: [
"PluginCoreLib",
+ "androidx.dynamicanimation_dynamicanimation",
],
java_version: "1.8",
- min_sdk_version: "26",
+ min_sdk_version: "current",
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
index e3e2367aeef8..07ad0c8a5120 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
@@ -33,9 +33,10 @@ import android.view.RenderNodeAnimator;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
-import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
+import androidx.annotation.DimenRes;
+import androidx.annotation.Keep;
import java.util.ArrayList;
import java.util.HashSet;
@@ -47,6 +48,8 @@ public class KeyButtonRipple extends Drawable {
private static final float GLOW_MAX_ALPHA_DARK = 0.1f;
private static final int ANIMATION_DURATION_SCALE = 350;
private static final int ANIMATION_DURATION_FADE = 450;
+ private static final Interpolator ALPHA_OUT_INTERPOLATOR =
+ new PathInterpolator(0f, 0f, 0.8f, 1f);
private Paint mRipplePaint;
private CanvasProperty<Float> mLeftProp;
@@ -86,8 +89,8 @@ public class KeyButtonRipple extends Drawable {
private Type mType = Type.ROUNDED_RECT;
- public KeyButtonRipple(Context ctx, View targetView) {
- mMaxWidth = ctx.getResources().getDimensionPixelSize(R.dimen.key_button_ripple_max_width);
+ public KeyButtonRipple(Context ctx, View targetView, @DimenRes int maxWidthResource) {
+ mMaxWidth = ctx.getResources().getDimensionPixelSize(maxWidthResource);
mTargetView = targetView;
}
@@ -184,19 +187,27 @@ public class KeyButtonRipple extends Drawable {
}
}
+ /** Gets the glow alpha, used by {@link android.animation.ObjectAnimator} via reflection. */
+ @Keep
public float getGlowAlpha() {
return mGlowAlpha;
}
+ /** Sets the glow alpha, used by {@link android.animation.ObjectAnimator} via reflection. */
+ @Keep
public void setGlowAlpha(float x) {
mGlowAlpha = x;
invalidateSelf();
}
+ /** Gets the glow scale, used by {@link android.animation.ObjectAnimator} via reflection. */
+ @Keep
public float getGlowScale() {
return mGlowScale;
}
+ /** Sets the glow scale, used by {@link android.animation.ObjectAnimator} via reflection. */
+ @Keep
public void setGlowScale(float x) {
mGlowScale = x;
invalidateSelf();
@@ -326,7 +337,7 @@ public class KeyButtonRipple extends Drawable {
private void exitSoftware() {
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "glowAlpha", mGlowAlpha, 0f);
- alphaAnimator.setInterpolator(Interpolators.ALPHA_OUT);
+ alphaAnimator.setInterpolator(ALPHA_OUT_INTERPOLATOR);
alphaAnimator.setDuration(ANIMATION_DURATION_FADE);
alphaAnimator.addListener(mAnimatorListener);
alphaAnimator.start();
@@ -449,7 +460,7 @@ public class KeyButtonRipple extends Drawable {
final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPaintProp,
RenderNodeAnimator.PAINT_ALPHA, 0);
opacityAnim.setDuration(ANIMATION_DURATION_FADE);
- opacityAnim.setInterpolator(Interpolators.ALPHA_OUT);
+ opacityAnim.setInterpolator(ALPHA_OUT_INTERPOLATOR);
opacityAnim.addListener(mAnimatorListener);
opacityAnim.addListener(mExitHwTraceAnimator);
opacityAnim.setTarget(mTargetView);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
new file mode 100644
index 000000000000..9010d5154156
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
@@ -0,0 +1,175 @@
+/*
+ * 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.systemui.shared.animation
+
+import android.graphics.Point
+import android.view.Surface
+import android.view.View
+import android.view.WindowManager
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import java.lang.ref.WeakReference
+
+/**
+ * Creates an animation where all registered views are moved into their final location
+ * by moving from the center of the screen to the sides
+ */
+class UnfoldMoveFromCenterAnimator @JvmOverloads constructor(
+ private val windowManager: WindowManager,
+ /**
+ * Allows to set custom translation applier
+ * Could be useful when a view could be translated from
+ * several sources and we want to set the translation
+ * using custom methods instead of [View.setTranslationX] or
+ * [View.setTranslationY]
+ */
+ private val translationApplier: TranslationApplier = object : TranslationApplier {},
+ /**
+ * Allows to set custom implementation for getting
+ * view location. Could be useful if logical view bounds
+ * are different than actual bounds (e.g. view container may
+ * have larger width than width of the items in the container)
+ */
+ private val viewCenterProvider: ViewCenterProvider = object : ViewCenterProvider {}
+) : UnfoldTransitionProgressProvider.TransitionProgressListener {
+
+ private val screenSize = Point()
+ private var isVerticalFold = false
+
+ private val animatedViews: MutableList<AnimatedView> = arrayListOf()
+
+ private var lastAnimationProgress: Float = 0f
+
+ /**
+ * Updates display properties in order to calculate the initial position for the views
+ * Must be called before [registerViewForAnimation]
+ */
+ fun updateDisplayProperties() {
+ windowManager.defaultDisplay.getSize(screenSize)
+
+ // Simple implementation to get current fold orientation,
+ // this might not be correct on all devices
+ // TODO: use JetPack WindowManager library to get the fold orientation
+ isVerticalFold = windowManager.defaultDisplay.rotation == Surface.ROTATION_0 ||
+ windowManager.defaultDisplay.rotation == Surface.ROTATION_180
+ }
+
+ /**
+ * If target view positions have changed (e.g. because of layout changes) call this method
+ * to re-query view positions and update the translations
+ */
+ fun updateViewPositions() {
+ animatedViews.forEach { animatedView ->
+ animatedView.view.get()?.let {
+ animatedView.updateAnimatedView(it)
+ }
+ }
+ onTransitionProgress(lastAnimationProgress)
+ }
+
+ /**
+ * Registers a view to be animated, the view should be measured and layouted
+ * After finishing the animation it is necessary to clear
+ * the views using [clearRegisteredViews]
+ */
+ fun registerViewForAnimation(view: View) {
+ val animatedView = createAnimatedView(view)
+ animatedViews.add(animatedView)
+ }
+
+ /**
+ * Unregisters all registered views and resets their translation
+ */
+ fun clearRegisteredViews() {
+ onTransitionProgress(1f)
+ animatedViews.clear()
+ }
+
+ override fun onTransitionProgress(progress: Float) {
+ animatedViews.forEach {
+ it.view.get()?.let { view ->
+ translationApplier.apply(
+ view = view,
+ x = it.startTranslationX * (1 - progress),
+ y = it.startTranslationY * (1 - progress)
+ )
+ }
+ }
+ lastAnimationProgress = progress
+ }
+
+ private fun createAnimatedView(view: View): AnimatedView =
+ AnimatedView(view = WeakReference(view)).updateAnimatedView(view)
+
+ private fun AnimatedView.updateAnimatedView(view: View): AnimatedView {
+ val viewCenter = Point()
+ viewCenterProvider.getViewCenter(view, viewCenter)
+
+ val viewCenterX = viewCenter.x
+ val viewCenterY = viewCenter.y
+
+ if (isVerticalFold) {
+ val distanceFromScreenCenterToViewCenter = screenSize.x / 2 - viewCenterX
+ startTranslationX = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE
+ startTranslationY = 0f
+ } else {
+ val distanceFromScreenCenterToViewCenter = screenSize.y / 2 - viewCenterY
+ startTranslationX = 0f
+ startTranslationY = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE
+ }
+
+ return this
+ }
+
+ /**
+ * Interface that allows to use custom logic to apply translation to view
+ */
+ interface TranslationApplier {
+ /**
+ * Called when we need to apply [x] and [y] translation to [view]
+ */
+ fun apply(view: View, x: Float, y: Float) {
+ view.translationX = x
+ view.translationY = y
+ }
+ }
+
+ /**
+ * Interface that allows to use custom logic to get the center of the view
+ */
+ interface ViewCenterProvider {
+ /**
+ * Called when we need to get the center of the view
+ */
+ fun getViewCenter(view: View, outPoint: Point) {
+ val viewLocation = IntArray(2)
+ view.getLocationOnScreen(viewLocation)
+
+ val viewX = viewLocation[0]
+ val viewY = viewLocation[1]
+
+ outPoint.x = viewX + view.width / 2
+ outPoint.y = viewY + view.height / 2
+ }
+ }
+
+ private class AnimatedView(
+ val view: WeakReference<View>,
+ var startTranslationX: Float = 0f,
+ var startTranslationY: Float = 0f
+ )
+}
+
+private const val TRANSLATION_PERCENTAGE = 0.3f
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
index 560d89af8e92..98083742d707 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.gestural;
+package com.android.systemui.shared.navigationbar;
import static android.view.Display.DEFAULT_DISPLAY;
-import android.content.res.Resources;
+import android.annotation.TargetApi;
import android.graphics.Rect;
+import android.os.Build;
import android.os.Handler;
import android.view.CompositionSamplingListener;
import android.view.SurfaceControl;
@@ -27,16 +28,22 @@ import android.view.View;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
-import com.android.systemui.R;
-
import java.io.PrintWriter;
+import java.util.concurrent.Executor;
/**
* A helper class to sample regions on the screen and inspect its luminosity.
*/
+@TargetApi(Build.VERSION_CODES.Q)
public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
View.OnLayoutChangeListener {
+ // Luminance threshold to determine black/white contrast for the navigation affordances.
+ // Passing the threshold of this luminance value will make the button black otherwise white
+ private static final float NAVIGATION_LUMINANCE_THRESHOLD = 0.5f;
+ // Luminance change threshold that allows applying new value if difference was exceeded
+ private static final float NAVIGATION_LUMINANCE_CHANGE_THRESHOLD = 0.05f;
+
private final Handler mHandler = new Handler();
private final View mSampledView;
@@ -52,6 +59,7 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
*/
private final Rect mRegisteredSamplingBounds = new Rect();
private final SamplingCallback mCallback;
+ private final Executor mBackgroundExecutor;
private boolean mSamplingEnabled = false;
private boolean mSamplingListenerRegistered = false;
@@ -60,13 +68,12 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
private boolean mWaitingOnDraw;
private boolean mIsDestroyed;
- // Passing the threshold of this luminance value will make the button black otherwise white
- private final float mLuminanceThreshold;
- private final float mLuminanceChangeThreshold;
private boolean mFirstSamplingAfterStart;
private boolean mWindowVisible;
private boolean mWindowHasBlurs;
private SurfaceControl mRegisteredStopLayer = null;
+ // A copy of mRegisteredStopLayer where we own the life cycle and can access from a bg thread.
+ private SurfaceControl mWrappedStopLayer = null;
private ViewTreeObserver.OnDrawListener mUpdateOnDraw = new ViewTreeObserver.OnDrawListener() {
@Override
public void onDraw() {
@@ -82,7 +89,9 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
}
};
- public RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback) {
+ public RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback,
+ Executor backgroundExecutor) {
+ mBackgroundExecutor = backgroundExecutor;
mSamplingListener = new CompositionSamplingListener(
sampledView.getContext().getMainExecutor()) {
@Override
@@ -96,9 +105,6 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
mSampledView.addOnAttachStateChangeListener(this);
mSampledView.addOnLayoutChangeListener(this);
- final Resources res = sampledView.getResources();
- mLuminanceThreshold = res.getFloat(R.dimen.navigation_luminance_threshold);
- mLuminanceChangeThreshold = res.getFloat(R.dimen.navigation_luminance_change_threshold);
mCallback = samplingCallback;
}
@@ -180,13 +186,21 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
}
if (!mSamplingRequestBounds.equals(mRegisteredSamplingBounds)
|| mRegisteredStopLayer != stopLayerControl) {
- // We only want to reregister if something actually changed
+ // We only want to re-register if something actually changed
unregisterSamplingListener();
mSamplingListenerRegistered = true;
- CompositionSamplingListener.register(mSamplingListener, DEFAULT_DISPLAY,
- stopLayerControl, mSamplingRequestBounds);
+ SurfaceControl wrappedStopLayer = stopLayerControl == null
+ ? null : new SurfaceControl(stopLayerControl, "regionSampling");
+ mBackgroundExecutor.execute(() -> {
+ if (wrappedStopLayer != null && !wrappedStopLayer.isValid()) {
+ return;
+ }
+ CompositionSamplingListener.register(mSamplingListener, DEFAULT_DISPLAY,
+ wrappedStopLayer, mSamplingRequestBounds);
+ });
mRegisteredSamplingBounds.set(mSamplingRequestBounds);
mRegisteredStopLayer = stopLayerControl;
+ mWrappedStopLayer = wrappedStopLayer;
}
mFirstSamplingAfterStart = false;
} else {
@@ -197,9 +211,15 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
private void unregisterSamplingListener() {
if (mSamplingListenerRegistered) {
mSamplingListenerRegistered = false;
+ SurfaceControl wrappedStopLayer = mWrappedStopLayer;
mRegisteredStopLayer = null;
mRegisteredSamplingBounds.setEmpty();
- CompositionSamplingListener.unregister(mSamplingListener);
+ mBackgroundExecutor.execute(() -> {
+ CompositionSamplingListener.unregister(mSamplingListener);
+ if (wrappedStopLayer != null && wrappedStopLayer.isValid()) {
+ wrappedStopLayer.release();
+ }
+ });
}
}
@@ -208,8 +228,10 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
// If the difference between the new luma and the current luma is larger than threshold
// then apply the current luma, this is to prevent small changes causing colors to flicker
- if (Math.abs(mCurrentMedianLuma - mLastMedianLuma) > mLuminanceChangeThreshold) {
- mCallback.onRegionDarknessChanged(medianLuma < mLuminanceThreshold /* isRegionDark */);
+ if (Math.abs(mCurrentMedianLuma - mLastMedianLuma)
+ > NAVIGATION_LUMINANCE_CHANGE_THRESHOLD) {
+ mCallback.onRegionDarknessChanged(
+ medianLuma < NAVIGATION_LUMINANCE_THRESHOLD /* isRegionDark */);
mLastMedianLuma = medianLuma;
}
}
@@ -251,6 +273,7 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
pw.println(" mWindowHasBlurs: " + mWindowHasBlurs);
pw.println(" mWaitingOnDraw: " + mWaitingOnDraw);
pw.println(" mRegisteredStopLayer: " + mRegisteredStopLayer);
+ pw.println(" mWrappedStopLayer: " + mWrappedStopLayer);
pw.println(" mIsDestroyed: " + mIsDestroyed);
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
new file mode 100644
index 000000000000..9ea4b578ad3b
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.plugins;
+
+import android.app.Notification;
+import android.app.Notification.Action;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.LayoutInflater;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Coordinates all the available plugins for a given action.
+ *
+ * The available plugins are queried from the {@link PackageManager} via an an {@link Intent}
+ * action.
+ *
+ * @param <T> The type of plugin that this contains.
+ */
+public class PluginActionManager<T extends Plugin> {
+
+ private static final boolean DEBUG = false;
+
+ private static final String TAG = "PluginInstanceManager";
+ public static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
+
+ private final Context mContext;
+ private final PluginListener<T> mListener;
+ private final String mAction;
+ private final boolean mAllowMultiple;
+ private final NotificationManager mNotificationManager;
+ private final PluginEnabler mPluginEnabler;
+ private final PluginInstance.Factory mPluginInstanceFactory;
+ private final ArraySet<String> mPrivilegedPlugins = new ArraySet<>();
+
+ @VisibleForTesting
+ private final ArrayList<PluginInstance<T>> mPluginInstances = new ArrayList<>();
+ private final boolean mIsDebuggable;
+ private final PackageManager mPm;
+ private final Class<T> mPluginClass;
+ private final Executor mMainExecutor;
+ private final Executor mBgExecutor;
+
+ private PluginActionManager(
+ Context context,
+ PackageManager pm,
+ String action,
+ PluginListener<T> listener,
+ Class<T> pluginClass,
+ boolean allowMultiple,
+ Executor mainExecutor,
+ Executor bgExecutor,
+ boolean debuggable,
+ NotificationManager notificationManager,
+ PluginEnabler pluginEnabler,
+ List<String> privilegedPlugins,
+ PluginInstance.Factory pluginInstanceFactory) {
+ mPluginClass = pluginClass;
+ mMainExecutor = mainExecutor;
+ mBgExecutor = bgExecutor;
+ mContext = context;
+ mPm = pm;
+ mAction = action;
+ mListener = listener;
+ mAllowMultiple = allowMultiple;
+ mNotificationManager = notificationManager;
+ mPluginEnabler = pluginEnabler;
+ mPluginInstanceFactory = pluginInstanceFactory;
+ mPrivilegedPlugins.addAll(privilegedPlugins);
+ mIsDebuggable = debuggable;
+ }
+
+ /** Load all plugins matching this instance's action. */
+ public void loadAll() {
+ if (DEBUG) Log.d(TAG, "startListening");
+ mBgExecutor.execute(this::queryAll);
+ }
+
+ /** Unload all plugins managed by this instance. */
+ public void destroy() {
+ if (DEBUG) Log.d(TAG, "stopListening");
+ ArrayList<PluginInstance<T>> plugins = new ArrayList<>(mPluginInstances);
+ for (PluginInstance<T> plugInstance : plugins) {
+ mMainExecutor.execute(() -> onPluginDisconnected(plugInstance));
+ }
+ }
+
+ /** Unload all matching plugins managed by this instance. */
+ public void onPackageRemoved(String pkg) {
+ mBgExecutor.execute(() -> removePkg(pkg));
+ }
+
+ /** Unload and then reload all matching plugins managed by this instance. */
+ public void reloadPackage(String pkg) {
+ mBgExecutor.execute(() -> {
+ removePkg(pkg);
+ queryPkg(pkg);
+ });
+ }
+
+ /** Disable a specific plugin managed by this instance. */
+ public boolean checkAndDisable(String className) {
+ boolean disableAny = false;
+ ArrayList<PluginInstance<T>> plugins = new ArrayList<>(mPluginInstances);
+ for (PluginInstance<T> info : plugins) {
+ if (className.startsWith(info.getPackage())) {
+ disableAny |= disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH);
+ }
+ }
+ return disableAny;
+ }
+
+ /** Disable all plugins managed by this instance. */
+ public boolean disableAll() {
+ ArrayList<PluginInstance<T>> plugins = new ArrayList<>(mPluginInstances);
+ boolean disabledAny = false;
+ for (int i = 0; i < plugins.size(); i++) {
+ disabledAny |= disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH);
+ }
+ return disabledAny;
+ }
+
+ boolean isPluginPrivileged(ComponentName pluginName) {
+ for (String componentNameOrPackage : mPrivilegedPlugins) {
+ ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage);
+ if (componentName == null) {
+ if (componentNameOrPackage.equals(pluginName.getPackageName())) {
+ return true;
+ }
+ } else {
+ if (componentName.equals(pluginName)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean disable(
+ PluginInstance<T> pluginInstance, @PluginEnabler.DisableReason int reason) {
+ // Live by the sword, die by the sword.
+ // Misbehaving plugins get disabled and won't come back until uninstall/reinstall.
+
+ ComponentName pluginComponent = pluginInstance.getComponentName();
+ // If a plugin is detected in the stack of a crash then this will be called for that
+ // plugin, if the plugin causing a crash cannot be identified, they are all disabled
+ // assuming one of them must be bad.
+ if (isPluginPrivileged(pluginComponent)) {
+ // Don't disable privileged plugins as they are a part of the OS.
+ return false;
+ }
+ Log.w(TAG, "Disabling plugin " + pluginComponent.flattenToShortString());
+ mPluginEnabler.setDisabled(pluginComponent, reason);
+
+ return true;
+ }
+
+ <C> boolean dependsOn(Plugin p, Class<C> cls) {
+ ArrayList<PluginInstance<T>> instances = new ArrayList<>(mPluginInstances);
+ for (PluginInstance<T> instance : instances) {
+ if (instance.containsPluginClass(p.getClass())) {
+ return instance.getVersionInfo() != null && instance.getVersionInfo().hasClass(cls);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s@%s (action=%s)",
+ getClass().getSimpleName(), hashCode(), mAction);
+ }
+
+ private void onPluginConnected(PluginInstance<T> pluginInstance) {
+ if (DEBUG) Log.d(TAG, "onPluginConnected");
+ PluginPrefs.setHasPlugins(mContext);
+ pluginInstance.onCreate(mContext, mListener);
+ }
+
+ private void onPluginDisconnected(PluginInstance<T> pluginInstance) {
+ if (DEBUG) Log.d(TAG, "onPluginDisconnected");
+ pluginInstance.onDestroy(mListener);
+ }
+
+ private void queryAll() {
+ if (DEBUG) Log.d(TAG, "queryAll " + mAction);
+ for (int i = mPluginInstances.size() - 1; i >= 0; i--) {
+ PluginInstance<T> pluginInstance = mPluginInstances.get(i);
+ mMainExecutor.execute(() -> onPluginDisconnected(pluginInstance));
+ }
+ mPluginInstances.clear();
+ handleQueryPlugins(null);
+ }
+
+ private void removePkg(String pkg) {
+ for (int i = mPluginInstances.size() - 1; i >= 0; i--) {
+ final PluginInstance<T> pluginInstance = mPluginInstances.get(i);
+ if (pluginInstance.getPackage().equals(pkg)) {
+ mMainExecutor.execute(() -> onPluginDisconnected(pluginInstance));
+ mPluginInstances.remove(i);
+ }
+ }
+ }
+
+ private void queryPkg(String pkg) {
+ if (DEBUG) Log.d(TAG, "queryPkg " + pkg + " " + mAction);
+ if (mAllowMultiple || (mPluginInstances.size() == 0)) {
+ handleQueryPlugins(pkg);
+ } else {
+ if (DEBUG) Log.d(TAG, "Too many of " + mAction);
+ }
+ }
+
+ private void handleQueryPlugins(String pkgName) {
+ // This isn't actually a service and shouldn't ever be started, but is
+ // a convenient PM based way to manage our plugins.
+ Intent intent = new Intent(mAction);
+ if (pkgName != null) {
+ intent.setPackage(pkgName);
+ }
+ List<ResolveInfo> result = mPm.queryIntentServices(intent, 0);
+ if (DEBUG) Log.d(TAG, "Found " + result.size() + " plugins");
+ if (result.size() > 1 && !mAllowMultiple) {
+ // TODO: Show warning.
+ Log.w(TAG, "Multiple plugins found for " + mAction);
+ if (DEBUG) {
+ for (ResolveInfo info : result) {
+ ComponentName name = new ComponentName(info.serviceInfo.packageName,
+ info.serviceInfo.name);
+ Log.w(TAG, " " + name);
+ }
+ }
+ return;
+ }
+ for (ResolveInfo info : result) {
+ ComponentName name = new ComponentName(info.serviceInfo.packageName,
+ info.serviceInfo.name);
+ PluginInstance<T> pluginInstance = loadPluginComponent(name);
+ if (pluginInstance != null) {
+ // add plugin before sending PLUGIN_CONNECTED message
+ mPluginInstances.add(pluginInstance);
+ mMainExecutor.execute(() -> onPluginConnected(pluginInstance));
+ }
+ }
+ }
+
+ private PluginInstance<T> loadPluginComponent(ComponentName component) {
+ // This was already checked, but do it again here to make extra extra sure, we don't
+ // use these on production builds.
+ if (!mIsDebuggable && !isPluginPrivileged(component)) {
+ // Never ever ever allow these on production builds, they are only for prototyping.
+ Log.w(TAG, "Plugin cannot be loaded on production build: " + component);
+ return null;
+ }
+ if (!mPluginEnabler.isEnabled(component)) {
+ if (DEBUG) {
+ Log.d(TAG, "Plugin is not enabled, aborting load: " + component);
+ }
+ return null;
+ }
+ String packageName = component.getPackageName();
+ try {
+ // TODO: This probably isn't needed given that we don't have IGNORE_SECURITY on
+ if (mPm.checkPermission(PLUGIN_PERMISSION, packageName)
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.d(TAG, "Plugin doesn't have permission: " + packageName);
+ return null;
+ }
+
+ ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, 0);
+ // TODO: Only create the plugin before version check if we need it for
+ // legacy version check.
+ if (DEBUG) {
+ Log.d(TAG, "createPlugin");
+ }
+ try {
+ return mPluginInstanceFactory.create(
+ mContext, appInfo, component,
+ mPluginClass);
+ } catch (InvalidVersionException e) {
+ reportInvalidVersion(component, component.getClassName(), e);
+ }
+ } catch (Throwable e) {
+ Log.w(TAG, "Couldn't load plugin: " + packageName, e);
+ return null;
+ }
+
+ return null;
+ }
+
+ private void reportInvalidVersion(
+ ComponentName component, String className, InvalidVersionException e) {
+ final int icon = Resources.getSystem().getIdentifier(
+ "stat_sys_warning", "drawable", "android");
+ final int color = Resources.getSystem().getIdentifier(
+ "system_notification_accent_color", "color", "android");
+ final Notification.Builder nb = new Notification.Builder(mContext,
+ PluginManager.NOTIFICATION_CHANNEL_ID)
+ .setStyle(new Notification.BigTextStyle())
+ .setSmallIcon(icon)
+ .setWhen(0)
+ .setShowWhen(false)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setColor(mContext.getColor(color));
+ String label = className;
+ try {
+ label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString();
+ } catch (NameNotFoundException e2) {
+ // no-op
+ }
+ if (!e.isTooNew()) {
+ // Localization not required as this will never ever appear in a user build.
+ nb.setContentTitle("Plugin \"" + label + "\" is too old")
+ .setContentText("Contact plugin developer to get an updated"
+ + " version.\n" + e.getMessage());
+ } else {
+ // Localization not required as this will never ever appear in a user build.
+ nb.setContentTitle("Plugin \"" + label + "\" is too new")
+ .setContentText("Check to see if an OTA is available.\n"
+ + e.getMessage());
+ }
+ Intent i = new Intent(PluginManagerImpl.DISABLE_PLUGIN).setData(
+ Uri.parse("package://" + component.flattenToString()));
+ PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i,
+ PendingIntent.FLAG_IMMUTABLE);
+ nb.addAction(new Action.Builder(null, "Disable plugin", pi).build());
+ mNotificationManager.notify(SystemMessage.NOTE_PLUGIN, nb.build());
+ // TODO: Warn user.
+ Log.w(TAG, "Plugin has invalid interface version " + e.getActualVersion()
+ + ", expected " + e.getExpectedVersion());
+ }
+
+ /**
+ * Construct a {@link PluginActionManager}
+ */
+ public static class Factory {
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final Executor mMainExecutor;
+ private final Executor mBgExecutor;
+ private final NotificationManager mNotificationManager;
+ private final PluginEnabler mPluginEnabler;
+ private final List<String> mPrivilegedPlugins;
+ private final PluginInstance.Factory mPluginInstanceFactory;
+
+ public Factory(Context context, PackageManager packageManager,
+ Executor mainExecutor, Executor bgExecutor,
+ NotificationManager notificationManager, PluginEnabler pluginEnabler,
+ List<String> privilegedPlugins, PluginInstance.Factory pluginInstanceFactory) {
+ mContext = context;
+ mPackageManager = packageManager;
+ mMainExecutor = mainExecutor;
+ mBgExecutor = bgExecutor;
+ mNotificationManager = notificationManager;
+ mPluginEnabler = pluginEnabler;
+ mPrivilegedPlugins = privilegedPlugins;
+ mPluginInstanceFactory = pluginInstanceFactory;
+ }
+
+ <T extends Plugin> PluginActionManager<T> create(
+ String action, PluginListener<T> listener, Class<T> pluginClass,
+ boolean allowMultiple, boolean debuggable) {
+ return new PluginActionManager<>(mContext, mPackageManager, action, listener,
+ pluginClass, allowMultiple, mMainExecutor, mBgExecutor,
+ debuggable, mNotificationManager, mPluginEnabler,
+ mPrivilegedPlugins, mPluginInstanceFactory);
+ }
+ }
+
+ /** */
+ public static class PluginContextWrapper extends ContextWrapper {
+ private final ClassLoader mClassLoader;
+ private LayoutInflater mInflater;
+
+ public PluginContextWrapper(Context base, ClassLoader classLoader) {
+ super(base);
+ mClassLoader = classLoader;
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return mClassLoader;
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (LAYOUT_INFLATER_SERVICE.equals(name)) {
+ if (mInflater == null) {
+ mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
+ }
+ return mInflater;
+ }
+ return getBaseContext().getSystemService(name);
+ }
+ }
+
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java
deleted file mode 100644
index 42bc1d0ea0ff..000000000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.shared.plugins;
-
-import android.content.Context;
-import android.os.Looper;
-
-/**
- * Provides necessary components for initializing {@link PluginManagerImpl}.
- */
-public interface PluginInitializer {
-
- Looper getBgLooper();
-
- /**
- * Called from the bg looper during initialization of {@link PluginManagerImpl}.
- */
- void onPluginManagerInit();
-
- String[] getWhitelistedPlugins(Context context);
-
- PluginEnabler getPluginEnabler(Context context);
-
- /**
- * Called from {@link PluginManagerImpl#handleWtfs()}.
- */
- void handleWtfs();
-
- /**
- * Returns if pluging manager should run in debug mode.
- */
- boolean isDebuggable();
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
new file mode 100644
index 000000000000..2f84602089e0
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -0,0 +1,228 @@
+/*
+ * 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.systemui.shared.plugins;
+
+import android.app.LoadedApk;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.PluginFragment;
+import com.android.systemui.plugins.PluginListener;
+
+import dalvik.system.PathClassLoader;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Contains a single instantiation of a Plugin.
+ *
+ * This class and its related Factory are in charge of actually instantiating a plugin and
+ * managing any state related to it.
+ *
+ * @param <T> The type of plugin that this contains.
+ */
+public class PluginInstance<T extends Plugin> {
+ private static final String TAG = "PluginInstance";
+ private static final Map<String, ClassLoader> sClassLoaders = new ArrayMap<>();
+
+ private final Context mPluginContext;
+ private final VersionInfo mVersionInfo;
+ private final ComponentName mComponentName;
+ private final T mPlugin;
+
+ /** */
+ public PluginInstance(ComponentName componentName, T plugin, Context pluginContext,
+ VersionInfo versionInfo) {
+ mComponentName = componentName;
+ mPlugin = plugin;
+ mPluginContext = pluginContext;
+ mVersionInfo = versionInfo;
+ }
+
+ /** Alerts listener and plugin that the plugin has been created. */
+ public void onCreate(Context appContext, PluginListener<T> listener) {
+ if (!(mPlugin instanceof PluginFragment)) {
+ // Only call onCreate for plugins that aren't fragments, as fragments
+ // will get the onCreate as part of the fragment lifecycle.
+ mPlugin.onCreate(appContext, mPluginContext);
+ }
+ listener.onPluginConnected(mPlugin, mPluginContext);
+ }
+
+ /** Alerts listener and plugin that the plugin is being shutdown. */
+ public void onDestroy(PluginListener<T> listener) {
+ listener.onPluginDisconnected(mPlugin);
+ if (!(mPlugin instanceof PluginFragment)) {
+ // Only call onDestroy for plugins that aren't fragments, as fragments
+ // will get the onDestroy as part of the fragment lifecycle.
+ mPlugin.onDestroy();
+ }
+ }
+
+ /**
+ * Returns if the contained plugin matches the passed in class name.
+ *
+ * It does this by string comparison of the class names.
+ **/
+ public boolean containsPluginClass(Class pluginClass) {
+ return mPlugin.getClass().getName().equals(pluginClass.getName());
+ }
+
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ public String getPackage() {
+ return mComponentName.getPackageName();
+ }
+
+ public VersionInfo getVersionInfo() {
+ return mVersionInfo;
+ }
+
+ @VisibleForTesting
+ Context getPluginContext() {
+ return mPluginContext;
+ }
+
+ /** Used to create new {@link PluginInstance}s. */
+ public static class Factory {
+ private final ClassLoader mBaseClassLoader;
+ private final InstanceFactory<?> mInstanceFactory;
+ private final VersionChecker mVersionChecker;
+ private final boolean mIsDebug;
+ private final List<String> mPrivilegedPlugins;
+
+ /** Factory used to construct {@link PluginInstance}s. */
+ public Factory(ClassLoader classLoader, InstanceFactory<?> instanceFactory,
+ VersionChecker versionChecker,
+ List<String> privilegedPlugins,
+ boolean isDebug) {
+ mPrivilegedPlugins = privilegedPlugins;
+ mBaseClassLoader = classLoader;
+ mInstanceFactory = instanceFactory;
+ mVersionChecker = versionChecker;
+ mIsDebug = isDebug;
+ }
+
+ /** Construct a new PluginInstance. */
+ public <T extends Plugin> PluginInstance<T> create(
+ Context context,
+ ApplicationInfo appInfo,
+ ComponentName componentName,
+ Class<T> pluginClass)
+ throws PackageManager.NameNotFoundException, ClassNotFoundException,
+ InstantiationException, IllegalAccessException {
+
+ ClassLoader classLoader = getClassLoader(appInfo, mBaseClassLoader);
+ Context pluginContext = new PluginActionManager.PluginContextWrapper(
+ context.createApplicationContext(appInfo, 0), classLoader);
+ Class<T> instanceClass = (Class<T>) Class.forName(
+ componentName.getClassName(), true, classLoader);
+ // TODO: Only create the plugin before version check if we need it for
+ // legacy version check.
+ T instance = (T) mInstanceFactory.create(instanceClass);
+ VersionInfo version = mVersionChecker.checkVersion(
+ instanceClass, pluginClass, instance);
+ return new PluginInstance<T>(componentName, instance, pluginContext, version);
+ }
+
+ private boolean isPluginPackagePrivileged(String packageName) {
+ for (String componentNameOrPackage : mPrivilegedPlugins) {
+ ComponentName componentName = ComponentName.unflattenFromString(
+ componentNameOrPackage);
+ if (componentName != null) {
+ if (componentName.getPackageName().equals(packageName)) {
+ return true;
+ }
+ } else if (componentNameOrPackage.equals(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private ClassLoader getParentClassLoader(ClassLoader baseClassLoader) {
+ return new PluginManagerImpl.ClassLoaderFilter(
+ baseClassLoader, "com.android.systemui.plugin");
+ }
+
+ /** Returns class loader specific for the given plugin. */
+ private ClassLoader getClassLoader(ApplicationInfo appInfo,
+ ClassLoader baseClassLoader) {
+ if (!mIsDebug && !isPluginPackagePrivileged(appInfo.packageName)) {
+ Log.w(TAG, "Cannot get class loader for non-privileged plugin. Src:"
+ + appInfo.sourceDir + ", pkg: " + appInfo.packageName);
+ return null;
+ }
+ if (sClassLoaders.containsKey(appInfo.packageName)) {
+ return sClassLoaders.get(appInfo.packageName);
+ }
+
+ List<String> zipPaths = new ArrayList<>();
+ List<String> libPaths = new ArrayList<>();
+ LoadedApk.makePaths(null, true, appInfo, zipPaths, libPaths);
+ ClassLoader classLoader = new PathClassLoader(
+ TextUtils.join(File.pathSeparator, zipPaths),
+ TextUtils.join(File.pathSeparator, libPaths),
+ getParentClassLoader(baseClassLoader));
+ sClassLoaders.put(appInfo.packageName, classLoader);
+ return classLoader;
+ }
+ }
+
+ /** Class that compares a plugin class against an implementation for version matching. */
+ public static class VersionChecker {
+ /** Compares two plugin classes. */
+ public <T extends Plugin> VersionInfo checkVersion(
+ Class<T> instanceClass, Class<T> pluginClass, Plugin plugin) {
+ VersionInfo pluginVersion = new VersionInfo().addClass(pluginClass);
+ VersionInfo instanceVersion = new VersionInfo().addClass(instanceClass);
+ if (instanceVersion.hasVersionInfo()) {
+ pluginVersion.checkVersion(instanceVersion);
+ } else {
+ int fallbackVersion = plugin.getVersion();
+ if (fallbackVersion != pluginVersion.getDefaultVersion()) {
+ throw new VersionInfo.InvalidVersionException("Invalid legacy version", false);
+ }
+ return null;
+ }
+ return instanceVersion;
+ }
+ }
+
+ /**
+ * Simple class to create a new instance. Useful for testing.
+ *
+ * @param <T> The type of plugin this create.
+ **/
+ public static class InstanceFactory<T extends Plugin> {
+ T create(Class cls) throws IllegalAccessException, InstantiationException {
+ return (T) cls.newInstance();
+ }
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java
deleted file mode 100644
index 2b35bcd9a3ea..000000000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.shared.plugins;
-
-import android.app.Notification;
-import android.app.Notification.Action;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.ArraySet;
-import android.util.Log;
-import android.view.LayoutInflater;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.systemui.plugins.Plugin;
-import com.android.systemui.plugins.PluginFragment;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-public class PluginInstanceManager<T extends Plugin> {
-
- private static final boolean DEBUG = false;
-
- private static final String TAG = "PluginInstanceManager";
- public static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
-
- private final Context mContext;
- private final PluginListener<T> mListener;
- private final String mAction;
- private final boolean mAllowMultiple;
- private final VersionInfo mVersion;
-
- @VisibleForTesting
- final MainHandler mMainHandler;
- @VisibleForTesting
- final PluginHandler mPluginHandler;
- private final boolean isDebuggable;
- private final PackageManager mPm;
- private final PluginManagerImpl mManager;
- private final ArraySet<String> mWhitelistedPlugins = new ArraySet<>();
-
- PluginInstanceManager(Context context, String action, PluginListener<T> listener,
- boolean allowMultiple, Looper looper, VersionInfo version, PluginManagerImpl manager) {
- this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version,
- manager, manager.isDebuggable(), manager.getWhitelistedPlugins());
- }
-
- @VisibleForTesting
- PluginInstanceManager(Context context, PackageManager pm, String action,
- PluginListener<T> listener, boolean allowMultiple, Looper looper, VersionInfo version,
- PluginManagerImpl manager, boolean debuggable, String[] pluginWhitelist) {
- mMainHandler = new MainHandler(Looper.getMainLooper());
- mPluginHandler = new PluginHandler(looper);
- mManager = manager;
- mContext = context;
- mPm = pm;
- mAction = action;
- mListener = listener;
- mAllowMultiple = allowMultiple;
- mVersion = version;
- mWhitelistedPlugins.addAll(Arrays.asList(pluginWhitelist));
- isDebuggable = debuggable;
- }
-
- public PluginInfo<T> getPlugin() {
- if (Looper.myLooper() != Looper.getMainLooper()) {
- throw new RuntimeException("Must be called from UI thread");
- }
- mPluginHandler.handleQueryPlugins(null /* All packages */);
- if (mPluginHandler.mPlugins.size() > 0) {
- mMainHandler.removeMessages(MainHandler.PLUGIN_CONNECTED);
- PluginInfo<T> info = mPluginHandler.mPlugins.get(0);
- PluginPrefs.setHasPlugins(mContext);
- info.mPlugin.onCreate(mContext, info.mPluginContext);
- return info;
- }
- return null;
- }
-
- public void loadAll() {
- if (DEBUG) Log.d(TAG, "startListening");
- mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL);
- }
-
- public void destroy() {
- if (DEBUG) Log.d(TAG, "stopListening");
- ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins);
- for (PluginInfo plugin : plugins) {
- mMainHandler.obtainMessage(MainHandler.PLUGIN_DISCONNECTED,
- plugin.mPlugin).sendToTarget();
- }
- }
-
- public void onPackageRemoved(String pkg) {
- mPluginHandler.obtainMessage(PluginHandler.REMOVE_PKG, pkg).sendToTarget();
- }
-
- public void onPackageChange(String pkg) {
- mPluginHandler.obtainMessage(PluginHandler.REMOVE_PKG, pkg).sendToTarget();
- mPluginHandler.obtainMessage(PluginHandler.QUERY_PKG, pkg).sendToTarget();
- }
-
- public boolean checkAndDisable(String className) {
- boolean disableAny = false;
- ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins);
- for (PluginInfo info : plugins) {
- if (className.startsWith(info.mPackage)) {
- disableAny |= disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH);
- }
- }
- return disableAny;
- }
-
- public boolean disableAll() {
- ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins);
- boolean disabledAny = false;
- for (int i = 0; i < plugins.size(); i++) {
- disabledAny |= disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH);
- }
- return disabledAny;
- }
-
- private boolean isPluginWhitelisted(ComponentName pluginName) {
- for (String componentNameOrPackage : mWhitelistedPlugins) {
- ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage);
- if (componentName == null) {
- if (componentNameOrPackage.equals(pluginName.getPackageName())) {
- return true;
- }
- } else {
- if (componentName.equals(pluginName)) {
- return true;
- }
- }
- }
- return false;
- }
-
- private boolean disable(PluginInfo info, @PluginEnabler.DisableReason int reason) {
- // Live by the sword, die by the sword.
- // Misbehaving plugins get disabled and won't come back until uninstall/reinstall.
-
- ComponentName pluginComponent = new ComponentName(info.mPackage, info.mClass);
- // If a plugin is detected in the stack of a crash then this will be called for that
- // plugin, if the plugin causing a crash cannot be identified, they are all disabled
- // assuming one of them must be bad.
- if (isPluginWhitelisted(pluginComponent)) {
- // Don't disable whitelisted plugins as they are a part of the OS.
- return false;
- }
- Log.w(TAG, "Disabling plugin " + pluginComponent.flattenToShortString());
- mManager.getPluginEnabler().setDisabled(pluginComponent, reason);
-
- return true;
- }
-
- public <T> boolean dependsOn(Plugin p, Class<T> cls) {
- ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins);
- for (PluginInfo info : plugins) {
- if (info.mPlugin.getClass().getName().equals(p.getClass().getName())) {
- return info.mVersion != null && info.mVersion.hasClass(cls);
- }
- }
- return false;
- }
-
- @Override
- public String toString() {
- return String.format("%s@%s (action=%s)",
- getClass().getSimpleName(), hashCode(), mAction);
- }
-
- private class MainHandler extends Handler {
- private static final int PLUGIN_CONNECTED = 1;
- private static final int PLUGIN_DISCONNECTED = 2;
-
- public MainHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case PLUGIN_CONNECTED:
- if (DEBUG) Log.d(TAG, "onPluginConnected");
- PluginPrefs.setHasPlugins(mContext);
- PluginInfo<T> info = (PluginInfo<T>) msg.obj;
- mManager.handleWtfs();
- if (!(msg.obj instanceof PluginFragment)) {
- // Only call onDestroy for plugins that aren't fragments, as fragments
- // will get the onCreate as part of the fragment lifecycle.
- info.mPlugin.onCreate(mContext, info.mPluginContext);
- }
- mListener.onPluginConnected(info.mPlugin, info.mPluginContext);
- break;
- case PLUGIN_DISCONNECTED:
- if (DEBUG) Log.d(TAG, "onPluginDisconnected");
- mListener.onPluginDisconnected((T) msg.obj);
- if (!(msg.obj instanceof PluginFragment)) {
- // Only call onDestroy for plugins that aren't fragments, as fragments
- // will get the onDestroy as part of the fragment lifecycle.
- ((T) msg.obj).onDestroy();
- }
- break;
- default:
- super.handleMessage(msg);
- break;
- }
- }
- }
-
- private class PluginHandler extends Handler {
- private static final int QUERY_ALL = 1;
- private static final int QUERY_PKG = 2;
- private static final int REMOVE_PKG = 3;
-
- private final ArrayList<PluginInfo<T>> mPlugins = new ArrayList<>();
-
- public PluginHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case QUERY_ALL:
- if (DEBUG) Log.d(TAG, "queryAll " + mAction);
- for (int i = mPlugins.size() - 1; i >= 0; i--) {
- PluginInfo<T> pluginInfo = mPlugins.get(i);
- mMainHandler.obtainMessage(
- MainHandler.PLUGIN_DISCONNECTED, pluginInfo.mPlugin).sendToTarget();
- }
- mPlugins.clear();
- handleQueryPlugins(null);
- break;
- case REMOVE_PKG:
- String pkg = (String) msg.obj;
- for (int i = mPlugins.size() - 1; i >= 0; i--) {
- final PluginInfo<T> plugin = mPlugins.get(i);
- if (plugin.mPackage.equals(pkg)) {
- mMainHandler.obtainMessage(MainHandler.PLUGIN_DISCONNECTED,
- plugin.mPlugin).sendToTarget();
- mPlugins.remove(i);
- }
- }
- break;
- case QUERY_PKG:
- String p = (String) msg.obj;
- if (DEBUG) Log.d(TAG, "queryPkg " + p + " " + mAction);
- if (mAllowMultiple || (mPlugins.size() == 0)) {
- handleQueryPlugins(p);
- } else {
- if (DEBUG) Log.d(TAG, "Too many of " + mAction);
- }
- break;
- default:
- super.handleMessage(msg);
- }
- }
-
- private void handleQueryPlugins(String pkgName) {
- // This isn't actually a service and shouldn't ever be started, but is
- // a convenient PM based way to manage our plugins.
- Intent intent = new Intent(mAction);
- if (pkgName != null) {
- intent.setPackage(pkgName);
- }
- List<ResolveInfo> result = mPm.queryIntentServices(intent, 0);
- if (DEBUG) Log.d(TAG, "Found " + result.size() + " plugins");
- if (result.size() > 1 && !mAllowMultiple) {
- // TODO: Show warning.
- Log.w(TAG, "Multiple plugins found for " + mAction);
- if (DEBUG) {
- for (ResolveInfo info : result) {
- ComponentName name = new ComponentName(info.serviceInfo.packageName,
- info.serviceInfo.name);
- Log.w(TAG, " " + name);
- }
- }
- return;
- }
- for (ResolveInfo info : result) {
- ComponentName name = new ComponentName(info.serviceInfo.packageName,
- info.serviceInfo.name);
- PluginInfo<T> t = handleLoadPlugin(name);
- if (t == null) continue;
-
- // add plugin before sending PLUGIN_CONNECTED message
- mPlugins.add(t);
- mMainHandler.obtainMessage(mMainHandler.PLUGIN_CONNECTED, t).sendToTarget();
- }
- }
-
- protected PluginInfo<T> handleLoadPlugin(ComponentName component) {
- // This was already checked, but do it again here to make extra extra sure, we don't
- // use these on production builds.
- if (!isDebuggable && !isPluginWhitelisted(component)) {
- // Never ever ever allow these on production builds, they are only for prototyping.
- Log.w(TAG, "Plugin cannot be loaded on production build: " + component);
- return null;
- }
- if (!mManager.getPluginEnabler().isEnabled(component)) {
- if (DEBUG) Log.d(TAG, "Plugin is not enabled, aborting load: " + component);
- return null;
- }
- String pkg = component.getPackageName();
- String cls = component.getClassName();
- try {
- ApplicationInfo info = mPm.getApplicationInfo(pkg, 0);
- // TODO: This probably isn't needed given that we don't have IGNORE_SECURITY on
- if (mPm.checkPermission(PLUGIN_PERMISSION, pkg)
- != PackageManager.PERMISSION_GRANTED) {
- Log.d(TAG, "Plugin doesn't have permission: " + pkg);
- return null;
- }
- // Create our own ClassLoader so we can use our own code as the parent.
- ClassLoader classLoader = mManager.getClassLoader(info);
- Context pluginContext = new PluginContextWrapper(
- mContext.createApplicationContext(info, 0), classLoader);
- Class<?> pluginClass = Class.forName(cls, true, classLoader);
- // TODO: Only create the plugin before version check if we need it for
- // legacy version check.
- T plugin = (T) pluginClass.newInstance();
- try {
- VersionInfo version = checkVersion(pluginClass, plugin, mVersion);
- if (DEBUG) Log.d(TAG, "createPlugin");
- return new PluginInfo(pkg, cls, plugin, pluginContext, version);
- } catch (InvalidVersionException e) {
- final int icon = Resources.getSystem().getIdentifier(
- "stat_sys_warning", "drawable", "android");
- final int color = Resources.getSystem().getIdentifier(
- "system_notification_accent_color", "color", "android");
- final Notification.Builder nb = new Notification.Builder(mContext,
- PluginManager.NOTIFICATION_CHANNEL_ID)
- .setStyle(new Notification.BigTextStyle())
- .setSmallIcon(icon)
- .setWhen(0)
- .setShowWhen(false)
- .setVisibility(Notification.VISIBILITY_PUBLIC)
- .setColor(mContext.getColor(color));
- String label = cls;
- try {
- label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString();
- } catch (NameNotFoundException e2) {
- }
- if (!e.isTooNew()) {
- // Localization not required as this will never ever appear in a user build.
- nb.setContentTitle("Plugin \"" + label + "\" is too old")
- .setContentText("Contact plugin developer to get an updated"
- + " version.\n" + e.getMessage());
- } else {
- // Localization not required as this will never ever appear in a user build.
- nb.setContentTitle("Plugin \"" + label + "\" is too new")
- .setContentText("Check to see if an OTA is available.\n"
- + e.getMessage());
- }
- Intent i = new Intent(PluginManagerImpl.DISABLE_PLUGIN).setData(
- Uri.parse("package://" + component.flattenToString()));
- PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i,
- PendingIntent.FLAG_IMMUTABLE);
- nb.addAction(new Action.Builder(null, "Disable plugin", pi).build());
- mContext.getSystemService(NotificationManager.class)
- .notify(SystemMessage.NOTE_PLUGIN, nb.build());
- // TODO: Warn user.
- Log.w(TAG, "Plugin has invalid interface version " + plugin.getVersion()
- + ", expected " + mVersion);
- return null;
- }
- } catch (Throwable e) {
- Log.w(TAG, "Couldn't load plugin: " + pkg, e);
- return null;
- }
- }
-
- private VersionInfo checkVersion(Class<?> pluginClass, T plugin, VersionInfo version)
- throws InvalidVersionException {
- VersionInfo pv = new VersionInfo().addClass(pluginClass);
- if (pv.hasVersionInfo()) {
- version.checkVersion(pv);
- } else {
- int fallbackVersion = plugin.getVersion();
- if (fallbackVersion != version.getDefaultVersion()) {
- throw new InvalidVersionException("Invalid legacy version", false);
- }
- return null;
- }
- return pv;
- }
- }
-
- public static class PluginContextWrapper extends ContextWrapper {
- private final ClassLoader mClassLoader;
- private LayoutInflater mInflater;
-
- public PluginContextWrapper(Context base, ClassLoader classLoader) {
- super(base);
- mClassLoader = classLoader;
- }
-
- @Override
- public ClassLoader getClassLoader() {
- return mClassLoader;
- }
-
- @Override
- public Object getSystemService(String name) {
- if (LAYOUT_INFLATER_SERVICE.equals(name)) {
- if (mInflater == null) {
- mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
- }
- return mInflater;
- }
- return getBaseContext().getSystemService(name);
- }
- }
-
- static class PluginInfo<T> {
- private final Context mPluginContext;
- private final VersionInfo mVersion;
- private String mClass;
- T mPlugin;
- String mPackage;
-
- public PluginInfo(String pkg, String cls, T plugin, Context pluginContext,
- VersionInfo info) {
- mPlugin = plugin;
- mClass = cls;
- mPackage = pkg;
- mPluginContext = pluginContext;
- mVersion = info;
- }
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java
index 3f907a8aa348..c89be869115b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java
@@ -27,18 +27,18 @@ public interface PluginManager {
// must be one of the channels created in NotificationChannels.java
String NOTIFICATION_CHANNEL_ID = "ALR";
- String[] getWhitelistedPlugins();
+ /** Returns plugins that don't get disabled when an exceptoin occurs. */
+ String[] getPrivilegedPlugins();
- <T extends Plugin> T getOneShotPlugin(Class<T> cls);
- <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls);
-
- <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls);
- <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,
+ /** */
+ <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls);
+ /** */
+ <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls,
boolean allowMultiple);
<T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
- Class<?> cls);
+ Class<T> cls);
<T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
- Class cls, boolean allowMultiple);
+ Class<T> cls, boolean allowMultiple);
void removePluginListener(PluginListener<?> listener);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
index 2b4cdd6cf575..7539f995dab4 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
@@ -14,48 +14,31 @@
package com.android.systemui.shared.plugins;
-import android.app.LoadedApk;
-import android.app.Notification;
-import android.app.Notification.Action;
import android.app.NotificationManager;
-import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
import android.os.SystemProperties;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.widget.Toast;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-import com.android.systemui.shared.plugins.PluginInstanceManager.PluginInfo;
-import dalvik.system.PathClassLoader;
-
-import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
+
/**
* @see Plugin
*/
@@ -64,113 +47,65 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
private static final String TAG = PluginManagerImpl.class.getSimpleName();
static final String DISABLE_PLUGIN = "com.android.systemui.action.DISABLE_PLUGIN";
- private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap
+ private final ArrayMap<PluginListener<?>, PluginActionManager<?>> mPluginMap
= new ArrayMap<>();
private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>();
- private final ArraySet<String> mOneShotPackages = new ArraySet<>();
- private final ArraySet<String> mWhitelistedPlugins = new ArraySet<>();
+ private final ArraySet<String> mPrivilegedPlugins = new ArraySet<>();
private final Context mContext;
- private final PluginInstanceManagerFactory mFactory;
+ private final PluginActionManager.Factory mActionManagerFactory;
private final boolean mIsDebuggable;
private final PluginPrefs mPluginPrefs;
private final PluginEnabler mPluginEnabler;
- private final PluginInitializer mPluginInitializer;
- private ClassLoaderFilter mParentClassLoader;
private boolean mListening;
- private boolean mHasOneShot;
- private Looper mLooper;
-
- public PluginManagerImpl(Context context, PluginInitializer initializer) {
- this(context, new PluginInstanceManagerFactory(), initializer.isDebuggable(),
- Thread.getUncaughtExceptionPreHandler(), initializer);
- }
- @VisibleForTesting
- PluginManagerImpl(Context context, PluginInstanceManagerFactory factory, boolean debuggable,
- UncaughtExceptionHandler defaultHandler, final PluginInitializer initializer) {
+ public PluginManagerImpl(Context context,
+ PluginActionManager.Factory actionManagerFactory,
+ boolean debuggable,
+ Optional<UncaughtExceptionHandler> defaultHandlerOptional,
+ PluginEnabler pluginEnabler,
+ PluginPrefs pluginPrefs,
+ List<String> privilegedPlugins) {
mContext = context;
- mFactory = factory;
- mLooper = initializer.getBgLooper();
+ mActionManagerFactory = actionManagerFactory;
mIsDebuggable = debuggable;
- mWhitelistedPlugins.addAll(Arrays.asList(initializer.getWhitelistedPlugins(mContext)));
- mPluginPrefs = new PluginPrefs(mContext);
- mPluginEnabler = initializer.getPluginEnabler(mContext);
- mPluginInitializer = initializer;
+ mPrivilegedPlugins.addAll(privilegedPlugins);
+ mPluginPrefs = pluginPrefs;
+ mPluginEnabler = pluginEnabler;
PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler(
- defaultHandler);
+ defaultHandlerOptional);
Thread.setUncaughtExceptionPreHandler(uncaughtExceptionHandler);
-
- new Handler(mLooper).post(new Runnable() {
- @Override
- public void run() {
- initializer.onPluginManagerInit();
- }
- });
}
public boolean isDebuggable() {
return mIsDebuggable;
}
- public String[] getWhitelistedPlugins() {
- return mWhitelistedPlugins.toArray(new String[0]);
- }
-
- public PluginEnabler getPluginEnabler() {
- return mPluginEnabler;
- }
-
- // TODO(mankoff): This appears to be only called from tests. Remove?
- public <T extends Plugin> T getOneShotPlugin(Class<T> cls) {
- ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class);
- if (info == null) {
- throw new RuntimeException(cls + " doesn't provide an interface");
- }
- if (TextUtils.isEmpty(info.action())) {
- throw new RuntimeException(cls + " doesn't provide an action");
- }
- return getOneShotPlugin(info.action(), cls);
- }
-
- public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) {
- if (Looper.myLooper() != Looper.getMainLooper()) {
- throw new RuntimeException("Must be called from UI thread");
- }
- // Passing null causes compiler to complain about incompatible (generic) types.
- PluginListener<Plugin> dummy = null;
- PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, dummy,
- false, mLooper, cls, this);
- mPluginPrefs.addAction(action);
- PluginInfo<T> info = p.getPlugin();
- if (info != null) {
- mOneShotPackages.add(info.mPackage);
- mHasOneShot = true;
- startListening();
- return info.mPlugin;
- }
- return null;
+ public String[] getPrivilegedPlugins() {
+ return mPrivilegedPlugins.toArray(new String[0]);
}
- public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) {
+ /** */
+ public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls) {
addPluginListener(listener, cls, false);
}
- public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,
+ /** */
+ public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls,
boolean allowMultiple) {
addPluginListener(PluginManager.Helper.getAction(cls), listener, cls, allowMultiple);
}
public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
- Class<?> cls) {
+ Class<T> cls) {
addPluginListener(action, listener, cls, false);
}
public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
- Class cls, boolean allowMultiple) {
+ Class<T> cls, boolean allowMultiple) {
mPluginPrefs.addAction(action);
- PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,
- allowMultiple, mLooper, cls, this);
+ PluginActionManager<T> p = mActionManagerFactory.create(action, listener, cls,
+ allowMultiple, isDebuggable());
p.loadAll();
synchronized (this) {
mPluginMap.put(listener, p);
@@ -202,14 +137,13 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
filter.addAction(PLUGIN_CHANGED);
filter.addAction(DISABLE_PLUGIN);
filter.addDataScheme("package");
- mContext.registerReceiver(this, filter, PluginInstanceManager.PLUGIN_PERMISSION, null);
+ mContext.registerReceiver(this, filter, PluginActionManager.PLUGIN_PERMISSION, null);
filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
mContext.registerReceiver(this, filter);
}
private void stopListening() {
- // Never stop listening if a one-shot is present.
- if (!mListening || mHasOneShot) return;
+ if (!mListening) return;
mListening = false;
mContext.unregisterReceiver(this);
}
@@ -218,7 +152,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
synchronized (this) {
- for (PluginInstanceManager manager : mPluginMap.values()) {
+ for (PluginActionManager<?> manager : mPluginMap.values()) {
manager.loadAll();
}
}
@@ -226,46 +160,17 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
Uri uri = intent.getData();
ComponentName component = ComponentName.unflattenFromString(
uri.toString().substring(10));
- if (isPluginWhitelisted(component)) {
- // Don't disable whitelisted plugins as they are a part of the OS.
+ if (isPluginPrivileged(component)) {
+ // Don't disable privileged plugins as they are a part of the OS.
return;
}
- getPluginEnabler().setDisabled(component, PluginEnabler.DISABLED_INVALID_VERSION);
+ mPluginEnabler.setDisabled(component, PluginEnabler.DISABLED_INVALID_VERSION);
mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(),
SystemMessage.NOTE_PLUGIN);
} else {
Uri data = intent.getData();
String pkg = data.getEncodedSchemeSpecificPart();
ComponentName componentName = ComponentName.unflattenFromString(pkg);
- if (mOneShotPackages.contains(pkg)) {
- int icon = Resources.getSystem().getIdentifier(
- "stat_sys_warning", "drawable", "android");
- int color = Resources.getSystem().getIdentifier(
- "system_notification_accent_color", "color", "android");
- String label = pkg;
- try {
- PackageManager pm = mContext.getPackageManager();
- label = pm.getApplicationInfo(pkg, 0).loadLabel(pm).toString();
- } catch (NameNotFoundException e) {
- }
- // Localization not required as this will never ever appear in a user build.
- final Notification.Builder nb =
- new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
- .setSmallIcon(icon)
- .setWhen(0)
- .setShowWhen(false)
- .setPriority(Notification.PRIORITY_MAX)
- .setVisibility(Notification.VISIBILITY_PUBLIC)
- .setColor(mContext.getColor(color))
- .setContentTitle("Plugin \"" + label + "\" has updated")
- .setContentText("Restart SysUI for changes to take effect.");
- Intent i = new Intent("com.android.systemui.action.RESTART").setData(
- Uri.parse("package://" + pkg));
- PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, PendingIntent.FLAG_MUTABLE_UNAUDITED);
- nb.addAction(new Action.Builder(null, "Restart SysUI", pi).build());
- mContext.getSystemService(NotificationManager.class)
- .notify(SystemMessage.NOTE_PLUGIN, nb.build());
- }
if (clearClassLoader(pkg)) {
if (Build.IS_ENG) {
Toast.makeText(mContext, "Reloading " + pkg, Toast.LENGTH_LONG).show();
@@ -276,22 +181,24 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())
&& componentName != null) {
@PluginEnabler.DisableReason int disableReason =
- getPluginEnabler().getDisableReason(componentName);
+ mPluginEnabler.getDisableReason(componentName);
if (disableReason == PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH
|| disableReason == PluginEnabler.DISABLED_FROM_SYSTEM_CRASH
|| disableReason == PluginEnabler.DISABLED_INVALID_VERSION) {
Log.i(TAG, "Re-enabling previously disabled plugin that has been "
+ "updated: " + componentName.flattenToShortString());
- getPluginEnabler().setEnabled(componentName);
+ mPluginEnabler.setEnabled(componentName);
}
}
synchronized (this) {
- if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
- for (PluginInstanceManager manager : mPluginMap.values()) {
- manager.onPackageChange(pkg);
+ if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
+ || Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())
+ || Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
+ for (PluginActionManager<?> actionManager : mPluginMap.values()) {
+ actionManager.reloadPackage(pkg);
}
} else {
- for (PluginInstanceManager manager : mPluginMap.values()) {
+ for (PluginActionManager<?> manager : mPluginMap.values()) {
manager.onPackageRemoved(pkg);
}
}
@@ -299,41 +206,10 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
}
}
- /** Returns class loader specific for the given plugin. */
- public ClassLoader getClassLoader(ApplicationInfo appInfo) {
- if (!mIsDebuggable && !isPluginPackageWhitelisted(appInfo.packageName)) {
- Log.w(TAG, "Cannot get class loader for non-whitelisted plugin. Src:"
- + appInfo.sourceDir + ", pkg: " + appInfo.packageName);
- return null;
- }
- if (mClassLoaders.containsKey(appInfo.packageName)) {
- return mClassLoaders.get(appInfo.packageName);
- }
-
- List<String> zipPaths = new ArrayList<>();
- List<String> libPaths = new ArrayList<>();
- LoadedApk.makePaths(null, true, appInfo, zipPaths, libPaths);
- ClassLoader classLoader = new PathClassLoader(
- TextUtils.join(File.pathSeparator, zipPaths),
- TextUtils.join(File.pathSeparator, libPaths),
- getParentClassLoader());
- mClassLoaders.put(appInfo.packageName, classLoader);
- return classLoader;
- }
-
private boolean clearClassLoader(String pkg) {
return mClassLoaders.remove(pkg) != null;
}
- ClassLoader getParentClassLoader() {
- if (mParentClassLoader == null) {
- // Lazily load this so it doesn't have any effect on devices without plugins.
- mParentClassLoader = new ClassLoaderFilter(getClass().getClassLoader(),
- "com.android.systemui.plugin");
- }
- return mParentClassLoader;
- }
-
public <T> boolean dependsOn(Plugin p, Class<T> cls) {
synchronized (this) {
for (int i = 0; i < mPluginMap.size(); i++) {
@@ -345,46 +221,18 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
return false;
}
- public void handleWtfs() {
- mPluginInitializer.handleWtfs();
- }
-
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (this) {
pw.println(String.format(" plugin map (%d):", mPluginMap.size()));
- for (PluginListener listener : mPluginMap.keySet()) {
+ for (PluginListener<?> listener : mPluginMap.keySet()) {
pw.println(String.format(" %s -> %s",
listener, mPluginMap.get(listener)));
}
}
}
- @VisibleForTesting
- public static class PluginInstanceManagerFactory {
- public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context,
- String action, PluginListener<T> listener, boolean allowMultiple, Looper looper,
- Class<?> cls, PluginManagerImpl manager) {
- return new PluginInstanceManager(context, action, listener, allowMultiple, looper,
- new VersionInfo().addClass(cls), manager);
- }
- }
-
- private boolean isPluginPackageWhitelisted(String packageName) {
- for (String componentNameOrPackage : mWhitelistedPlugins) {
- ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage);
- if (componentName != null) {
- if (componentName.getPackageName().equals(packageName)) {
- return true;
- }
- } else if (componentNameOrPackage.equals(packageName)) {
- return true;
- }
- }
- return false;
- }
-
- private boolean isPluginWhitelisted(ComponentName pluginName) {
- for (String componentNameOrPackage : mWhitelistedPlugins) {
+ private boolean isPluginPrivileged(ComponentName pluginName) {
+ for (String componentNameOrPackage : mPrivilegedPlugins) {
ComponentName componentName = ComponentName.unflattenFromString(componentNameOrPackage);
if (componentName != null) {
if (componentName.equals(pluginName)) {
@@ -399,7 +247,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
// This allows plugins to include any libraries or copied code they want by only including
// classes from the plugin library.
- private static class ClassLoaderFilter extends ClassLoader {
+ static class ClassLoaderFilter extends ClassLoader {
private final String mPackage;
private final ClassLoader mBase;
@@ -417,16 +265,20 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
}
private class PluginExceptionHandler implements UncaughtExceptionHandler {
- private final UncaughtExceptionHandler mHandler;
+ private final Optional<UncaughtExceptionHandler> mExceptionHandlerOptional;
- private PluginExceptionHandler(UncaughtExceptionHandler handler) {
- mHandler = handler;
+ private PluginExceptionHandler(
+ Optional<UncaughtExceptionHandler> exceptionHandlerOptional) {
+ mExceptionHandlerOptional = exceptionHandlerOptional;
}
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
if (SystemProperties.getBoolean("plugin.debugging", false)) {
- mHandler.uncaughtException(thread, throwable);
+ Throwable finalThrowable = throwable;
+ mExceptionHandlerOptional.ifPresent(
+ handler -> handler.uncaughtException(thread, finalThrowable));
+
return;
}
// Search for and disable plugins that may have been involved in this crash.
@@ -436,7 +288,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
// disable all the plugins, so we can be sure that SysUI is running as
// best as possible.
synchronized (this) {
- for (PluginInstanceManager manager : mPluginMap.values()) {
+ for (PluginActionManager<?> manager : mPluginMap.values()) {
disabledAny |= manager.disableAll();
}
}
@@ -446,7 +298,9 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
}
// Run the normal exception handler so we can crash and cleanup our state.
- mHandler.uncaughtException(thread, throwable);
+ Throwable finalThrowable = throwable;
+ mExceptionHandlerOptional.ifPresent(
+ handler -> handler.uncaughtException(thread, finalThrowable));
}
private boolean checkStack(Throwable throwable) {
@@ -454,7 +308,7 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
boolean disabledAny = false;
synchronized (this) {
for (StackTraceElement element : throwable.getStackTrace()) {
- for (PluginInstanceManager manager : mPluginMap.values()) {
+ for (PluginActionManager<?> manager : mPluginMap.values()) {
disabledAny |= manager.checkAndDisable(element.getClassName());
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/VersionInfo.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/VersionInfo.java
index bb845cd87923..6be3243879d6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/VersionInfo.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/VersionInfo.java
@@ -119,6 +119,8 @@ public class VersionInfo {
public static class InvalidVersionException extends RuntimeException {
private final boolean mTooNew;
+ private int mExpected;
+ private int mActual;
public InvalidVersionException(String str, boolean tooNew) {
super(str);
@@ -128,11 +130,21 @@ public class VersionInfo {
public InvalidVersionException(Class<?> cls, boolean tooNew, int expected, int actual) {
super(cls.getSimpleName() + " expected version " + expected + " but had " + actual);
mTooNew = tooNew;
+ mExpected = expected;
+ mActual = actual;
}
public boolean isTooNew() {
return mTooNew;
}
+
+ public int getExpectedVersion() {
+ return mExpected;
+ }
+
+ public int getActualVersion() {
+ return mActual;
+ }
}
private static class Version {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 277b2e31f7be..8bd0f910dac3 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -77,8 +77,22 @@ oneway interface IOverviewProxy {
void onSplitScreenSecondaryBoundsChanged(in Rect bounds, in Rect insets) = 17;
/**
- * Sent IME status changes
+ * Sent when suggested rotation button could be shown
*/
- void onImeWindowStatusChanged(int displayId, IBinder token, int vis, int backDisposition,
- boolean showImeSwitcher) = 18;
+ void onRotationProposal(int rotation, boolean isValid) = 18;
+
+ /**
+ * Sent when disable flags change
+ */
+ void disable(int displayId, int state1, int state2, boolean animate) = 19;
+
+ /**
+ * Sent when behavior changes. See WindowInsetsController#@Behavior
+ */
+ void onSystemBarAttributesChanged(int displayId, int behavior) = 20;
+
+ /**
+ * Sent when screen turned on and ready to use (blocker scrim is hidden)
+ */
+ void onScreenTurnedOn() = 21;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index f72245b9b252..5b7e500c4630 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -140,5 +140,15 @@ interface ISystemUiProxy {
/** Notifies that a swipe-up gesture has started */
oneway void notifySwipeUpGestureStarted() = 46;
- // Next id = 47
+ /** Notifies when taskbar status updated */
+ oneway void notifyTaskbarStatus(boolean visible, boolean stashed) = 47;
+
+ /**
+ * Notifies sysui when taskbar requests autoHide to stop auto-hiding
+ * If called to suspend, caller is also responsible for calling this method to un-suspend
+ * @param suspend should be true to stop auto-hide, false to resume normal behavior
+ */
+ oneway void notifyTaskbarAutohideSuspend(boolean suspend) = 48;
+
+ // Next id = 49
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
index 7dffc2613956..6154d84d5b37 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -16,13 +16,27 @@
package com.android.systemui.shared.recents.utilities;
+import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
+import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
+import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
+
+import android.annotation.TargetApi;
+import android.content.Context;
import android.graphics.Color;
+import android.graphics.Rect;
+import android.inputmethodservice.InputMethodService;
+import android.os.Build;
import android.os.Handler;
import android.os.Message;
+import android.util.DisplayMetrics;
+import android.view.Surface;
+import android.view.WindowManager;
/* Common code */
public class Utilities {
+ private static final float TABLET_MIN_DPS = 600;
+
/**
* Posts a runnable on a handler at the front of the queue ignoring any sync barriers.
*/
@@ -31,6 +45,23 @@ public class Utilities {
h.sendMessageAtFrontOfQueue(msg);
}
+ public static boolean isRotationAnimationCCW(int from, int to) {
+ // All 180deg WM rotation animations are CCW, match that
+ if (from == Surface.ROTATION_0 && to == Surface.ROTATION_90) return false;
+ if (from == Surface.ROTATION_0 && to == Surface.ROTATION_180) return true; //180d so CCW
+ if (from == Surface.ROTATION_0 && to == Surface.ROTATION_270) return true;
+ if (from == Surface.ROTATION_90 && to == Surface.ROTATION_0) return true;
+ if (from == Surface.ROTATION_90 && to == Surface.ROTATION_180) return false;
+ if (from == Surface.ROTATION_90 && to == Surface.ROTATION_270) return true; //180d so CCW
+ if (from == Surface.ROTATION_180 && to == Surface.ROTATION_0) return true; //180d so CCW
+ if (from == Surface.ROTATION_180 && to == Surface.ROTATION_90) return true;
+ if (from == Surface.ROTATION_180 && to == Surface.ROTATION_270) return false;
+ if (from == Surface.ROTATION_270 && to == Surface.ROTATION_0) return false;
+ if (from == Surface.ROTATION_270 && to == Surface.ROTATION_90) return true; //180d so CCW
+ if (from == Surface.ROTATION_270 && to == Surface.ROTATION_180) return true;
+ return false; // Default
+ }
+
/** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
public static float computeContrastBetweenColors(int bg, int fg) {
float bgR = Color.red(bg) / 255f;
@@ -58,4 +89,51 @@ public class Utilities {
public static float clamp(float value, float min, float max) {
return Math.max(min, Math.min(max, value));
}
+
+ /**
+ * @return updated set of flags from InputMethodService based off {@param oldHints}
+ * Leaves original hints unmodified
+ */
+ public static int calculateBackDispositionHints(int oldHints, int backDisposition,
+ boolean imeShown, boolean showImeSwitcher) {
+ int hints = oldHints;
+ switch (backDisposition) {
+ case InputMethodService.BACK_DISPOSITION_DEFAULT:
+ case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
+ case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
+ if (imeShown) {
+ hints |= NAVIGATION_HINT_BACK_ALT;
+ } else {
+ hints &= ~NAVIGATION_HINT_BACK_ALT;
+ }
+ break;
+ case InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING:
+ hints &= ~NAVIGATION_HINT_BACK_ALT;
+ break;
+ }
+ if (showImeSwitcher) {
+ hints |= NAVIGATION_HINT_IME_SHOWN;
+ } else {
+ hints &= ~NAVIGATION_HINT_IME_SHOWN;
+ }
+
+ return hints;
+ }
+
+ /** @return whether or not {@param context} represents that of a large screen device or not */
+ @TargetApi(Build.VERSION_CODES.R)
+ public static boolean isTablet(Context context) {
+ final WindowManager windowManager = context.getSystemService(WindowManager.class);
+ final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
+
+ float originalSmallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()),
+ context.getResources().getConfiguration().densityDpi);
+ return dpiFromPx(Math.min(bounds.width(), bounds.height()), DENSITY_DEVICE_STABLE)
+ >= TABLET_MIN_DPS && originalSmallestWidth >= TABLET_MIN_DPS;
+ }
+
+ public static float dpiFromPx(float size, int densityDpi) {
+ float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;
+ return (size / densityRatio);
+ }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/ViewRippler.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/ViewRippler.java
new file mode 100644
index 000000000000..5581a1c90527
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/ViewRippler.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 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.systemui.shared.recents.utilities;
+
+import android.view.View;
+
+/**
+ * Shows view ripples by toggling the provided Views "pressed" state.
+ * Ripples 4 times.
+ */
+public class ViewRippler {
+ private static final int RIPPLE_OFFSET_MS = 50;
+ private static final int RIPPLE_INTERVAL_MS = 2000;
+ private View mRoot;
+
+ public void start(View root) {
+ stop(); // Stop any pending ripple animations
+
+ mRoot = root;
+
+ // Schedule pending ripples, offset the 1st to avoid problems with visibility change
+ mRoot.postOnAnimationDelayed(mRipple, RIPPLE_OFFSET_MS);
+ mRoot.postOnAnimationDelayed(mRipple, RIPPLE_INTERVAL_MS);
+ mRoot.postOnAnimationDelayed(mRipple, 2 * RIPPLE_INTERVAL_MS);
+ mRoot.postOnAnimationDelayed(mRipple, 3 * RIPPLE_INTERVAL_MS);
+ mRoot.postOnAnimationDelayed(mRipple, 4 * RIPPLE_INTERVAL_MS);
+ }
+
+ public void stop() {
+ if (mRoot != null) mRoot.removeCallbacks(mRipple);
+ }
+
+ private final Runnable mRipple = new Runnable() {
+ @Override
+ public void run() { // Cause the ripple to fire via false presses
+ if (!mRoot.isAttachedToWindow()) return;
+ mRoot.setPressed(true /* pressed */);
+ mRoot.setPressed(false /* pressed */);
+ }
+ };
+} \ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
new file mode 100644
index 000000000000..cbf739732361
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
@@ -0,0 +1,257 @@
+/*
+ * 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.systemui.shared.rotation;
+
+import android.annotation.DimenRes;
+import android.annotation.IdRes;
+import android.annotation.LayoutRes;
+import android.annotation.StringRes;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.FrameLayout;
+
+import androidx.core.view.OneShotPreDrawListener;
+
+import com.android.systemui.shared.R;
+import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position;
+
+/**
+ * Containing logic for the rotation button on the physical left bottom corner of the screen.
+ */
+public class FloatingRotationButton implements RotationButton {
+
+ private static final int MARGIN_ANIMATION_DURATION_MILLIS = 300;
+
+ private final WindowManager mWindowManager;
+ private final ViewGroup mKeyButtonContainer;
+ private final FloatingRotationButtonView mKeyButtonView;
+
+ private final int mContainerSize;
+
+ private AnimatedVectorDrawable mAnimatedDrawable;
+ private boolean mIsShowing;
+ private boolean mCanShow = true;
+ private int mDisplayRotation;
+
+ private boolean mIsTaskbarVisible = false;
+ private boolean mIsTaskbarStashed = false;
+
+ private final FloatingRotationButtonPositionCalculator mPositionCalculator;
+
+ private RotationButtonController mRotationButtonController;
+ private RotationButtonUpdatesCallback mUpdatesCallback;
+ private Position mPosition;
+
+ public FloatingRotationButton(Context context, @StringRes int contentDescription,
+ @LayoutRes int layout, @IdRes int keyButtonId, @DimenRes int minMargin,
+ @DimenRes int roundedContentPadding, @DimenRes int taskbarLeftMargin,
+ @DimenRes int taskbarBottomMargin, @DimenRes int buttonDiameter,
+ @DimenRes int rippleMaxWidth) {
+ mWindowManager = context.getSystemService(WindowManager.class);
+ mKeyButtonContainer = (ViewGroup) LayoutInflater.from(context).inflate(layout, null);
+ mKeyButtonView = mKeyButtonContainer.findViewById(keyButtonId);
+ mKeyButtonView.setVisibility(View.VISIBLE);
+ mKeyButtonView.setContentDescription(context.getString(contentDescription));
+ mKeyButtonView.setRipple(rippleMaxWidth);
+
+ Resources res = context.getResources();
+
+ int defaultMargin = Math.max(
+ res.getDimensionPixelSize(minMargin),
+ res.getDimensionPixelSize(roundedContentPadding));
+
+ int taskbarMarginLeft =
+ res.getDimensionPixelSize(taskbarLeftMargin);
+ int taskbarMarginBottom =
+ res.getDimensionPixelSize(taskbarBottomMargin);
+
+ mPositionCalculator = new FloatingRotationButtonPositionCalculator(defaultMargin,
+ taskbarMarginLeft, taskbarMarginBottom);
+
+ final int diameter = res.getDimensionPixelSize(buttonDiameter);
+ mContainerSize = diameter + Math.max(defaultMargin, Math.max(taskbarMarginLeft,
+ taskbarMarginBottom));
+ }
+
+ @Override
+ public void setRotationButtonController(RotationButtonController rotationButtonController) {
+ mRotationButtonController = rotationButtonController;
+ updateIcon(mRotationButtonController.getLightIconColor(),
+ mRotationButtonController.getDarkIconColor());
+ }
+
+ @Override
+ public void setUpdatesCallback(RotationButtonUpdatesCallback updatesCallback) {
+ mUpdatesCallback = updatesCallback;
+ }
+
+ @Override
+ public View getCurrentView() {
+ return mKeyButtonView;
+ }
+
+ @Override
+ public boolean show() {
+ if (!mCanShow || mIsShowing) {
+ return false;
+ }
+
+ mIsShowing = true;
+ int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+
+ // TODO(b/200103245): add new window type that has z-index above
+ // TYPE_NAVIGATION_BAR_PANEL as currently it could be below the taskbar which has
+ // the same window type
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ mContainerSize,
+ mContainerSize,
+ 0, 0, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, flags,
+ PixelFormat.TRANSLUCENT);
+
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ lp.setTitle("FloatingRotationButton");
+ lp.setFitInsetsTypes(0 /*types */);
+
+ mDisplayRotation = mWindowManager.getDefaultDisplay().getRotation();
+ mPosition = mPositionCalculator
+ .calculatePosition(mDisplayRotation, mIsTaskbarVisible, mIsTaskbarStashed);
+
+ lp.gravity = mPosition.getGravity();
+ ((FrameLayout.LayoutParams) mKeyButtonView.getLayoutParams()).gravity =
+ mPosition.getGravity();
+
+ updateTranslation(mPosition, /* animate */ false);
+
+ mWindowManager.addView(mKeyButtonContainer, lp);
+ if (mAnimatedDrawable != null) {
+ mAnimatedDrawable.reset();
+ mAnimatedDrawable.start();
+ }
+
+ // Notify about visibility only after first traversal so we can properly calculate
+ // the touch region for the button
+ OneShotPreDrawListener.add(mKeyButtonView, () -> {
+ if (mIsShowing && mUpdatesCallback != null) {
+ mUpdatesCallback.onVisibilityChanged(true);
+ }
+ });
+
+ return true;
+ }
+
+ @Override
+ public boolean hide() {
+ if (!mIsShowing) {
+ return false;
+ }
+ mWindowManager.removeViewImmediate(mKeyButtonContainer);
+ mIsShowing = false;
+ if (mUpdatesCallback != null) {
+ mUpdatesCallback.onVisibilityChanged(false);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isVisible() {
+ return mIsShowing;
+ }
+
+ @Override
+ public void updateIcon(int lightIconColor, int darkIconColor) {
+ mAnimatedDrawable = (AnimatedVectorDrawable) mKeyButtonView.getContext()
+ .getDrawable(mRotationButtonController.getIconResId());
+ mKeyButtonView.setImageDrawable(mAnimatedDrawable);
+ mKeyButtonView.setColors(lightIconColor, darkIconColor);
+ }
+
+ @Override
+ public void setOnClickListener(View.OnClickListener onClickListener) {
+ mKeyButtonView.setOnClickListener(onClickListener);
+ }
+
+ @Override
+ public void setOnHoverListener(View.OnHoverListener onHoverListener) {
+ mKeyButtonView.setOnHoverListener(onHoverListener);
+ }
+
+ @Override
+ public Drawable getImageDrawable() {
+ return mAnimatedDrawable;
+ }
+
+ @Override
+ public void setDarkIntensity(float darkIntensity) {
+ mKeyButtonView.setDarkIntensity(darkIntensity);
+ }
+
+ @Override
+ public void setCanShowRotationButton(boolean canShow) {
+ mCanShow = canShow;
+ if (!mCanShow) {
+ hide();
+ }
+ }
+
+ @Override
+ public void onTaskbarStateChanged(boolean taskbarVisible, boolean taskbarStashed) {
+ mIsTaskbarVisible = taskbarVisible;
+ mIsTaskbarStashed = taskbarStashed;
+
+ if (!mIsShowing) return;
+
+ final Position newPosition = mPositionCalculator
+ .calculatePosition(mDisplayRotation, mIsTaskbarVisible, mIsTaskbarStashed);
+
+ if (newPosition.getTranslationX() != mPosition.getTranslationX()
+ || newPosition.getTranslationY() != mPosition.getTranslationY()) {
+ updateTranslation(newPosition, /* animate */ true);
+ mPosition = newPosition;
+ }
+ }
+
+ private void updateTranslation(Position position, boolean animate) {
+ final int translationX = position.getTranslationX();
+ final int translationY = position.getTranslationY();
+
+ if (animate) {
+ mKeyButtonView
+ .animate()
+ .translationX(translationX)
+ .translationY(translationY)
+ .setDuration(MARGIN_ANIMATION_DURATION_MILLIS)
+ .setInterpolator(new AccelerateDecelerateInterpolator())
+ .withEndAction(() -> {
+ if (mUpdatesCallback != null && mIsShowing) {
+ mUpdatesCallback.onPositionChanged();
+ }
+ })
+ .start();
+ } else {
+ mKeyButtonView.setTranslationX(translationX);
+ mKeyButtonView.setTranslationY(translationY);
+ }
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
new file mode 100644
index 000000000000..ec3c073dc68d
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
@@ -0,0 +1,65 @@
+package com.android.systemui.shared.rotation
+
+import android.view.Gravity
+import android.view.Surface
+
+/**
+ * Calculates gravity and translation that is necessary to display
+ * the button in the correct position based on the current state
+ */
+class FloatingRotationButtonPositionCalculator(
+ private val defaultMargin: Int,
+ private val taskbarMarginLeft: Int,
+ private val taskbarMarginBottom: Int
+) {
+
+ fun calculatePosition(
+ currentRotation: Int,
+ taskbarVisible: Boolean,
+ taskbarStashed: Boolean
+ ): Position {
+
+ val isTaskbarSide = currentRotation == Surface.ROTATION_0
+ || currentRotation == Surface.ROTATION_90
+ val useTaskbarMargin = isTaskbarSide && taskbarVisible && !taskbarStashed
+
+ val gravity = resolveGravity(currentRotation)
+
+ val marginLeft = if (useTaskbarMargin) taskbarMarginLeft else defaultMargin
+ val marginBottom = if (useTaskbarMargin) taskbarMarginBottom else defaultMargin
+
+ val translationX =
+ if (gravity and Gravity.RIGHT == Gravity.RIGHT) {
+ -marginLeft
+ } else {
+ marginLeft
+ }
+ val translationY =
+ if (gravity and Gravity.BOTTOM == Gravity.BOTTOM) {
+ -marginBottom
+ } else {
+ marginBottom
+ }
+
+ return Position(
+ gravity = gravity,
+ translationX = translationX,
+ translationY = translationY
+ )
+ }
+
+ data class Position(
+ val gravity: Int,
+ val translationX: Int,
+ val translationY: Int
+ )
+
+ private fun resolveGravity(rotation: Int): Int =
+ when (rotation) {
+ Surface.ROTATION_0 -> Gravity.BOTTOM or Gravity.LEFT
+ Surface.ROTATION_90 -> Gravity.BOTTOM or Gravity.RIGHT
+ Surface.ROTATION_180 -> Gravity.TOP or Gravity.RIGHT
+ Surface.ROTATION_270 -> Gravity.TOP or Gravity.LEFT
+ else -> throw IllegalArgumentException("Invalid rotation $rotation")
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java
new file mode 100644
index 000000000000..c5f8fc15b3b7
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java
@@ -0,0 +1,86 @@
+/*
+ * 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.systemui.shared.rotation;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.annotation.DimenRes;
+
+import com.android.systemui.navigationbar.buttons.KeyButtonRipple;
+
+public class FloatingRotationButtonView extends ImageView {
+
+ private static final float BACKGROUND_ALPHA = 0.92f;
+
+ private KeyButtonRipple mRipple;
+ private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+
+ public FloatingRotationButtonView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FloatingRotationButtonView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ setClickable(true);
+
+ setWillNotDraw(false);
+ forceHasOverlappingRendering(false);
+ }
+
+ public void setRipple(@DimenRes int rippleMaxWidthResource) {
+ mRipple = new KeyButtonRipple(getContext(), this, rippleMaxWidthResource);
+ setBackground(mRipple);
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ if (visibility != View.VISIBLE) {
+ jumpDrawablesToCurrentState();
+ }
+ }
+
+ public void setColors(int lightColor, int darkColor) {
+ getDrawable().setColorFilter(new PorterDuffColorFilter(lightColor, PorterDuff.Mode.SRC_IN));
+
+ final int ovalBackgroundColor = Color.valueOf(Color.red(darkColor),
+ Color.green(darkColor), Color.blue(darkColor), BACKGROUND_ALPHA).toArgb();
+
+ mOvalBgPaint.setColor(ovalBackgroundColor);
+ mRipple.setType(KeyButtonRipple.Type.OVAL);
+ }
+
+ public void setDarkIntensity(float darkIntensity) {
+ mRipple.setDarkIntensity(darkIntensity);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ int d = Math.min(getWidth(), getHeight());
+ canvas.drawOval(0, 0, d, d, mOvalBgPaint);
+ super.draw(canvas);
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.java
new file mode 100644
index 000000000000..89f71ebf3dce
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButton.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.systemui.shared.rotation;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+/**
+ * Interface of a rotation button that interacts {@link RotationButtonController}.
+ * This interface exists because of the two different styles of rotation button in Sysui,
+ * one in contextual for 3 button nav and a floating rotation button for gestural.
+ */
+public interface RotationButton {
+ default void setRotationButtonController(RotationButtonController rotationButtonController) { }
+ default void setUpdatesCallback(RotationButtonUpdatesCallback updatesCallback) { }
+
+ default View getCurrentView() {
+ return null;
+ }
+ default boolean show() { return false; }
+ default boolean hide() { return false; }
+ default boolean isVisible() {
+ return false;
+ }
+ default void setCanShowRotationButton(boolean canShow) {}
+ default void onTaskbarStateChanged(boolean taskbarVisible, boolean taskbarStashed) {}
+ default void updateIcon(int lightIconColor, int darkIconColor) { }
+ default void setOnClickListener(View.OnClickListener onClickListener) { }
+ default void setOnHoverListener(View.OnHoverListener onHoverListener) { }
+ default Drawable getImageDrawable() {
+ return null;
+ }
+ default void setDarkIntensity(float darkIntensity) { }
+ default boolean acceptRotationProposal() {
+ return getCurrentView() != null;
+ }
+
+ /**
+ * Callback for updates provided by a rotation button
+ */
+ interface RotationButtonUpdatesCallback {
+ default void onVisibilityChanged(boolean isVisible) {};
+ default void onPositionChanged() {};
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 8ea9ae3c21c5..2dbd5dee76aa 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright 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.
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar;
+package com.android.systemui.shared.rotation;
+
+import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
@@ -23,45 +25,51 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
+import android.annotation.SuppressLint;
import android.app.StatusBarManager;
import android.content.ContentResolver;
import android.content.Context;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.Log;
-import android.view.IRotationWatcher.Stub;
+import android.view.IRotationWatcher;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.WindowInsetsController;
-import android.view.WindowInsetsController.Behavior;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
+import com.android.internal.view.RotationPolicy;
+import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.utilities.ViewRippler;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.RotationLockController;
import java.util.Optional;
import java.util.function.Consumer;
+import java.util.function.Supplier;
-/** Contains logic that deals with showing a rotate suggestion button with animation. */
+/**
+ * Contains logic that deals with showing a rotate suggestion button with animation.
+ */
public class RotationButtonController {
private static final String TAG = "StatusBar/RotationButtonController";
private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000;
+ private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
@@ -69,6 +77,7 @@ public class RotationButtonController {
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
private final ViewRippler mViewRippler = new ViewRippler();
+ private final Supplier<Integer> mWindowRotationProvider;
private RotationButton mRotationButton;
private boolean mIsRecentsAnimationRunning;
@@ -76,17 +85,30 @@ public class RotationButtonController {
private int mLastRotationSuggestion;
private boolean mPendingRotationSuggestion;
private boolean mHoveringRotationSuggestion;
- private RotationLockController mRotationLockController;
- private AccessibilityManagerWrapper mAccessibilityManagerWrapper;
- private TaskStackListenerImpl mTaskStackListener;
+ private final AccessibilityManager mAccessibilityManager;
+ private final TaskStackListenerImpl mTaskStackListener;
private Consumer<Integer> mRotWatcherListener;
+
private boolean mListenersRegistered = false;
private boolean mIsNavigationBarShowing;
- private @Behavior int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT;
+ @SuppressLint("InlinedApi")
+ private @WindowInsetsController.Behavior
+ int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT;
private boolean mSkipOverrideUserLockPrefsOnce;
- private int mLightIconColor;
- private int mDarkIconColor;
- private int mIconResId = R.drawable.ic_sysbar_rotate_button_ccw_start_90;
+ private final int mLightIconColor;
+ private final int mDarkIconColor;
+
+ @DrawableRes
+ private final int mIconCcwStart0ResId;
+ @DrawableRes
+ private final int mIconCcwStart90ResId;
+ @DrawableRes
+ private final int mIconCwStart0ResId;
+ @DrawableRes
+ private final int mIconCwStart90ResId;
+
+ @DrawableRes
+ private int mIconResId;
private final Runnable mRemoveRotationProposal =
() -> setRotateSuggestionButtonState(false /* visible */);
@@ -94,19 +116,20 @@ public class RotationButtonController {
() -> mPendingRotationSuggestion = false;
private Animator mRotateHideAnimator;
- private final Stub mRotationWatcher = new Stub() {
+
+ private final IRotationWatcher.Stub mRotationWatcher = new IRotationWatcher.Stub() {
@Override
- public void onRotationChanged(final int rotation) throws RemoteException {
+ public void onRotationChanged(final int rotation) {
// We need this to be scheduled as early as possible to beat the redrawing of
// window in response to the orientation change.
mMainThreadHandler.postAtFrontOfQueue(() -> {
// If the screen rotation changes while locked, potentially update lock to flow with
// new screen rotation and hide any showing suggestions.
- if (mRotationLockController.isRotationLocked()) {
+ if (isRotationLocked()) {
if (shouldOverrideUserLockPrefs(rotation)) {
setRotationLockedAtAngle(rotation);
}
- setRotateSuggestionButtonState(false /* visible */, true /* hideImmediately */);
+ setRotateSuggestionButtonState(false /* visible */, true /* forced */);
}
if (mRotWatcherListener != null) {
@@ -118,35 +141,64 @@ public class RotationButtonController {
/**
* Determines if rotation suggestions disabled2 flag exists in flag
+ *
* @param disable2Flags see if rotation suggestion flag exists in this flag
* @return whether flag exists
*/
- static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) {
+ public static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) {
return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0;
}
- RotationButtonController(Context context, @ColorInt int lightIconColor,
- @ColorInt int darkIconColor) {
+ public RotationButtonController(Context context,
+ @ColorInt int lightIconColor, @ColorInt int darkIconColor,
+ @DrawableRes int iconCcwStart0ResId,
+ @DrawableRes int iconCcwStart90ResId,
+ @DrawableRes int iconCwStart0ResId,
+ @DrawableRes int iconCwStart90ResId,
+ Supplier<Integer> windowRotationProvider) {
+
mContext = context;
mLightIconColor = lightIconColor;
mDarkIconColor = darkIconColor;
- mIsNavigationBarShowing = true;
- mRotationLockController = Dependency.get(RotationLockController.class);
- mAccessibilityManagerWrapper = Dependency.get(AccessibilityManagerWrapper.class);
+ mIconCcwStart0ResId = iconCcwStart0ResId;
+ mIconCcwStart90ResId = iconCcwStart90ResId;
+ mIconCwStart0ResId = iconCwStart0ResId;
+ mIconCwStart90ResId = iconCwStart90ResId;
+ mIconResId = mIconCcwStart90ResId;
+
+ mAccessibilityManager = AccessibilityManager.getInstance(context);
mTaskStackListener = new TaskStackListenerImpl();
+ mWindowRotationProvider = windowRotationProvider;
}
- void setRotationButton(RotationButton rotationButton,
- Consumer<Boolean> visibilityChangedCallback) {
+ public void setRotationButton(RotationButton rotationButton,
+ RotationButtonUpdatesCallback updatesCallback) {
mRotationButton = rotationButton;
mRotationButton.setRotationButtonController(this);
mRotationButton.setOnClickListener(this::onRotateSuggestionClick);
mRotationButton.setOnHoverListener(this::onRotateSuggestionHover);
- mRotationButton.setVisibilityChangedCallback(visibilityChangedCallback);
+ mRotationButton.setUpdatesCallback(updatesCallback);
+ }
+
+ public Context getContext() {
+ return mContext;
}
- void registerListeners() {
+ public void init() {
+ registerListeners();
+ if (mContext.getDisplay().getDisplayId() != DEFAULT_DISPLAY) {
+ // Currently there is no accelerometer sensor on non-default display, disable fixed
+ // rotation for non-default display
+ onDisable2FlagChanged(StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS);
+ }
+ }
+
+ public void onDestroy() {
+ unregisterListeners();
+ }
+
+ public void registerListeners() {
if (mListenersRegistered) {
return;
}
@@ -154,18 +206,19 @@ public class RotationButtonController {
mListenersRegistered = true;
try {
WindowManagerGlobal.getWindowManagerService()
- .watchRotation(mRotationWatcher, mContext.getDisplay().getDisplayId());
+ .watchRotation(mRotationWatcher, DEFAULT_DISPLAY);
} catch (IllegalArgumentException e) {
mListenersRegistered = false;
Log.w(TAG, "RegisterListeners for the display failed");
} catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ Log.e(TAG, "RegisterListeners caught a RemoteException", e);
+ return;
}
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
}
- void unregisterListeners() {
+ public void unregisterListeners() {
if (!mListenersRegistered) {
return;
}
@@ -174,34 +227,31 @@ public class RotationButtonController {
try {
WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
} catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ Log.e(TAG, "UnregisterListeners caught a RemoteException", e);
+ return;
}
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
}
- void setRotationCallback(Consumer<Integer> watcher) {
+ public void setRotationCallback(Consumer<Integer> watcher) {
mRotWatcherListener = watcher;
}
- void setRotationLockedAtAngle(int rotationSuggestion) {
- mRotationLockController.setRotationLockedAtAngle(true /* locked */, rotationSuggestion);
+ public void setRotationLockedAtAngle(int rotationSuggestion) {
+ RotationPolicy.setRotationLockAtAngle(mContext, true, rotationSuggestion);
}
public boolean isRotationLocked() {
- return mRotationLockController.isRotationLocked();
+ return RotationPolicy.isRotationLocked(mContext);
}
- void setRotateSuggestionButtonState(boolean visible) {
- setRotateSuggestionButtonState(visible, false /* hideImmediately */);
+ public void setRotateSuggestionButtonState(boolean visible) {
+ setRotateSuggestionButtonState(visible, false /* force */);
}
- /**
- * Change the visibility of rotate suggestion button. If {@code hideImmediately} is true,
- * it doesn't wait until the completion of the running animation.
- */
- void setRotateSuggestionButtonState(final boolean visible, final boolean hideImmediately) {
- // At any point the the button can become invisible because an a11y service became active.
+ void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
+ // At any point the button can become invisible because an a11y service became active.
// Similarly, a call to make the button visible may be rejected because an a11y service is
// active. Must account for this.
// Rerun a show animation to indicate change but don't rerun a hide animation
@@ -210,7 +260,7 @@ public class RotationButtonController {
final View view = mRotationButton.getCurrentView();
if (view == null) return;
- final KeyButtonDrawable currentDrawable = mRotationButton.getImageDrawable();
+ final Drawable currentDrawable = mRotationButton.getImageDrawable();
if (currentDrawable == null) return;
// Clear any pending suggestion flag as it has either been nullified or is being shown
@@ -229,11 +279,13 @@ public class RotationButtonController {
view.setAlpha(1f);
// Run the rotate icon's animation if it has one
- if (currentDrawable.canAnimate()) {
- currentDrawable.resetAnimation();
- currentDrawable.startAnimation();
+ if (currentDrawable instanceof AnimatedVectorDrawable) {
+ ((AnimatedVectorDrawable) currentDrawable).reset();
+ ((AnimatedVectorDrawable) currentDrawable).start();
}
+ // TODO(b/187754252): No idea why this doesn't work. If we remove the "false"
+ // we see the animation show the pressed state... but it only shows the first time.
if (!isRotateSuggestionIntroduced()) mViewRippler.start(view);
// Set visibility unless a11y service is active.
@@ -241,7 +293,7 @@ public class RotationButtonController {
} else { // Hide
mViewRippler.stop(); // Prevent any pending ripples, force hide or not
- if (hideImmediately) {
+ if (force) {
// If a hide animator is running stop it and make invisible
if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
mRotateHideAnimator.pause();
@@ -255,7 +307,7 @@ public class RotationButtonController {
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 0f);
fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
- fadeOut.setInterpolator(Interpolators.LINEAR);
+ fadeOut.setInterpolator(LINEAR_INTERPOLATOR);
fadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -268,29 +320,34 @@ public class RotationButtonController {
}
}
- void setRecentsAnimationRunning(boolean running) {
+ public void setDarkIntensity(float darkIntensity) {
+ mRotationButton.setDarkIntensity(darkIntensity);
+ }
+
+ public void setRecentsAnimationRunning(boolean running) {
mIsRecentsAnimationRunning = running;
updateRotationButtonStateInOverview();
}
- void setHomeRotationEnabled(boolean enabled) {
+ public void setHomeRotationEnabled(boolean enabled) {
mHomeRotationEnabled = enabled;
updateRotationButtonStateInOverview();
}
private void updateRotationButtonStateInOverview() {
if (mIsRecentsAnimationRunning && !mHomeRotationEnabled) {
- setRotateSuggestionButtonState(false, true /* hideImmediately */ );
+ setRotateSuggestionButtonState(false, true /* hideImmediately */);
}
}
- void setDarkIntensity(float darkIntensity) {
- mRotationButton.setDarkIntensity(darkIntensity);
- }
+ public void onRotationProposal(int rotation, boolean isValid) {
+ int windowRotation = mWindowRotationProvider.get();
- void onRotationProposal(int rotation, int windowRotation, boolean isValid) {
- if (!mRotationButton.acceptRotationProposal() || (!mHomeRotationEnabled
- && mIsRecentsAnimationRunning)) {
+ if (!mRotationButton.acceptRotationProposal()) {
+ return;
+ }
+
+ if (!mHomeRotationEnabled && mIsRecentsAnimationRunning) {
return;
}
@@ -311,15 +368,11 @@ public class RotationButtonController {
// Prepare to show the navbar icon by updating the icon style to change anim params
mLastRotationSuggestion = rotation; // Remember rotation for click
- final boolean rotationCCW = isRotationAnimationCCW(windowRotation, rotation);
+ final boolean rotationCCW = Utilities.isRotationAnimationCCW(windowRotation, rotation);
if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) {
- mIconResId = rotationCCW
- ? R.drawable.ic_sysbar_rotate_button_ccw_start_90
- : R.drawable.ic_sysbar_rotate_button_cw_start_90;
+ mIconResId = rotationCCW ? mIconCcwStart0ResId : mIconCwStart0ResId;
} else { // 90 or 270
- mIconResId = rotationCCW
- ? R.drawable.ic_sysbar_rotate_button_ccw_start_0
- : R.drawable.ic_sysbar_rotate_button_ccw_start_0;
+ mIconResId = rotationCCW ? mIconCcwStart90ResId : mIconCwStart90ResId;
}
mRotationButton.updateIcon(mLightIconColor, mDarkIconColor);
@@ -336,56 +389,66 @@ public class RotationButtonController {
}
}
- void onDisable2FlagChanged(int state2) {
+ public void onDisable2FlagChanged(int state2) {
final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2);
if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled();
}
- void onNavigationBarWindowVisibilityChange(boolean showing) {
- if (mIsNavigationBarShowing != showing) {
- mIsNavigationBarShowing = showing;
- showPendingRotationButtonIfNeeded();
+ public void onBehaviorChanged(int displayId, @WindowInsetsController.Behavior int behavior) {
+ if (DEFAULT_DISPLAY != displayId) {
+ return;
}
- }
- void onBehaviorChanged(@Behavior int behavior) {
if (mBehavior != behavior) {
mBehavior = behavior;
showPendingRotationButtonIfNeeded();
}
}
+ public void onNavigationBarWindowVisibilityChange(boolean showing) {
+ if (mIsNavigationBarShowing != showing) {
+ mIsNavigationBarShowing = showing;
+ showPendingRotationButtonIfNeeded();
+ }
+ }
+
+ public void onTaskbarStateChange(boolean visible, boolean stashed) {
+ getRotationButton().onTaskbarStateChanged(visible, stashed);
+ }
+
private void showPendingRotationButtonIfNeeded() {
if (canShowRotationButton() && mPendingRotationSuggestion) {
showAndLogRotationSuggestion();
}
}
- /** Return true when either the nav bar is visible or it's in visual immersive mode. */
+ /**
+ * Return true when either the task bar is visible or it's in visual immersive mode.
+ */
+ @SuppressLint("InlinedApi")
private boolean canShowRotationButton() {
return mIsNavigationBarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT;
}
- public Context getContext() {
- return mContext;
- }
-
- RotationButton getRotationButton() {
- return mRotationButton;
- }
-
- public @DrawableRes int getIconResId() {
+ @DrawableRes
+ public int getIconResId() {
return mIconResId;
}
- public @ColorInt int getLightIconColor() {
+ @ColorInt
+ public int getLightIconColor() {
return mLightIconColor;
}
- public @ColorInt int getDarkIconColor() {
+ @ColorInt
+ public int getDarkIconColor() {
return mDarkIconColor;
}
+ public RotationButton getRotationButton() {
+ return mRotationButton;
+ }
+
private void onRotateSuggestionClick(View v) {
mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED);
incrementNumAcceptedRotationSuggestionsIfNeeded();
@@ -417,7 +480,7 @@ public class RotationButtonController {
* avoid losing original user rotation when display rotation is changed by entering the fixed
* orientation overview.
*/
- void setSkipOverrideUserLockPrefsOnce() {
+ public void setSkipOverrideUserLockPrefsOnce() {
mSkipOverrideUserLockPrefsOnce = true;
}
@@ -431,23 +494,6 @@ public class RotationButtonController {
return rotation == NATURAL_ROTATION;
}
- private boolean isRotationAnimationCCW(int from, int to) {
- // All 180deg WM rotation animations are CCW, match that
- if (from == Surface.ROTATION_0 && to == Surface.ROTATION_90) return false;
- if (from == Surface.ROTATION_0 && to == Surface.ROTATION_180) return true; //180d so CCW
- if (from == Surface.ROTATION_0 && to == Surface.ROTATION_270) return true;
- if (from == Surface.ROTATION_90 && to == Surface.ROTATION_0) return true;
- if (from == Surface.ROTATION_90 && to == Surface.ROTATION_180) return false;
- if (from == Surface.ROTATION_90 && to == Surface.ROTATION_270) return true; //180d so CCW
- if (from == Surface.ROTATION_180 && to == Surface.ROTATION_0) return true; //180d so CCW
- if (from == Surface.ROTATION_180 && to == Surface.ROTATION_90) return true;
- if (from == Surface.ROTATION_180 && to == Surface.ROTATION_270) return false;
- if (from == Surface.ROTATION_270 && to == Surface.ROTATION_0) return false;
- if (from == Surface.ROTATION_270 && to == Surface.ROTATION_90) return true; //180d so CCW
- if (from == Surface.ROTATION_270 && to == Surface.ROTATION_180) return true;
- return false; // Default
- }
-
private void rescheduleRotationTimeout(final boolean reasonHover) {
// May be called due to a new rotation proposal or a change in hover state
if (reasonHover) {
@@ -465,7 +511,7 @@ public class RotationButtonController {
}
private int computeRotationProposalTimeout() {
- return mAccessibilityManagerWrapper.getRecommendedTimeoutMillis(
+ return mAccessibilityManager.getRecommendedTimeoutMillis(
mHoveringRotationSuggestion ? 16000 : 5000,
AccessibilityManager.FLAG_CONTENT_CONTROLS);
}
@@ -520,38 +566,6 @@ public class RotationButtonController {
}
}
- private class ViewRippler {
- private static final int RIPPLE_OFFSET_MS = 50;
- private static final int RIPPLE_INTERVAL_MS = 2000;
- private View mRoot;
-
- public void start(View root) {
- stop(); // Stop any pending ripple animations
-
- mRoot = root;
-
- // Schedule pending ripples, offset the 1st to avoid problems with visibility change
- mRoot.postOnAnimationDelayed(mRipple, RIPPLE_OFFSET_MS);
- mRoot.postOnAnimationDelayed(mRipple, RIPPLE_INTERVAL_MS);
- mRoot.postOnAnimationDelayed(mRipple, 2 * RIPPLE_INTERVAL_MS);
- mRoot.postOnAnimationDelayed(mRipple, 3 * RIPPLE_INTERVAL_MS);
- mRoot.postOnAnimationDelayed(mRipple, 4 * RIPPLE_INTERVAL_MS);
- }
-
- public void stop() {
- if (mRoot != null) mRoot.removeCallbacks(mRipple);
- }
-
- private final Runnable mRipple = new Runnable() {
- @Override
- public void run() { // Cause the ripple to fire via false presses
- if (!mRoot.isAttachedToWindow()) return;
- mRoot.setPressed(true /* pressed */);
- mRoot.setPressed(false /* pressed */);
- }
- };
- }
-
enum RotationButtonEvent implements UiEventLogger.UiEventEnum {
@UiEvent(doc = "The rotation button was shown")
ROTATION_SUGGESTION_SHOWN(206),
@@ -559,11 +573,15 @@ public class RotationButtonController {
ROTATION_SUGGESTION_ACCEPTED(207);
private final int mId;
+
RotationButtonEvent(int id) {
mId = id;
}
- @Override public int getId() {
+
+ @Override
+ public int getId() {
return mId;
}
}
}
+
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index 442716878af4..b82d896e6bea 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -80,8 +80,7 @@ public final class InteractionJankMonitorWrapper {
public static void begin(View v, @CujType int cujType, long timeout) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return;
Configuration.Builder builder =
- new Configuration.Builder(cujType)
- .setView(v)
+ Configuration.Builder.withView(cujType, v)
.setTimeout(timeout);
InteractionJankMonitor.getInstance().begin(builder);
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index c468e416f8a5..d447b485f33b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -112,6 +112,14 @@ public class QuickStepContract {
public static final int SYSUI_STATE_IME_SHOWING = 1 << 18;
// The window magnification is overlapped with system gesture insets at the bottom.
public static final int SYSUI_STATE_MAGNIFICATION_OVERLAP = 1 << 19;
+ // ImeSwitcher is showing
+ public static final int SYSUI_STATE_IME_SWITCHER_SHOWING = 1 << 20;
+ // Device dozing/AOD state
+ public static final int SYSUI_STATE_DEVICE_DOZING = 1 << 21;
+ // The home feature is disabled (either by SUW/SysUI/device policy)
+ public static final int SYSUI_STATE_BACK_DISABLED = 1 << 22;
+ // The bubble stack is expanded AND the mange menu for bubbles is expanded on top of it.
+ public static final int SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1 << 23;
@Retention(RetentionPolicy.SOURCE)
@IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -133,7 +141,11 @@ public class QuickStepContract {
SYSUI_STATE_ONE_HANDED_ACTIVE,
SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
SYSUI_STATE_IME_SHOWING,
- SYSUI_STATE_MAGNIFICATION_OVERLAP
+ SYSUI_STATE_MAGNIFICATION_OVERLAP,
+ SYSUI_STATE_IME_SWITCHER_SHOWING,
+ SYSUI_STATE_DEVICE_DOZING,
+ SYSUI_STATE_BACK_DISABLED,
+ SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED
})
public @interface SystemUiStateFlags {}
@@ -162,6 +174,11 @@ public class QuickStepContract {
? "allow_gesture" : "");
str.add((flags & SYSUI_STATE_IME_SHOWING) != 0 ? "ime_visible" : "");
str.add((flags & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0 ? "magnification_overlap" : "");
+ str.add((flags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0 ? "ime_switcher_showing" : "");
+ str.add((flags & SYSUI_STATE_DEVICE_DOZING) != 0 ? "device_dozing" : "");
+ str.add((flags & SYSUI_STATE_BACK_DISABLED) != 0 ? "back_disabled" : "");
+ str.add((flags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0
+ ? "bubbles_mange_menu_expanded" : "");
return str.toString();
}
@@ -273,8 +290,8 @@ public class QuickStepContract {
* These values are expressed in pixels because they should not respect display or font
* scaling, this means that we don't have to reload them on config changes.
*/
- public static float getWindowCornerRadius(Resources resources) {
- return ScreenDecorationsUtils.getWindowCornerRadius(resources);
+ public static float getWindowCornerRadius(Context context) {
+ return ScreenDecorationsUtils.getWindowCornerRadius(context);
}
/**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index ee55bf0aa8b7..dcc4ea119376 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -37,9 +37,10 @@ import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
+import android.window.RemoteTransition;
import android.window.TransitionInfo;
-import java.util.ArrayList;
+import com.android.wm.shell.util.CounterRotator;
/**
* @see RemoteAnimationAdapter
@@ -62,14 +63,16 @@ public class RemoteAnimationAdapterCompat {
/** Helper to just build a remote transition. Use this if the legacy adapter isn't needed. */
public static RemoteTransitionCompat buildRemoteTransition(RemoteAnimationRunnerCompat runner) {
- return new RemoteTransitionCompat(wrapRemoteTransition(runner));
+ return new RemoteTransitionCompat(
+ new RemoteTransition(wrapRemoteTransition(runner)));
}
public RemoteTransitionCompat getRemoteTransition() {
return mRemoteTransition;
}
- private static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner(
+ /** Wraps a RemoteAnimationRunnerCompat in an IRemoteAnimationRunner. */
+ public static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner(
final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
return new IRemoteAnimationRunner.Stub() {
@Override
@@ -106,52 +109,6 @@ public class RemoteAnimationAdapterCompat {
};
}
- private static class CounterRotator {
- SurfaceControl mSurface = null;
- ArrayList<SurfaceControl> mRotateChildren = null;
-
- void setup(SurfaceControl.Transaction t, SurfaceControl parent, int rotateDelta,
- float displayW, float displayH) {
- if (rotateDelta == 0) return;
- mRotateChildren = new ArrayList<>();
- // We want to counter-rotate, so subtract from 4
- rotateDelta = 4 - (rotateDelta + 4) % 4;
- mSurface = new SurfaceControl.Builder()
- .setName("Transition Unrotate")
- .setContainerLayer()
- .setParent(parent)
- .build();
- // column-major
- if (rotateDelta == 1) {
- t.setMatrix(mSurface, 0, 1, -1, 0);
- t.setPosition(mSurface, displayW, 0);
- } else if (rotateDelta == 2) {
- t.setMatrix(mSurface, -1, 0, 0, -1);
- t.setPosition(mSurface, displayW, displayH);
- } else if (rotateDelta == 3) {
- t.setMatrix(mSurface, 0, -1, 1, 0);
- t.setPosition(mSurface, 0, displayH);
- }
- t.show(mSurface);
- }
-
- void addChild(SurfaceControl.Transaction t, SurfaceControl child) {
- if (mSurface == null) return;
- t.reparent(child, mSurface);
- mRotateChildren.add(child);
- }
-
- void cleanUp(SurfaceControl rootLeash) {
- if (mSurface == null) return;
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- for (int i = mRotateChildren.size() - 1; i >= 0; --i) {
- t.reparent(mRotateChildren.get(i), rootLeash);
- }
- t.remove(mSurface);
- t.apply();
- }
- }
-
private static IRemoteTransition.Stub wrapRemoteTransition(
final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
return new IRemoteTransition.Stub() {
@@ -201,14 +158,14 @@ public class RemoteAnimationAdapterCompat {
if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(),
rotateDelta, displayW, displayH);
- if (counterLauncher.mSurface != null) {
- t.setLayer(counterLauncher.mSurface, launcherLayer);
+ if (counterLauncher.getSurface() != null) {
+ t.setLayer(counterLauncher.getSurface(), launcherLayer);
}
}
if (isReturnToHome) {
- if (counterLauncher.mSurface != null) {
- t.setLayer(counterLauncher.mSurface, info.getChanges().size() * 3);
+ if (counterLauncher.getSurface() != null) {
+ t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
}
// Need to "boost" the closing things since that's what launcher expects.
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -234,8 +191,8 @@ public class RemoteAnimationAdapterCompat {
if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(),
rotateDelta, displayW, displayH);
- if (counterWallpaper.mSurface != null) {
- t.setLayer(counterWallpaper.mSurface, -1);
+ if (counterWallpaper.getSurface() != null) {
+ t.setLayer(counterWallpaper.getSurface(), -1);
counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
}
}
@@ -260,7 +217,7 @@ public class RemoteAnimationAdapterCompat {
t.remove(leashMap.valueAt(i));
}
t.apply();
- finishCallback.onTransitionFinished(null /* wct */);
+ finishCallback.onTransitionFinished(null /* wct */, null /* sct */);
} catch (RemoteException e) {
Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
+ " finished callback", e);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 2407d216b4ab..30db13611f4a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -68,11 +68,16 @@ public class RemoteAnimationTargetCompat {
public final boolean isNotInRecents;
public final Rect contentInsets;
public final ActivityManager.RunningTaskInfo taskInfo;
+ public final boolean allowEnterPip;
public final int rotationChange;
public final int windowType;
+ public final WindowConfiguration windowConfiguration;
private final SurfaceControl mStartLeash;
+ // Fields used only to unrap into RemoteAnimationTarget
+ private final Rect startBounds;
+
public RemoteAnimationTargetCompat(RemoteAnimationTarget app) {
taskId = app.taskId;
mode = app.mode;
@@ -88,10 +93,13 @@ public class RemoteAnimationTargetCompat {
contentInsets = app.contentInsets;
activityType = app.windowConfiguration.getActivityType();
taskInfo = app.taskInfo;
+ allowEnterPip = app.allowEnterPip;
rotationChange = 0;
mStartLeash = app.startLeash;
windowType = app.windowType;
+ windowConfiguration = app.windowConfiguration;
+ startBounds = app.startBounds;
}
private static int newModeToLegacyMode(int newMode) {
@@ -107,6 +115,14 @@ public class RemoteAnimationTargetCompat {
}
}
+ public RemoteAnimationTarget unwrap() {
+ return new RemoteAnimationTarget(
+ taskId, mode, leash.getSurfaceControl(), isTranslucent, clipRect, contentInsets,
+ prefixOrderIndex, position, localBounds, screenSpaceBounds, windowConfiguration,
+ isNotInRecents, mStartLeash, startBounds, taskInfo, allowEnterPip, windowType
+ );
+ }
+
/**
* Almost a copy of Transitions#setupStartState.
@@ -214,9 +230,15 @@ public class RemoteAnimationTargetCompat {
activityType = ACTIVITY_TYPE_UNDEFINED;
}
taskInfo = change.getTaskInfo();
+ allowEnterPip = change.getAllowEnterPip();
mStartLeash = null;
rotationChange = change.getEndRotation() - change.getStartRotation();
windowType = INVALID_WINDOW_TYPE;
+
+ windowConfiguration = change.getTaskInfo() != null
+ ? change.getTaskInfo().configuration.windowConfiguration
+ : new WindowConfiguration();
+ startBounds = change.getStartAbsBounds();
}
public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 653d73020c4f..9ec95a3c992d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -17,14 +17,20 @@
package com.android.systemui.shared.system;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionFilter.CONTAINER_ORDER_TOP;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.content.ComponentName;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.Parcelable;
@@ -36,6 +42,7 @@ import android.view.SurfaceControl;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
import android.window.PictureInPictureSurfaceTransaction;
+import android.window.RemoteTransition;
import android.window.TransitionFilter;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
@@ -57,23 +64,23 @@ import java.util.concurrent.Executor;
public class RemoteTransitionCompat implements Parcelable {
private static final String TAG = "RemoteTransitionCompat";
- @NonNull final IRemoteTransition mTransition;
+ @NonNull final RemoteTransition mTransition;
@Nullable TransitionFilter mFilter = null;
- RemoteTransitionCompat(IRemoteTransition transition) {
+ RemoteTransitionCompat(RemoteTransition transition) {
mTransition = transition;
}
public RemoteTransitionCompat(@NonNull RemoteTransitionRunner runner,
@NonNull Executor executor) {
- mTransition = new IRemoteTransition.Stub() {
+ IRemoteTransition remote = new IRemoteTransition.Stub() {
@Override
public void startAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t,
IRemoteTransitionFinishedCallback finishedCallback) {
final Runnable finishAdapter = () -> {
try {
- finishedCallback.onTransitionFinished(null /* wct */);
+ finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
} catch (RemoteException e) {
Log.e(TAG, "Failed to call transition finished callback", e);
}
@@ -87,7 +94,7 @@ public class RemoteTransitionCompat implements Parcelable {
IRemoteTransitionFinishedCallback finishedCallback) {
final Runnable finishAdapter = () -> {
try {
- finishedCallback.onTransitionFinished(null /* wct */);
+ finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
} catch (RemoteException e) {
Log.e(TAG, "Failed to call transition finished callback", e);
}
@@ -96,12 +103,13 @@ public class RemoteTransitionCompat implements Parcelable {
finishAdapter));
}
};
+ mTransition = new RemoteTransition(remote);
}
/** Constructor specifically for recents animation */
public RemoteTransitionCompat(RecentsAnimationListener recents,
RecentsAnimationControllerCompat controller) {
- mTransition = new IRemoteTransition.Stub() {
+ IRemoteTransition remote = new IRemoteTransition.Stub() {
final RecentsControllerWrap mRecentsSession = new RecentsControllerWrap();
IBinder mToken = null;
@@ -119,13 +127,20 @@ public class RemoteTransitionCompat implements Parcelable {
// This transition is for opening recents, so recents is on-top. We want to draw
// the current going-away task on top of recents, though, so move it to front
WindowContainerToken pausingTask = null;
+ WindowContainerToken pipTask = null;
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
if (change.getMode() == TRANSIT_CLOSE || change.getMode() == TRANSIT_TO_BACK) {
t.setLayer(leashMap.get(change.getLeash()),
info.getChanges().size() * 3 - i);
- if (change.getTaskInfo() != null) {
- pausingTask = change.getTaskInfo().token;
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null) {
+ continue;
+ }
+ pausingTask = taskInfo.token;
+ if (taskInfo.pictureInPictureParams != null
+ && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) {
+ pipTask = taskInfo.token;
}
}
}
@@ -134,8 +149,8 @@ public class RemoteTransitionCompat implements Parcelable {
t.setAlpha(wallpapers[i].leash.mSurfaceControl, 1);
}
t.apply();
- mRecentsSession.setup(controller, info, finishedCallback, pausingTask,
- leashMap);
+ mRecentsSession.setup(controller, info, finishedCallback, pausingTask, pipTask,
+ leashMap, mToken);
recents.onAnimationStart(mRecentsSession, apps, wallpapers, new Rect(0, 0, 0, 0),
new Rect());
}
@@ -147,23 +162,31 @@ public class RemoteTransitionCompat implements Parcelable {
if (!mergeTarget.equals(mToken)) return;
if (!mRecentsSession.merge(info, t, recents)) return;
try {
- finishedCallback.onTransitionFinished(null /* wct */);
+ finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
} catch (RemoteException e) {
Log.e(TAG, "Error merging transition.", e);
}
}
};
+ mTransition = new RemoteTransition(remote);
}
/** Adds a filter check that restricts this remote transition to home open transitions. */
- public void addHomeOpenCheck() {
+ public void addHomeOpenCheck(ComponentName homeActivity) {
if (mFilter == null) {
mFilter = new TransitionFilter();
}
+ // No need to handle the transition that also dismisses keyguard.
+ mFilter.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
mFilter.mRequirements =
- new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
+ new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(),
+ new TransitionFilter.Requirement()};
mFilter.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME;
+ mFilter.mRequirements[0].mTopActivity = homeActivity;
mFilter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+ mFilter.mRequirements[0].mOrder = CONTAINER_ORDER_TOP;
+ mFilter.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD;
+ mFilter.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
}
/**
@@ -175,13 +198,17 @@ public class RemoteTransitionCompat implements Parcelable {
private RecentsAnimationControllerCompat mWrapped = null;
private IRemoteTransitionFinishedCallback mFinishCB = null;
private WindowContainerToken mPausingTask = null;
+ private WindowContainerToken mPipTask = null;
private TransitionInfo mInfo = null;
private SurfaceControl mOpeningLeash = null;
private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
+ private PictureInPictureSurfaceTransaction mPipTransaction = null;
+ private IBinder mTransition = null;
void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info,
IRemoteTransitionFinishedCallback finishCB, WindowContainerToken pausingTask,
- ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
+ WindowContainerToken pipTask, ArrayMap<SurfaceControl, SurfaceControl> leashMap,
+ IBinder transition) {
if (mInfo != null) {
throw new IllegalStateException("Trying to run a new recents animation while"
+ " recents is already active.");
@@ -190,7 +217,9 @@ public class RemoteTransitionCompat implements Parcelable {
mInfo = info;
mFinishCB = finishCB;
mPausingTask = pausingTask;
+ mPipTask = pipTask;
mLeashMap = leashMap;
+ mTransition = transition;
}
@SuppressLint("NewApi")
@@ -247,6 +276,7 @@ public class RemoteTransitionCompat implements Parcelable {
@Override public void setFinishTaskTransaction(int taskId,
PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
+ mPipTransaction = finishTransaction;
if (mWrapped != null) {
mWrapped.setFinishTaskTransaction(taskId, finishTransaction, overlay);
}
@@ -263,10 +293,13 @@ public class RemoteTransitionCompat implements Parcelable {
try {
if (!toHome && mPausingTask != null && mOpeningLeash == null) {
// The gesture went back to opening the app rather than continuing with
- // recents, so end the transition by moving the app back to the top.
+ // recents, so end the transition by moving the app back to the top (and also
+ // re-showing it's task).
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mPausingTask, true /* onTop */);
- mFinishCB.onTransitionFinished(wct);
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.show(mInfo.getChange(mPausingTask).getLeash());
+ mFinishCB.onTransitionFinished(wct, t);
} else {
if (mOpeningLeash != null) {
// TODO: the launcher animation should handle this
@@ -275,7 +308,18 @@ public class RemoteTransitionCompat implements Parcelable {
t.setAlpha(mOpeningLeash, 1.f);
t.apply();
}
- mFinishCB.onTransitionFinished(null /* wct */);
+ if (mPipTask != null && mPipTransaction != null) {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.show(mInfo.getChange(mPipTask).getLeash());
+ PictureInPictureSurfaceTransaction.apply(mPipTransaction,
+ mInfo.getChange(mPipTask).getLeash(), t);
+ mPipTask = null;
+ mPipTransaction = null;
+ mFinishCB.onTransitionFinished(null /* wct */, t);
+ } else {
+ mFinishCB.onTransitionFinished(null /* wct */, null /* sct */);
+ }
+
}
} catch (RemoteException e) {
Log.e("RemoteTransitionCompat", "Failed to call animation finish callback", e);
@@ -298,6 +342,7 @@ public class RemoteTransitionCompat implements Parcelable {
mInfo = null;
mOpeningLeash = null;
mLeashMap = null;
+ mTransition = null;
}
@Override public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
@@ -318,11 +363,28 @@ public class RemoteTransitionCompat implements Parcelable {
@Override public boolean removeTask(int taskId) {
return mWrapped != null ? mWrapped.removeTask(taskId) : false;
}
+
+ /**
+ * @see IRecentsAnimationController#detachNavigationBarFromApp
+ */
+ @Override public void detachNavigationBarFromApp(boolean moveHomeToTop) {
+ try {
+ ActivityTaskManager.getService().detachNavigationBarFromApp(mTransition);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to detach the navigation bar from app", e);
+ }
+ }
+
+ /**
+ * @see IRecentsAnimationController#animateNavigationBarToApp(long)
+ */
+ @Override public void animateNavigationBarToApp(long duration) {
+ }
}
- // Code below generated by codegen v1.0.21.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -334,8 +396,21 @@ public class RemoteTransitionCompat implements Parcelable {
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
+
+ @DataClass.Generated.Member
+ /* package-private */ RemoteTransitionCompat(
+ @NonNull RemoteTransition transition,
+ @Nullable TransitionFilter filter) {
+ this.mTransition = transition;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTransition);
+ this.mFilter = filter;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
@DataClass.Generated.Member
- public @NonNull IRemoteTransition getTransition() {
+ public @NonNull RemoteTransition getTransition() {
return mTransition;
}
@@ -353,7 +428,7 @@ public class RemoteTransitionCompat implements Parcelable {
byte flg = 0;
if (mFilter != null) flg |= 0x2;
dest.writeByte(flg);
- dest.writeStrongInterface(mTransition);
+ dest.writeTypedObject(mTransition, flags);
if (mFilter != null) dest.writeTypedObject(mFilter, flags);
}
@@ -369,7 +444,7 @@ public class RemoteTransitionCompat implements Parcelable {
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
- IRemoteTransition transition = IRemoteTransition.Stub.asInterface(in.readStrongBinder());
+ RemoteTransition transition = (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
TransitionFilter filter = (flg & 0x2) == 0 ? null : (TransitionFilter) in.readTypedObject(TransitionFilter.CREATOR);
this.mTransition = transition;
@@ -401,20 +476,20 @@ public class RemoteTransitionCompat implements Parcelable {
@DataClass.Generated.Member
public static class Builder {
- private @NonNull IRemoteTransition mTransition;
+ private @NonNull RemoteTransition mTransition;
private @Nullable TransitionFilter mFilter;
private long mBuilderFieldsSet = 0L;
public Builder(
- @NonNull IRemoteTransition transition) {
+ @NonNull RemoteTransition transition) {
mTransition = transition;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mTransition);
}
@DataClass.Generated.Member
- public @NonNull Builder setTransition(@NonNull IRemoteTransition value) {
+ public @NonNull Builder setTransition(@NonNull RemoteTransition value) {
checkNotUsed();
mBuilderFieldsSet |= 0x1;
mTransition = value;
@@ -437,8 +512,9 @@ public class RemoteTransitionCompat implements Parcelable {
if ((mBuilderFieldsSet & 0x2) == 0) {
mFilter = null;
}
- RemoteTransitionCompat o = new RemoteTransitionCompat(mTransition);
- o.mFilter = this.mFilter;
+ RemoteTransitionCompat o = new RemoteTransitionCompat(
+ mTransition,
+ mFilter);
return o;
}
@@ -451,10 +527,10 @@ public class RemoteTransitionCompat implements Parcelable {
}
@DataClass.Generated(
- time = 1606862689344L,
- codegenVersion = "1.0.21",
+ time = 1629321609807L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java",
- inputSignatures = "final @android.annotation.NonNull com.android.systemui.shared.system.IRemoteTransition mTransition\n @android.annotation.Nullable android.window.TransitionFilter mFilter\npublic void addHomeOpenCheck()\nclass RemoteTransitionCompat extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass")
+ inputSignatures = "private static final java.lang.String TAG\nfinal @android.annotation.NonNull android.window.RemoteTransition mTransition\n @android.annotation.Nullable android.window.TransitionFilter mFilter\npublic void addHomeOpenCheck(android.content.ComponentName)\nclass RemoteTransitionCompat extends java.lang.Object implements [android.os.Parcelable]\nprivate com.android.systemui.shared.system.RecentsAnimationControllerCompat mWrapped\nprivate android.window.IRemoteTransitionFinishedCallback mFinishCB\nprivate android.window.WindowContainerToken mPausingTask\nprivate android.window.WindowContainerToken mPipTask\nprivate android.window.TransitionInfo mInfo\nprivate android.view.SurfaceControl mOpeningLeash\nprivate android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl> mLeashMap\nprivate android.window.PictureInPictureSurfaceTransaction mPipTransaction\nprivate android.os.IBinder mTransition\n void setup(com.android.systemui.shared.system.RecentsAnimationControllerCompat,android.window.TransitionInfo,android.window.IRemoteTransitionFinishedCallback,android.window.WindowContainerToken,android.window.WindowContainerToken,android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl>,android.os.IBinder)\n @android.annotation.SuppressLint boolean merge(android.window.TransitionInfo,android.view.SurfaceControl.Transaction,com.android.systemui.shared.system.RecentsAnimationListener)\npublic @java.lang.Override com.android.systemui.shared.recents.model.ThumbnailData screenshotTask(int)\npublic @java.lang.Override void setInputConsumerEnabled(boolean)\npublic @java.lang.Override void setAnimationTargetsBehindSystemBars(boolean)\npublic @java.lang.Override void hideCurrentInputMethod()\npublic @java.lang.Override void setFinishTaskTransaction(int,android.window.PictureInPictureSurfaceTransaction,android.view.SurfaceControl)\npublic @java.lang.Override @android.annotation.SuppressLint void finish(boolean,boolean)\npublic @java.lang.Override void setDeferCancelUntilNextTransition(boolean,boolean)\npublic @java.lang.Override void cleanupScreenshot()\npublic @java.lang.Override void setWillFinishToHome(boolean)\npublic @java.lang.Override boolean removeTask(int)\npublic @java.lang.Override void detachNavigationBarFromApp(boolean)\npublic @java.lang.Override void animateNavigationBarToApp(long)\nclass RecentsControllerWrap extends com.android.systemui.shared.system.RecentsAnimationControllerCompat implements []\n@com.android.internal.util.DataClass")
@Deprecated
private void __metadata() {}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index b38270cb8609..85d5de0ea4f6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -27,10 +27,12 @@ import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
+import android.view.InsetsController;
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
+import android.view.animation.Interpolator;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
import com.android.systemui.shared.recents.view.RecentsTransition;
@@ -89,6 +91,9 @@ public class WindowManagerWrapper {
public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT =
InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
+ public static final int ANIMATION_DURATION_RESIZE = InsetsController.ANIMATION_DURATION_RESIZE;
+ public static final Interpolator RESIZE_INTERPOLATOR = InsetsController.RESIZE_INTERPOLATOR;
+
private static final WindowManagerWrapper sInstance = new WindowManagerWrapper();
public static WindowManagerWrapper getInstance() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java b/packages/SystemUI/shared/src/com/android/systemui/statusbar/policy/CallbackController.java
index 047ff75468ed..047ff75468ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/statusbar/policy/CallbackController.java
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
new file mode 100644
index 000000000000..b6be6edc7a10
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+@file:JvmName("UnfoldTransitionFactory")
+
+package com.android.systemui.unfold
+
+import android.content.Context
+import android.hardware.SensorManager
+import android.hardware.devicestate.DeviceStateManager
+import android.os.Handler
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
+import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
+import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
+import com.android.systemui.unfold.updates.DeviceFoldStateProvider
+import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
+import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
+import java.lang.IllegalStateException
+import java.util.concurrent.Executor
+
+fun createUnfoldTransitionProgressProvider(
+ context: Context,
+ config: UnfoldTransitionConfig,
+ screenStatusProvider: ScreenStatusProvider,
+ deviceStateManager: DeviceStateManager,
+ sensorManager: SensorManager,
+ mainHandler: Handler,
+ mainExecutor: Executor
+): UnfoldTransitionProgressProvider {
+
+ if (!config.isEnabled) {
+ throw IllegalStateException("Trying to create " +
+ "UnfoldTransitionProgressProvider when the transition is disabled")
+ }
+
+ val hingeAngleProvider =
+ if (config.isHingeAngleEnabled) {
+ HingeSensorAngleProvider(sensorManager)
+ } else {
+ EmptyHingeAngleProvider()
+ }
+
+ val foldStateProvider = DeviceFoldStateProvider(
+ context,
+ hingeAngleProvider,
+ screenStatusProvider,
+ deviceStateManager,
+ mainExecutor
+ )
+
+ return if (config.isHingeAngleEnabled) {
+ PhysicsBasedUnfoldTransitionProgressProvider(
+ mainHandler,
+ foldStateProvider
+ )
+ } else {
+ FixedTimingTransitionProgressProvider(foldStateProvider)
+ }
+}
+
+fun createConfig(context: Context): UnfoldTransitionConfig =
+ ResourceUnfoldTransitionConfig(context)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
new file mode 100644
index 000000000000..e17f43e64b21
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.systemui.unfold
+
+import android.annotation.FloatRange
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.statusbar.policy.CallbackController
+
+/**
+ * Interface that allows to receive unfold transition progress updates.
+ * It can be used to update view properties based on the current animation progress.
+ * onTransitionProgress callback could be called on each frame.
+ *
+ * Use [createUnfoldTransitionProgressProvider] to create instances of this interface
+ */
+interface UnfoldTransitionProgressProvider : CallbackController<TransitionProgressListener> {
+
+ fun destroy()
+
+ interface TransitionProgressListener {
+ fun onTransitionStarted() {}
+ fun onTransitionFinished() {}
+ fun onTransitionProgress(@FloatRange(from = 0.0, to = 1.0) progress: Float) {}
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
new file mode 100644
index 000000000000..3f027e30b473
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.systemui.unfold.config
+
+import android.content.Context
+import android.os.SystemProperties
+
+internal class ResourceUnfoldTransitionConfig(
+ private val context: Context
+) : UnfoldTransitionConfig {
+
+ override val isEnabled: Boolean
+ get() = readIsEnabledResource() && isPropertyEnabled
+
+ override val isHingeAngleEnabled: Boolean
+ get() = readIsHingeAngleEnabled()
+
+ private val isPropertyEnabled: Boolean
+ get() = SystemProperties.getInt(UNFOLD_TRANSITION_MODE_PROPERTY_NAME,
+ UNFOLD_TRANSITION_PROPERTY_ENABLED) == UNFOLD_TRANSITION_PROPERTY_ENABLED
+
+ private fun readIsEnabledResource(): Boolean = context.resources
+ .getBoolean(com.android.internal.R.bool.config_unfoldTransitionEnabled)
+
+ private fun readIsHingeAngleEnabled(): Boolean = context.resources
+ .getBoolean(com.android.internal.R.bool.config_unfoldTransitionHingeAngle)
+}
+
+/**
+ * Temporary persistent property to control unfold transition mode
+ * See [com.android.unfold.config.AnimationMode]
+ */
+private const val UNFOLD_TRANSITION_MODE_PROPERTY_NAME = "persist.unfold.transition_enabled"
+private const val UNFOLD_TRANSITION_PROPERTY_ENABLED = 1
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
new file mode 100644
index 000000000000..5b187b3486c6
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.systemui.unfold.config
+
+interface UnfoldTransitionConfig {
+ val isEnabled: Boolean
+ val isHingeAngleEnabled: Boolean
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
new file mode 100644
index 000000000000..732882e99038
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.systemui.unfold.progress
+
+import android.animation.Animator
+import android.animation.ObjectAnimator
+import android.util.FloatProperty
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
+import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
+import com.android.systemui.unfold.updates.FoldStateProvider
+import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
+
+/**
+ * Emits animation progress with fixed timing after unfolding
+ */
+internal class FixedTimingTransitionProgressProvider(
+ private val foldStateProvider: FoldStateProvider
+) : UnfoldTransitionProgressProvider, FoldStateProvider.FoldUpdatesListener {
+
+ private val animatorListener = AnimatorListener()
+ private val animator =
+ ObjectAnimator.ofFloat(this, AnimationProgressProperty, 0f, 1f)
+ .apply {
+ duration = TRANSITION_TIME_MILLIS
+ addListener(animatorListener)
+ }
+
+
+ private var transitionProgress: Float = 0.0f
+ set(value) {
+ listeners.forEach { it.onTransitionProgress(value) }
+ field = value
+ }
+
+ private val listeners: MutableList<TransitionProgressListener> = mutableListOf()
+
+ init {
+ foldStateProvider.addCallback(this)
+ foldStateProvider.start()
+ }
+
+ override fun destroy() {
+ animator.cancel()
+ foldStateProvider.removeCallback(this)
+ foldStateProvider.stop()
+ }
+
+ override fun onFoldUpdate(@FoldUpdate update: Int) {
+ when (update) {
+ FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE ->
+ animator.start()
+ FOLD_UPDATE_FINISH_CLOSED ->
+ animator.cancel()
+ }
+ }
+
+ override fun addCallback(listener: TransitionProgressListener) {
+ listeners.add(listener)
+ }
+
+ override fun removeCallback(listener: TransitionProgressListener) {
+ listeners.remove(listener)
+ }
+
+ override fun onHingeAngleUpdate(angle: Float) {
+ }
+
+ private object AnimationProgressProperty :
+ FloatProperty<FixedTimingTransitionProgressProvider>("animation_progress") {
+
+ override fun setValue(
+ provider: FixedTimingTransitionProgressProvider,
+ value: Float
+ ) {
+ provider.transitionProgress = value
+ }
+
+ override fun get(provider: FixedTimingTransitionProgressProvider): Float =
+ provider.transitionProgress
+ }
+
+ private inner class AnimatorListener : Animator.AnimatorListener {
+
+ override fun onAnimationStart(animator: Animator) {
+ listeners.forEach { it.onTransitionStarted() }
+ }
+
+ override fun onAnimationEnd(animator: Animator) {
+ listeners.forEach { it.onTransitionFinished() }
+ }
+
+ override fun onAnimationRepeat(animator: Animator) {
+ }
+
+ override fun onAnimationCancel(animator: Animator) {
+ }
+ }
+
+ private companion object {
+ private const val TRANSITION_TIME_MILLIS = 400L
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
new file mode 100644
index 000000000000..90f5998053b8
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -0,0 +1,218 @@
+/*
+ * 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.systemui.unfold.progress
+
+import android.os.Handler
+import android.util.Log
+import android.util.MathUtils.saturate
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
+import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
+import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
+import com.android.systemui.unfold.updates.FoldStateProvider
+import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
+import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
+
+/**
+ * Maps fold updates to unfold transition progress using DynamicAnimation.
+ *
+ * TODO(b/193793338) Current limitations:
+ * - doesn't handle postures
+ */
+internal class PhysicsBasedUnfoldTransitionProgressProvider(
+ private val handler: Handler,
+ private val foldStateProvider: FoldStateProvider
+) :
+ UnfoldTransitionProgressProvider,
+ FoldUpdatesListener,
+ DynamicAnimation.OnAnimationEndListener {
+
+ private val springAnimation = SpringAnimation(this, AnimationProgressProperty)
+ .apply {
+ addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider)
+ }
+
+ private val timeoutRunnable = TimeoutRunnable()
+
+ private var isTransitionRunning = false
+ private var isAnimatedCancelRunning = false
+
+ private var transitionProgress: Float = 0.0f
+ set(value) {
+ if (isTransitionRunning) {
+ listeners.forEach { it.onTransitionProgress(value) }
+ }
+ field = value
+ }
+
+ private val listeners: MutableList<TransitionProgressListener> = mutableListOf()
+
+ init {
+ foldStateProvider.addCallback(this)
+ foldStateProvider.start()
+ }
+
+ override fun destroy() {
+ foldStateProvider.stop()
+ }
+
+ override fun onHingeAngleUpdate(angle: Float) {
+ if (!isTransitionRunning || isAnimatedCancelRunning) return
+ val progress = saturate(angle / FINAL_HINGE_ANGLE_POSITION)
+ springAnimation.animateToFinalPosition(progress)
+ }
+
+ override fun onFoldUpdate(@FoldUpdate update: Int) {
+ when (update) {
+ FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> {
+ startTransition(startValue = 0f)
+
+ // Stop the animation if the device has already opened by the time when
+ // the display is available as we won't receive the full open event anymore
+ if (foldStateProvider.isFullyOpened) {
+ cancelTransition(endValue = 1f, animate = true)
+ }
+ }
+ FOLD_UPDATE_FINISH_FULL_OPEN -> {
+ // Do not cancel if we haven't started the transition yet.
+ // This could happen when we fully unfolded the device before the screen
+ // became available. In this case we start and immediately cancel the animation
+ // in FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE event handler, so we don't need to
+ // cancel it here.
+ if (isTransitionRunning) {
+ cancelTransition(endValue = 1f, animate = true)
+ }
+ }
+ FOLD_UPDATE_FINISH_CLOSED -> {
+ cancelTransition(endValue = 0f, animate = false)
+ }
+ FOLD_UPDATE_START_CLOSING -> {
+ startTransition(startValue = 1f)
+ }
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "onFoldUpdate = $update")
+ }
+ }
+
+ private fun cancelTransition(endValue: Float, animate: Boolean) {
+ handler.removeCallbacks(timeoutRunnable)
+
+ if (isTransitionRunning && animate) {
+ isAnimatedCancelRunning = true
+ springAnimation.animateToFinalPosition(endValue)
+ } else {
+ transitionProgress = endValue
+ isAnimatedCancelRunning = false
+ isTransitionRunning = false
+ springAnimation.cancel()
+
+ listeners.forEach {
+ it.onTransitionFinished()
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "onTransitionFinished")
+ }
+ }
+ }
+
+ override fun onAnimationEnd(
+ animation: DynamicAnimation<out DynamicAnimation<*>>,
+ canceled: Boolean,
+ value: Float,
+ velocity: Float
+ ) {
+ if (isAnimatedCancelRunning) {
+ cancelTransition(value, animate = false)
+ }
+ }
+
+ private fun onStartTransition() {
+ listeners.forEach {
+ it.onTransitionStarted()
+ }
+ isTransitionRunning = true
+
+ if (DEBUG) {
+ Log.d(TAG, "onTransitionStarted")
+ }
+ }
+
+ private fun startTransition(startValue: Float) {
+ if (!isTransitionRunning) onStartTransition()
+
+ springAnimation.apply {
+ spring = SpringForce().apply {
+ finalPosition = startValue
+ dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
+ stiffness = SPRING_STIFFNESS
+ }
+ minimumVisibleChange = MINIMAL_VISIBLE_CHANGE
+ setStartValue(startValue)
+ setMinValue(0f)
+ setMaxValue(1f)
+ }
+
+ springAnimation.start()
+
+ handler.postDelayed(timeoutRunnable, TRANSITION_TIMEOUT_MILLIS)
+ }
+
+ override fun addCallback(listener: TransitionProgressListener) {
+ listeners.add(listener)
+ }
+
+ override fun removeCallback(listener: TransitionProgressListener) {
+ listeners.remove(listener)
+ }
+
+ private inner class TimeoutRunnable : Runnable {
+
+ override fun run() {
+ cancelTransition(endValue = 1f, animate = true)
+ }
+ }
+
+ private object AnimationProgressProperty :
+ FloatPropertyCompat<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") {
+
+ override fun setValue(
+ provider: PhysicsBasedUnfoldTransitionProgressProvider,
+ value: Float
+ ) {
+ provider.transitionProgress = value
+ }
+
+ override fun getValue(provider: PhysicsBasedUnfoldTransitionProgressProvider): Float =
+ provider.transitionProgress
+ }
+}
+
+private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
+private const val DEBUG = true
+
+private const val TRANSITION_TIMEOUT_MILLIS = 2000L
+private const val SPRING_STIFFNESS = 200.0f
+private const val MINIMAL_VISIBLE_CHANGE = 0.001f
+private const val FINAL_HINGE_ANGLE_POSITION = 165f
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
new file mode 100644
index 000000000000..35e2b30d0a39
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.systemui.unfold.updates
+
+import android.content.Context
+import android.hardware.devicestate.DeviceStateManager
+import androidx.core.util.Consumer
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
+import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
+import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
+import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES
+import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
+import java.util.concurrent.Executor
+
+class DeviceFoldStateProvider(
+ context: Context,
+ private val hingeAngleProvider: HingeAngleProvider,
+ private val screenStatusProvider: ScreenStatusProvider,
+ private val deviceStateManager: DeviceStateManager,
+ private val mainExecutor: Executor
+) : FoldStateProvider {
+
+ private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
+
+ @FoldUpdate
+ private var lastFoldUpdate: Int? = null
+
+ private val hingeAngleListener = HingeAngleListener()
+ private val screenListener = ScreenStatusListener()
+ private val foldStateListener = FoldStateListener(context)
+
+ private var isFolded = false
+ private var isUnfoldHandled = true
+
+ override fun start() {
+ deviceStateManager.registerCallback(
+ mainExecutor,
+ foldStateListener
+ )
+ screenStatusProvider.addCallback(screenListener)
+ hingeAngleProvider.addCallback(hingeAngleListener)
+ }
+
+ override fun stop() {
+ screenStatusProvider.removeCallback(screenListener)
+ deviceStateManager.unregisterCallback(foldStateListener)
+ hingeAngleProvider.removeCallback(hingeAngleListener)
+ hingeAngleProvider.stop()
+ }
+
+ override fun addCallback(listener: FoldUpdatesListener) {
+ outputListeners.add(listener)
+ }
+
+ override fun removeCallback(listener: FoldUpdatesListener) {
+ outputListeners.remove(listener)
+ }
+
+ override val isFullyOpened: Boolean
+ get() = !isFolded && lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN
+
+ private fun onHingeAngle(angle: Float) {
+ when (lastFoldUpdate) {
+ FOLD_UPDATE_FINISH_FULL_OPEN -> {
+ if (FULLY_OPEN_DEGREES - angle > START_CLOSING_THRESHOLD_DEGREES) {
+ lastFoldUpdate = FOLD_UPDATE_START_CLOSING
+ outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_CLOSING) }
+ }
+ }
+ FOLD_UPDATE_START_OPENING -> {
+ if (FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES) {
+ lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN
+ outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }
+ }
+ }
+ FOLD_UPDATE_START_CLOSING -> {
+ if (FULLY_OPEN_DEGREES - angle < START_CLOSING_THRESHOLD_DEGREES) {
+ lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN
+ outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }
+ }
+ }
+ }
+
+ outputListeners.forEach { it.onHingeAngleUpdate(angle) }
+ }
+
+ private inner class FoldStateListener(context: Context) :
+ DeviceStateManager.FoldStateListener(context, { folded: Boolean ->
+ isFolded = folded
+
+ if (folded) {
+ lastFoldUpdate = FOLD_UPDATE_FINISH_CLOSED
+ outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) }
+ hingeAngleProvider.stop()
+ isUnfoldHandled = false
+ } else {
+ lastFoldUpdate = FOLD_UPDATE_START_OPENING
+ outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_OPENING) }
+ hingeAngleProvider.start()
+ }
+ })
+
+ private inner class ScreenStatusListener :
+ ScreenStatusProvider.ScreenListener {
+
+ override fun onScreenTurnedOn() {
+ // Trigger this event only if we are unfolded and this is the first screen
+ // turned on event since unfold started. This prevents running the animation when
+ // turning on the internal display using the power button.
+ // Initially isUnfoldHandled is true so it will be reset to false *only* when we
+ // receive 'folded' event. If SystemUI started when device is already folded it will
+ // still receive 'folded' event on startup.
+ if (!isFolded && !isUnfoldHandled) {
+ outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) }
+ isUnfoldHandled = true
+ }
+ }
+ }
+
+ private inner class HingeAngleListener : Consumer<Float> {
+
+ override fun accept(angle: Float) {
+ onHingeAngle(angle)
+ }
+ }
+}
+
+private const val START_CLOSING_THRESHOLD_DEGREES = 95f
+private const val FULLY_OPEN_THRESHOLD_DEGREES = 15f \ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
new file mode 100644
index 000000000000..643ece353522
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.systemui.unfold.updates
+
+import android.annotation.FloatRange
+import android.annotation.IntDef
+import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
+import com.android.systemui.statusbar.policy.CallbackController
+
+/**
+ * Allows to subscribe to main events related to fold/unfold process such as hinge angle update,
+ * start folding/unfolding, screen availability
+ */
+interface FoldStateProvider : CallbackController<FoldUpdatesListener> {
+ fun start()
+ fun stop()
+
+ val isFullyOpened: Boolean
+
+ interface FoldUpdatesListener {
+ fun onHingeAngleUpdate(@FloatRange(from = 0.0, to = 180.0) angle: Float)
+ fun onFoldUpdate(@FoldUpdate update: Int)
+ }
+
+ @IntDef(prefix = ["FOLD_UPDATE_"], value = [
+ FOLD_UPDATE_START_OPENING,
+ FOLD_UPDATE_HALF_OPEN,
+ FOLD_UPDATE_START_CLOSING,
+ FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE,
+ FOLD_UPDATE_FINISH_HALF_OPEN,
+ FOLD_UPDATE_FINISH_FULL_OPEN,
+ FOLD_UPDATE_FINISH_CLOSED
+ ])
+ @Retention(AnnotationRetention.SOURCE)
+ annotation class FoldUpdate
+}
+
+const val FOLD_UPDATE_START_OPENING = 0
+const val FOLD_UPDATE_HALF_OPEN = 1
+const val FOLD_UPDATE_START_CLOSING = 2
+const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 3
+const val FOLD_UPDATE_FINISH_HALF_OPEN = 4
+const val FOLD_UPDATE_FINISH_FULL_OPEN = 5
+const val FOLD_UPDATE_FINISH_CLOSED = 6
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
new file mode 100644
index 000000000000..9b58b1fcad46
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
@@ -0,0 +1,17 @@
+package com.android.systemui.unfold.updates.hinge
+
+import androidx.core.util.Consumer
+
+internal class EmptyHingeAngleProvider : HingeAngleProvider {
+ override fun start() {
+ }
+
+ override fun stop() {
+ }
+
+ override fun removeCallback(listener: Consumer<Float>) {
+ }
+
+ override fun addCallback(listener: Consumer<Float>) {
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt
new file mode 100644
index 000000000000..6f524560de99
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt
@@ -0,0 +1,18 @@
+package com.android.systemui.unfold.updates.hinge
+
+import androidx.core.util.Consumer
+import com.android.systemui.statusbar.policy.CallbackController
+
+/**
+ * Emits device hinge angle values (angle between two integral parts of the device).
+ * The hinge angle could be from 0 to 360 degrees inclusive.
+ * For foldable devices usually 0 corresponds to fully closed (folded) state and
+ * 180 degrees corresponds to fully open (flat) state
+ */
+interface HingeAngleProvider : CallbackController<Consumer<Float>> {
+ fun start()
+ fun stop()
+}
+
+const val FULLY_OPEN_DEGREES = 180f
+const val FULLY_CLOSED_DEGREES = 0f
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
new file mode 100644
index 000000000000..a42ebef04de1
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
@@ -0,0 +1,42 @@
+package com.android.systemui.unfold.updates.hinge
+
+import android.hardware.Sensor
+import android.hardware.SensorEvent
+import android.hardware.SensorEventListener
+import android.hardware.SensorManager
+import androidx.core.util.Consumer
+
+internal class HingeSensorAngleProvider(
+ private val sensorManager: SensorManager
+) : HingeAngleProvider {
+
+ private val sensorListener = HingeAngleSensorListener()
+ private val listeners: MutableList<Consumer<Float>> = arrayListOf()
+
+ override fun start() {
+ val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE)
+ sensorManager.registerListener(sensorListener, sensor, SensorManager.SENSOR_DELAY_FASTEST)
+ }
+
+ override fun stop() {
+ sensorManager.unregisterListener(sensorListener)
+ }
+
+ override fun removeCallback(listener: Consumer<Float>) {
+ listeners.remove(listener)
+ }
+
+ override fun addCallback(listener: Consumer<Float>) {
+ listeners.add(listener)
+ }
+
+ private inner class HingeAngleSensorListener : SensorEventListener {
+
+ override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
+ }
+
+ override fun onSensorChanged(event: SensorEvent) {
+ listeners.forEach { it.accept(event.values[0]) }
+ }
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
new file mode 100644
index 000000000000..1eec8033ac5d
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.systemui.unfold.updates.screen
+
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
+import com.android.systemui.statusbar.policy.CallbackController
+
+interface ScreenStatusProvider : CallbackController<ScreenListener> {
+
+ interface ScreenListener {
+ /**
+ * Called when the screen is on and ready (windows are drawn and screen blocker is removed)
+ */
+ fun onScreenTurnedOn()
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
new file mode 100644
index 000000000000..e072d41e4eee
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
@@ -0,0 +1,73 @@
+package com.android.systemui.unfold.util
+
+import android.content.Context
+import android.os.RemoteException
+import android.util.Log
+import android.view.IRotationWatcher
+import android.view.IWindowManager
+import android.view.Surface
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+
+/**
+ * [UnfoldTransitionProgressProvider] that emits transition progress only when the display has
+ * default rotation or 180 degrees opposite rotation (ROTATION_0 or ROTATION_180).
+ * It could be helpful to run the animation only when the display's rotation is perpendicular
+ * to the fold.
+ */
+class NaturalRotationUnfoldProgressProvider(
+ private val context: Context,
+ private val windowManagerInterface: IWindowManager,
+ unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider
+) : UnfoldTransitionProgressProvider {
+
+ private val scopedUnfoldTransitionProgressProvider =
+ ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider)
+ private val rotationWatcher = RotationWatcher()
+
+ private var isNaturalRotation: Boolean = false
+
+ fun init() {
+ try {
+ windowManagerInterface.watchRotation(rotationWatcher, context.display.displayId)
+ } catch (e: RemoteException) {
+ throw e.rethrowFromSystemServer()
+ }
+
+ onRotationChanged(context.display.rotation)
+ }
+
+ private fun onRotationChanged(rotation: Int) {
+ val isNewRotationNatural = rotation == Surface.ROTATION_0 ||
+ rotation == Surface.ROTATION_180
+
+ if (isNaturalRotation != isNewRotationNatural) {
+ isNaturalRotation = isNewRotationNatural
+ scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(isNewRotationNatural)
+ }
+ }
+
+ override fun destroy() {
+ try {
+ windowManagerInterface.removeRotationWatcher(rotationWatcher)
+ } catch (e: RemoteException) {
+ e.rethrowFromSystemServer()
+ }
+
+ scopedUnfoldTransitionProgressProvider.destroy()
+ }
+
+ override fun addCallback(listener: TransitionProgressListener) {
+ scopedUnfoldTransitionProgressProvider.addCallback(listener)
+ }
+
+ override fun removeCallback(listener: TransitionProgressListener) {
+ scopedUnfoldTransitionProgressProvider.removeCallback(listener)
+ }
+
+ private inner class RotationWatcher : IRotationWatcher.Stub() {
+ override fun onRotationChanged(rotation: Int) {
+ this@NaturalRotationUnfoldProgressProvider.onRotationChanged(rotation)
+ }
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java
new file mode 100644
index 000000000000..543232da303e
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.java
@@ -0,0 +1,142 @@
+/*
+ * 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.systemui.unfold.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages progress listeners that can have smaller lifespan than the unfold animation.
+ * Allows to limit getting transition updates to only when
+ * {@link ScopedUnfoldTransitionProgressProvider#setReadyToHandleTransition} is called
+ * with readyToHandleTransition = true
+ *
+ * If the transition has already started by the moment when the clients are ready to play
+ * the transition then it will report transition started callback and current animation progress.
+ */
+public final class ScopedUnfoldTransitionProgressProvider implements
+ UnfoldTransitionProgressProvider, TransitionProgressListener {
+
+ private static final float PROGRESS_UNSET = -1f;
+
+ @Nullable
+ private UnfoldTransitionProgressProvider mSource;
+
+ private final List<TransitionProgressListener> mListeners = new ArrayList<>();
+
+ private boolean mIsReadyToHandleTransition;
+ private boolean mIsTransitionRunning;
+ private float mLastTransitionProgress = PROGRESS_UNSET;
+
+ public ScopedUnfoldTransitionProgressProvider() {
+ this(null);
+ }
+
+ public ScopedUnfoldTransitionProgressProvider(
+ @Nullable UnfoldTransitionProgressProvider source) {
+ setSourceProvider(source);
+ }
+
+ /**
+ * Sets the source for the unfold transition progress updates,
+ * it replaces current provider if it is already set
+ * @param provider transition provider that emits transition progress updates
+ */
+ public void setSourceProvider(@Nullable UnfoldTransitionProgressProvider provider) {
+ if (mSource != null) {
+ mSource.removeCallback(this);
+ }
+
+ if (provider != null) {
+ mSource = provider;
+ mSource.addCallback(this);
+ } else {
+ mSource = null;
+ }
+ }
+
+ /**
+ * Allows to notify this provide whether the listeners can play the transition or not.
+ * Call this method with readyToHandleTransition = true when all listeners
+ * are ready to consume the transition progress events.
+ * Call it with readyToHandleTransition = false when listeners can't process the events.
+ */
+ public void setReadyToHandleTransition(boolean isReadyToHandleTransition) {
+ if (mIsTransitionRunning) {
+ if (isReadyToHandleTransition) {
+ mListeners.forEach(TransitionProgressListener::onTransitionStarted);
+
+ if (mLastTransitionProgress != PROGRESS_UNSET) {
+ mListeners.forEach(listener ->
+ listener.onTransitionProgress(mLastTransitionProgress));
+ }
+ } else {
+ mIsTransitionRunning = false;
+ mListeners.forEach(TransitionProgressListener::onTransitionFinished);
+ }
+ }
+
+ mIsReadyToHandleTransition = isReadyToHandleTransition;
+ }
+
+ @Override
+ public void addCallback(@NonNull TransitionProgressListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeCallback(@NonNull TransitionProgressListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public void destroy() {
+ mSource.removeCallback(this);
+ }
+
+ @Override
+ public void onTransitionStarted() {
+ this.mIsTransitionRunning = true;
+ if (mIsReadyToHandleTransition) {
+ mListeners.forEach(TransitionProgressListener::onTransitionStarted);
+ }
+ }
+
+ @Override
+ public void onTransitionProgress(float progress) {
+ if (mIsReadyToHandleTransition) {
+ mListeners.forEach(listener -> listener.onTransitionProgress(progress));
+ }
+
+ mLastTransitionProgress = progress;
+ }
+
+ @Override
+ public void onTransitionFinished() {
+ if (mIsReadyToHandleTransition) {
+ mListeners.forEach(TransitionProgressListener::onTransitionFinished);
+ }
+
+ mIsTransitionRunning = false;
+ mLastTransitionProgress = PROGRESS_UNSET;
+ }
+}
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
new file mode 100644
index 000000000000..1eeb51601682
--- /dev/null
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
@@ -0,0 +1,215 @@
+/*
+ * 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.systemui.flags;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+/**
+ * Concrete implementation of the a Flag manager that returns default values for debug builds
+ *
+ * Flags can be set (or unset) via the following adb command:
+ *
+ * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>]
+ *
+ * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
+ */
+@SysUISingleton
+public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
+ private static final String TAG = "SysUIFlags";
+
+ private static final String SYSPROP_PREFIX = "persist.systemui.flag_";
+ private static final String FIELD_TYPE = "type";
+ private static final String FIELD_ID = "id";
+ private static final String FIELD_VALUE = "value";
+ private static final String TYPE_BOOLEAN = "boolean";
+ private static final String ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG";
+ private static final String FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS";
+ private final SystemPropertiesHelper mSystemPropertiesHelper;
+
+ private final Map<Integer, Boolean> mBooleanFlagCache = new HashMap<>();
+
+ @Inject
+ public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper, Context context,
+ DumpManager dumpManager) {
+ mSystemPropertiesHelper = systemPropertiesHelper;
+
+ IntentFilter filter = new IntentFilter(ACTION_SET_FLAG);
+ context.registerReceiver(mReceiver, filter, FLAGS_PERMISSION, null);
+ dumpManager.registerDumpable(TAG, this);
+ }
+
+ /** Return a {@link BooleanFlag}'s value. */
+ @Override
+ public boolean isEnabled(int id, boolean defaultValue) {
+ if (!mBooleanFlagCache.containsKey(id)) {
+ Boolean result = isEnabledInternal(id);
+ mBooleanFlagCache.put(id, result == null ? defaultValue : result);
+ }
+
+ return mBooleanFlagCache.get(id);
+ }
+
+ /** Returns the stored value or null if not set. */
+ private Boolean isEnabledInternal(int id) {
+ String data = mSystemPropertiesHelper.get(keyToSysPropKey(id));
+ if (data.isEmpty()) {
+ return null;
+ }
+ JSONObject json;
+ try {
+ json = new JSONObject(data);
+ if (!assertType(json, TYPE_BOOLEAN)) {
+ return null;
+ }
+
+ return json.getBoolean(FIELD_VALUE);
+ } catch (JSONException e) {
+ eraseInternal(id); // Don't restart SystemUI in this case.
+ }
+ return null;
+ }
+
+ /** Set whether a given {@link BooleanFlag} is enabled or not. */
+ @Override
+ public void setEnabled(int id, boolean value) {
+ Boolean currentValue = isEnabledInternal(id);
+ if (currentValue != null && currentValue == value) {
+ return;
+ }
+
+ JSONObject json = new JSONObject();
+ try {
+ json.put(FIELD_TYPE, TYPE_BOOLEAN);
+ json.put(FIELD_VALUE, value);
+ mSystemPropertiesHelper.set(keyToSysPropKey(id), json.toString());
+ Log.i(TAG, "Set id " + id + " to " + value);
+ restartSystemUI();
+ } catch (JSONException e) {
+ // no-op
+ }
+ }
+
+ /** Erase a flag's overridden value if there is one. */
+ public void eraseFlag(int id) {
+ eraseInternal(id);
+ restartSystemUI();
+ }
+
+ /** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */
+ private void eraseInternal(int id) {
+ // We can't actually "erase" things from sysprops, but we can set them to empty!
+ mSystemPropertiesHelper.set(keyToSysPropKey(id), "");
+ Log.i(TAG, "Erase id " + id);
+ }
+
+ @Override
+ public void addListener(Listener run) {}
+
+ @Override
+ public void removeListener(Listener run) {}
+
+ private void restartSystemUI() {
+ Log.i(TAG, "Restarting SystemUI");
+ // SysUI starts back when up exited. Is there a better way to do this?
+ System.exit(0);
+ }
+
+ private static String keyToSysPropKey(int key) {
+ return SYSPROP_PREFIX + key;
+ }
+
+ private static boolean assertType(JSONObject json, String type) {
+ try {
+ return json.getString(FIELD_TYPE).equals(TYPE_BOOLEAN);
+ } catch (JSONException e) {
+ return false;
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+
+ if (ACTION_SET_FLAG.equals(action)) {
+ handleSetFlag(intent.getExtras());
+ }
+ }
+
+ private void handleSetFlag(Bundle extras) {
+ int id = extras.getInt(FIELD_ID);
+ if (id <= 0) {
+ Log.w(TAG, "ID not set or less than or equal to 0: " + id);
+ return;
+ }
+
+ Map<Integer, Flag<?>> flagMap = Flags.collectFlags();
+ if (!flagMap.containsKey(id)) {
+ Log.w(TAG, "Tried to set unknown id: " + id);
+ return;
+ }
+ Flag<?> flag = flagMap.get(id);
+
+ if (!extras.containsKey(FIELD_VALUE)) {
+ eraseFlag(id);
+ return;
+ }
+
+ if (flag instanceof BooleanFlag) {
+ setEnabled(id, extras.getBoolean(FIELD_VALUE));
+ }
+ }
+ };
+
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("can override: true");
+ ArrayList<String> flagStrings = new ArrayList<>(mBooleanFlagCache.size());
+ for (Map.Entry<Integer, Boolean> entry : mBooleanFlagCache.entrySet()) {
+ flagStrings.add(" sysui_flag_" + entry.getKey() + ": " + entry.getValue());
+ }
+ flagStrings.sort(String.CASE_INSENSITIVE_ORDER);
+ for (String flagString : flagStrings) {
+ pw.println(flagString);
+ }
+ }
+}
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java
new file mode 100644
index 000000000000..e501a073fe18
--- /dev/null
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java
@@ -0,0 +1,64 @@
+/*
+ * 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.systemui.flags;
+
+import android.content.Context;
+import android.util.SparseBooleanArray;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+
+/**
+ * Default implementation of the a Flag manager that returns default values for release builds
+ *
+ * There's a version of this file in src-debug which allows overriding, and has documentation about
+ * how to set flags.
+ */
+@SysUISingleton
+public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
+ SparseBooleanArray mAccessedFlags = new SparseBooleanArray();
+ @Inject
+ public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper, Context context,
+ DumpManager dumpManager) {
+ dumpManager.registerDumpable("SysUIFlags", this);
+ }
+ @Override
+ public boolean isEnabled(int key, boolean defaultValue) {
+ mAccessedFlags.append(key, defaultValue);
+ return defaultValue;
+ }
+ @Override
+ public void setEnabled(int key, boolean value) {}
+
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("can override: false");
+ int size = mAccessedFlags.size();
+ for (int i = 0; i < size; i++) {
+ pw.println(" sysui_flag_" + mAccessedFlags.keyAt(i)
+ + ": " + mAccessedFlags.valueAt(i));
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index a5b25097a56e..3d4e896178f6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -11,9 +11,13 @@ import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnPreDrawListener;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
+import androidx.annotation.IntDef;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.colorextraction.ColorExtractor;
import com.android.keyguard.dagger.KeyguardStatusViewScope;
import com.android.systemui.R;
@@ -22,6 +26,8 @@ import com.android.systemui.plugins.ClockPlugin;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.TimeZone;
@@ -35,7 +41,14 @@ public class KeyguardClockSwitch extends RelativeLayout {
private static final long CLOCK_OUT_MILLIS = 150;
private static final long CLOCK_IN_MILLIS = 200;
- private static final long SMARTSPACE_MOVE_MILLIS = 350;
+ private static final long STATUS_AREA_MOVE_MILLIS = 350;
+
+ @IntDef({LARGE, SMALL})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ClockSize { }
+
+ public static final int LARGE = 0;
+ public static final int SMALL = 1;
/**
* Optional/alternative clock injected via plugin.
@@ -50,13 +63,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
private AnimatableClockView mClockView;
private AnimatableClockView mLargeClockView;
- /**
- * Status area (date and other stuff) shown below the clock. Plugin can decide whether or not to
- * show it below the alternate clock.
- */
- private View mKeyguardStatusArea;
- /** Mutually exclusive with mKeyguardStatusArea */
- private View mSmartspaceView;
+ private View mStatusArea;
private int mSmartspaceTopOffset;
/**
@@ -65,14 +72,14 @@ public class KeyguardClockSwitch extends RelativeLayout {
private float mDarkAmount;
/**
- * Boolean value indicating if notifications are visible on lock screen. Use null to signify
- * it is uninitialized.
+ * Indicates which clock is currently displayed - should be one of {@link ClockSize}.
+ * Use null to signify it is uninitialized.
*/
- private Boolean mHasVisibleNotifications = null;
+ @ClockSize private Integer mDisplayedClockSize = null;
- private AnimatorSet mClockInAnim = null;
- private AnimatorSet mClockOutAnim = null;
- private ObjectAnimator mSmartspaceAnim = null;
+ @VisibleForTesting AnimatorSet mClockInAnim = null;
+ @VisibleForTesting AnimatorSet mClockOutAnim = null;
+ private ObjectAnimator mStatusAreaAnim = null;
/**
* If the Keyguard Slice has a header (big center-aligned text.)
@@ -81,6 +88,8 @@ public class KeyguardClockSwitch extends RelativeLayout {
private int[] mColorPalette;
private int mClockSwitchYAmount;
+ @VisibleForTesting boolean mChildrenAreLaidOut = false;
+ private OnPreDrawListener mPreDrawListener;
public KeyguardClockSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -117,7 +126,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
mClockView = findViewById(R.id.animatable_clock_view);
mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
mLargeClockView = findViewById(R.id.animatable_clock_view_large);
- mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
+ mStatusArea = findViewById(R.id.keyguard_status_area);
onDensityOrFontScaleChanged();
}
@@ -186,22 +195,22 @@ public class KeyguardClockSwitch extends RelativeLayout {
private void animateClockChange(boolean useLargeClock) {
if (mClockInAnim != null) mClockInAnim.cancel();
if (mClockOutAnim != null) mClockOutAnim.cancel();
- if (mSmartspaceAnim != null) mSmartspaceAnim.cancel();
+ if (mStatusAreaAnim != null) mStatusAreaAnim.cancel();
View in, out;
int direction = 1;
- float smartspaceYTranslation;
+ float statusAreaYTranslation;
if (useLargeClock) {
out = mClockFrame;
in = mLargeClockFrame;
if (indexOfChild(in) == -1) addView(in);
direction = -1;
- smartspaceYTranslation = mSmartspaceView == null ? 0
- : mClockFrame.getTop() - mSmartspaceView.getTop() + mSmartspaceTopOffset;
+ statusAreaYTranslation = mClockFrame.getTop() - mStatusArea.getTop()
+ + mSmartspaceTopOffset;
} else {
in = mClockFrame;
out = mLargeClockFrame;
- smartspaceYTranslation = 0f;
+ statusAreaYTranslation = 0f;
// Must remove in order for notifications to appear in the proper place
removeView(out);
@@ -237,18 +246,16 @@ public class KeyguardClockSwitch extends RelativeLayout {
mClockInAnim.start();
mClockOutAnim.start();
- if (mSmartspaceView != null) {
- mSmartspaceAnim = ObjectAnimator.ofFloat(mSmartspaceView, View.TRANSLATION_Y,
- smartspaceYTranslation);
- mSmartspaceAnim.setDuration(SMARTSPACE_MOVE_MILLIS);
- mSmartspaceAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mSmartspaceAnim.addListener(new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animation) {
- mSmartspaceAnim = null;
- }
- });
- mSmartspaceAnim.start();
- }
+ mStatusAreaAnim = ObjectAnimator.ofFloat(mStatusArea, View.TRANSLATION_Y,
+ statusAreaYTranslation);
+ mStatusAreaAnim.setDuration(STATUS_AREA_MOVE_MILLIS);
+ mStatusAreaAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mStatusAreaAnim.addListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animation) {
+ mStatusAreaAnim = null;
+ }
+ });
+ mStatusAreaAnim.start();
}
/**
@@ -264,19 +271,43 @@ public class KeyguardClockSwitch extends RelativeLayout {
}
/**
- * Based upon whether notifications are showing or not, display/hide the large clock and
- * the smaller version.
+ * Display the desired clock and hide the other one
+ *
+ * @return true if desired clock appeared and false if it was already visible
*/
- boolean willSwitchToLargeClock(boolean hasVisibleNotifications) {
- if (mHasVisibleNotifications != null
- && hasVisibleNotifications == mHasVisibleNotifications) {
+ boolean switchToClock(@ClockSize int clockSize) {
+ if (mDisplayedClockSize != null && clockSize == mDisplayedClockSize) {
return false;
}
- boolean useLargeClock = !hasVisibleNotifications;
- animateClockChange(useLargeClock);
- mHasVisibleNotifications = hasVisibleNotifications;
- return useLargeClock;
+ // let's make sure clock is changed only after all views were laid out so we can
+ // translate them properly
+ if (mChildrenAreLaidOut) {
+ animateClockChange(clockSize == LARGE);
+ mDisplayedClockSize = clockSize;
+ } else if (mPreDrawListener == null) {
+ mPreDrawListener = () -> {
+ switchToClock(clockSize);
+ getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener);
+ mPreDrawListener = null;
+ return true;
+ };
+ getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
+ }
+ return true;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ mChildrenAreLaidOut = true;
+ }
+
+ void onViewDetached() {
+ if (mPreDrawListener != null) {
+ getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener);
+ mPreDrawListener = null;
+ }
}
public Paint getPaint() {
@@ -320,10 +351,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
}
}
- void setSmartspaceView(View smartspaceView) {
- mSmartspaceView = smartspaceView;
- }
-
void updateColors(ColorExtractor.GradientColors colors) {
mSupportsDarkText = colors.supportsDarkText();
mColorPalette = colors.getColorPalette();
@@ -337,8 +364,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
pw.println(" mClockPlugin: " + mClockPlugin);
pw.println(" mClockFrame: " + mClockFrame);
pw.println(" mLargeClockFrame: " + mLargeClockFrame);
- pw.println(" mKeyguardStatusArea: " + mKeyguardStatusArea);
- pw.println(" mSmartspaceView: " + mSmartspaceView);
+ pw.println(" mStatusArea: " + mStatusArea);
pw.println(" mDarkAmount: " + mDarkAmount);
pw.println(" mSupportsDarkText: " + mSupportsDarkText);
pw.println(" mColorPalette: " + Arrays.toString(mColorPalette));
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 7a01b4e8c971..1931c0a1645c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -19,11 +19,15 @@ package com.android.keyguard;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static com.android.keyguard.KeyguardClockSwitch.LARGE;
+
import android.app.WallpaperManager;
import android.content.res.Resources;
import android.text.TextUtils;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.android.internal.colorextraction.ColorExtractor;
@@ -97,7 +101,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
- // If set, will replace keyguard_status_area
+ private ViewGroup mStatusArea;
+ // If set will replace keyguard_slice_view
private View mSmartspaceView;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@@ -189,8 +194,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
if (mOnlyClock) {
- View ksa = mView.findViewById(R.id.keyguard_status_area);
- ksa.setVisibility(View.GONE);
+ View ksv = mView.findViewById(R.id.keyguard_slice_view);
+ ksv.setVisibility(View.GONE);
View nic = mView.findViewById(
R.id.left_aligned_notification_icon_container);
@@ -199,19 +204,18 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
updateAodIcons();
+ mStatusArea = mView.findViewById(R.id.keyguard_status_area);
+
if (mSmartspaceController.isEnabled()) {
mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
+ View ksv = mView.findViewById(R.id.keyguard_slice_view);
+ int ksvIndex = mStatusArea.indexOfChild(ksv);
+ ksv.setVisibility(View.GONE);
- View ksa = mView.findViewById(R.id.keyguard_status_area);
- int ksaIndex = mView.indexOfChild(ksa);
- ksa.setVisibility(View.GONE);
-
- // Place smartspace view below normal clock...
- RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
MATCH_PARENT, WRAP_CONTENT);
- lp.addRule(RelativeLayout.BELOW, R.id.lockscreen_clock_view);
- mView.addView(mSmartspaceView, ksaIndex, lp);
+ mStatusArea.addView(mSmartspaceView, ksvIndex, lp);
int startPadding = getContext().getResources()
.getDimensionPixelSize(R.dimen.below_clock_padding_start);
int endPadding = getContext().getResources()
@@ -219,14 +223,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);
updateClockLayout();
-
- View nic = mView.findViewById(
- R.id.left_aligned_notification_icon_container);
- lp = (RelativeLayout.LayoutParams) nic.getLayoutParams();
- lp.addRule(RelativeLayout.BELOW, mSmartspaceView.getId());
- nic.setLayoutParams(lp);
-
- mView.setSmartspaceView(mSmartspaceView);
mSmartspaceTransitionController.setLockscreenSmartspace(mSmartspaceView);
}
}
@@ -242,18 +238,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
mColorExtractor.removeOnColorsChangedListener(mColorsListener);
mView.setClockPlugin(null, mStatusBarStateController.getState());
-
- mSmartspaceController.disconnect();
-
- // TODO: This is an unfortunate necessity since smartspace plugin retains a single instance
- // of the smartspace view -- if we don't remove the view, it can't be reused by a later
- // instance of this class. In order to fix this, we need to modify the plugin so that
- // (a) we get a new view each time and (b) we can properly clean up an old view by making
- // it unregister itself as a plugin listener.
- if (mSmartspaceView != null) {
- mView.removeView(mSmartspaceView);
- mSmartspaceView = null;
- }
+ mView.onViewDetached();
}
/**
@@ -281,10 +266,12 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
/**
- * Set whether or not the lock screen is showing notifications.
+ * Set which clock should be displayed on the keyguard. The other one will be automatically
+ * hidden.
*/
- public void setHasVisibleNotifications(boolean hasVisibleNotifications) {
- if (mView.willSwitchToLargeClock(hasVisibleNotifications)) {
+ public void displayClock(@KeyguardClockSwitch.ClockSize int clockSize) {
+ boolean appeared = mView.switchToClock(clockSize);
+ if (appeared && clockSize == LARGE) {
mLargeClockViewController.animateAppear();
}
}
@@ -334,8 +321,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y,
scale, props, animate);
- if (mSmartspaceView != null) {
- PropertyAnimator.setProperty(mSmartspaceView, AnimatableProperty.TRANSLATION_X,
+ if (mStatusArea != null) {
+ PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X,
x, props, animate);
// If we're unlocking with the SmartSpace shared element transition, let the controller
@@ -346,7 +333,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
mKeyguardSliceViewController.updatePosition(x, props, animate);
- mNotificationIconAreaController.updatePosition(x, props, animate);
}
/** Sets an alpha value on every child view except for the smartspace. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 97d3a5a4cd18..75425e1e6ca3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -28,6 +28,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -166,6 +167,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
private final TelephonyManager mTelephonyManager;
private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
private final FalsingCollector mFalsingCollector;
+ private final DevicePostureController mDevicePostureController;
@Inject
public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -175,7 +177,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor,
@Main Resources resources, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
- EmergencyButtonController.Factory emergencyButtonControllerFactory) {
+ EmergencyButtonController.Factory emergencyButtonControllerFactory,
+ DevicePostureController devicePostureController) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -187,6 +190,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
mTelephonyManager = telephonyManager;
mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
mFalsingCollector = falsingCollector;
+ mDevicePostureController = devicePostureController;
}
/** Create a new {@link KeyguardInputViewController}. */
@@ -200,7 +204,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
return new KeyguardPatternViewController((KeyguardPatternView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mLatencyTracker, mFalsingCollector,
- emergencyButtonController, mMessageAreaControllerFactory);
+ emergencyButtonController, mMessageAreaControllerFactory,
+ mDevicePostureController);
} else if (keyguardInputView instanceof KeyguardPasswordView) {
return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
@@ -212,7 +217,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
- mLiftToActivateListener, emergencyButtonController, mFalsingCollector);
+ mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
+ mDevicePostureController);
} else if (keyguardInputView instanceof KeyguardSimPinView) {
return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index 62411dbff5fd..099dd5d82a10 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -27,8 +27,10 @@ import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.TextView;
+import com.android.internal.policy.SystemBarUtils;
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -55,6 +57,8 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp
private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR);
private boolean mBouncerVisible;
private boolean mAltBouncerShowing;
+ private ViewGroup mContainer;
+ private int mTopMargin;
public KeyguardMessageArea(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -65,6 +69,24 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mContainer = getRootView().findViewById(R.id.keyguard_message_area_container);
+ }
+
+ void onConfigChanged() {
+ final int newTopMargin = SystemBarUtils.getStatusBarHeight(getContext());
+ if (mTopMargin == newTopMargin) {
+ return;
+ }
+ mTopMargin = newTopMargin;
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) mContainer.getLayoutParams();
+ lp.topMargin = mTopMargin;
+ mContainer.setLayoutParams(lp);
+ }
+
+ @Override
public void setNextMessageColor(ColorStateList colorState) {
mNextMessageColorState = colorState;
}
@@ -153,6 +175,10 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp
colorState = mNextMessageColorState;
mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR);
}
+ if (mAltBouncerShowing) {
+ // alt bouncer has a black scrim, so always show the text in white
+ colorState = ColorStateList.valueOf(Color.WHITE);
+ }
setTextColor(colorState);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index 51ded3fcafdf..05318bb0df78 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -17,6 +17,7 @@
package com.android.keyguard;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -48,6 +49,11 @@ public class KeyguardMessageAreaController extends ViewController<KeyguardMessag
private ConfigurationListener mConfigurationListener = new ConfigurationListener() {
@Override
+ public void onConfigChanged(Configuration newConfig) {
+ mView.onConfigChanged();
+ }
+
+ @Override
public void onThemeChanged() {
mView.onThemeChanged();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 8fc4240e1054..1efda7edee2f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -16,20 +16,23 @@
package com.android.keyguard;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
+
import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.View;
-import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
-import android.widget.LinearLayout;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils;
import com.android.systemui.R;
-
-import java.util.List;
+import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
/**
* Displays a PIN pad for unlocking.
@@ -39,13 +42,10 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
private final AppearAnimationUtils mAppearAnimationUtils;
private final DisappearAnimationUtils mDisappearAnimationUtils;
private final DisappearAnimationUtils mDisappearAnimationUtilsLocked;
- private ViewGroup mContainer;
- private ViewGroup mRow0;
- private ViewGroup mRow1;
- private ViewGroup mRow2;
- private ViewGroup mRow3;
+ private ConstraintLayout mContainer;
private int mDisappearYTranslation;
private View[][] mViews;
+ @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
public KeyguardPINView(Context context) {
this(context, null);
@@ -72,6 +72,11 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
updateMargins();
}
+ void onDevicePostureChanged(@DevicePostureInt int posture) {
+ mLastDevicePosture = posture;
+ updateMargins();
+ }
+
@Override
protected void resetState() {
}
@@ -82,30 +87,48 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
}
private void updateMargins() {
+ // Re-apply everything to the keys...
int bottomMargin = mContext.getResources().getDimensionPixelSize(
- R.dimen.num_pad_row_margin_bottom);
-
- for (ViewGroup vg : List.of(mRow1, mRow2, mRow3)) {
- ((LinearLayout.LayoutParams) vg.getLayoutParams()).setMargins(0, 0, 0, bottomMargin);
- }
-
- bottomMargin = mContext.getResources().getDimensionPixelSize(
R.dimen.num_pad_entry_row_margin_bottom);
- ((LinearLayout.LayoutParams) mRow0.getLayoutParams()).setMargins(0, 0, 0, bottomMargin);
-
- if (mEcaView != null) {
- int ecaTopMargin = mContext.getResources().getDimensionPixelSize(
- R.dimen.keyguard_eca_top_margin);
- int ecaBottomMargin = mContext.getResources().getDimensionPixelSize(
- R.dimen.keyguard_eca_bottom_margin);
- ((LinearLayout.LayoutParams) mEcaView.getLayoutParams()).setMargins(0, ecaTopMargin,
- 0, ecaBottomMargin);
+ int rightMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.num_pad_key_margin_end);
+ String ratio = mContext.getResources().getString(R.string.num_pad_key_ratio);
+
+ // mView contains all Views that make up the PIN pad; row0 = the entry test field, then
+ // rows 1-4 contain the buttons. Iterate over all views that make up the buttons in the pad,
+ // and re-set all the margins.
+ for (int row = 1; row < 5; row++) {
+ for (int column = 0; column < 3; column++) {
+ View key = mViews[row][column];
+
+ ConstraintLayout.LayoutParams lp =
+ (ConstraintLayout.LayoutParams) key.getLayoutParams();
+
+ lp.dimensionRatio = ratio;
+
+ // Don't set any margins on the last row of buttons.
+ if (row != 4) {
+ lp.bottomMargin = bottomMargin;
+ }
+
+ // Don't set margins on the rightmost buttons.
+ if (column != 2) {
+ lp.rightMargin = rightMargin;
+ }
+
+ key.setLayoutParams(lp);
+ }
}
- View entryView = findViewById(R.id.pinEntry);
- ViewGroup.LayoutParams lp = entryView.getLayoutParams();
- lp.height = mContext.getResources().getDimensionPixelSize(R.dimen.keyguard_password_height);
- entryView.setLayoutParams(lp);
+ // Update the guideline based on the device posture...
+ float halfOpenPercentage =
+ mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);
+
+ ConstraintSet cs = new ConstraintSet();
+ cs.clone(mContainer);
+ cs.setGuidelinePercent(R.id.pin_pad_top_guideline,
+ mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
+ cs.applyTo(mContainer);
}
@Override
@@ -113,13 +136,9 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
super.onFinishInflate();
mContainer = findViewById(R.id.pin_container);
- mRow0 = findViewById(R.id.row0);
- mRow1 = findViewById(R.id.row1);
- mRow2 = findViewById(R.id.row2);
- mRow3 = findViewById(R.id.row3);
mViews = new View[][]{
new View[]{
- mRow0, null, null
+ findViewById(R.id.row0), null, null
},
new View[]{
findViewById(R.id.key1), findViewById(R.id.key2),
@@ -188,9 +207,6 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
private void enableClipping(boolean enable) {
mContainer.setClipToPadding(enable);
mContainer.setClipChildren(enable);
- mRow1.setClipToPadding(enable);
- mRow2.setClipToPadding(enable);
- mRow3.setClipToPadding(enable);
setClipChildren(enable);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 92f8454fc93e..0529cdbcbb13 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -208,8 +208,7 @@ public class KeyguardPasswordViewController
mView.post(() -> {
if (mView.isShown()) {
mPasswordEntry.requestFocus();
- mInputMethodManager.showSoftInput(
- mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
+ mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime());
}
});
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 98e7fb48b7a6..1862fc7f6603 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -15,6 +15,8 @@
*/
package com.android.keyguard;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+
import android.content.Context;
import android.graphics.Rect;
import android.os.SystemClock;
@@ -22,16 +24,19 @@ import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
+
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternView;
import com.android.settingslib.animation.AppearAnimationCreator;
import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils;
import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
public class KeyguardPatternView extends KeyguardInputView
implements AppearAnimationCreator<LockPatternView.CellState> {
@@ -68,7 +73,7 @@ public class KeyguardPatternView extends KeyguardInputView
KeyguardMessageArea mSecurityMessageDisplay;
private View mEcaView;
- private ViewGroup mContainer;
+ private ConstraintLayout mContainer;
public KeyguardPatternView(Context context) {
this(context, null);
@@ -90,6 +95,18 @@ public class KeyguardPatternView extends KeyguardInputView
mContext, android.R.interpolator.fast_out_linear_in));
}
+ void onDevicePostureChanged(@DevicePostureInt int posture) {
+ // Update the guideline based on the device posture...
+ float halfOpenPercentage =
+ mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);
+
+ ConstraintSet cs = new ConstraintSet();
+ cs.clone(mContainer);
+ cs.setGuidelinePercent(R.id.pattern_top_guideline, posture == DEVICE_POSTURE_HALF_OPENED
+ ? halfOpenPercentage : 0.0f);
+ cs.applyTo(mContainer);
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index d5be7bacaadc..94e07b713915 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -38,6 +38,7 @@ import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingClassifier;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import java.util.List;
@@ -56,6 +57,9 @@ public class KeyguardPatternViewController
private final FalsingCollector mFalsingCollector;
private final EmergencyButtonController mEmergencyButtonController;
private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory;
+ private final DevicePostureController mPostureController;
+ private final DevicePostureController.Callback mPostureCallback =
+ posture -> mView.onDevicePostureChanged(posture);
private KeyguardMessageAreaController mMessageAreaController;
private LockPatternView mLockPatternView;
@@ -192,7 +196,8 @@ public class KeyguardPatternViewController
LatencyTracker latencyTracker,
FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController,
- KeyguardMessageAreaController.Factory messageAreaControllerFactory) {
+ KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+ DevicePostureController postureController) {
super(view, securityMode, keyguardSecurityCallback, emergencyButtonController);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
@@ -203,6 +208,7 @@ public class KeyguardPatternViewController
KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
mMessageAreaController = mMessageAreaControllerFactory.create(kma);
mLockPatternView = mView.findViewById(R.id.lockPatternView);
+ mPostureController = postureController;
}
@Override
@@ -235,6 +241,7 @@ public class KeyguardPatternViewController
getKeyguardSecurityCallback().onCancelClicked();
});
}
+ mPostureController.addCallback(mPostureCallback);
}
@Override
@@ -247,6 +254,7 @@ public class KeyguardPatternViewController
if (cancelBtn != null) {
cancelBtn.setOnClickListener(null);
}
+ mPostureController.removeCallback(mPostureCallback);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 262bed3f695c..9f4585fb1a92 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -23,10 +23,14 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.statusbar.policy.DevicePostureController;
public class KeyguardPinViewController
extends KeyguardPinBasedInputViewController<KeyguardPINView> {
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final DevicePostureController mPostureController;
+ private final DevicePostureController.Callback mPostureCallback = posture ->
+ mView.onDevicePostureChanged(posture);
protected KeyguardPinViewController(KeyguardPINView view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -35,11 +39,13 @@ public class KeyguardPinViewController
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
EmergencyButtonController emergencyButtonController,
- FalsingCollector falsingCollector) {
+ FalsingCollector falsingCollector,
+ DevicePostureController postureController) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
emergencyButtonController, falsingCollector);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mPostureController = postureController;
}
@Override
@@ -53,6 +59,14 @@ public class KeyguardPinViewController
getKeyguardSecurityCallback().onCancelClicked();
});
}
+
+ mPostureController.addCallback(mPostureCallback);
+ }
+
+ @Override
+ protected void onViewDetached() {
+ super.onViewDetached();
+ mPostureController.removeCallback(mPostureCallback);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index ca4d73b6de5d..0328b5a62b06 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -23,6 +23,7 @@ import static java.lang.Integer.max;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
@@ -36,10 +37,11 @@ import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
-import android.view.ViewPropertyAnimator;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.WindowManager;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
@@ -55,7 +57,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.shared.system.SysUiStatsLog;
import java.util.ArrayList;
import java.util.List;
@@ -85,6 +87,13 @@ public class KeyguardSecurityContainer extends FrameLayout {
private static final long IME_DISAPPEAR_DURATION_MS = 125;
+ // The duration of the animation to switch bouncer sides.
+ private static final long BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS = 500;
+
+ // How much of the switch sides animation should be dedicated to fading the bouncer out. The
+ // remainder will fade it back in again.
+ private static final float BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION = 0.2f;
+
@VisibleForTesting
KeyguardSecurityViewFlipper mSecurityViewFlipper;
private AlertDialog mAlertDialog;
@@ -104,8 +113,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
private boolean mIsSecurityViewLeftAligned = true;
private boolean mOneHandedMode = false;
- private SecurityMode mSecurityMode = SecurityMode.Invalid;
- private ViewPropertyAnimator mRunningOneHandedAnimator;
+ @Nullable private ValueAnimator mRunningOneHandedAnimator;
private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
@@ -248,66 +256,47 @@ public class KeyguardSecurityContainer extends FrameLayout {
}
void onResume(SecurityMode securityMode, boolean faceAuthEnabled) {
- mSecurityMode = securityMode;
mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
updateBiometricRetry(securityMode, faceAuthEnabled);
-
- updateLayoutForSecurityMode(securityMode);
}
- void updateLayoutForSecurityMode(SecurityMode securityMode) {
- mSecurityMode = securityMode;
- mOneHandedMode = canUseOneHandedBouncer();
-
- if (mOneHandedMode) {
- mIsSecurityViewLeftAligned = isOneHandedKeyguardLeftAligned(mContext);
- }
-
+ /**
+ * Sets whether this security container is in one handed mode. If so, it will measure its
+ * child SecurityViewFlipper in one half of the screen, and move it when tapping on the opposite
+ * side of the screen.
+ */
+ public void setOneHandedMode(boolean oneHandedMode) {
+ mOneHandedMode = oneHandedMode;
updateSecurityViewGravity();
updateSecurityViewLocation(false);
}
- /** Update keyguard position based on a tapped X coordinate. */
- public void updateKeyguardPosition(float x) {
- if (mOneHandedMode) {
- moveBouncerForXCoordinate(x, /* animate= */false);
- }
+ /** Returns whether this security container is in one-handed mode. */
+ public boolean isOneHandedMode() {
+ return mOneHandedMode;
}
- /** Return whether the one-handed keyguard should be enabled. */
- private boolean canUseOneHandedBouncer() {
- // Is it enabled?
- if (!getResources().getBoolean(
- com.android.internal.R.bool.config_enableDynamicKeyguardPositioning)) {
- return false;
- }
-
- if (!KeyguardSecurityModel.isSecurityViewOneHanded(mSecurityMode)) {
- return false;
- }
-
- return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
+ /**
+ * When in one-handed mode, sets if the inner SecurityViewFlipper should be aligned to the
+ * left-hand side of the screen or not, and whether to animate when moving between the two.
+ */
+ public void setOneHandedModeLeftAligned(boolean leftAligned, boolean animate) {
+ mIsSecurityViewLeftAligned = leftAligned;
+ updateSecurityViewLocation(animate);
}
- /** Read whether the one-handed keyguard should be on the left/right from settings. */
- private boolean isOneHandedKeyguardLeftAligned(Context context) {
- try {
- return Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.ONE_HANDED_KEYGUARD_SIDE)
- == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
- } catch (Settings.SettingNotFoundException ex) {
- return true;
- }
+ /** Returns whether the inner SecurityViewFlipper is left-aligned when in one-handed mode. */
+ public boolean isOneHandedModeLeftAligned() {
+ return mIsSecurityViewLeftAligned;
}
private void updateSecurityViewGravity() {
- View securityView = findKeyguardSecurityView();
-
- if (securityView == null) {
+ if (mSecurityViewFlipper == null) {
return;
}
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) securityView.getLayoutParams();
+ FrameLayout.LayoutParams lp =
+ (FrameLayout.LayoutParams) mSecurityViewFlipper.getLayoutParams();
if (mOneHandedMode) {
lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
@@ -315,7 +304,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
lp.gravity = Gravity.CENTER_HORIZONTAL;
}
- securityView.setLayoutParams(lp);
+ mSecurityViewFlipper.setLayoutParams(lp);
}
/**
@@ -324,14 +313,12 @@ public class KeyguardSecurityContainer extends FrameLayout {
* by the security view .
*/
private void updateSecurityViewLocation(boolean animate) {
- View securityView = findKeyguardSecurityView();
-
- if (securityView == null) {
+ if (mSecurityViewFlipper == null) {
return;
}
if (!mOneHandedMode) {
- securityView.setTranslationX(0);
+ mSecurityViewFlipper.setTranslationX(0);
return;
}
@@ -340,40 +327,105 @@ public class KeyguardSecurityContainer extends FrameLayout {
mRunningOneHandedAnimator = null;
}
- int targetTranslation = mIsSecurityViewLeftAligned ? 0 : (int) (getMeasuredWidth() / 2f);
+ int targetTranslation = mIsSecurityViewLeftAligned
+ ? 0 : (int) (getMeasuredWidth() - mSecurityViewFlipper.getWidth());
if (animate) {
- mRunningOneHandedAnimator = securityView.animate().translationX(targetTranslation);
- mRunningOneHandedAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mRunningOneHandedAnimator.setListener(new AnimatorListenerAdapter() {
+ // This animation is a bit fun to implement. The bouncer needs to move, and fade in/out
+ // at the same time. The issue is, the bouncer should only move a short amount (120dp or
+ // so), but obviously needs to go from one side of the screen to the other. This needs a
+ // pretty custom animation.
+ //
+ // This works as follows. It uses a ValueAnimation to simply drive the animation
+ // progress. This animator is responsible for both the translation of the bouncer, and
+ // the current fade. It will fade the bouncer out while also moving it along the 120dp
+ // path. Once the bouncer is fully faded out though, it will "snap" the bouncer closer
+ // to its destination, then fade it back in again. The effect is that the bouncer will
+ // move from 0 -> X while fading out, then (destination - X) -> destination while fading
+ // back in again.
+ // TODO(b/195012405): Make this animation properly abortable.
+ Interpolator positionInterpolator = AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_extra_slow_in);
+ Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN;
+ Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
+
+ mRunningOneHandedAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
+ mRunningOneHandedAnimator.setDuration(BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS);
+ mRunningOneHandedAnimator.setInterpolator(Interpolators.LINEAR);
+
+ int initialTranslation = (int) mSecurityViewFlipper.getTranslationX();
+ int totalTranslation = (int) getResources().getDimension(
+ R.dimen.one_handed_bouncer_move_animation_translation);
+
+ final boolean shouldRestoreLayerType = mSecurityViewFlipper.hasOverlappingRendering()
+ && mSecurityViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE;
+ if (shouldRestoreLayerType) {
+ mSecurityViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null);
+ }
+
+ float initialAlpha = mSecurityViewFlipper.getAlpha();
+
+ mRunningOneHandedAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mRunningOneHandedAnimator = null;
}
});
+ mRunningOneHandedAnimator.addUpdateListener(animation -> {
+ float switchPoint = BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION;
+ boolean isFadingOut = animation.getAnimatedFraction() < switchPoint;
+
+ int currentTranslation = (int) (positionInterpolator.getInterpolation(
+ animation.getAnimatedFraction()) * totalTranslation);
+ int translationRemaining = totalTranslation - currentTranslation;
+
+ // Flip the sign if we're going from right to left.
+ if (mIsSecurityViewLeftAligned) {
+ currentTranslation = -currentTranslation;
+ translationRemaining = -translationRemaining;
+ }
- mRunningOneHandedAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- mRunningOneHandedAnimator.start();
- } else {
- securityView.setTranslationX(targetTranslation);
- }
- }
+ if (isFadingOut) {
+ // The bouncer fades out over the first X%.
+ float fadeOutFraction = MathUtils.constrainedMap(
+ /* rangeMin= */1.0f,
+ /* rangeMax= */0.0f,
+ /* valueMin= */0.0f,
+ /* valueMax= */switchPoint,
+ animation.getAnimatedFraction());
+ float opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction);
+
+ // When fading out, the alpha needs to start from the initial opacity of the
+ // view flipper, otherwise we get a weird bit of jank as it ramps back to 100%.
+ mSecurityViewFlipper.setAlpha(opacity * initialAlpha);
+
+ // Animate away from the source.
+ mSecurityViewFlipper.setTranslationX(initialTranslation + currentTranslation);
+ } else {
+ // And in again over the remaining (100-X)%.
+ float fadeInFraction = MathUtils.constrainedMap(
+ /* rangeMin= */0.0f,
+ /* rangeMax= */1.0f,
+ /* valueMin= */switchPoint,
+ /* valueMax= */1.0f,
+ animation.getAnimatedFraction());
+
+ float opacity = fadeInInterpolator.getInterpolation(fadeInFraction);
+ mSecurityViewFlipper.setAlpha(opacity);
+
+ // Fading back in, animate towards the destination.
+ mSecurityViewFlipper.setTranslationX(targetTranslation - translationRemaining);
+ }
- @Nullable
- private KeyguardSecurityViewFlipper findKeyguardSecurityView() {
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
+ if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) {
+ mSecurityViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null);
+ }
+ });
- if (isKeyguardSecurityView(child)) {
- return (KeyguardSecurityViewFlipper) child;
- }
+ mRunningOneHandedAnimator.start();
+ } else {
+ mSecurityViewFlipper.setTranslationX(targetTranslation);
}
-
- return null;
- }
-
- private boolean isKeyguardSecurityView(View view) {
- return view instanceof KeyguardSecurityViewFlipper;
}
public void onPause() {
@@ -510,6 +562,11 @@ public class KeyguardSecurityContainer extends FrameLayout {
mIsSecurityViewLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT
: Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT);
+ int keyguardState = mIsSecurityViewLeftAligned
+ ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_LEFT
+ : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_RIGHT;
+ SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, keyguardState);
+
updateSecurityViewLocation(animate);
}
}
@@ -635,7 +692,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
for (int i = 0; i < getChildCount(); i++) {
final View view = getChildAt(i);
if (view.getVisibility() != GONE) {
- if (mOneHandedMode && isKeyguardSecurityView(view)) {
+ if (mOneHandedMode && view == mSecurityViewFlipper) {
measureChildWithMargins(view, halfWidthMeasureSpec, 0,
heightMeasureSpec, 0);
} else {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index dd7c7ea6b5a5..d4d3d5b3ea2d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -32,6 +32,7 @@ import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.metrics.LogMaker;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
import android.util.Slog;
import android.view.MotionEvent;
@@ -49,6 +50,8 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.keyguard.dagger.KeyguardBouncerScope;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Gefingerpoken;
+import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -74,12 +77,14 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
private final SecurityCallback mSecurityCallback;
private final ConfigurationController mConfigurationController;
+ private final FalsingCollector mFalsingCollector;
private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
- private final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() {
+ @VisibleForTesting
+ final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() {
private MotionEvent mTouchDown;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
@@ -91,6 +96,17 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
// Do just a bit of our own falsing. People should only be tapping on the input, not
// swiping.
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ // If we're in one handed mode, the user can tap on the opposite side of the screen
+ // to move the bouncer across. In that case, inhibit the falsing (otherwise the taps
+ // to move the bouncer to each screen side can end up closing it instead).
+ if (mView.isOneHandedMode()) {
+ if ((mView.isOneHandedModeLeftAligned() && ev.getX() > mView.getWidth() / 2f)
+ || (!mView.isOneHandedModeLeftAligned()
+ && ev.getX() <= mView.getWidth() / 2f)) {
+ mFalsingCollector.avoidGesture();
+ }
+ }
+
if (mTouchDown != null) {
mTouchDown.recycle();
mTouchDown = null;
@@ -135,9 +151,17 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
+ int bouncerSide = SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__DEFAULT;
+ if (canUseOneHandedBouncer()) {
+ bouncerSide = isOneHandedKeyguardLeftAligned()
+ ? SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__LEFT
+ : SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__RIGHT;
+ }
+
if (success) {
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
- SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS);
+ SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS,
+ bouncerSide);
mLockPatternUtils.reportSuccessfulPasswordAttempt(userId);
// Force a garbage collection in an attempt to erase any lockscreen password left in
// memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard
@@ -152,7 +176,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
});
} else {
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
- SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE);
+ SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE,
+ bouncerSide);
reportFailedUnlockAttempt(userId, timeoutMs);
}
mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
@@ -184,7 +209,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
mSecurityViewFlipperController.reloadColors();
}
@@ -204,7 +229,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
KeyguardStateController keyguardStateController,
SecurityCallback securityCallback,
KeyguardSecurityViewFlipperController securityViewFlipperController,
- ConfigurationController configurationController) {
+ ConfigurationController configurationController,
+ FalsingCollector falsingCollector) {
super(view);
mLockPatternUtils = lockPatternUtils;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -218,6 +244,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mKeyguardSecurityCallback);
mConfigurationController = configurationController;
mLastOrientation = getResources().getConfiguration().orientation;
+ mFalsingCollector = falsingCollector;
}
@Override
@@ -296,6 +323,14 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
@Override
public void onResume(int reason) {
if (mCurrentSecurityMode != SecurityMode.None) {
+ int state = SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN;
+ if (canUseOneHandedBouncer()) {
+ state = mView.isOneHandedModeLeftAligned()
+ ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_LEFT
+ : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_RIGHT;
+ }
+ SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, state);
+
getCurrentSecurityController().onResume(reason);
}
mView.onResume(
@@ -442,13 +477,49 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
if (newView != null) {
newView.onResume(KeyguardSecurityView.VIEW_REVEALED);
mSecurityViewFlipperController.show(newView);
- mView.updateLayoutForSecurityMode(securityMode);
+ configureOneHandedMode();
}
mSecurityCallback.onSecurityModeChanged(
securityMode, newView != null && newView.needsInput());
}
+ /** Read whether the one-handed keyguard should be on the left/right from settings. */
+ private boolean isOneHandedKeyguardLeftAligned() {
+ try {
+ return Settings.Global.getInt(mView.getContext().getContentResolver(),
+ Settings.Global.ONE_HANDED_KEYGUARD_SIDE)
+ == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
+ } catch (Settings.SettingNotFoundException ex) {
+ return true;
+ }
+ }
+
+ private boolean canUseOneHandedBouncer() {
+ // Is it enabled?
+ if (!getResources().getBoolean(
+ com.android.internal.R.bool.config_enableDynamicKeyguardPositioning)) {
+ return false;
+ }
+
+ if (!KeyguardSecurityModel.isSecurityViewOneHanded(mCurrentSecurityMode)) {
+ return false;
+ }
+
+ return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
+ }
+
+ private void configureOneHandedMode() {
+ boolean oneHandedMode = canUseOneHandedBouncer();
+
+ mView.setOneHandedMode(oneHandedMode);
+
+ if (oneHandedMode) {
+ mView.setOneHandedModeLeftAligned(
+ isOneHandedKeyguardLeftAligned(), /* animate= */false);
+ }
+ }
+
public void reportFailedUnlockAttempt(int userId, int timeoutMs) {
// +1 for this time
final int failedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(userId) + 1;
@@ -513,13 +584,15 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
int newOrientation = getResources().getConfiguration().orientation;
if (newOrientation != mLastOrientation) {
mLastOrientation = newOrientation;
- mView.updateLayoutForSecurityMode(mCurrentSecurityMode);
+ configureOneHandedMode();
}
}
/** Update keyguard position based on a tapped X coordinate. */
public void updateKeyguardPosition(float x) {
- mView.updateKeyguardPosition(x);
+ if (mView.isOneHandedMode()) {
+ mView.setOneHandedModeLeftAligned(x <= mView.getWidth() / 2f, false);
+ }
}
static class Factory {
@@ -535,6 +608,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private final KeyguardStateController mKeyguardStateController;
private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
private final ConfigurationController mConfigurationController;
+ private final FalsingCollector mFalsingCollector;
@Inject
Factory(KeyguardSecurityContainer view,
@@ -547,7 +621,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
UiEventLogger uiEventLogger,
KeyguardStateController keyguardStateController,
KeyguardSecurityViewFlipperController securityViewFlipperController,
- ConfigurationController configurationController) {
+ ConfigurationController configurationController,
+ FalsingCollector falsingCollector) {
mView = view;
mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
mLockPatternUtils = lockPatternUtils;
@@ -558,6 +633,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mKeyguardStateController = keyguardStateController;
mSecurityViewFlipperController = securityViewFlipperController;
mConfigurationController = configurationController;
+ mFalsingCollector = falsingCollector;
}
public KeyguardSecurityContainerController create(
@@ -566,7 +642,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
mKeyguardStateController, securityCallback, mSecurityViewFlipperController,
- mConfigurationController);
+ mConfigurationController, mFalsingCollector);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 428006ebf446..9b76bab5c2a7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -33,12 +33,10 @@ import android.os.Trace;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
-import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.animation.Animation;
import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.slice.SliceItem;
@@ -85,8 +83,6 @@ public class KeyguardSliceView extends LinearLayout {
private boolean mHasHeader;
private View.OnClickListener mOnClickListener;
- private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
-
public KeyguardSliceView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -136,35 +132,6 @@ public class KeyguardSliceView extends LinearLayout {
}
}
- /**
- * Updates the lockscreen mode which may change the layout of the keyguard slice view.
- */
- public void updateLockScreenMode(int mode) {
- mLockScreenMode = mode;
- if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- mTitle.setPaddingRelative(0, 0, 0, 0);
- mTitle.setGravity(Gravity.START);
- setGravity(Gravity.START);
- RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) getLayoutParams();
- lp.removeRule(RelativeLayout.CENTER_HORIZONTAL);
- setLayoutParams(lp);
- } else {
- final int horizontalPaddingDpValue = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP,
- 44,
- getResources().getDisplayMetrics()
- );
- mTitle.setPaddingRelative(horizontalPaddingDpValue, 0, horizontalPaddingDpValue, 0);
- mTitle.setGravity(Gravity.CENTER_HORIZONTAL);
- setGravity(Gravity.CENTER_HORIZONTAL);
- RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) getLayoutParams();
- lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
- setLayoutParams(lp);
- }
- mRow.setLockscreenMode(mode);
- requestLayout();
- }
-
Map<View, PendingIntent> showSlice(RowContent header, List<SliceContent> subItems) {
Trace.beginSection("KeyguardSliceView#showSlice");
mHasHeader = header != null;
@@ -189,8 +156,7 @@ public class KeyguardSliceView extends LinearLayout {
final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it
mRow.setVisibility(subItemsCount > 0 ? VISIBLE : GONE);
LinearLayout.LayoutParams layoutParams = (LayoutParams) mRow.getLayoutParams();
- layoutParams.gravity = mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL
- ? Gravity.START : Gravity.CENTER;
+ layoutParams.gravity = Gravity.START;
mRow.setLayoutParams(layoutParams);
for (int i = startIndex; i < subItemsCount; i++) {
@@ -224,8 +190,7 @@ public class KeyguardSliceView extends LinearLayout {
final int iconSize = mHasHeader ? mIconSizeWithHeader : mIconSize;
iconDrawable = icon.getIcon().loadDrawable(mContext);
if (iconDrawable != null) {
- if ((iconDrawable instanceof InsetDrawable)
- && mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+ if (iconDrawable instanceof InsetDrawable) {
// System icons (DnD) use insets which are fine for centered slice content
// but will cause a slight indent for left/right-aligned slice views
iconDrawable = ((InsetDrawable) iconDrawable).getDrawable();
@@ -321,7 +286,6 @@ public class KeyguardSliceView extends LinearLayout {
pw.println(" mTextColor: " + Integer.toHexString(mTextColor));
pw.println(" mDarkAmount: " + mDarkAmount);
pw.println(" mHasHeader: " + mHasHeader);
- pw.println(" mLockScreenMode: " + mLockScreenMode);
}
@Override
@@ -332,7 +296,6 @@ public class KeyguardSliceView extends LinearLayout {
public static class Row extends LinearLayout {
private Set<KeyguardSliceTextView> mKeyguardSliceTextViewSet = new HashSet();
- private int mLockScreenModeRow = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
/**
* This view is visible in AOD, which means that the device will sleep if we
@@ -407,11 +370,7 @@ public class KeyguardSliceView extends LinearLayout {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child instanceof KeyguardSliceTextView) {
- if (mLockScreenModeRow == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- ((KeyguardSliceTextView) child).setMaxWidth(Integer.MAX_VALUE);
- } else {
- ((KeyguardSliceTextView) child).setMaxWidth(width / 3);
- }
+ ((KeyguardSliceTextView) child).setMaxWidth(Integer.MAX_VALUE);
}
}
@@ -443,7 +402,6 @@ public class KeyguardSliceView extends LinearLayout {
super.addView(view, index);
if (view instanceof KeyguardSliceTextView) {
- ((KeyguardSliceTextView) view).setLockScreenMode(mLockScreenModeRow);
mKeyguardSliceTextViewSet.add((KeyguardSliceTextView) view);
}
}
@@ -455,24 +413,6 @@ public class KeyguardSliceView extends LinearLayout {
mKeyguardSliceTextViewSet.remove((KeyguardSliceTextView) view);
}
}
-
- /**
- * Updates the lockscreen mode which may change the layout of this view.
- */
- public void setLockscreenMode(int mode) {
- mLockScreenModeRow = mode;
- if (mLockScreenModeRow == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- setOrientation(LinearLayout.VERTICAL);
- setGravity(Gravity.START);
- } else {
- setOrientation(LinearLayout.HORIZONTAL);
- setGravity(Gravity.CENTER);
- }
-
- for (KeyguardSliceTextView textView : mKeyguardSliceTextViewSet) {
- textView.setLockScreenMode(mLockScreenModeRow);
- }
- }
}
/**
@@ -480,7 +420,6 @@ public class KeyguardSliceView extends LinearLayout {
*/
@VisibleForTesting
static class KeyguardSliceTextView extends TextView {
- private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
@StyleRes
private static int sStyleId = R.style.TextAppearance_Keyguard_Secondary;
@@ -509,13 +448,8 @@ public class KeyguardSliceView extends LinearLayout {
boolean hasText = !TextUtils.isEmpty(getText());
int padding = (int) getContext().getResources()
.getDimension(R.dimen.widget_horizontal_padding) / 2;
- if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- // orientation is vertical, so add padding to top & bottom
- setPadding(0, padding, 0, hasText ? padding : 0);
- } else {
- // orientation is horizontal, so add padding to left & right
- setPadding(padding, 0, padding * (hasText ? 1 : -1), 0);
- }
+ // orientation is vertical, so add padding to top & bottom
+ setPadding(0, padding, 0, hasText ? padding : 0);
setCompoundDrawablePadding((int) mContext.getResources()
.getDimension(R.dimen.widget_icon_padding));
@@ -543,18 +477,5 @@ public class KeyguardSliceView extends LinearLayout {
}
}
}
-
- /**
- * Updates the lockscreen mode which may change the layout of this view.
- */
- public void setLockScreenMode(int mode) {
- mLockScreenMode = mode;
- if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
- } else {
- setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
- }
- updatePadding();
- }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index 8038ce4c7b69..d05cc4ea8101 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -73,7 +73,6 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie
private Uri mKeyguardSliceUri;
private Slice mSlice;
private Map<View, PendingIntent> mClickActions;
- private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
TunerService.Tunable mTunable = (key, newValue) -> setupUri(newValue);
@@ -84,7 +83,7 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie
mView.onDensityOrFontScaleChanged();
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
mView.onOverlayChanged();
}
};
@@ -137,7 +136,6 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie
TAG + "@" + Integer.toHexString(
KeyguardSliceViewController.this.hashCode()),
KeyguardSliceViewController.this);
- mView.updateLockScreenMode(mLockScreenMode);
}
@Override
@@ -160,14 +158,6 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie
}
/**
- * Updates the lockscreen mode which may change the layout of the keyguard slice view.
- */
- public void updateLockScreenMode(int mode) {
- mLockScreenMode = mode;
- mView.updateLockScreenMode(mLockScreenMode);
- }
-
- /**
* Sets the slice provider Uri.
*/
public void setupUri(String uriString) {
@@ -249,6 +239,5 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println(" mSlice: " + mSlice);
pw.println(" mClickActions: " + mClickActions);
- pw.println(" mLockScreenMode: " + mLockScreenMode);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 2362a1ad74a0..a72a050ee023 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -88,7 +88,7 @@ public class KeyguardStatusView extends GridLayout {
mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext));
}
- mKeyguardSlice = findViewById(R.id.keyguard_status_area);
+ mKeyguardSlice = findViewById(R.id.keyguard_slice_view);
mTextColor = mClockView.getCurrentTextColor();
mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 6b3e9c27c25f..c58710ceec65 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -19,6 +19,7 @@ package com.android.keyguard;
import android.graphics.Rect;
import android.util.Slog;
+import com.android.keyguard.KeyguardClockSwitch.ClockSize;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
@@ -116,10 +117,11 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
}
/**
- * Set whether or not the lock screen is showing notifications.
+ * Set which clock should be displayed on the keyguard. The other one will be automatically
+ * hidden.
*/
- public void setHasVisibleNotifications(boolean hasVisibleNotifications) {
- mKeyguardClockSwitchController.setHasVisibleNotifications(hasVisibleNotifications);
+ public void displayClock(@ClockSize int clockSize) {
+ mKeyguardClockSwitchController.displayClock(clockSize);
}
/**
@@ -247,11 +249,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@Override
- public void onLockScreenModeChanged(int mode) {
- mKeyguardSliceViewController.updateLockScreenMode(mode);
- }
-
- @Override
public void onTimeChanged() {
refreshTime();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 877e76480b1e..a41a49799c2d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -71,7 +71,6 @@ import android.os.ServiceManager;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.Vibrator;
import android.provider.Settings;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
@@ -86,10 +85,11 @@ import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import androidx.annotation.Nullable;
import androidx.lifecycle.Observer;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.fuelgauge.BatteryStatus;
@@ -97,16 +97,15 @@ import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
@@ -190,9 +189,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private static final int MSG_TIME_FORMAT_UPDATE = 344;
private static final int MSG_REQUIRE_NFC_UNLOCK = 345;
- public static final int LOCK_SCREEN_MODE_NORMAL = 0;
- public static final int LOCK_SCREEN_MODE_LAYOUT_1 = 1;
-
/** Biometric authentication state: Not listening. */
private static final int BIOMETRIC_STATE_STOPPED = 0;
@@ -276,7 +272,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private boolean mBouncer; // true if bouncerIsOrWillBeShowing
private boolean mAuthInterruptActive;
private boolean mNeedsSlowUnlockTransition;
- private boolean mHasLockscreenWallpaper;
private boolean mAssistantVisible;
private boolean mKeyguardOccluded;
private boolean mOccludingAppRequestingFp;
@@ -285,9 +280,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@VisibleForTesting
protected boolean mTelephonyCapable;
- private final boolean mAcquiredHapticEnabled = false;
- @Nullable private final Vibrator mVibrator;
-
// Device provisioning state
private boolean mDeviceProvisioned;
@@ -321,6 +313,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private boolean mIsDreaming;
private final DevicePolicyManager mDevicePolicyManager;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final InteractionJankMonitor mInteractionJankMonitor;
+ private final LatencyTracker mLatencyTracker;
private boolean mLogoutEnabled;
// cached value to avoid IPCs
private boolean mIsUdfpsEnrolled;
@@ -1405,7 +1399,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@VisibleForTesting
final FingerprintManager.AuthenticationCallback mFingerprintAuthenticationCallback
= new AuthenticationCallback() {
- private boolean mPlayedAcquiredHaptic;
@Override
public void onAuthenticationFailed() {
@@ -1417,11 +1410,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
handleFingerprintAuthenticated(result.getUserId(), result.isStrongBiometric());
Trace.endSection();
-
- // on auth success, we sometimes never received an acquired haptic
- if (!mPlayedAcquiredHaptic && isUdfpsEnrolled()) {
- playAcquiredHaptic();
- }
}
@Override
@@ -1437,17 +1425,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@Override
public void onAuthenticationAcquired(int acquireInfo) {
handleFingerprintAcquired(acquireInfo);
- if (acquireInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD
- && isUdfpsEnrolled()) {
- mPlayedAcquiredHaptic = true;
- playAcquiredHaptic();
- }
}
@Override
public void onUdfpsPointerDown(int sensorId) {
Log.d(TAG, "onUdfpsPointerDown, sensorId: " + sensorId);
- mPlayedAcquiredHaptic = false;
}
@Override
@@ -1456,17 +1438,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
};
- /**
- * Play haptic to signal udfps fingeprrint acquired.
- */
- @VisibleForTesting
- public void playAcquiredHaptic() {
- if (mAcquiredHapticEnabled && mVibrator != null) {
- mVibrator.vibrate(UdfpsController.EFFECT_CLICK,
- UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES);
- }
- }
-
private final FaceManager.FaceDetectionCallback mFaceDetectionCallback
= (sensorId, userId, isStrongBiometric) -> {
// Trigger the face success path so the bouncer can be shown
@@ -1770,7 +1741,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
AuthController authController,
TelephonyListenerManager telephonyListenerManager,
FeatureFlags featureFlags,
- @Nullable Vibrator vibrator) {
+ InteractionJankMonitor interactionJankMonitor,
+ LatencyTracker latencyTracker) {
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
mTelephonyListenerManager = telephonyListenerManager;
@@ -1778,6 +1750,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged);
mBackgroundExecutor = backgroundExecutor;
mBroadcastDispatcher = broadcastDispatcher;
+ mInteractionJankMonitor = interactionJankMonitor;
+ mLatencyTracker = latencyTracker;
mRingerModeTracker = ringerModeTracker;
mStatusBarStateController = statusBarStateController;
mStatusBarStateController.addCallback(mStatusBarStateControllerListener);
@@ -1785,7 +1759,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
dumpManager.registerDumpable(getClass().getName(), this);
- mVibrator = vibrator;
mHandler = new Handler(mainLooper) {
@Override
@@ -1894,9 +1867,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
case MSG_KEYGUARD_GOING_AWAY:
handleKeyguardGoingAway((boolean) msg.obj);
break;
- case MSG_LOCK_SCREEN_MODE:
- handleLockScreenMode();
- break;
case MSG_TIME_FORMAT_UPDATE:
handleTimeFormatUpdate((String) msg.obj);
break;
@@ -2038,8 +2008,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
- updateLockScreenMode(featureFlags.isKeyguardLayoutEnabled());
-
mTimeFormatChangeObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -2055,14 +2023,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
}
- private void updateLockScreenMode(boolean isEnabled) {
- final int newMode = isEnabled ? LOCK_SCREEN_MODE_LAYOUT_1 : LOCK_SCREEN_MODE_NORMAL;
- if (newMode != mLockScreenMode) {
- mLockScreenMode = newMode;
- mHandler.sendEmptyMessage(MSG_LOCK_SCREEN_MODE);
- }
- }
-
private void updateUdfpsEnrolled(int userId) {
mIsUdfpsEnrolled = mAuthController.isUdfpsEnrolled(userId);
}
@@ -2575,31 +2535,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
/**
- * Update the state whether Keyguard currently has a lockscreen wallpaper.
- *
- * @param hasLockscreenWallpaper Whether Keyguard has a lockscreen wallpaper.
- */
- public void setHasLockscreenWallpaper(boolean hasLockscreenWallpaper) {
- Assert.isMainThread();
- if (hasLockscreenWallpaper != mHasLockscreenWallpaper) {
- mHasLockscreenWallpaper = hasLockscreenWallpaper;
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onHasLockscreenWallpaperChanged(hasLockscreenWallpaper);
- }
- }
- }
- }
-
- /**
- * @return Whether Keyguard has a lockscreen wallpaper.
- */
- public boolean hasLockscreenWallpaper() {
- return mHasLockscreenWallpaper;
- }
-
- /**
* Handle {@link #MSG_DPM_STATE_CHANGED}
*/
private void handleDevicePolicyManagerStateChanged(int userId) {
@@ -2637,7 +2572,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
/**
* Handle {@link #MSG_USER_SWITCH_COMPLETE}
*/
- private void handleUserSwitchComplete(int userId) {
+ @VisibleForTesting
+ void handleUserSwitchComplete(int userId) {
Assert.isMainThread();
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -2645,6 +2581,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
cb.onUserSwitchComplete(userId);
}
}
+ mInteractionJankMonitor.end(InteractionJankMonitor.CUJ_USER_SWITCH);
+ mLatencyTracker.onActionEnd(LatencyTracker.ACTION_USER_SWITCH);
}
/**
@@ -2716,20 +2654,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
/**
- * Handle {@link #MSG_LOCK_SCREEN_MODE}
- */
- private void handleLockScreenMode() {
- Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleLockScreenMode(" + mLockScreenMode + ")");
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onLockScreenModeChanged(mLockScreenMode);
- }
- }
- }
-
- /**
* Handle (@line #MSG_TIMEZONE_UPDATE}
*/
private void handleTimeZoneUpdate(String timeZone) {
@@ -2908,6 +2832,20 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
updateBiometricListeningState();
}
+ /** Notifies that the occluded state changed. */
+ public void onKeyguardOccludedChanged(boolean occluded) {
+ Assert.isMainThread();
+ if (DEBUG) {
+ Log.d(TAG, "onKeyguardOccludedChanged(" + occluded + ")");
+ }
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onKeyguardOccludedChanged(occluded);
+ }
+ }
+ }
+
/**
* Handle {@link #MSG_KEYGUARD_RESET}
*/
@@ -3090,9 +3028,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
callback.onPhoneStateChanged(mPhoneState);
callback.onRefreshCarrierInfo();
callback.onClockVisibilityChanged();
+ callback.onKeyguardOccludedChanged(mKeyguardOccluded);
callback.onKeyguardVisibilityChangedRaw(mKeyguardIsVisible);
callback.onTelephonyCapable(mTelephonyCapable);
- callback.onLockScreenModeChanged(mLockScreenMode);
for (Entry<Integer, SimData> data : mSimDatas.entrySet()) {
final SimData state = data.getValue();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 9849a7efe837..12431984c9b9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -88,6 +88,12 @@ public class KeyguardUpdateMonitorCallback {
*/
public void onKeyguardVisibilityChanged(boolean showing) { }
+ /**
+ * Called when the keyguard occluded state changes.
+ * @param occluded Indicates if the keyguard is now occluded.
+ */
+ public void onKeyguardOccludedChanged(boolean occluded) { }
+
public void onKeyguardVisibilityChangedRaw(boolean showing) {
final long now = SystemClock.elapsedRealtime();
if (showing == mShowing
@@ -286,11 +292,6 @@ public class KeyguardUpdateMonitorCallback {
public void onStrongAuthStateChanged(int userId) { }
/**
- * Called when the state whether we have a lockscreen wallpaper has changed.
- */
- public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) { }
-
- /**
* Called when the dream's window state is changed.
* @param dreaming true if the dream's window has been created and is visible
*/
@@ -324,11 +325,6 @@ public class KeyguardUpdateMonitorCallback {
public void onSecondaryLockscreenRequirementChanged(int userId) { }
/**
- * Called to switch lock screen layout/clock layouts
- */
- public void onLockScreenModeChanged(int mode) { }
-
- /**
* Called when notifying user to unlock in order to use NFC.
*/
public void onRequireUnlockForNfc() { }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index ecc8c0074e18..db729da9c8bf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -18,7 +18,6 @@ package com.android.keyguard;
import android.os.Bundle;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewRootImpl;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -186,14 +185,12 @@ public interface KeyguardViewController {
/**
* Registers the StatusBar to which this Keyguard View is mounted.
* @param statusBar
- * @param container
* @param notificationPanelViewController
* @param biometricUnlockController
* @param notificationContainer
* @param bypassController
*/
void registerStatusBar(StatusBar statusBar,
- ViewGroup container,
NotificationPanelViewController notificationPanelViewController,
BiometricUnlockController biometricUnlockController,
View notificationContainer,
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index ef4353b93179..68132f4c598b 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -26,6 +26,7 @@ import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -40,6 +41,17 @@ import java.io.PrintWriter;
* A view positioned under the notification shade.
*/
public class LockIconView extends FrameLayout implements Dumpable {
+ @IntDef({ICON_NONE, ICON_LOCK, ICON_FINGERPRINT, ICON_UNLOCK})
+ public @interface IconType {}
+
+ public static final int ICON_NONE = -1;
+ public static final int ICON_LOCK = 0;
+ public static final int ICON_FINGERPRINT = 1;
+ public static final int ICON_UNLOCK = 2;
+
+ private @IconType int mIconType;
+ private boolean mAod;
+
@NonNull private final RectF mSensorRect;
@NonNull private PointF mLockIconCenter = new PointF(0f, 0f);
private int mRadius;
@@ -49,6 +61,7 @@ public class LockIconView extends FrameLayout implements Dumpable {
private int mLockIconColor;
private boolean mUseBackground = false;
+ private float mDozeAmount = 0f;
public LockIconView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -62,11 +75,17 @@ public class LockIconView extends FrameLayout implements Dumpable {
mBgView = findViewById(R.id.lock_icon_bg);
}
+ void setDozeAmount(float dozeAmount) {
+ mDozeAmount = dozeAmount;
+ updateColorAndBackgroundVisibility();
+ }
+
void updateColorAndBackgroundVisibility() {
if (mUseBackground && mLockIcon.getDrawable() != null) {
mLockIconColor = Utils.getColorAttrDefaultColor(getContext(),
android.R.attr.textColorPrimary);
mBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg));
+ mBgView.setAlpha(1f - mDozeAmount);
mBgView.setVisibility(View.VISIBLE);
} else {
mLockIconColor = Utils.getColorAttrDefaultColor(getContext(),
@@ -129,10 +148,75 @@ public class LockIconView extends FrameLayout implements Dumpable {
return mLockIconCenter.y - mRadius;
}
+ /**
+ * Updates the icon its default state where no visual is shown.
+ */
+ public void clearIcon() {
+ updateIcon(ICON_NONE, false);
+ }
+
+ /**
+ * Transition the current icon to a new state
+ * @param icon type (ie: lock icon, unlock icon, fingerprint icon)
+ * @param aod whether to use the aod icon variant (some icons don't have aod variants and will
+ * therefore show no icon)
+ */
+ public void updateIcon(@IconType int icon, boolean aod) {
+ mIconType = icon;
+ mAod = aod;
+
+ mLockIcon.setImageState(getLockIconState(mIconType, mAod), true);
+ }
+
+ private static int[] getLockIconState(@IconType int icon, boolean aod) {
+ if (icon == ICON_NONE) {
+ return new int[0];
+ }
+
+ int[] lockIconState = new int[2];
+ switch (icon) {
+ case ICON_LOCK:
+ lockIconState[0] = android.R.attr.state_first;
+ break;
+ case ICON_FINGERPRINT:
+ lockIconState[0] = android.R.attr.state_middle;
+ break;
+ case ICON_UNLOCK:
+ lockIconState[0] = android.R.attr.state_last;
+ break;
+ }
+
+ if (aod) {
+ lockIconState[1] = android.R.attr.state_single;
+ } else {
+ lockIconState[1] = -android.R.attr.state_single;
+ }
+
+ return lockIconState;
+ }
+
+ private String typeToString(@IconType int type) {
+ switch (type) {
+ case ICON_NONE:
+ return "none";
+ case ICON_LOCK:
+ return "lock";
+ case ICON_FINGERPRINT:
+ return "fingerprint";
+ case ICON_UNLOCK:
+ return "unlock";
+ }
+
+ return "invalid";
+ }
+
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("Center in px (x, y)= (" + mLockIconCenter.x + ", " + mLockIconCenter.y + ")");
pw.println("Radius in pixels: " + mRadius);
pw.println("topLeft= (" + getX() + ", " + getY() + ")");
+ pw.println("topLeft= (" + getX() + ", " + getY() + ")");
+ pw.println("mIconType=" + typeToString(mIconType));
+ pw.println("mAod=" + mAod);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 52ebf2fa09d0..3c80a186a4a7 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -18,17 +18,20 @@ package com.android.keyguard;
import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
+import static com.android.keyguard.LockIconView.ICON_FINGERPRINT;
+import static com.android.keyguard.LockIconView.ICON_LOCK;
+import static com.android.keyguard.LockIconView.ICON_UNLOCK;
import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset;
-import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
+import android.graphics.drawable.AnimatedStateListDrawable;
import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.media.AudioAttributes;
import android.os.Process;
@@ -37,6 +40,7 @@ import android.util.DisplayMetrics;
import android.util.MathUtils;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
@@ -97,14 +101,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@NonNull private final AccessibilityManager mAccessibilityManager;
@NonNull private final ConfigurationController mConfigurationController;
@NonNull private final DelayableExecutor mExecutor;
+ @NonNull private final LayoutInflater mLayoutInflater;
private boolean mUdfpsEnrolled;
- @NonNull private LottieAnimationView mAodFp;
+ @Nullable private LottieAnimationView mAodFp;
+ @NonNull private final AnimatedStateListDrawable mIcon;
- @NonNull private final AnimatedVectorDrawable mFpToUnlockIcon;
- @NonNull private final AnimatedVectorDrawable mLockToUnlockIcon;
- @NonNull private final Drawable mLockIcon;
- @NonNull private final Drawable mUnlockIcon;
@NonNull private CharSequence mUnlockedLabel;
@NonNull private CharSequence mLockedLabel;
@Nullable private final Vibrator mVibrator;
@@ -130,13 +132,13 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
private boolean mShowLockIcon;
// for udfps when strong auth is required or unlocked on AOD
+ private boolean mShowAodLockIcon;
private boolean mShowAODFpIcon;
private final int mMaxBurnInOffsetX;
private final int mMaxBurnInOffsetY;
private float mInterpolatedDarkAmount;
private boolean mDownDetected;
- private boolean mDetectedLongPress;
private final Rect mSensorTouchLocation = new Rect();
@Inject
@@ -153,7 +155,9 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@NonNull ConfigurationController configurationController,
@NonNull @Main DelayableExecutor executor,
@Nullable Vibrator vibrator,
- @Nullable AuthRippleController authRippleController
+ @Nullable AuthRippleController authRippleController,
+ @NonNull @Main Resources resources,
+ @NonNull LayoutInflater inflater
) {
super(view);
mStatusBarStateController = statusBarStateController;
@@ -167,27 +171,16 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mExecutor = executor;
mVibrator = vibrator;
mAuthRippleController = authRippleController;
+ mLayoutInflater = inflater;
- final Context context = view.getContext();
- mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp);
- mMaxBurnInOffsetX = context.getResources()
- .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
- mMaxBurnInOffsetY = context.getResources()
- .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
-
- mUnlockIcon = mView.getContext().getResources().getDrawable(
- R.drawable.ic_unlock,
- mView.getContext().getTheme());
- mLockIcon = mView.getContext().getResources().getDrawable(
- R.anim.lock_to_unlock,
- mView.getContext().getTheme());
- mFpToUnlockIcon = (AnimatedVectorDrawable) mView.getContext().getResources().getDrawable(
- R.anim.fp_to_unlock, mView.getContext().getTheme());
- mLockToUnlockIcon = (AnimatedVectorDrawable) mView.getContext().getResources().getDrawable(
- R.anim.lock_to_unlock,
- mView.getContext().getTheme());
- mUnlockedLabel = context.getResources().getString(R.string.accessibility_unlock_button);
- mLockedLabel = context.getResources().getString(R.string.accessibility_lock_icon);
+ mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
+ mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
+
+ mIcon = (AnimatedStateListDrawable)
+ resources.getDrawable(R.drawable.super_lock_icon, mView.getContext().getTheme());
+ mView.setImageDrawable(mIcon);
+ mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button);
+ mLockedLabel = resources.getString(R.string.accessibility_lock_icon);
dumpManager.registerDumpable("LockIconViewController", this);
}
@@ -259,47 +252,52 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
return;
}
+ boolean wasShowingUnlock = mShowUnlockIcon;
boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon;
- boolean wasShowingLockIcon = mShowLockIcon;
- boolean wasShowingUnlockIcon = mShowUnlockIcon;
mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen()
- && (!mUdfpsEnrolled || !mRunningFPS);
- mShowUnlockIcon = mCanDismissLockScreen && isLockScreen();
- mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS;
+ && (!mUdfpsEnrolled || !mRunningFPS);
+ mShowUnlockIcon = (mCanDismissLockScreen || mUserUnlockedWithBiometric) && isLockScreen();
+ mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && mCanDismissLockScreen;
+ mShowAodLockIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && !mCanDismissLockScreen;
final CharSequence prevContentDescription = mView.getContentDescription();
if (mShowLockIcon) {
- mView.setImageDrawable(mLockIcon);
- mView.setVisibility(View.VISIBLE);
+ mView.updateIcon(ICON_LOCK, false);
mView.setContentDescription(mLockedLabel);
+ mView.setVisibility(View.VISIBLE);
} else if (mShowUnlockIcon) {
- if (!wasShowingUnlockIcon) {
- if (wasShowingFpIcon) {
- mView.setImageDrawable(mFpToUnlockIcon);
- mFpToUnlockIcon.forceAnimationOnUI();
- mFpToUnlockIcon.start();
- } else if (wasShowingLockIcon) {
- mView.setImageDrawable(mLockToUnlockIcon);
- mLockToUnlockIcon.forceAnimationOnUI();
- mLockToUnlockIcon.start();
- } else {
- mView.setImageDrawable(mUnlockIcon);
- }
+ if (wasShowingFpIcon) {
+ // fp icon was shown by UdfpsView, and now we still want to animate the transition
+ // in this drawable
+ mView.updateIcon(ICON_FINGERPRINT, false);
}
- mView.setVisibility(View.VISIBLE);
+ mView.updateIcon(ICON_UNLOCK, false);
mView.setContentDescription(mUnlockedLabel);
+ mView.setVisibility(View.VISIBLE);
} else if (mShowAODFpIcon) {
- mView.setImageDrawable(null);
+ // AOD fp icon is special cased as a lottie view (it updates for each burn-in offset),
+ // this state shows a transparent view
mView.setContentDescription(null);
mAodFp.setVisibility(View.VISIBLE);
mAodFp.setContentDescription(mCanDismissLockScreen ? mUnlockedLabel : mLockedLabel);
+
+ mView.updateIcon(ICON_FINGERPRINT, true); // this shows no icon
+ mView.setVisibility(View.VISIBLE);
+ } else if (mShowAodLockIcon) {
+ if (wasShowingUnlock) {
+ // transition to the unlock icon first
+ mView.updateIcon(ICON_LOCK, false);
+ }
+ mView.updateIcon(ICON_LOCK, true);
+ mView.setContentDescription(mLockedLabel);
mView.setVisibility(View.VISIBLE);
} else {
+ mView.clearIcon();
mView.setVisibility(View.INVISIBLE);
mView.setContentDescription(null);
}
- if (!mShowAODFpIcon) {
+ if (!mShowAODFpIcon && mAodFp != null) {
mAodFp.setVisibility(View.INVISIBLE);
mAodFp.setContentDescription(null);
}
@@ -366,8 +364,9 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
private void updateLockIconLocation() {
if (mUdfpsSupported) {
FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0);
- mView.setCenterLocation(new PointF(props.sensorLocationX, props.sensorLocationY),
- props.sensorRadius);
+ final SensorLocationInternal location = props.getLocation();
+ mView.setCenterLocation(new PointF(location.sensorLocationX, location.sensorLocationY),
+ location.sensorRadius);
} else {
mView.setCenterLocation(
new PointF(mWidthPixels / 2,
@@ -383,6 +382,11 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
pw.println("mUdfpsSupported: " + mUdfpsSupported);
pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled);
pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing);
+ pw.println(" mIcon: ");
+ for (int state : mIcon.getState()) {
+ pw.print(" " + state);
+ }
+ pw.println();
pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
pw.println(" mShowLockIcon: " + mShowLockIcon);
pw.println(" mShowAODFpIcon: " + mShowAODFpIcon);
@@ -414,10 +418,17 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
- mMaxBurnInOffsetY, mInterpolatedDarkAmount);
float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount);
- mAodFp.setTranslationX(offsetX);
- mAodFp.setTranslationY(offsetY);
- mAodFp.setProgress(progress);
- mAodFp.setAlpha(255 * mInterpolatedDarkAmount);
+ if (mAodFp != null) {
+ mAodFp.setTranslationX(offsetX);
+ mAodFp.setTranslationY(offsetY);
+ mAodFp.setProgress(progress);
+ mAodFp.setAlpha(255 * mInterpolatedDarkAmount);
+ }
+
+ if (mShowAodLockIcon) {
+ mView.setTranslationX(offsetX);
+ mView.setTranslationY(offsetY);
+ }
}
private void updateIsUdfpsEnrolled() {
@@ -428,6 +439,10 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mView.setUseBackground(mUdfpsSupported);
mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
+ if (!wasUdfpsEnrolled && mUdfpsEnrolled && mAodFp == null) {
+ mLayoutInflater.inflate(R.layout.udfps_aod_lock_icon, mView);
+ mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp);
+ }
if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) {
updateVisibility();
}
@@ -438,6 +453,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@Override
public void onDozeAmountChanged(float linear, float eased) {
mInterpolatedDarkAmount = eased;
+ mView.setDozeAmount(eased);
updateBurnInOffsets();
}
@@ -475,13 +491,15 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@Override
public void onBiometricRunningStateChanged(boolean running,
BiometricSourceType biometricSourceType) {
+ final boolean wasRunningFps = mRunningFPS;
+ final boolean wasUserUnlockedWithBiometric = mUserUnlockedWithBiometric;
mUserUnlockedWithBiometric =
mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(
KeyguardUpdateMonitor.getCurrentUser());
if (biometricSourceType == FINGERPRINT) {
mRunningFPS = running;
- if (!mRunningFPS) {
+ if (wasRunningFps && !mRunningFPS) {
if (mCancelDelayedUpdateVisibilityRunnable != null) {
mCancelDelayedUpdateVisibilityRunnable.run();
}
@@ -491,10 +509,14 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
// button in this case, so we delay updating the visibility by 50ms.
mCancelDelayedUpdateVisibilityRunnable =
mExecutor.executeDelayed(() -> updateVisibility(), 50);
- } else {
- updateVisibility();
+ return;
}
}
+
+ if (wasUserUnlockedWithBiometric != mUserUnlockedWithBiometric
+ || wasRunningFps != mRunningFPS) {
+ updateVisibility();
+ }
}
};
@@ -543,11 +565,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
}
@Override
- public void onOverlayChanged() {
- updateColors();
- }
-
- @Override
public void onConfigChanged(Configuration newConfig) {
updateConfiguration();
updateColors();
@@ -557,7 +574,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
private final GestureDetector mGestureDetector =
new GestureDetector(new SimpleOnGestureListener() {
public boolean onDown(MotionEvent e) {
- mDetectedLongPress = false;
if (!isClickable()) {
mDownDetected = false;
return false;
@@ -582,7 +598,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
if (!wasClickableOnDownEvent()) {
return;
}
- mDetectedLongPress = true;
if (onAffordanceClick() && mVibrator != null) {
// only vibrate if the click went through and wasn't intercepted by falsing
@@ -648,7 +663,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) {
if (mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
&& (mView.getVisibility() == View.VISIBLE
- || mAodFp.getVisibility() == View.VISIBLE)) {
+ || (mAodFp != null && mAodFp.getVisibility() == View.VISIBLE))) {
mOnGestureDetectedRunnable = onGestureDetectedRunnable;
mGestureDetector.onTouchEvent(event);
}
@@ -680,8 +695,11 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
@Override
public void onAllAuthenticatorsRegistered() {
- updateIsUdfpsEnrolled();
- updateConfiguration();
+ // must be called from the main thread since it may update the views
+ mExecutor.execute(() -> {
+ updateIsUdfpsEnrolled();
+ updateConfiguration();
+ });
}
};
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 57407f1f34c0..f4ce643a085c 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -18,6 +18,7 @@ package com.android.keyguard;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
+import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.graphics.drawable.VectorDrawable;
@@ -26,8 +27,6 @@ import android.view.MotionEvent;
import androidx.annotation.Nullable;
-import com.android.settingslib.Utils;
-
/**
* Similar to the {@link NumPadKey}, but displays an image.
*/
@@ -59,7 +58,9 @@ public class NumPadButton extends AlphaOptimizedImageButton {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Set width/height to the same value to ensure a smooth circle for the bg, but shrink
- // the height to match the old pin bouncer
+ // the height to match the old pin bouncer.
+ // This is only used for PIN/PUK; the main PIN pad now uses ConstraintLayout, which will
+ // force our width/height to conform to the ratio in the layout.
int width = getMeasuredWidth();
boolean shortenHeight = mAnimator == null
@@ -90,8 +91,10 @@ public class NumPadButton extends AlphaOptimizedImageButton {
public void reloadColors() {
if (mAnimator != null) mAnimator.reloadColors(getContext());
- int textColor = Utils.getColorAttrDefaultColor(getContext(),
- android.R.attr.colorBackground);
- ((VectorDrawable) getDrawable()).setTintList(ColorStateList.valueOf(textColor));
+ int[] customAttrs = {android.R.attr.textColorPrimaryInverse};
+ TypedArray a = getContext().obtainStyledAttributes(customAttrs);
+ int imageColor = a.getColor(0, 0);
+ a.recycle();
+ ((VectorDrawable) getDrawable()).setTintList(ColorStateList.valueOf(imageColor));
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 232c6fc99068..e79ea9a44843 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -175,7 +175,9 @@ public class NumPadKey extends ViewGroup {
measureChildren(widthMeasureSpec, heightMeasureSpec);
// Set width/height to the same value to ensure a smooth circle for the bg, but shrink
- // the height to match the old pin bouncer
+ // the height to match the old pin bouncer.
+ // This is only used for PIN/PUK; the main PIN pad now uses ConstraintLayout, which will
+ // force our width/height to conform to the ratio in the layout.
int width = getMeasuredWidth();
boolean shortenHeight = mAnimator == null
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
index 99e122ef74e9..7517deed7cbb 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
@@ -91,7 +91,7 @@ public class AnalogClockController implements ClockPlugin {
mResources = res;
mLayoutInflater = inflater;
mColorExtractor = colorExtractor;
- mClockPosition = new SmallClockPosition(res);
+ mClockPosition = new SmallClockPosition(inflater.getContext());
}
private void createViews() {
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
index fac923c01af5..1add1a3abf5a 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
@@ -91,7 +91,7 @@ public class BubbleClockController implements ClockPlugin {
mResources = res;
mLayoutInflater = inflater;
mColorExtractor = colorExtractor;
- mClockPosition = new SmallClockPosition(res);
+ mClockPosition = new SmallClockPosition(inflater.getContext());
}
private void createViews() {
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 3775628df0e0..013cdac94ab8 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -41,7 +41,6 @@ import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.settings.CurrentUserObservable;
import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.util.InjectionInflationController;
import java.util.ArrayList;
import java.util.Collection;
@@ -125,16 +124,16 @@ public final class ClockManager {
private final int mHeight;
@Inject
- public ClockManager(Context context, InjectionInflationController injectionInflater,
+ public ClockManager(Context context, LayoutInflater layoutInflater,
PluginManager pluginManager, SysuiColorExtractor colorExtractor,
@Nullable DockManager dockManager, BroadcastDispatcher broadcastDispatcher) {
- this(context, injectionInflater, pluginManager, colorExtractor,
+ this(context, layoutInflater, pluginManager, colorExtractor,
context.getContentResolver(), new CurrentUserObservable(broadcastDispatcher),
new SettingsWrapper(context.getContentResolver()), dockManager);
}
@VisibleForTesting
- ClockManager(Context context, InjectionInflationController injectionInflater,
+ ClockManager(Context context, LayoutInflater layoutInflater,
PluginManager pluginManager, SysuiColorExtractor colorExtractor,
ContentResolver contentResolver, CurrentUserObservable currentUserObservable,
SettingsWrapper settingsWrapper, DockManager dockManager) {
@@ -147,7 +146,6 @@ public final class ClockManager {
mPreviewClocks = new AvailableClocks();
Resources res = context.getResources();
- LayoutInflater layoutInflater = injectionInflater.injectable(LayoutInflater.from(context));
addBuiltinClock(() -> new DefaultClockController(res, layoutInflater, colorExtractor));
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java b/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java
index b3040744ce7a..4e51b98b0a4c 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java
@@ -16,10 +16,11 @@
package com.android.keyguard.clock;
-import android.content.res.Resources;
+import android.content.Context;
import android.util.MathUtils;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.R;
/**
@@ -40,11 +41,11 @@ class SmallClockPosition {
*/
private float mDarkAmount;
- SmallClockPosition(Resources res) {
- this(res.getDimensionPixelSize(R.dimen.status_bar_height),
- res.getDimensionPixelSize(R.dimen.keyguard_lock_padding),
- res.getDimensionPixelSize(R.dimen.keyguard_lock_height),
- res.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y)
+ SmallClockPosition(Context context) {
+ this(SystemBarUtils.getStatusBarHeight(context),
+ context.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_padding),
+ context.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_height),
+ context.getResources().getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y)
);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java
index 49a617eeb6c0..153da4b6695a 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java
@@ -19,6 +19,7 @@ package com.android.keyguard.dagger;
import com.android.keyguard.KeyguardStatusViewController;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import dagger.BindsInstance;
import dagger.Subcomponent;
@@ -34,7 +35,10 @@ public interface KeyguardStatusBarViewComponent {
/** Simple factory for {@link KeyguardStatusBarViewComponent}. */
@Subcomponent.Factory
interface Factory {
- KeyguardStatusBarViewComponent build(@BindsInstance KeyguardStatusBarView view);
+ KeyguardStatusBarViewComponent build(
+ @BindsInstance KeyguardStatusBarView view,
+ @BindsInstance NotificationPanelViewController.NotificationPanelViewStateProvider
+ notificationPanelViewStateProvider);
}
/** Builds a {@link KeyguardStatusViewController}. */
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
index a6725234e4af..fc14b6a99008 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
@@ -18,6 +18,7 @@ package com.android.keyguard.dagger;
import com.android.keyguard.CarrierText;
import com.android.systemui.R;
+import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import dagger.Module;
@@ -31,4 +32,11 @@ public abstract class KeyguardStatusBarViewModule {
static CarrierText getCarrierText(KeyguardStatusBarView view) {
return view.findViewById(R.id.keyguard_carrier_text);
}
+
+ /** */
+ @Provides
+ @KeyguardStatusBarViewScope
+ static BatteryMeterView getBatteryMeterView(KeyguardStatusBarView view) {
+ return view.findViewById(R.id.battery);
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java
index 1d51e5925de8..b8841eda1de4 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java
@@ -34,6 +34,6 @@ public abstract class KeyguardStatusViewModule {
@Provides
static KeyguardSliceView getKeyguardSliceView(KeyguardClockSwitch keyguardClockSwitch) {
- return keyguardClockSwitch.findViewById(R.id.keyguard_status_area);
+ return keyguardClockSwitch.findViewById(R.id.keyguard_slice_view);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
index 62d5a458d51d..cc166c210078 100644
--- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
@@ -39,113 +39,116 @@ import dagger.Lazy;
@SysUISingleton
public class ActivityStarterDelegate implements ActivityStarter {
- private Optional<Lazy<StatusBar>> mActualStarter;
+ private Lazy<Optional<StatusBar>> mActualStarterOptionalLazy;
@Inject
- public ActivityStarterDelegate(Optional<Lazy<StatusBar>> statusBar) {
- mActualStarter = statusBar;
+ public ActivityStarterDelegate(Lazy<Optional<StatusBar>> statusBarOptionalLazy) {
+ mActualStarterOptionalLazy = statusBarOptionalLazy;
}
@Override
public void startPendingIntentDismissingKeyguard(PendingIntent intent) {
- mActualStarter.ifPresent(
- starter -> starter.get().startPendingIntentDismissingKeyguard(intent));
+ mActualStarterOptionalLazy.get().ifPresent(
+ starter -> starter.startPendingIntentDismissingKeyguard(intent));
}
@Override
public void startPendingIntentDismissingKeyguard(PendingIntent intent,
Runnable intentSentUiThreadCallback) {
- mActualStarter.ifPresent(
- starter -> starter.get().startPendingIntentDismissingKeyguard(intent,
- intentSentUiThreadCallback));
+ mActualStarterOptionalLazy.get().ifPresent(
+ starter -> starter.startPendingIntentDismissingKeyguard(
+ intent, intentSentUiThreadCallback));
}
@Override
public void startPendingIntentDismissingKeyguard(PendingIntent intent,
Runnable intentSentUiThreadCallback, View associatedView) {
- mActualStarter.ifPresent(
- starter -> starter.get().startPendingIntentDismissingKeyguard(intent,
- intentSentUiThreadCallback, associatedView));
+ mActualStarterOptionalLazy.get().ifPresent(
+ starter -> starter.startPendingIntentDismissingKeyguard(
+ intent, intentSentUiThreadCallback, associatedView));
}
@Override
public void startPendingIntentDismissingKeyguard(PendingIntent intent,
Runnable intentSentUiThreadCallback,
ActivityLaunchAnimator.Controller animationController) {
- mActualStarter.ifPresent(
- starter -> starter.get().startPendingIntentDismissingKeyguard(intent,
- intentSentUiThreadCallback, animationController));
+ mActualStarterOptionalLazy.get().ifPresent(
+ starter -> starter.startPendingIntentDismissingKeyguard(
+ intent, intentSentUiThreadCallback, animationController));
}
@Override
public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade,
int flags) {
- mActualStarter.ifPresent(
- starter -> starter.get().startActivity(intent, onlyProvisioned, dismissShade,
- flags));
+ mActualStarterOptionalLazy.get().ifPresent(
+ starter -> starter.startActivity(intent, onlyProvisioned, dismissShade, flags));
}
@Override
public void startActivity(Intent intent, boolean dismissShade) {
- mActualStarter.ifPresent(starter -> starter.get().startActivity(intent, dismissShade));
+ mActualStarterOptionalLazy.get().ifPresent(
+ starter -> starter.startActivity(intent, dismissShade));
}
@Override
public void startActivity(Intent intent, boolean dismissShade,
- @Nullable ActivityLaunchAnimator.Controller animationController) {
- mActualStarter.ifPresent(
- starter -> starter.get().startActivity(intent, dismissShade, animationController));
+ @Nullable ActivityLaunchAnimator.Controller animationController,
+ boolean showOverLockscreenWhenLocked) {
+ mActualStarterOptionalLazy.get().ifPresent(
+ starter -> starter.startActivity(intent, dismissShade, animationController,
+ showOverLockscreenWhenLocked));
}
@Override
public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade) {
- mActualStarter.ifPresent(
- starter -> starter.get().startActivity(intent, onlyProvisioned, dismissShade));
+ mActualStarterOptionalLazy.get().ifPresent(
+ starter -> starter.startActivity(intent, onlyProvisioned, dismissShade));
}
@Override
public void startActivity(Intent intent, boolean dismissShade, Callback callback) {
- mActualStarter.ifPresent(
- starter -> starter.get().startActivity(intent, dismissShade, callback));
+ mActualStarterOptionalLazy.get().ifPresent(
+ starter -> starter.startActivity(intent, dismissShade, callback));
}
@Override
public void postStartActivityDismissingKeyguard(Intent intent, int delay) {
- mActualStarter.ifPresent(
- starter -> starter.get().postStartActivityDismissingKeyguard(intent, delay));
+ mActualStarterOptionalLazy.get().ifPresent(
+ starter -> starter.postStartActivityDismissingKeyguard(intent, delay));
}
@Override
public void postStartActivityDismissingKeyguard(Intent intent, int delay,
@Nullable ActivityLaunchAnimator.Controller animationController) {
- mActualStarter.ifPresent(
- starter -> starter.get().postStartActivityDismissingKeyguard(intent, delay,
- animationController));
+ mActualStarterOptionalLazy.get().ifPresent(
+ starter -> starter.postStartActivityDismissingKeyguard(
+ intent, delay, animationController));
}
@Override
public void postStartActivityDismissingKeyguard(PendingIntent intent) {
- mActualStarter.ifPresent(
- starter -> starter.get().postStartActivityDismissingKeyguard(intent));
+ mActualStarterOptionalLazy.get().ifPresent(
+ starter -> starter.postStartActivityDismissingKeyguard(intent));
}
@Override
public void postStartActivityDismissingKeyguard(PendingIntent intent,
ActivityLaunchAnimator.Controller animationController) {
- mActualStarter.ifPresent(starter ->
- starter.get().postStartActivityDismissingKeyguard(intent, animationController));
+ mActualStarterOptionalLazy.get().ifPresent(
+ starter -> starter.postStartActivityDismissingKeyguard(
+ intent, animationController));
}
@Override
public void postQSRunnableDismissingKeyguard(Runnable runnable) {
- mActualStarter.ifPresent(
- starter -> starter.get().postQSRunnableDismissingKeyguard(runnable));
+ mActualStarterOptionalLazy.get().ifPresent(
+ starter -> starter.postQSRunnableDismissingKeyguard(runnable));
}
@Override
public void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancel,
boolean afterKeyguardGone) {
- mActualStarter.ifPresent(starter -> starter.get().dismissKeyguardThenExecute(action, cancel,
- afterKeyguardGone));
+ mActualStarterOptionalLazy.get().ifPresent(
+ starter -> starter.dismissKeyguardThenExecute(action, cancel, afterKeyguardGone));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java b/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
index 5ed9eaad0a00..12dd8f06de17 100644
--- a/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
@@ -86,7 +86,7 @@ public class AutoReinflateContainer extends FrameLayout implements
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
inflateLayout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 00b33a416df4..4e4034a28cb0 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -47,6 +47,7 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -67,14 +68,12 @@ import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.qs.ReduceBrightColorsController;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.recents.Recents;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -90,8 +89,12 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager.Keyg
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.stack.AmbientState;
+import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -100,10 +103,10 @@ import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -117,7 +120,6 @@ import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.RotationLockController;
@@ -247,7 +249,6 @@ public class Dependency {
@Inject Lazy<BluetoothController> mBluetoothController;
@Inject Lazy<LocationController> mLocationController;
@Inject Lazy<RotationLockController> mRotationLockController;
- @Inject Lazy<NetworkController> mNetworkController;
@Inject Lazy<ZenModeController> mZenModeController;
@Inject Lazy<HotspotController> mHotspotController;
@Inject Lazy<CastController> mCastController;
@@ -351,8 +352,6 @@ public class Dependency {
@Inject Lazy<DozeParameters> mDozeParameters;
@Inject Lazy<IWallpaperManager> mWallpaperManager;
@Inject Lazy<CommandQueue> mCommandQueue;
- @Inject Lazy<Recents> mRecents;
- @Inject Lazy<StatusBar> mStatusBar;
@Inject Lazy<RecordingController> mRecordingController;
@Inject Lazy<ProtoTracer> mProtoTracer;
@Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory;
@@ -366,6 +365,11 @@ public class Dependency {
@Inject Lazy<FeatureFlags> mFeatureFlagsLazy;
@Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy;
@Inject Lazy<InternetDialogFactory> mInternetDialogFactory;
+ @Inject Lazy<NotificationSectionsManager> mNotificationSectionsManagerLazy;
+ @Inject Lazy<UnlockedScreenOffAnimationController> mUnlockedScreenOffAnimationControllerLazy;
+ @Inject Lazy<AmbientState> mAmbientStateLazy;
+ @Inject Lazy<GroupMembershipManager> mGroupMembershipManagerLazy;
+ @Inject Lazy<GroupExpansionManager> mGroupExpansionManagerLazy;
@Inject
public Dependency() {
@@ -395,8 +399,6 @@ public class Dependency {
mProviders.put(RotationLockController.class, mRotationLockController::get);
- mProviders.put(NetworkController.class, mNetworkController::get);
-
mProviders.put(ZenModeController.class, mZenModeController::get);
mProviders.put(HotspotController.class, mHotspotController::get);
@@ -557,8 +559,6 @@ public class Dependency {
mProviders.put(DozeParameters.class, mDozeParameters::get);
mProviders.put(IWallpaperManager.class, mWallpaperManager::get);
mProviders.put(CommandQueue.class, mCommandQueue::get);
- mProviders.put(Recents.class, mRecents::get);
- mProviders.put(StatusBar.class, mStatusBar::get);
mProviders.put(ProtoTracer.class, mProtoTracer::get);
mProviders.put(DeviceConfigProxy.class, mDeviceConfigProxy::get);
mProviders.put(TelephonyListenerManager.class, mTelephonyListenerManager::get);
@@ -584,6 +584,12 @@ public class Dependency {
mProviders.put(UiEventLogger.class, mUiEventLogger::get);
mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get);
mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get);
+ mProviders.put(NotificationSectionsManager.class, mNotificationSectionsManagerLazy::get);
+ mProviders.put(UnlockedScreenOffAnimationController.class,
+ mUnlockedScreenOffAnimationControllerLazy::get);
+ mProviders.put(AmbientState.class, mAmbientStateLazy::get);
+ mProviders.put(GroupMembershipManager.class, mGroupMembershipManagerLazy::get);
+ mProviders.put(GroupExpansionManager.class, mGroupExpansionManagerLazy::get);
Dependency.setInstance(this);
}
@@ -607,11 +613,6 @@ public class Dependency {
if (obj == null) {
obj = createDependency(key);
mDependencies.put(key, obj);
-
- // TODO: Get dependencies to register themselves instead
- if (autoRegisterModulesForDump() && obj instanceof Dumpable) {
- mDumpManager.registerDumpable(obj.getClass().getName(), (Dumpable) obj);
- }
}
return obj;
}
@@ -629,17 +630,6 @@ public class Dependency {
return provider.createDependency();
}
- // Currently, there are situations in tests where we might create more than one instance of a
- // thing that should be a singleton: the "real" one (created by Dagger, usually as a result of
- // inflating a view), and a mocked one (injected into Dependency). If we register the mocked
- // one, the DumpManager will throw an exception complaining (rightly) that we have too many
- // things registered with that name. So in tests, we disable the auto-registration until the
- // root cause is fixed, i.e. inflated views in tests with Dagger dependencies.
- @VisibleForTesting
- protected boolean autoRegisterModulesForDump() {
- return true;
- }
-
private static Dependency sDependency;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
index d859a63d0943..15e8c4e7abfb 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
@@ -42,7 +42,8 @@ public class ForegroundServiceController {
private final Handler mMainHandler;
@Inject
- public ForegroundServiceController(AppOpsController appOpsController,
+ public ForegroundServiceController(
+ AppOpsController appOpsController,
@Main Handler mainHandler) {
mMainHandler = mainHandler;
appOpsController.addCallback(APP_OPS, (code, uid, packageName, active) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 8379ccf3154a..d1739aaccac2 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -126,7 +126,13 @@ public class ImageWallpaper extends WallpaperService {
mRenderer = getRendererInstance();
setFixedSizeAllowed(true);
updateSurfaceSize();
- mRenderer.setOnBitmapChanged(this::updateMiniBitmap);
+
+ mRenderer.setOnBitmapChanged(b -> {
+ mLocalColorsToAdd.addAll(mColorAreas);
+ if (mLocalColorsToAdd.size() > 0) {
+ updateMiniBitmapAndNotify(b);
+ }
+ });
getDisplayContext().getSystemService(DisplayManager.class)
.registerDisplayListener(this, mWorker.getThreadHandler());
Trace.endSection();
@@ -170,7 +176,7 @@ public class ImageWallpaper extends WallpaperService {
computeAndNotifyLocalColors(new ArrayList<>(mColorAreas), mMiniBitmap));
}
- private void updateMiniBitmap(Bitmap b) {
+ private void updateMiniBitmapAndNotify(Bitmap b) {
if (b == null) return;
int size = Math.min(b.getWidth(), b.getHeight());
float scale = 1.0f;
@@ -186,11 +192,13 @@ public class ImageWallpaper extends WallpaperService {
}
private void updateSurfaceSize() {
+ Trace.beginSection("ImageWallpaper#updateSurfaceSize");
SurfaceHolder holder = getSurfaceHolder();
Size frameSize = mRenderer.reportSurfaceSize();
int width = Math.max(MIN_SURFACE_WIDTH, frameSize.getWidth());
int height = Math.max(MIN_SURFACE_HEIGHT, frameSize.getHeight());
holder.setFixedSize(width, height);
+ Trace.endSection();
}
@Override
@@ -209,12 +217,10 @@ public class ImageWallpaper extends WallpaperService {
.unregisterDisplayListener(this);
mMiniBitmap = null;
mWorker.getThreadHandler().post(() -> {
- Trace.beginSection("ImageWallpaper.Engine#onDestroy");
mRenderer.finish();
mRenderer = null;
mEglHelper.finish();
mEglHelper = null;
- Trace.endSection();
});
}
@@ -232,6 +238,7 @@ public class ImageWallpaper extends WallpaperService {
Bitmap bitmap = mMiniBitmap;
if (bitmap == null) {
mLocalColorsToAdd.addAll(regions);
+ if (mRenderer != null) mRenderer.use(this::updateMiniBitmapAndNotify);
} else {
computeAndNotifyLocalColors(regions, bitmap);
}
@@ -437,10 +444,8 @@ public class ImageWallpaper extends WallpaperService {
public void postRender() {
// This method should only be invoked from worker thread.
- Trace.beginSection("ImageWallpaper#postRender");
scheduleFinishRendering();
reportEngineShown(false /* waitForEngineShown */);
- Trace.endSection();
}
private void cancelFinishRenderingTask() {
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index 186917fc8807..d325b92cf89f 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -16,18 +16,13 @@
package com.android.systemui;
-import static android.os.PowerManager.WAKE_REASON_UNKNOWN;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.biometrics.BiometricSourceType;
import android.os.Build;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
@@ -48,20 +43,15 @@ public class LatencyTester extends SystemUI {
private static final String
ACTION_FACE_WAKE =
"com.android.systemui.latency.ACTION_FACE_WAKE";
- private static final String
- ACTION_TURN_ON_SCREEN =
- "com.android.systemui.latency.ACTION_TURN_ON_SCREEN";
private final BiometricUnlockController mBiometricUnlockController;
- private final PowerManager mPowerManager;
private final BroadcastDispatcher mBroadcastDispatcher;
@Inject
public LatencyTester(Context context, BiometricUnlockController biometricUnlockController,
- PowerManager powerManager, BroadcastDispatcher broadcastDispatcher) {
+ BroadcastDispatcher broadcastDispatcher) {
super(context);
mBiometricUnlockController = biometricUnlockController;
- mPowerManager = powerManager;
mBroadcastDispatcher = broadcastDispatcher;
}
@@ -74,7 +64,6 @@ public class LatencyTester extends SystemUI {
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_FINGERPRINT_WAKE);
filter.addAction(ACTION_FACE_WAKE);
- filter.addAction(ACTION_TURN_ON_SCREEN);
mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -83,22 +72,11 @@ public class LatencyTester extends SystemUI {
fakeWakeAndUnlock(BiometricSourceType.FINGERPRINT);
} else if (ACTION_FACE_WAKE.equals(action)) {
fakeWakeAndUnlock(BiometricSourceType.FACE);
- } else if (ACTION_TURN_ON_SCREEN.equals(action)) {
- fakeTurnOnScreen();
}
}
}, filter);
}
- private void fakeTurnOnScreen() {
- if (LatencyTracker.isEnabled(mContext)) {
- LatencyTracker.getInstance(mContext).onActionStart(
- LatencyTracker.ACTION_TURN_ON_SCREEN);
- }
- mPowerManager.wakeUp(
- SystemClock.uptimeMillis(), WAKE_REASON_UNKNOWN, "android.policy:LATENCY_TESTS");
- }
-
private void fakeWakeAndUnlock(BiometricSourceType type) {
mBiometricUnlockController.onBiometricAcquired(type);
mBiometricUnlockController.onBiometricAuthenticated(
diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
index f9617cad848e..c7f1006a4042 100644
--- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
@@ -53,7 +53,7 @@ public class PluginInflateContainer extends AutoReinflateContainer
private static final String TAG = "PluginInflateContainer";
- private Class<?> mClass;
+ private Class<ViewProvider> mClass;
private View mPluginView;
public PluginInflateContainer(Context context, @Nullable AttributeSet attrs) {
@@ -61,7 +61,7 @@ public class PluginInflateContainer extends AutoReinflateContainer
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PluginInflateContainer);
String viewType = a.getString(R.styleable.PluginInflateContainer_viewType);
try {
- mClass = Class.forName(viewType);
+ mClass = (Class<ViewProvider>) Class.forName(viewType);
} catch (Exception e) {
Log.d(TAG, "Problem getting class info " + viewType, e);
mClass = null;
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index e9c565377530..80c3616f693e 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -42,6 +42,7 @@ import android.content.IntentFilter;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
@@ -59,6 +60,7 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.util.DisplayMetrics;
+import android.util.DisplayUtils;
import android.util.Log;
import android.view.Display;
import android.view.DisplayCutout;
@@ -66,6 +68,7 @@ import android.view.DisplayCutout.BoundsPosition;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.LayoutInflater;
+import android.view.RoundedCorners;
import android.view.Surface;
import android.view.View;
import android.view.View.OnLayoutChangeListener;
@@ -157,8 +160,11 @@ public class ScreenDecorations extends SystemUI implements Tunable {
private Handler mHandler;
private boolean mPendingRotationChange;
private boolean mIsRoundedCornerMultipleRadius;
- private int mStatusBarHeightPortrait;
- private int mStatusBarHeightLandscape;
+ private boolean mIsPrivacyDotEnabled;
+ private Drawable mRoundedCornerDrawable;
+ private Drawable mRoundedCornerDrawableTop;
+ private Drawable mRoundedCornerDrawableBottom;
+ private String mDisplayUniqueId;
private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback =
new CameraAvailabilityListener.CameraTransitionCallback() {
@@ -244,10 +250,12 @@ public class ScreenDecorations extends SystemUI implements Tunable {
private void startOnScreenDecorationsThread() {
mRotation = mContext.getDisplay().getRotation();
+ mDisplayUniqueId = mContext.getDisplay().getUniqueId();
+ mIsRoundedCornerMultipleRadius = isRoundedCornerMultipleRadius(mContext, mDisplayUniqueId);
+ mIsPrivacyDotEnabled = mContext.getResources().getBoolean(R.bool.config_enablePrivacyDot);
mWindowManager = mContext.getSystemService(WindowManager.class);
mDisplayManager = mContext.getSystemService(DisplayManager.class);
- mIsRoundedCornerMultipleRadius = mContext.getResources().getBoolean(
- R.bool.config_roundedCornerMultipleRadius);
+ updateRoundedCornerDrawable();
updateRoundedCornerRadii();
setupDecorations();
setupCameraListener();
@@ -287,6 +295,14 @@ public class ScreenDecorations extends SystemUI implements Tunable {
}
}
}
+ final String newUniqueId = mContext.getDisplay().getUniqueId();
+ if ((newUniqueId != null && !newUniqueId.equals(mDisplayUniqueId))
+ || (mDisplayUniqueId != null && !mDisplayUniqueId.equals(newUniqueId))) {
+ mDisplayUniqueId = newUniqueId;
+ mIsRoundedCornerMultipleRadius =
+ isRoundedCornerMultipleRadius(mContext, mDisplayUniqueId);
+ updateRoundedCornerDrawable();
+ }
updateOrientation();
}
};
@@ -296,24 +312,24 @@ public class ScreenDecorations extends SystemUI implements Tunable {
}
private void setupDecorations() {
- if (hasRoundedCorners() || shouldDrawCutout()) {
- updateStatusBarHeight();
+ if (hasRoundedCorners() || shouldDrawCutout() || mIsPrivacyDotEnabled) {
final DisplayCutout cutout = getCutout();
- final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll();
- int rotatedPos;
for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
- rotatedPos = getBoundPositionFromRotation(i, mRotation);
- if ((bounds != null && !bounds[rotatedPos].isEmpty())
- || shouldShowRoundedCorner(i)) {
- createOverlay(i);
+ if (shouldShowCutout(i, cutout) || shouldShowRoundedCorner(i, cutout)
+ || shouldShowPrivacyDot(i, cutout)) {
+ createOverlay(i, cutout);
} else {
removeOverlay(i);
}
}
- // Overlays have been created, send the dots to the controller
- //TODO: need a better way to do this
- mDotViewController.initialize(
- mTopLeftDot, mTopRightDot, mBottomLeftDot, mBottomRightDot);
+
+ if (mTopLeftDot != null && mTopRightDot != null && mBottomLeftDot != null
+ && mBottomRightDot != null) {
+ // Overlays have been created, send the dots to the controller
+ //TODO: need a better way to do this
+ mDotViewController.initialize(
+ mTopLeftDot, mTopRightDot, mBottomLeftDot, mBottomRightDot);
+ }
} else {
removeAllOverlays();
}
@@ -400,7 +416,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
mOverlays[pos] = null;
}
- private void createOverlay(@BoundsPosition int pos) {
+ private void createOverlay(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
if (mOverlays == null) {
mOverlays = new View[BOUNDS_POSITION_LENGTH];
}
@@ -412,7 +428,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
if (mOverlays[pos] != null) {
return;
}
- mOverlays[pos] = overlayForPosition(pos);
+ mOverlays[pos] = overlayForPosition(pos, cutout);
mCutoutViews[pos] = new DisplayCutoutView(mContext, pos, this);
((ViewGroup) mOverlays[pos]).addView(mCutoutViews[pos]);
@@ -421,7 +437,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
mOverlays[pos].setAlpha(0);
mOverlays[pos].setForceDarkAllowed(false);
- updateView(pos);
+ updateView(pos, cutout);
mWindowManager.addView(mOverlays[pos], getWindowLayoutParams(pos));
@@ -444,36 +460,58 @@ public class ScreenDecorations extends SystemUI implements Tunable {
/**
* Allow overrides for top/bottom positions
*/
- private View overlayForPosition(@BoundsPosition int pos) {
+ private View overlayForPosition(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
+ final int layoutId = (pos == BOUNDS_POSITION_LEFT || pos == BOUNDS_POSITION_TOP)
+ ? R.layout.rounded_corners_top : R.layout.rounded_corners_bottom;
+ final ViewGroup vg = (ViewGroup) LayoutInflater.from(mContext).inflate(layoutId, null);
+ initPrivacyDotView(vg, pos, cutout);
+ return vg;
+ }
+
+ private void initPrivacyDotView(@NonNull ViewGroup viewGroup, @BoundsPosition int pos,
+ @Nullable DisplayCutout cutout) {
+ final View left = viewGroup.findViewById(R.id.privacy_dot_left_container);
+ final View right = viewGroup.findViewById(R.id.privacy_dot_right_container);
+ if (!shouldShowPrivacyDot(pos, cutout)) {
+ viewGroup.removeView(left);
+ viewGroup.removeView(right);
+ return;
+ }
+
switch (pos) {
- case BOUNDS_POSITION_TOP:
- case BOUNDS_POSITION_LEFT:
- View top = LayoutInflater.from(mContext)
- .inflate(R.layout.rounded_corners_top, null);
- mTopLeftDot = top.findViewById(R.id.privacy_dot_left_container);
- mTopRightDot = top.findViewById(R.id.privacy_dot_right_container);
- return top;
- case BOUNDS_POSITION_BOTTOM:
- case BOUNDS_POSITION_RIGHT:
- View bottom = LayoutInflater.from(mContext)
- .inflate(R.layout.rounded_corners_bottom, null);
- mBottomLeftDot = bottom.findViewById(R.id.privacy_dot_left_container);
- mBottomRightDot = bottom.findViewById(R.id.privacy_dot_right_container);
- return bottom;
- default:
- throw new IllegalArgumentException("Unknown bounds position");
+ case BOUNDS_POSITION_LEFT: {
+ mTopLeftDot = left;
+ mBottomLeftDot = right;
+ break;
+ }
+ case BOUNDS_POSITION_TOP: {
+ mTopLeftDot = left;
+ mTopRightDot = right;
+ break;
+ }
+ case BOUNDS_POSITION_RIGHT: {
+ mTopRightDot = left;
+ mBottomRightDot = right;
+ break;
+ }
+ case BOUNDS_POSITION_BOTTOM: {
+ mBottomLeftDot = left;
+ mBottomRightDot = right;
+ break;
+ }
}
}
- private void updateView(@BoundsPosition int pos) {
+ private void updateView(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
if (mOverlays == null || mOverlays[pos] == null) {
return;
}
// update rounded corner view rotation
- updateRoundedCornerView(pos, R.id.left);
- updateRoundedCornerView(pos, R.id.right);
+ updateRoundedCornerView(pos, R.id.left, cutout);
+ updateRoundedCornerView(pos, R.id.right, cutout);
updateRoundedCornerSize(mRoundedDefault, mRoundedDefaultTop, mRoundedDefaultBottom);
+ updateRoundedCornerImageView();
// update cutout view rotation
if (mCutoutViews != null && mCutoutViews[pos] != null) {
@@ -551,7 +589,8 @@ public class ScreenDecorations extends SystemUI implements Tunable {
}
}
- private static int getBoundPositionFromRotation(@BoundsPosition int pos, int rotation) {
+ @VisibleForTesting
+ static int getBoundPositionFromRotation(@BoundsPosition int pos, int rotation) {
return (pos - rotation) < 0
? pos - rotation + DisplayCutout.BOUNDS_POSITION_LENGTH
: pos - rotation;
@@ -653,51 +692,43 @@ public class ScreenDecorations extends SystemUI implements Tunable {
if (mOverlays != null) {
updateLayoutParams();
+ final DisplayCutout cutout = getCutout();
for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
if (mOverlays[i] == null) {
continue;
}
- updateView(i);
+ updateView(i, cutout);
}
}
}
}
- private void updateStatusBarHeight() {
- mStatusBarHeightLandscape = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height_landscape);
- mStatusBarHeightPortrait = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height_portrait);
- mDotViewController.setStatusBarHeights(mStatusBarHeightPortrait, mStatusBarHeightLandscape);
- }
-
private void updateRoundedCornerRadii() {
// We should eventually move to just using the intrinsic size of the drawables since
// they should be sized to the exact pixels they want to cover. Therefore I'm purposely not
// upgrading all of the configs to contain (width, height) pairs. Instead assume that a
// device configured using the single integer config value is okay with drawing the corners
// as a square
- final int newRoundedDefault = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.rounded_corner_radius);
- final int newRoundedDefaultTop = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.rounded_corner_radius_top);
- final int newRoundedDefaultBottom = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.rounded_corner_radius_bottom);
+ final int newRoundedDefault = RoundedCorners.getRoundedCornerRadius(
+ mContext.getResources(), mDisplayUniqueId);
+ final int newRoundedDefaultTop = RoundedCorners.getRoundedCornerTopRadius(
+ mContext.getResources(), mDisplayUniqueId);
+ final int newRoundedDefaultBottom = RoundedCorners.getRoundedCornerBottomRadius(
+ mContext.getResources(), mDisplayUniqueId);
final boolean changed = mRoundedDefault.x != newRoundedDefault
|| mRoundedDefaultTop.x != newRoundedDefaultTop
|| mRoundedDefaultBottom.x != newRoundedDefaultBottom;
-
if (changed) {
// If config_roundedCornerMultipleRadius set as true, ScreenDecorations respect the
// (width, height) size of drawable/rounded.xml instead of rounded_corner_radius
if (mIsRoundedCornerMultipleRadius) {
- Drawable d = mContext.getDrawable(R.drawable.rounded);
- mRoundedDefault.set(d.getIntrinsicWidth(), d.getIntrinsicHeight());
- d = mContext.getDrawable(R.drawable.rounded_corner_top);
- mRoundedDefaultTop.set(d.getIntrinsicWidth(), d.getIntrinsicHeight());
- d = mContext.getDrawable(R.drawable.rounded_corner_bottom);
- mRoundedDefaultBottom.set(d.getIntrinsicWidth(), d.getIntrinsicHeight());
+ mRoundedDefault.set(mRoundedCornerDrawable.getIntrinsicWidth(),
+ mRoundedCornerDrawable.getIntrinsicHeight());
+ mRoundedDefaultTop.set(mRoundedCornerDrawableTop.getIntrinsicWidth(),
+ mRoundedCornerDrawableTop.getIntrinsicHeight());
+ mRoundedDefaultBottom.set(mRoundedCornerDrawableBottom.getIntrinsicWidth(),
+ mRoundedCornerDrawableBottom.getIntrinsicHeight());
} else {
mRoundedDefault.set(newRoundedDefault, newRoundedDefault);
mRoundedDefaultTop.set(newRoundedDefaultTop, newRoundedDefaultTop);
@@ -707,13 +738,97 @@ public class ScreenDecorations extends SystemUI implements Tunable {
}
}
- private void updateRoundedCornerView(@BoundsPosition int pos, int id) {
+ /**
+ * Gets whether the rounded corners are multiple radii for current display.
+ *
+ * Loads the default config {@link R.bool#config_roundedCornerMultipleRadius} if
+ * {@link com.android.internal.R.array#config_displayUniqueIdArray} is not set.
+ */
+ private static boolean isRoundedCornerMultipleRadius(Context context, String displayUniqueId) {
+ final Resources res = context.getResources();
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final TypedArray array = res.obtainTypedArray(
+ R.array.config_roundedCornerMultipleRadiusArray);
+ boolean isMultipleRadius;
+ if (index >= 0 && index < array.length()) {
+ isMultipleRadius = array.getBoolean(index, false);
+ } else {
+ isMultipleRadius = res.getBoolean(R.bool.config_roundedCornerMultipleRadius);
+ }
+ array.recycle();
+ return isMultipleRadius;
+ }
+
+ /**
+ * Gets the rounded corner drawable for current display.
+ *
+ * Loads the default config {@link R.drawable#rounded} if
+ * {@link com.android.internal.R.array#config_displayUniqueIdArray} is not set.
+ */
+ private static Drawable getRoundedCornerDrawable(Context context, String displayUniqueId) {
+ final Resources res = context.getResources();
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final TypedArray array = res.obtainTypedArray(R.array.config_roundedCornerDrawableArray);
+ Drawable drawable;
+ if (index >= 0 && index < array.length()) {
+ drawable = array.getDrawable(index);
+ } else {
+ drawable = context.getDrawable(R.drawable.rounded);
+ }
+ array.recycle();
+ return drawable;
+ }
+
+ /**
+ * Gets the rounded corner top drawable for current display.
+ *
+ * Loads the default config {@link R.drawable#rounded_corner_top} if
+ * {@link com.android.internal.R.array#config_displayUniqueIdArray} is not set.
+ */
+ private static Drawable getRoundedCornerTopDrawable(Context context, String displayUniqueId) {
+ final Resources res = context.getResources();
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final TypedArray array = res.obtainTypedArray(R.array.config_roundedCornerTopDrawableArray);
+ Drawable drawable;
+ if (index >= 0 && index < array.length()) {
+ drawable = array.getDrawable(index);
+ } else {
+ drawable = context.getDrawable(R.drawable.rounded_corner_top);
+ }
+ array.recycle();
+ return drawable;
+ }
+
+ /**
+ * Gets the rounded corner bottom drawable for current display.
+ *
+ * Loads the default config {@link R.drawable#rounded_corner_bottom} if
+ * {@link com.android.internal.R.array#config_displayUniqueIdArray} is not set.
+ */
+ private static Drawable getRoundedCornerBottomDrawable(
+ Context context, String displayUniqueId) {
+ final Resources res = context.getResources();
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final TypedArray array = res.obtainTypedArray(
+ R.array.config_roundedCornerBottomDrawableArray);
+ Drawable drawable;
+ if (index >= 0 && index < array.length()) {
+ drawable = array.getDrawable(index);
+ } else {
+ drawable = context.getDrawable(R.drawable.rounded_corner_bottom);
+ }
+ array.recycle();
+ return drawable;
+ }
+
+ private void updateRoundedCornerView(@BoundsPosition int pos, int id,
+ @Nullable DisplayCutout cutout) {
final View rounded = mOverlays[pos].findViewById(id);
if (rounded == null) {
return;
}
rounded.setVisibility(View.GONE);
- if (shouldShowRoundedCorner(pos)) {
+ if (shouldShowRoundedCorner(pos, cutout)) {
final int gravity = getRoundedCornerGravity(pos, id == R.id.left);
((FrameLayout.LayoutParams) rounded.getLayoutParams()).gravity = gravity;
setRoundedCornerOrientation(rounded, gravity);
@@ -772,12 +887,8 @@ public class ScreenDecorations extends SystemUI implements Tunable {
|| mIsRoundedCornerMultipleRadius;
}
- private boolean shouldShowRoundedCorner(@BoundsPosition int pos) {
- if (!hasRoundedCorners()) {
- return false;
- }
-
- DisplayCutout cutout = getCutout();
+ private boolean isDefaultShownOverlayPos(@BoundsPosition int pos,
+ @Nullable DisplayCutout cutout) {
// for cutout is null or cutout with only waterfall.
final boolean emptyBoundsOrWaterfall = cutout == null || cutout.isBoundsEmpty();
// Shows rounded corner on left and right overlays only when there is no top or bottom
@@ -792,13 +903,28 @@ public class ScreenDecorations extends SystemUI implements Tunable {
}
}
+ private boolean shouldShowRoundedCorner(@BoundsPosition int pos,
+ @Nullable DisplayCutout cutout) {
+ return hasRoundedCorners() && isDefaultShownOverlayPos(pos, cutout);
+ }
+
+ private boolean shouldShowPrivacyDot(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
+ return mIsPrivacyDotEnabled && isDefaultShownOverlayPos(pos, cutout);
+ }
+
+ private boolean shouldShowCutout(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
+ final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll();
+ final int rotatedPos = getBoundPositionFromRotation(pos, mRotation);
+ return (bounds != null && !bounds[rotatedPos].isEmpty());
+ }
+
private boolean shouldDrawCutout() {
return shouldDrawCutout(mContext);
}
static boolean shouldDrawCutout(Context context) {
- return context.getResources().getBoolean(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout);
+ return DisplayCutout.getFillBuiltInDisplayCutout(
+ context.getResources(), context.getDisplay().getUniqueId());
}
private void updateLayoutParams() {
@@ -837,6 +963,52 @@ public class ScreenDecorations extends SystemUI implements Tunable {
});
}
+ private void updateRoundedCornerDrawable() {
+ mRoundedCornerDrawable = getRoundedCornerDrawable(mContext, mDisplayUniqueId);
+ mRoundedCornerDrawableTop = getRoundedCornerTopDrawable(mContext, mDisplayUniqueId);
+ mRoundedCornerDrawableBottom = getRoundedCornerBottomDrawable(mContext, mDisplayUniqueId);
+ updateRoundedCornerImageView();
+ }
+
+ private void updateRoundedCornerImageView() {
+ final Drawable top = mRoundedCornerDrawableTop != null
+ ? mRoundedCornerDrawableTop : mRoundedCornerDrawable;
+ final Drawable bottom = mRoundedCornerDrawableBottom != null
+ ? mRoundedCornerDrawableBottom : mRoundedCornerDrawable;
+
+ if (mOverlays == null) {
+ return;
+ }
+ for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
+ if (mOverlays[i] == null) {
+ continue;
+ }
+ ((ImageView) mOverlays[i].findViewById(R.id.left)).setImageDrawable(
+ isTopRoundedCorner(i, R.id.left) ? top : bottom);
+ ((ImageView) mOverlays[i].findViewById(R.id.right)).setImageDrawable(
+ isTopRoundedCorner(i, R.id.right) ? top : bottom);
+ }
+ }
+
+ @VisibleForTesting
+ boolean isTopRoundedCorner(@BoundsPosition int pos, int id) {
+ switch (pos) {
+ case BOUNDS_POSITION_LEFT:
+ case BOUNDS_POSITION_RIGHT:
+ if (mRotation == ROTATION_270) {
+ return id == R.id.left ? false : true;
+ } else {
+ return id == R.id.left ? true : false;
+ }
+ case BOUNDS_POSITION_TOP:
+ return true;
+ case BOUNDS_POSITION_BOTTOM:
+ return false;
+ default:
+ throw new IllegalArgumentException("Unknown bounds position");
+ }
+ }
+
private void updateRoundedCornerSize(
Point sizeDefault,
Point sizeTop,
@@ -855,21 +1027,10 @@ public class ScreenDecorations extends SystemUI implements Tunable {
if (mOverlays[i] == null) {
continue;
}
- if (i == BOUNDS_POSITION_LEFT || i == BOUNDS_POSITION_RIGHT) {
- if (mRotation == ROTATION_270) {
- setSize(mOverlays[i].findViewById(R.id.left), sizeBottom);
- setSize(mOverlays[i].findViewById(R.id.right), sizeTop);
- } else {
- setSize(mOverlays[i].findViewById(R.id.left), sizeTop);
- setSize(mOverlays[i].findViewById(R.id.right), sizeBottom);
- }
- } else if (i == BOUNDS_POSITION_TOP) {
- setSize(mOverlays[i].findViewById(R.id.left), sizeTop);
- setSize(mOverlays[i].findViewById(R.id.right), sizeTop);
- } else if (i == BOUNDS_POSITION_BOTTOM) {
- setSize(mOverlays[i].findViewById(R.id.left), sizeBottom);
- setSize(mOverlays[i].findViewById(R.id.right), sizeBottom);
- }
+ setSize(mOverlays[i].findViewById(R.id.left),
+ isTopRoundedCorner(i, R.id.left) ? sizeTop : sizeBottom);
+ setSize(mOverlays[i].findViewById(R.id.right),
+ isTopRoundedCorner(i, R.id.right) ? sizeTop : sizeBottom);
}
}
@@ -1085,7 +1246,8 @@ public class ScreenDecorations extends SystemUI implements Tunable {
int dw = flipped ? lh : lw;
int dh = flipped ? lw : lh;
- Path path = DisplayCutout.pathFromResources(getResources(), dw, dh);
+ Path path = DisplayCutout.pathFromResources(
+ getResources(), getDisplay().getUniqueId(), dw, dh);
if (path != null) {
mBoundingPath.set(path);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 8c63f7bec23c..3555e8d8e193 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -25,6 +25,8 @@ import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.PendingIntent;
import android.content.res.Resources;
import android.graphics.RectF;
import android.os.Handler;
@@ -73,6 +75,9 @@ public class SwipeHelper implements Gefingerpoken {
private final FlingAnimationUtils mFlingAnimationUtils;
private float mPagingTouchSlop;
private final float mSlopMultiplier;
+ private int mTouchSlop;
+ private float mTouchSlopMultiplier;
+
private final Callback mCallback;
private final int mSwipeDirection;
private final VelocityTracker mVelocityTracker;
@@ -105,6 +110,10 @@ public class SwipeHelper implements Gefingerpoken {
final int y = (int) mDownLocation[1] - mViewOffset[1];
mTouchedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
((ExpandableNotificationRow) mTouchedView).doLongClickCallback(x, y);
+
+ if (isAvailableToDragAndDrop(mTouchedView)) {
+ mCallback.onLongPressSent(mTouchedView);
+ }
}
}
}
@@ -126,6 +135,8 @@ public class SwipeHelper implements Gefingerpoken {
mVelocityTracker = VelocityTracker.obtain();
mPagingTouchSlop = viewConfiguration.getScaledPagingTouchSlop();
mSlopMultiplier = viewConfiguration.getScaledAmbiguousGestureMultiplier();
+ mTouchSlop = viewConfiguration.getScaledTouchSlop();
+ mTouchSlopMultiplier = viewConfiguration.getAmbiguousGestureMultiplier();
// Extra long-press!
mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
@@ -297,7 +308,9 @@ public class SwipeHelper implements Gefingerpoken {
mIsSwiping = false;
mSnappingChild = false;
mLongPressSent = false;
+ mCallback.onLongPressSent(null);
mVelocityTracker.clear();
+ cancelLongPress();
mTouchedView = mCallback.getChildAtPosition(ev);
if (mTouchedView != null) {
@@ -349,6 +362,7 @@ public class SwipeHelper implements Gefingerpoken {
mIsSwiping = false;
mTouchedView = null;
mLongPressSent = false;
+ mCallback.onLongPressSent(null);
mMenuRowIntercepting = false;
cancelLongPress();
if (captured) return true;
@@ -599,11 +613,7 @@ public class SwipeHelper implements Gefingerpoken {
@Override
public boolean onTouchEvent(MotionEvent ev) {
- if (mLongPressSent && !mMenuRowIntercepting) {
- return true;
- }
-
- if (!mIsSwiping && !mMenuRowIntercepting) {
+ if (!mIsSwiping && !mMenuRowIntercepting && !mLongPressSent) {
if (mCallback.getChildAtPosition(ev) != null) {
// We are dragging directly over a card, make sure that we also catch the gesture
// even if nobody else wants the touch event.
@@ -629,30 +639,40 @@ public class SwipeHelper implements Gefingerpoken {
if (absDelta >= getFalsingThreshold()) {
mTouchAboveFalsingThreshold = true;
}
- // don't let items that can't be dismissed be dragged more than
- // maxScrollDistance
- if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissedInDirection(
- mTouchedView,
- delta > 0)) {
- float size = getSize(mTouchedView);
- float maxScrollDistance = MAX_SCROLL_SIZE_FRACTION * size;
- if (absDelta >= size) {
- delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
- } else {
- int startPosition = mCallback.getConstrainSwipeStartPosition();
- if (absDelta > startPosition) {
- int signedStartPosition =
- (int) (startPosition * Math.signum(delta));
- delta = signedStartPosition
- + maxScrollDistance * (float) Math.sin(
- ((delta - signedStartPosition) / size) * (Math.PI / 2));
+
+ if (mLongPressSent) {
+ if (absDelta >= getTouchSlop(ev)) {
+ if (mTouchedView instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) mTouchedView)
+ .doDragCallback(ev.getX(), ev.getY());
+ }
+ }
+ } else {
+ // don't let items that can't be dismissed be dragged more than
+ // maxScrollDistance
+ if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissedInDirection(
+ mTouchedView,
+ delta > 0)) {
+ float size = getSize(mTouchedView);
+ float maxScrollDistance = MAX_SCROLL_SIZE_FRACTION * size;
+ if (absDelta >= size) {
+ delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
+ } else {
+ int startPosition = mCallback.getConstrainSwipeStartPosition();
+ if (absDelta > startPosition) {
+ int signedStartPosition =
+ (int) (startPosition * Math.signum(delta));
+ delta = signedStartPosition
+ + maxScrollDistance * (float) Math.sin(
+ ((delta - signedStartPosition) / size) * (Math.PI / 2));
+ }
}
}
- }
- setTranslation(mTouchedView, mTranslation + delta);
- updateSwipeProgressFromOffset(mTouchedView, mCanCurrViewBeDimissed);
- onMoveUpdate(mTouchedView, ev, mTranslation + delta, delta);
+ setTranslation(mTouchedView, mTranslation + delta);
+ updateSwipeProgressFromOffset(mTouchedView, mCanCurrViewBeDimissed);
+ onMoveUpdate(mTouchedView, ev, mTranslation + delta, delta);
+ }
}
break;
case MotionEvent.ACTION_UP:
@@ -753,6 +773,29 @@ public class SwipeHelper implements Gefingerpoken {
mIsSwiping = false;
}
+ private float getTouchSlop(MotionEvent event) {
+ // Adjust the touch slop if another gesture may be being performed.
+ return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
+ ? mTouchSlop * mTouchSlopMultiplier
+ : mTouchSlop;
+ }
+
+ private boolean isAvailableToDragAndDrop(View v) {
+ if (v.getResources().getBoolean(R.bool.config_notificationToContents)) {
+ if (v instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow enr = (ExpandableNotificationRow) v;
+ boolean canBubble = enr.getEntry().canBubble();
+ Notification notif = enr.getEntry().getSbn().getNotification();
+ PendingIntent dragIntent = notif.contentIntent != null ? notif.contentIntent
+ : notif.fullScreenIntent;
+ if (dragIntent != null && dragIntent.isActivity() && !canBubble) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
public interface Callback {
View getChildAtPosition(MotionEvent ev);
@@ -777,6 +820,13 @@ public class SwipeHelper implements Gefingerpoken {
void onDragCancelled(View v);
/**
+ * Called when the child is long pressed and available to start drag and drop.
+ *
+ * @param v the view that was long pressed.
+ */
+ void onLongPressSent(View v);
+
+ /**
* Called when the child is snapped to a position.
*
* @param animView the view that was snapped.
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index a28223de2bf1..c64f416f9672 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -118,6 +118,7 @@ public class SystemUIFactory {
.setTaskViewFactory(mWMComponent.getTaskViewFactory())
.setTransitions(mWMComponent.getTransitions())
.setStartingSurface(mWMComponent.getStartingSurface())
+ .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper());
} else {
// TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
@@ -133,6 +134,7 @@ public class SystemUIFactory {
.setAppPairs(Optional.ofNullable(null))
.setTaskViewFactory(Optional.ofNullable(null))
.setTransitions(Transitions.createEmptyForTesting())
+ .setDisplayAreaHelper(Optional.ofNullable(null))
.setStartingSurface(Optional.ofNullable(null))
.setTaskSurfaceHelper(Optional.ofNullable(null));
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java
index 9bedb1ea7563..fbb909f30121 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java
@@ -17,6 +17,7 @@
package com.android.systemui.accessibility;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
import android.annotation.IntDef;
@@ -49,7 +50,8 @@ public class AccessibilityButtonModeObserver extends
@Retention(RetentionPolicy.SOURCE)
@IntDef({
ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR,
- ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU
+ ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
+ ACCESSIBILITY_BUTTON_MODE_GESTURE
})
public @interface AccessibilityButtonMode {}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index 17178fa8e606..e521c90961fb 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -310,7 +310,8 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL
}
void onConfigurationChanged(int configDiff) {
- if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+ if ((configDiff & (ActivityInfo.CONFIG_ORIENTATION | ActivityInfo.CONFIG_SCREEN_SIZE))
+ != 0) {
final Rect previousDraggableBounds = new Rect(mDraggableWindowBounds);
mDraggableWindowBounds.set(getDraggableWindowBounds());
// Keep the Y position with the same height ratio before the window bounds and
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index ca2c034c5d32..2f88291d1994 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -59,6 +59,7 @@ import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.util.Assert;
import java.util.Locale;
+import java.util.Optional;
import javax.inject.Inject;
@@ -139,10 +140,10 @@ public class SystemActions extends SystemUI {
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
private final SystemActionsBroadcastReceiver mReceiver;
- private final Recents mRecents;
+ private final Optional<Recents> mRecentsOptional;
private Locale mLocale;
private final AccessibilityManager mA11yManager;
- private final Lazy<StatusBar> mStatusBar;
+ private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
private final NotificationShadeWindowController mNotificationShadeController;
private final StatusBarWindowCallback mNotificationShadeCallback;
private boolean mDismissNotificationShadeActionRegistered;
@@ -150,10 +151,10 @@ public class SystemActions extends SystemUI {
@Inject
public SystemActions(Context context,
NotificationShadeWindowController notificationShadeController,
- Lazy<StatusBar> statusBar,
- Recents recents) {
+ Lazy<Optional<StatusBar>> statusBarOptionalLazy,
+ Optional<Recents> recentsOptional) {
super(context);
- mRecents = recents;
+ mRecentsOptional = recentsOptional;
mReceiver = new SystemActionsBroadcastReceiver();
mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
mA11yManager = (AccessibilityManager) mContext.getSystemService(
@@ -161,9 +162,9 @@ public class SystemActions extends SystemUI {
mNotificationShadeController = notificationShadeController;
// Saving in instance variable since to prevent GC since
// NotificationShadeWindowController.registerCallback() only keeps weak references.
- mNotificationShadeCallback = (keyguardShowing, keyguardOccluded, bouncerShowing) ->
+ mNotificationShadeCallback = (keyguardShowing, keyguardOccluded, bouncerShowing, mDozing) ->
registerOrUnregisterDismissNotificationShadeAction();
- mStatusBar = statusBar;
+ mStatusBarOptionalLazy = statusBarOptionalLazy;
}
@Override
@@ -242,8 +243,9 @@ public class SystemActions extends SystemUI {
// Saving state in instance variable since this callback is called quite often to avoid
// binder calls
- StatusBar statusBar = mStatusBar.get();
- if (statusBar.isPanelExpanded() && !statusBar.isKeyguardShowing()) {
+ final Optional<StatusBar> statusBarOptional = mStatusBarOptionalLazy.get();
+ if (statusBarOptional.map(StatusBar::isPanelExpanded).orElse(false)
+ && !statusBarOptional.get().isKeyguardShowing()) {
if (!mDismissNotificationShadeActionRegistered) {
mA11yManager.registerSystemAction(
createRemoteAction(
@@ -368,15 +370,16 @@ public class SystemActions extends SystemUI {
}
private void handleRecents() {
- mRecents.toggleRecentApps();
+ mRecentsOptional.ifPresent(Recents::toggleRecentApps);
}
private void handleNotifications() {
- mStatusBar.get().animateExpandNotificationsPanel();
+ mStatusBarOptionalLazy.get().ifPresent(StatusBar::animateExpandNotificationsPanel);
}
private void handleQuickSettings() {
- mStatusBar.get().animateExpandSettingsPanel(null);
+ mStatusBarOptionalLazy.get().ifPresent(
+ statusBar -> statusBar.animateExpandSettingsPanel(null));
}
private void handlePowerDialog() {
@@ -425,7 +428,9 @@ public class SystemActions extends SystemUI {
}
private void handleAccessibilityDismissNotificationShade() {
- mStatusBar.get().animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, false /* force */);
+ mStatusBarOptionalLazy.get().ifPresent(
+ statusBar -> statusBar.animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_NONE, false /* force */));
}
private class SystemActionsBroadcastReceiver extends BroadcastReceiver {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index cee395bd9a6a..32813479dc0a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -91,7 +91,7 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall
final Context windowContext = mContext.createWindowContext(display,
TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null);
final WindowMagnificationController controller = new WindowMagnificationController(
- mContext,
+ windowContext,
mHandler, new SfVsyncFrameCallbackProvider(), null,
new SurfaceControl.Transaction(), mWindowMagnifierCallback, mSysUiState);
return new WindowMagnificationAnimationController(windowContext, controller);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 717c7156f917..0893e895b102 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -28,6 +28,7 @@ import android.annotation.Nullable;
import android.annotation.UiContext;
import android.content.Context;
import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Matrix;
@@ -35,6 +36,7 @@ import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
@@ -76,6 +78,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
MirrorWindowControl.MirrorWindowDelegate, MagnificationGestureDetector.OnGestureListener {
private static final String TAG = "WindowMagnificationController";
+ @SuppressWarnings("isloggabletaglength")
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Build.IS_DEBUGGABLE;
// Delay to avoid updating state description too frequently.
private static final int UPDATE_STATE_DESCRIPTION_DELAY_MS = 100;
// It should be consistent with the value defined in WindowMagnificationGestureHandler.
@@ -158,14 +162,15 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mRotation = display.getRotation();
mWm = context.getSystemService(WindowManager.class);
- mWindowBounds = mWm.getCurrentWindowMetrics().getBounds();
+ mWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds());
mResources = mContext.getResources();
mScale = mResources.getInteger(R.integer.magnification_default_scale);
mBounceEffectDuration = mResources.getInteger(
com.android.internal.R.integer.config_shortAnimTime);
updateDimensions();
- setInitialStartBounds();
+ setMagnificationFrameWith(mWindowBounds, mWindowBounds.width() / 2,
+ mWindowBounds.height() / 2);
computeBounceAnimationScale();
mMirrorWindowControl = mirrorWindowControl;
@@ -286,18 +291,59 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
* @param configDiff a bit mask of the differences between the configurations
*/
void onConfigurationChanged(int configDiff) {
+ if (DEBUG) {
+ Log.d(TAG, "onConfigurationChanged = " + Configuration.configurationDiffToString(
+ configDiff));
+ }
+ if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+ onRotate();
+ }
+
+ if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) {
+ updateAccessibilityWindowTitleIfNeeded();
+ }
+
+ boolean reCreateWindow = false;
if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) {
updateDimensions();
computeBounceAnimationScale();
- if (isWindowVisible()) {
- deleteWindowMagnification();
- enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN);
+ reCreateWindow = true;
+ }
+
+ if ((configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) {
+ reCreateWindow |= handleScreenSizeChanged();
+ }
+
+ // Recreate the window again to correct the window appearance due to density or
+ // window size changed not caused by rotation.
+ if (isWindowVisible() && reCreateWindow) {
+ deleteWindowMagnification();
+ enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN);
+ }
+ }
+
+ /**
+ * Calculates the magnification frame if the window bounds is changed.
+ * Note that the orientation also changes the wind bounds, so it should be handled first.
+ *
+ * @return {@code true} if the magnification frame is changed with the new window bounds.
+ */
+ private boolean handleScreenSizeChanged() {
+ final Rect oldWindowBounds = new Rect(mWindowBounds);
+ final Rect currentWindowBounds = mWm.getCurrentWindowMetrics().getBounds();
+
+ if (currentWindowBounds.equals(oldWindowBounds)) {
+ if (DEBUG) {
+ Log.d(TAG, "updateMagnificationFrame -- window bounds is not changed");
}
- } else if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
- onRotate();
- } else if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) {
- updateAccessibilityWindowTitleIfNeeded();
+ return false;
}
+ mWindowBounds.set(currentWindowBounds);
+ final float newCenterX = (getCenterX()) * mWindowBounds.width() / oldWindowBounds.width();
+ final float newCenterY = (getCenterY()) * mWindowBounds.height() / oldWindowBounds.height();
+ setMagnificationFrameWith(mWindowBounds, (int) newCenterX, (int) newCenterY);
+ calculateMagnificationFrameBoundary();
+ return true;
}
private void updateSystemUIStateIfNeeded() {
@@ -311,30 +357,42 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mWm.updateViewLayout(mMirrorView, params);
}
- /** Handles MirrorWindow position when the device rotation changed. */
+ /**
+ * Keep MirrorWindow position on the screen unchanged when device rotates 90° clockwise or
+ * anti-clockwise.
+ */
private void onRotate() {
final Display display = mContext.getDisplay();
final int oldRotation = mRotation;
- mWindowBounds = mWm.getCurrentWindowMetrics().getBounds();
-
- setMagnificationFrameBoundary();
mRotation = display.getRotation();
+ final int rotationDegree = getDegreeFromRotation(mRotation, oldRotation);
+ if (rotationDegree == 0 || rotationDegree == 180) {
+ Log.w(TAG, "onRotate -- rotate with the device. skip it");
+ return;
+ }
+ final Rect currentWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds());
+ if (currentWindowBounds.width() != mWindowBounds.height()
+ || currentWindowBounds.height() != mWindowBounds.width()) {
+ Log.w(TAG, "onRotate -- unexpected window height/width");
+ return;
+ }
+
+ mWindowBounds.set(currentWindowBounds);
+
+ calculateMagnificationFrameBoundary();
if (!isWindowVisible()) {
return;
}
// Keep MirrorWindow position on the screen unchanged when device rotates 90°
// clockwise or anti-clockwise.
- final int rotationDegree = getDegreeFromRotation(mRotation, oldRotation);
+
final Matrix matrix = new Matrix();
matrix.setRotate(rotationDegree);
if (rotationDegree == 90) {
matrix.postTranslate(mWindowBounds.width(), 0);
} else if (rotationDegree == 270) {
matrix.postTranslate(0, mWindowBounds.height());
- } else {
- Log.w(TAG, "Invalid rotation change. " + rotationDegree);
- return;
}
// The rect of MirrorView is going to be transformed.
LayoutParams params =
@@ -440,12 +498,15 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
}
- private void setInitialStartBounds() {
- // Sets the initial frame area for the mirror and places it in the center of the display.
- final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 2
- + 2 * mMirrorSurfaceMargin;
- final int initX = mWindowBounds.width() / 2 - initSize / 2;
- final int initY = mWindowBounds.height() / 2 - initSize / 2;
+ private void setMagnificationFrameWith(Rect windowBounds, int centerX, int centerY) {
+ // Sets the initial frame area for the mirror and place it to the given center on the
+ // display.
+ int initSize = Math.min(windowBounds.width(), windowBounds.height()) / 2;
+ initSize = Math.min(mResources.getDimensionPixelSize(R.dimen.magnification_max_frame_size),
+ initSize);
+ initSize += 2 * mMirrorSurfaceMargin;
+ final int initX = centerX - initSize / 2;
+ final int initY = centerY - initSize / 2;
mMagnificationFrame.set(initX, initY, initX + initSize, initY + initSize);
}
@@ -553,7 +614,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mSourceBounds.set(left, top, right, bottom);
}
- private void setMagnificationFrameBoundary() {
+ private void calculateMagnificationFrameBoundary() {
// Calculates width and height for magnification frame could exceed out the screen.
// TODO : re-calculating again when scale is changed.
// The half width of magnification frame.
@@ -644,7 +705,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
: centerY - mMagnificationFrame.exactCenterY();
mScale = Float.isNaN(scale) ? mScale : scale;
- setMagnificationFrameBoundary();
+ calculateMagnificationFrameBoundary();
updateMagnificationFramePosition((int) offsetX, (int) offsetY);
if (!isWindowVisible()) {
createMirrorWindow();
@@ -764,6 +825,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
pw.println(" mOverlapWithGestureInsets:" + mOverlapWithGestureInsets);
pw.println(" mScale:" + mScale);
pw.println(" mMirrorViewBounds:" + (isWindowVisible() ? mMirrorViewBounds : "empty"));
+ pw.println(" mSourceBounds:"
+ + (isWindowVisible() ? mSourceBounds : "empty"));
pw.println(" mSystemGestureTop:" + mSystemGestureTop);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
index 05256e646948..f182e772f75e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
@@ -41,6 +41,7 @@ import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Prefs;
+import com.android.systemui.shared.system.SysUiStatsLog;
/**
* Contains logic for an accessibility floating menu view.
@@ -177,6 +178,9 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
}
private void onDragEnd(Position position) {
+ SysUiStatsLog.write(SysUiStatsLog.ACCESSIBILITY_FLOATING_MENU_UI_CHANGED,
+ position.getPercentageX(), position.getPercentageY(),
+ mContext.getResources().getConfiguration().orientation);
savePosition(mContext, position);
showDockTooltipIfNecessary(mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
index 17818cd9c7ee..59d9aff2ef46 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
@@ -19,8 +19,9 @@ package com.android.systemui.accessibility.floatingmenu;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.util.MathUtils.constrain;
import static android.util.MathUtils.sq;
+import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
-import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.systemBars;
import static java.util.Objects.requireNonNull;
@@ -41,7 +42,6 @@ import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Handler;
import android.os.Looper;
-import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -95,7 +95,6 @@ public class AccessibilityFloatingMenuView extends FrameLayout
private boolean mIsShowing;
private boolean mIsDownInEnlargedTouchArea;
private boolean mIsDragging = false;
- private boolean mImeVisibility;
@Alignment
private int mAlignment;
@SizeType
@@ -108,8 +107,10 @@ public class AccessibilityFloatingMenuView extends FrameLayout
private int mRadiusType;
private int mMargin;
private int mPadding;
- private int mScreenHeight;
- private int mScreenWidth;
+ // The display width excludes the window insets of the system bar and display cutout.
+ private int mDisplayHeight;
+ // The display Height excludes the window insets of the system bar and display cutout.
+ private int mDisplayWidth;
private int mIconWidth;
private int mIconHeight;
private int mInset;
@@ -118,6 +119,8 @@ public class AccessibilityFloatingMenuView extends FrameLayout
private int mRelativeToPointerDownX;
private int mRelativeToPointerDownY;
private float mRadius;
+ private final Rect mDisplayInsetsRect = new Rect();
+ private final Rect mImeInsetsRect = new Rect();
private final Position mPosition;
private float mSquareScaledTouchSlop;
private final Configuration mLastConfiguration;
@@ -506,9 +509,21 @@ public class AccessibilityFloatingMenuView extends FrameLayout
}
private WindowInsets onWindowInsetsApplied(WindowInsets insets) {
- final boolean currentImeVisibility = insets.isVisible(ime());
- if (currentImeVisibility != mImeVisibility) {
- mImeVisibility = currentImeVisibility;
+ final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
+ final Rect displayWindowInsetsRect = getDisplayInsets(windowMetrics).toRect();
+ if (!displayWindowInsetsRect.equals(mDisplayInsetsRect)) {
+ updateDisplaySizeWith(windowMetrics);
+ updateLocationWith(mPosition);
+ }
+
+ final Rect imeInsetsRect = windowMetrics.getWindowInsets().getInsets(ime()).toRect();
+ if (!imeInsetsRect.equals(mImeInsetsRect)) {
+ if (isImeVisible(imeInsetsRect)) {
+ mImeInsetsRect.set(imeInsetsRect);
+ } else {
+ mImeInsetsRect.setEmpty();
+ }
+
updateLocationWith(mPosition);
}
@@ -520,6 +535,11 @@ public class AccessibilityFloatingMenuView extends FrameLayout
|| (side == Alignment.LEFT && downX > currentRawX);
}
+ private boolean isImeVisible(Rect imeInsetsRect) {
+ return imeInsetsRect.left != 0 || imeInsetsRect.top != 0 || imeInsetsRect.right != 0
+ || imeInsetsRect.bottom != 0;
+ }
+
private boolean hasExceededTouchSlop(int startX, int startY, int endX, int endY) {
return (sq(endX - startX) + sq(endY - startY)) > mSquareScaledTouchSlop;
}
@@ -546,9 +566,9 @@ public class AccessibilityFloatingMenuView extends FrameLayout
private void updateDimensions() {
final Resources res = getResources();
- final DisplayMetrics dm = res.getDisplayMetrics();
- mScreenWidth = dm.widthPixels;
- mScreenHeight = dm.heightPixels;
+
+ updateDisplaySizeWith(mWindowManager.getCurrentWindowMetrics());
+
mMargin =
res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin);
mInset =
@@ -560,6 +580,15 @@ public class AccessibilityFloatingMenuView extends FrameLayout
updateItemViewDimensionsWith(mSizeType);
}
+ private void updateDisplaySizeWith(WindowMetrics metrics) {
+ final Rect displayBounds = metrics.getBounds();
+ final Insets displayInsets = getDisplayInsets(metrics);
+ mDisplayInsetsRect.set(displayInsets.toRect());
+ displayBounds.inset(displayInsets);
+ mDisplayWidth = displayBounds.width();
+ mDisplayHeight = displayBounds.height();
+ }
+
private void updateItemViewDimensionsWith(@SizeType int sizeType) {
final Resources res = getResources();
final int paddingResId =
@@ -684,11 +713,11 @@ public class AccessibilityFloatingMenuView extends FrameLayout
}
private int getMaxWindowX() {
- return mScreenWidth - getMarginStartEndWith(mLastConfiguration) - getLayoutWidth();
+ return mDisplayWidth - getMarginStartEndWith(mLastConfiguration) - getLayoutWidth();
}
private int getMaxWindowY() {
- return mScreenHeight - getWindowHeight();
+ return mDisplayHeight - getWindowHeight();
}
private InstantInsetLayerDrawable getMenuLayerDrawable() {
@@ -699,8 +728,13 @@ public class AccessibilityFloatingMenuView extends FrameLayout
return (GradientDrawable) getMenuLayerDrawable().getDrawable(INDEX_MENU_ITEM);
}
+ private Insets getDisplayInsets(WindowMetrics metrics) {
+ return metrics.getWindowInsets().getInsetsIgnoringVisibility(
+ systemBars() | displayCutout());
+ }
+
/**
- * Updates the floating menu to be fixed at the side of the screen.
+ * Updates the floating menu to be fixed at the side of the display.
*/
private void updateLocationWith(Position position) {
final @Alignment int alignment = transformToAlignment(position.getPercentageX());
@@ -716,15 +750,9 @@ public class AccessibilityFloatingMenuView extends FrameLayout
* @return the moving interval if they overlap each other, otherwise 0.
*/
private int getInterval() {
- if (!mImeVisibility) {
- return 0;
- }
-
- final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
- final Insets imeInsets = windowMetrics.getWindowInsets().getInsets(
- ime() | navigationBars());
- final int imeY = mScreenHeight - imeInsets.bottom;
- final int layoutBottomY = mCurrentLayoutParams.y + getWindowHeight();
+ final int currentLayoutY = (int) (mPosition.getPercentageY() * getMaxWindowY());
+ final int imeY = mDisplayHeight - mImeInsetsRect.bottom;
+ final int layoutBottomY = currentLayoutY + getWindowHeight();
return layoutBottomY > imeY ? (layoutBottomY - imeY) : 0;
}
@@ -855,11 +883,12 @@ public class AccessibilityFloatingMenuView extends FrameLayout
@VisibleForTesting
Rect getAvailableBounds() {
- return new Rect(0, 0, mScreenWidth - getWindowWidth(), mScreenHeight - getWindowHeight());
+ return new Rect(0, 0, mDisplayWidth - getWindowWidth(),
+ mDisplayHeight - getWindowHeight());
}
private int getMaxLayoutHeight() {
- return mScreenHeight - mMargin * 2;
+ return mDisplayHeight - mMargin * 2;
}
private int getLayoutWidth() {
@@ -875,7 +904,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
}
private int getWindowHeight() {
- return Math.min(mScreenHeight, mMargin * 2 + getLayoutHeight());
+ return Math.min(mDisplayHeight, mMargin * 2 + getLayoutHeight());
}
private void setSystemGestureExclusion() {
diff --git a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
index 169a9c0c6eac..f13730e602a0 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
@@ -70,7 +70,7 @@ public final class PhoneStateMonitor {
};
private final Context mContext;
- private final Optional<Lazy<StatusBar>> mStatusBarOptionalLazy;
+ private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
private final StatusBarStateController mStatusBarStateController;
private boolean mLauncherShowing;
@@ -78,7 +78,7 @@ public final class PhoneStateMonitor {
@Inject
PhoneStateMonitor(Context context, BroadcastDispatcher broadcastDispatcher,
- Optional<Lazy<StatusBar>> statusBarOptionalLazy, BootCompleteCache bootCompleteCache) {
+ Lazy<Optional<StatusBar>> statusBarOptionalLazy, BootCompleteCache bootCompleteCache) {
mContext = context;
mStatusBarOptionalLazy = statusBarOptionalLazy;
mStatusBarStateController = Dependency.get(StatusBarStateController.class);
@@ -180,8 +180,7 @@ public final class PhoneStateMonitor {
}
private boolean isBouncerShowing() {
- return mStatusBarOptionalLazy.map(
- statusBarLazy -> statusBarLazy.get().isBouncerShowing()).orElse(false);
+ return mStatusBarOptionalLazy.get().map(StatusBar::isBouncerShowing).orElse(false);
}
private boolean isKeyguardLocked() {
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index deceb951c2bb..39088c367a27 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -13,64 +13,51 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui;
+package com.android.systemui.battery;
import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.annotation.IntDef;
-import android.app.ActivityManager;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.database.ContentObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Handler;
+import android.os.UserHandle;
import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
-import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.StyleRes;
+import androidx.annotation.VisibleForTesting;
import com.android.settingslib.graph.ThemedBatteryDrawable;
+import com.android.systemui.DualToneHandler;
+import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.settings.CurrentUserTracker;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerService.Tunable;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.text.NumberFormat;
-public class BatteryMeterView extends LinearLayout implements
- BatteryStateChangeCallback, Tunable, DarkReceiver, ConfigurationListener {
-
+public class BatteryMeterView extends LinearLayout implements DarkReceiver {
@Retention(SOURCE)
@IntDef({MODE_DEFAULT, MODE_ON, MODE_OFF, MODE_ESTIMATE})
@@ -81,21 +68,14 @@ public class BatteryMeterView extends LinearLayout implements
public static final int MODE_ESTIMATE = 3;
private final ThemedBatteryDrawable mDrawable;
- private final String mSlotBattery;
private final ImageView mBatteryIconView;
- private final CurrentUserTracker mUserTracker;
private TextView mBatteryPercentView;
- private BatteryController mBatteryController;
- private SettingObserver mSettingObserver;
private final @StyleRes int mPercentageStyleId;
private int mTextColor;
private int mLevel;
private int mShowPercentMode = MODE_DEFAULT;
private boolean mShowPercentAvailable;
- // Some places may need to show the battery conditionally, and not obey the tuner
- private boolean mIgnoreTunerUpdates;
- private boolean mIsSubscribedForTunerUpdates;
private boolean mCharging;
// Error state where we know nothing about the current battery state
private boolean mBatteryStateUnknown;
@@ -103,19 +83,19 @@ public class BatteryMeterView extends LinearLayout implements
private Drawable mUnknownStateDrawable;
private DualToneHandler mDualToneHandler;
- private int mUser;
private int mNonAdaptedSingleToneColor;
private int mNonAdaptedForegroundColor;
private int mNonAdaptedBackgroundColor;
+ private BatteryEstimateFetcher mBatteryEstimateFetcher;
+
public BatteryMeterView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- BroadcastDispatcher broadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
@@ -128,14 +108,11 @@ public class BatteryMeterView extends LinearLayout implements
mDrawable = new ThemedBatteryDrawable(context, frameColor);
atts.recycle();
- mSettingObserver = new SettingObserver(new Handler(context.getMainLooper()));
mShowPercentAvailable = context.getResources().getBoolean(
com.android.internal.R.bool.config_battery_percentage_setting_available);
setupLayoutTransition();
- mSlotBattery = context.getString(
- com.android.internal.R.string.status_bar_battery);
mBatteryIconView = new ImageView(context);
mBatteryIconView.setImageDrawable(mDrawable);
final MarginLayoutParams mlp = new MarginLayoutParams(
@@ -150,21 +127,8 @@ public class BatteryMeterView extends LinearLayout implements
// Init to not dark at all.
onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
- mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
- @Override
- public void onUserSwitched(int newUserId) {
- mUser = newUserId;
- getContext().getContentResolver().unregisterContentObserver(mSettingObserver);
- getContext().getContentResolver().registerContentObserver(
- Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver,
- newUserId);
- updateShowPercent();
- }
- };
-
setClipChildren(false);
setClipToPadding(false);
- Dependency.get(ConfigurationController.class).observe(viewAttachLifecycle(this), this);
}
private void setupLayoutTransition() {
@@ -192,6 +156,7 @@ public class BatteryMeterView extends LinearLayout implements
* 0 - No preference
* 1 - Force on
* 2 - Force off
+ * 3 - Estimate
* @param mode desired mode (none, on, off)
*/
public void setPercentShowMode(@BatteryPercentMode int mode) {
@@ -200,42 +165,10 @@ public class BatteryMeterView extends LinearLayout implements
updateShowPercent();
}
- /**
- * Set {@code true} to turn off BatteryMeterView's subscribing to the tuner for updates, and
- * thus avoid it controlling its own visibility
- *
- * @param ignore whether to ignore the tuner or not
- */
- public void setIgnoreTunerUpdates(boolean ignore) {
- mIgnoreTunerUpdates = ignore;
- updateTunerSubscription();
- }
-
- private void updateTunerSubscription() {
- if (mIgnoreTunerUpdates) {
- unsubscribeFromTunerUpdates();
- } else {
- subscribeForTunerUpdates();
- }
- }
-
- private void subscribeForTunerUpdates() {
- if (mIsSubscribedForTunerUpdates || mIgnoreTunerUpdates) {
- return;
- }
-
- Dependency.get(TunerService.class)
- .addTunable(this, StatusBarIconController.ICON_HIDE_LIST);
- mIsSubscribedForTunerUpdates = true;
- }
-
- private void unsubscribeFromTunerUpdates() {
- if (!mIsSubscribedForTunerUpdates) {
- return;
- }
-
- Dependency.get(TunerService.class).removeTunable(this);
- mIsSubscribedForTunerUpdates = false;
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updatePercentView();
}
public void setColorsFromContext(Context context) {
@@ -251,42 +184,7 @@ public class BatteryMeterView extends LinearLayout implements
return false;
}
- @Override
- public void onTuningChanged(String key, String newValue) {
- if (StatusBarIconController.ICON_HIDE_LIST.equals(key)) {
- ArraySet<String> icons = StatusBarIconController.getIconHideList(
- getContext(), newValue);
- setVisibility(icons.contains(mSlotBattery) ? View.GONE : View.VISIBLE);
- }
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- mBatteryController = Dependency.get(BatteryController.class);
- mBatteryController.addCallback(this);
- mUser = ActivityManager.getCurrentUser();
- getContext().getContentResolver().registerContentObserver(
- Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver, mUser);
- getContext().getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME),
- false, mSettingObserver);
- updateShowPercent();
- subscribeForTunerUpdates();
- mUserTracker.startTracking();
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mUserTracker.stopTracking();
- mBatteryController.removeCallback(this);
- getContext().getContentResolver().unregisterContentObserver(mSettingObserver);
- unsubscribeFromTunerUpdates();
- }
-
- @Override
- public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+ void onBatteryLevelChanged(int level, boolean pluggedIn) {
mDrawable.setCharging(pluggedIn);
mDrawable.setBatteryLevel(level);
mCharging = pluggedIn;
@@ -294,8 +192,7 @@ public class BatteryMeterView extends LinearLayout implements
updatePercentText();
}
- @Override
- public void onPowerSaveChanged(boolean isPowerSave) {
+ void onPowerSaveChanged(boolean isPowerSave) {
mDrawable.setPowerSaveEnabled(isPowerSave);
}
@@ -315,19 +212,28 @@ public class BatteryMeterView extends LinearLayout implements
updateShowPercent();
}
- private void updatePercentText() {
+ /**
+ * Sets the fetcher that should be used to get the estimated time remaining for the user's
+ * battery.
+ */
+ void setBatteryEstimateFetcher(BatteryEstimateFetcher fetcher) {
+ mBatteryEstimateFetcher = fetcher;
+ }
+
+ void updatePercentText() {
if (mBatteryStateUnknown) {
setContentDescription(getContext().getString(R.string.accessibility_battery_unknown));
return;
}
- if (mBatteryController == null) {
+ if (mBatteryEstimateFetcher == null) {
return;
}
if (mBatteryPercentView != null) {
if (mShowPercentMode == MODE_ESTIMATE && !mCharging) {
- mBatteryController.getEstimatedTimeRemainingString((String estimate) -> {
+ mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate(
+ (String estimate) -> {
if (mBatteryPercentView == null) {
return;
}
@@ -361,12 +267,12 @@ public class BatteryMeterView extends LinearLayout implements
: R.string.accessibility_battery_level, mLevel));
}
- private void updateShowPercent() {
+ void updateShowPercent() {
final boolean showing = mBatteryPercentView != null;
// TODO(b/140051051)
final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System
.getIntForUser(getContext().getContentResolver(),
- SHOW_BATTERY_PERCENT, 0, mUser));
+ SHOW_BATTERY_PERCENT, 0, UserHandle.USER_CURRENT));
boolean shouldShow =
(mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
|| mShowPercentMode == MODE_ON
@@ -394,11 +300,6 @@ public class BatteryMeterView extends LinearLayout implements
}
}
- @Override
- public void onDensityOrFontScaleChanged() {
- scaleBatteryMeterViews();
- }
-
private Drawable getUnknownStateDrawable() {
if (mUnknownStateDrawable == null) {
mUnknownStateDrawable = mContext.getDrawable(R.drawable.ic_battery_unknown);
@@ -408,8 +309,7 @@ public class BatteryMeterView extends LinearLayout implements
return mUnknownStateDrawable;
}
- @Override
- public void onBatteryUnknownStateChanged(boolean isUnknown) {
+ void onBatteryUnknownStateChanged(boolean isUnknown) {
if (mBatteryStateUnknown == isUnknown) {
return;
}
@@ -428,7 +328,7 @@ public class BatteryMeterView extends LinearLayout implements
/**
* Looks up the scale factor for status bar icons and scales the battery view by that amount.
*/
- private void scaleBatteryMeterViews() {
+ void scaleBatteryMeterViews() {
Resources res = getContext().getResources();
TypedValue typedValue = new TypedValue();
@@ -489,20 +389,15 @@ public class BatteryMeterView extends LinearLayout implements
pw.println(" mMode: " + mShowPercentMode);
}
- private final class SettingObserver extends ContentObserver {
- public SettingObserver(Handler handler) {
- super(handler);
- }
+ @VisibleForTesting
+ CharSequence getBatteryPercentViewText() {
+ return mBatteryPercentView.getText();
+ }
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- super.onChange(selfChange, uri);
- updateShowPercent();
- if (TextUtils.equals(uri.getLastPathSegment(),
- Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME)) {
- // update the text for sure if the estimate in the cache was updated
- updatePercentText();
- }
- }
+ /** An interface that will fetch the estimated time remaining for the user's battery. */
+ public interface BatteryEstimateFetcher {
+ void fetchBatteryTimeRemainingEstimate(
+ BatteryController.EstimateFetchCompletion completion);
}
}
+
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
new file mode 100644
index 000000000000..ae9a32309d45
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -0,0 +1,203 @@
+/*
+ * 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.systemui.battery;
+
+import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
+
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.view.View;
+
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+
+/** Controller for {@link BatteryMeterView}. **/
+public class BatteryMeterViewController extends ViewController<BatteryMeterView> {
+ private final ConfigurationController mConfigurationController;
+ private final TunerService mTunerService;
+ private final ContentResolver mContentResolver;
+ private final BatteryController mBatteryController;
+
+ private final String mSlotBattery;
+ private final SettingObserver mSettingObserver;
+ private final CurrentUserTracker mCurrentUserTracker;
+
+ private final ConfigurationController.ConfigurationListener mConfigurationListener =
+ new ConfigurationController.ConfigurationListener() {
+ @Override
+ public void onDensityOrFontScaleChanged() {
+ mView.scaleBatteryMeterViews();
+ }
+ };
+
+ private final TunerService.Tunable mTunable = new TunerService.Tunable() {
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ if (StatusBarIconController.ICON_HIDE_LIST.equals(key)) {
+ ArraySet<String> icons = StatusBarIconController.getIconHideList(
+ getContext(), newValue);
+ mView.setVisibility(icons.contains(mSlotBattery) ? View.GONE : View.VISIBLE);
+ }
+ }
+ };
+
+ private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback =
+ new BatteryController.BatteryStateChangeCallback() {
+ @Override
+ public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+ mView.onBatteryLevelChanged(level, pluggedIn);
+ }
+
+ @Override
+ public void onPowerSaveChanged(boolean isPowerSave) {
+ mView.onPowerSaveChanged(isPowerSave);
+ }
+
+ @Override
+ public void onBatteryUnknownStateChanged(boolean isUnknown) {
+ mView.onBatteryUnknownStateChanged(isUnknown);
+ }
+ };
+
+ // Some places may need to show the battery conditionally, and not obey the tuner
+ private boolean mIgnoreTunerUpdates;
+ private boolean mIsSubscribedForTunerUpdates;
+
+ @Inject
+ public BatteryMeterViewController(
+ BatteryMeterView view,
+ ConfigurationController configurationController,
+ TunerService tunerService,
+ BroadcastDispatcher broadcastDispatcher,
+ @Main Handler mainHandler,
+ ContentResolver contentResolver,
+ BatteryController batteryController) {
+ super(view);
+ mConfigurationController = configurationController;
+ mTunerService = tunerService;
+ mContentResolver = contentResolver;
+ mBatteryController = batteryController;
+
+ mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
+
+ mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
+ mSettingObserver = new SettingObserver(mainHandler);
+ mCurrentUserTracker = new CurrentUserTracker(broadcastDispatcher) {
+ @Override
+ public void onUserSwitched(int newUserId) {
+ contentResolver.unregisterContentObserver(mSettingObserver);
+ registerShowBatteryPercentObserver(newUserId);
+ mView.updateShowPercent();
+ }
+ };
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mConfigurationController.addCallback(mConfigurationListener);
+ subscribeForTunerUpdates();
+ mBatteryController.addCallback(mBatteryStateChangeCallback);
+
+ registerShowBatteryPercentObserver(ActivityManager.getCurrentUser());
+ registerGlobalBatteryUpdateObserver();
+ mCurrentUserTracker.startTracking();
+
+ mView.updateShowPercent();
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mConfigurationController.removeCallback(mConfigurationListener);
+ unsubscribeFromTunerUpdates();
+ mBatteryController.removeCallback(mBatteryStateChangeCallback);
+
+ mCurrentUserTracker.stopTracking();
+ mContentResolver.unregisterContentObserver(mSettingObserver);
+ }
+
+ /**
+ * Turn off {@link BatteryMeterView}'s subscribing to the tuner for updates, and thus avoid it
+ * controlling its own visibility.
+ */
+ public void ignoreTunerUpdates() {
+ mIgnoreTunerUpdates = true;
+ unsubscribeFromTunerUpdates();
+ }
+
+ private void subscribeForTunerUpdates() {
+ if (mIsSubscribedForTunerUpdates || mIgnoreTunerUpdates) {
+ return;
+ }
+
+ mTunerService.addTunable(mTunable, StatusBarIconController.ICON_HIDE_LIST);
+ mIsSubscribedForTunerUpdates = true;
+ }
+
+ private void unsubscribeFromTunerUpdates() {
+ if (!mIsSubscribedForTunerUpdates) {
+ return;
+ }
+
+ mTunerService.removeTunable(mTunable);
+ mIsSubscribedForTunerUpdates = false;
+ }
+
+ private void registerShowBatteryPercentObserver(int user) {
+ mContentResolver.registerContentObserver(
+ Settings.System.getUriFor(SHOW_BATTERY_PERCENT),
+ false,
+ mSettingObserver,
+ user);
+ }
+
+ private void registerGlobalBatteryUpdateObserver() {
+ mContentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME),
+ false,
+ mSettingObserver);
+ }
+
+ private final class SettingObserver extends ContentObserver {
+ public SettingObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ mView.updateShowPercent();
+ if (TextUtils.equals(uri.getLastPathSegment(),
+ Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME)) {
+ // update the text for sure if the estimate in the cache was updated
+ mView.updatePercentText();
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
index c32c1a2f3e53..48f6431aec69 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
@@ -16,7 +16,6 @@
package com.android.systemui.biometrics;
-import android.annotation.NonNull;
import android.content.Context;
import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -30,6 +29,7 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -163,6 +163,18 @@ public class AuthBiometricFaceView extends AuthBiometricView {
}
@Nullable @VisibleForTesting IconController mFaceIconController;
+ @NonNull private final OnAttachStateChangeListener mOnAttachStateChangeListener =
+ new OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ mFaceIconController.deactivate();
+ }
+ };
public AuthBiometricFaceView(Context context) {
this(context, null);
@@ -181,6 +193,8 @@ public class AuthBiometricFaceView extends AuthBiometricView {
protected void onFinishInflate() {
super.onFinishInflate();
mFaceIconController = new IconController(mContext, mIconView, mIndicatorView);
+
+ addOnAttachStateChangeListener(mOnAttachStateChangeListener);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 60b06378a61a..f11dc9313852 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -824,11 +824,25 @@ public abstract class AuthBiometricView extends LinearLayout {
return new AuthDialog.LayoutParams(width, totalHeight);
}
+ private boolean isLargeDisplay() {
+ return com.android.systemui.util.Utils.shouldUseSplitNotificationShade(getResources());
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int height = MeasureSpec.getSize(heightMeasureSpec);
- final int newWidth = Math.min(width, height);
+
+ final boolean isLargeDisplay = isLargeDisplay();
+
+ final int newWidth;
+ if (isLargeDisplay) {
+ // TODO(b/201811580): Unless we can come up with a one-size-fits-all equation, we may
+ // want to consider moving this to an overlay.
+ newWidth = 2 * Math.min(width, height) / 3;
+ } else {
+ newWidth = Math.min(width, height);
+ }
// Use "newWidth" instead, so the landscape dialog width is the same as the portrait
// width.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 0790af94f287..f4b446b50c9e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -29,6 +29,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.PointF;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricConstants;
@@ -97,7 +98,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
private final Provider<UdfpsController> mUdfpsControllerFactory;
private final Provider<SidefpsController> mSidefpsControllerFactory;
@Nullable private final PointF mFaceAuthSensorLocation;
- @Nullable private final PointF mFingerprintLocation;
+ @Nullable private PointF mFingerprintLocation;
private final Set<Callback> mCallbacks = new HashSet<>();
// TODO: These should just be saved from onSaveState
@@ -114,7 +115,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@VisibleForTesting
IBiometricSysuiReceiver mReceiver;
@VisibleForTesting
- @NonNull final BiometricOrientationEventListener mOrientationListener;
+ @NonNull final BiometricDisplayListener mOrientationListener;
@Nullable private final List<FaceSensorPropertiesInternal> mFaceProps;
@Nullable private List<FingerprintSensorPropertiesInternal> mFpProps;
@Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps;
@@ -459,13 +460,15 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
mSidefpsControllerFactory = sidefpsControllerFactory;
mWindowManager = windowManager;
mUdfpsEnrolledForUser = new SparseBooleanArray();
- mOrientationListener = new BiometricOrientationEventListener(context,
+ mOrientationListener = new BiometricDisplayListener(
+ context,
+ displayManager,
+ handler,
+ BiometricDisplayListener.SensorType.Generic.INSTANCE,
() -> {
onOrientationChanged();
return Unit.INSTANCE;
- },
- displayManager,
- handler);
+ });
mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
@@ -479,9 +482,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
(float) faceAuthLocation[1]);
}
- mFingerprintLocation = new PointF(DisplayUtils.getWidth(mContext) / 2,
- mContext.getResources().getDimensionPixelSize(
- com.android.systemui.R.dimen.physical_fingerprint_sensor_center_screen_location_y));
+ updateFingerprintLocation();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
@@ -489,6 +490,21 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
context.registerReceiver(mBroadcastReceiver, filter);
}
+ private void updateFingerprintLocation() {
+ int xLocation = DisplayUtils.getWidth(mContext) / 2;
+ try {
+ xLocation = mContext.getResources().getDimensionPixelSize(
+ com.android.systemui.R.dimen
+ .physical_fingerprint_sensor_center_screen_location_x);
+ } catch (Resources.NotFoundException e) {
+ }
+ int yLocation = mContext.getResources().getDimensionPixelSize(
+ com.android.systemui.R.dimen.physical_fingerprint_sensor_center_screen_location_y);
+ mFingerprintLocation = new PointF(
+ xLocation,
+ yLocation);
+ }
+
@SuppressWarnings("deprecation")
@Override
public void start() {
@@ -765,6 +781,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+ updateFingerprintLocation();
// Save the state of the current dialog (buttons showing, etc)
if (mCurrentDialog != null) {
@@ -794,6 +811,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
private void onOrientationChanged() {
+ updateFingerprintLocation();
if (mCurrentDialog != null) {
mCurrentDialog.onOrientationChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
index fa50f895f83e..f1e42e0c5454 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
@@ -117,24 +117,6 @@ public class AuthPanelController extends ViewOutlineProvider {
mUseFullScreen = fullScreen;
}
- public ValueAnimator getTranslationAnimator(float relativeTranslationY) {
- final ValueAnimator animator = ValueAnimator.ofFloat(
- mPanelView.getY(), mPanelView.getY() - relativeTranslationY);
- animator.addUpdateListener(animation -> {
- final float translation = (float) animation.getAnimatedValue();
- mPanelView.setTranslationY(translation);
- });
- return animator;
- }
-
- public ValueAnimator getAlphaAnimator(float alpha) {
- final ValueAnimator animator = ValueAnimator.ofFloat(mPanelView.getAlpha(), alpha);
- animator.addUpdateListener(animation -> {
- mPanelView.setAlpha((float) animation.getAnimatedValue());
- });
- return animator;
- }
-
public void updateForContentDimensions(int contentWidth, int contentHeight,
int animateDurationMs) {
if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 2630f119de00..8b04bf59658a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -18,9 +18,9 @@ package com.android.systemui.biometrics
import android.animation.ValueAnimator
import android.content.Context
-import android.content.res.Configuration
import android.graphics.PointF
import android.hardware.biometrics.BiometricSourceType
+import android.util.DisplayMetrics
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
@@ -45,6 +45,7 @@ import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Provider
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.util.leak.RotationUtils
private const val WAKE_AND_UNLOCK_FADE_DURATION = 180L
@@ -123,6 +124,7 @@ class AuthRippleController @Inject constructor(
return
}
+ updateSensorLocation()
if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
fingerprintSensorLocation != null) {
mView.setSensorLocation(fingerprintSensorLocation!!)
@@ -182,7 +184,7 @@ class AuthRippleController @Inject constructor(
}
fun updateSensorLocation() {
- fingerprintSensorLocation = authController.fingerprintSensorLocation
+ updateFingerprintLocation()
faceSensorLocation = authController.faceAuthSensorLocation
fingerprintSensorLocation?.let {
circleReveal = CircleReveal(
@@ -197,6 +199,35 @@ class AuthRippleController @Inject constructor(
}
}
+ private fun updateFingerprintLocation() {
+ val displayMetrics = DisplayMetrics()
+ sysuiContext.display?.getRealMetrics(displayMetrics)
+ val width = displayMetrics.widthPixels
+ val height = displayMetrics.heightPixels
+
+ authController.fingerprintSensorLocation?.let {
+ fingerprintSensorLocation = when (RotationUtils.getRotation(sysuiContext)) {
+ RotationUtils.ROTATION_LANDSCAPE -> {
+ val normalizedYPos: Float = it.y / width
+ val normalizedXPos: Float = it.x / height
+ PointF(width * normalizedYPos, height * (1 - normalizedXPos))
+ }
+ RotationUtils.ROTATION_UPSIDE_DOWN -> {
+ PointF(width - it.x, height - it.y)
+ }
+ RotationUtils.ROTATION_SEASCAPE -> {
+ val normalizedYPos: Float = it.y / width
+ val normalizedXPos: Float = it.x / height
+ PointF(width * (1 - normalizedYPos), height * normalizedXPos)
+ }
+ else -> {
+ // ROTATION_NONE
+ PointF(it.x, it.y)
+ }
+ }
+ }
+ }
+
private fun updateRippleColor() {
mView.setColor(
Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor)
@@ -235,18 +266,12 @@ class AuthRippleController @Inject constructor(
private val configurationChangedListener =
object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration?) {
- updateSensorLocation()
- }
override fun onUiModeChanged() {
updateRippleColor()
}
override fun onThemeChanged() {
updateRippleColor()
}
- override fun onOverlayChanged() {
- updateRippleColor()
- }
}
private val udfpsControllerCallback =
@@ -275,7 +300,7 @@ class AuthRippleController @Inject constructor(
private fun updateUdfpsDependentParams() {
authController.udfpsProps?.let {
if (it.size > 0) {
- udfpsRadius = it[0].sensorRadius.toFloat()
+ udfpsRadius = it[0].location.sensorRadius.toFloat()
udfpsController = udfpsControllerProvider.get()
if (mView.isAttachedToWindow) {
@@ -314,10 +339,12 @@ class AuthRippleController @Inject constructor(
}
}
"fingerprint" -> {
+ updateSensorLocation()
pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation")
showRipple(BiometricSourceType.FINGERPRINT)
}
"face" -> {
+ updateSensorLocation()
pw.println("face ripple sensorLocation=$faceSensorLocation")
showRipple(BiometricSourceType.FACE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt
new file mode 100644
index 000000000000..b7404dfeb1cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.systemui.biometrics
+
+import android.content.Context
+import android.hardware.display.DisplayManager
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.os.Handler
+import android.view.Surface
+import com.android.systemui.biometrics.BiometricDisplayListener.SensorType.Generic
+
+/**
+ * A listener for keeping overlays for biometric sensors aligned with the physical device
+ * device's screen. The [onChanged] will be dispatched on the [handler]
+ * whenever a relevant change to the device's configuration (orientation, fold, display change,
+ * etc.) may require the UI to change for the given [sensorType].
+ */
+class BiometricDisplayListener(
+ private val context: Context,
+ private val displayManager: DisplayManager,
+ private val handler: Handler,
+ private val sensorType: SensorType = SensorType.Generic,
+ private val onChanged: () -> Unit
+) : DisplayManager.DisplayListener {
+
+ private var lastRotation = context.display?.rotation ?: Surface.ROTATION_0
+
+ override fun onDisplayAdded(displayId: Int) {}
+ override fun onDisplayRemoved(displayId: Int) {}
+ override fun onDisplayChanged(displayId: Int) {
+ val rotationChanged = didRotationChange()
+
+ when (sensorType) {
+ is SensorType.SideFingerprint -> onChanged()
+ else -> {
+ if (rotationChanged) {
+ onChanged()
+ }
+ }
+ }
+ }
+
+ private fun didRotationChange(): Boolean {
+ val rotation = context.display?.rotation ?: return false
+ val last = lastRotation
+ lastRotation = rotation
+ return last != rotation
+ }
+
+ /** Listen for changes. */
+ fun enable() {
+ displayManager.registerDisplayListener(this, handler)
+ }
+
+ /** Stop listening for changes. */
+ fun disable() {
+ displayManager.unregisterDisplayListener(this)
+ }
+
+ /**
+ * Type of sensor to determine what kind of display changes require layouts.
+ *
+ * The [Generic] type should be used in cases where the modality can vary, such as
+ * biometric prompt (and this object will likely change as multi-mode auth is added).
+ */
+ sealed class SensorType {
+ object Generic : SensorType()
+ data class UnderDisplayFingerprint(
+ val properties: FingerprintSensorPropertiesInternal
+ ) : SensorType()
+ data class SideFingerprint(
+ val properties: FingerprintSensorPropertiesInternal
+ ) : SensorType()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricOrientationEventListener.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricOrientationEventListener.kt
deleted file mode 100644
index 98a03a1c444b..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricOrientationEventListener.kt
+++ /dev/null
@@ -1,57 +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.systemui.biometrics
-
-import android.content.Context
-import android.hardware.display.DisplayManager
-import android.os.Handler
-import android.view.OrientationEventListener
-import android.view.Surface
-
-/**
- * An [OrientationEventListener] that invokes the [onOrientationChanged] callback whenever
- * the orientation of the device has changed in order to keep overlays for biometric sensors
- * aligned with the device's screen.
- */
-class BiometricOrientationEventListener(
- private val context: Context,
- private val onOrientationChanged: () -> Unit,
- private val displayManager: DisplayManager,
- private val handler: Handler
-) : DisplayManager.DisplayListener {
-
- private var lastRotation = context.display?.rotation ?: Surface.ROTATION_0
-
- override fun onDisplayAdded(displayId: Int) {}
- override fun onDisplayRemoved(displayId: Int) {}
- override fun onDisplayChanged(displayId: Int) {
- val rotation = context.display?.rotation ?: return
- if (lastRotation != rotation) {
- lastRotation = rotation
-
- onOrientationChanged()
- }
- }
-
- fun enable() {
- displayManager.registerDisplayListener(this, handler)
- }
-
- fun disable() {
- displayManager.unregisterDisplayListener(this)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS b/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
index 947466f3baaf..adb10f01b5e1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
@@ -1,3 +1,4 @@
set noparent
include /services/core/java/com/android/server/biometrics/OWNERS
+beverlyt@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java
deleted file mode 100644
index 8f6e2498a00b..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java
+++ /dev/null
@@ -1,239 +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.systemui.biometrics;
-
-import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.internal.util.Preconditions.checkNotNull;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.PixelFormat;
-import android.hardware.display.DisplayManager;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.fingerprint.ISidefpsController;
-import android.os.Handler;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.Surface;
-import android.view.WindowManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import javax.inject.Inject;
-
-import kotlin.Unit;
-
-/**
- * Shows and hides the side fingerprint sensor (side-fps) overlay and handles side fps touch events.
- */
-@SysUISingleton
-public class SidefpsController {
- private static final String TAG = "SidefpsController";
- @NonNull private final Context mContext;
- @NonNull private final LayoutInflater mInflater;
- private final FingerprintManager mFingerprintManager;
- private final WindowManager mWindowManager;
- private final DelayableExecutor mFgExecutor;
- @VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener;
-
- // TODO: update mDisplayHeight and mDisplayWidth for multi-display devices
- private final int mDisplayHeight;
- private final int mDisplayWidth;
-
- private boolean mIsVisible = false;
- @Nullable private SidefpsView mView;
-
- static final int SFPS_AFFORDANCE_WIDTH = 50; // in default portrait mode
-
- @NonNull
- private final ISidefpsController mSidefpsControllerImpl = new ISidefpsController.Stub() {
- @Override
- public void show() {
- mFgExecutor.execute(() -> {
- SidefpsController.this.show();
- mIsVisible = true;
- });
- }
-
- @Override
- public void hide() {
- mFgExecutor.execute(() -> {
- SidefpsController.this.hide();
- mIsVisible = false;
- });
- }
- };
-
- @VisibleForTesting
- final FingerprintSensorPropertiesInternal mSensorProps;
- private final WindowManager.LayoutParams mCoreLayoutParams;
-
- @Inject
- public SidefpsController(@NonNull Context context,
- @NonNull LayoutInflater inflater,
- @Nullable FingerprintManager fingerprintManager,
- @NonNull WindowManager windowManager,
- @Main DelayableExecutor fgExecutor,
- @NonNull DisplayManager displayManager,
- @Main Handler handler) {
- mContext = context;
- mInflater = inflater;
- mFingerprintManager = checkNotNull(fingerprintManager);
- mWindowManager = windowManager;
- mFgExecutor = fgExecutor;
- mOrientationListener = new BiometricOrientationEventListener(
- context,
- () -> {
- onOrientationChanged();
- return Unit.INSTANCE;
- },
- displayManager,
- handler);
-
- mSensorProps = findFirstSidefps();
- checkArgument(mSensorProps != null);
-
- mCoreLayoutParams = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
- getCoreLayoutParamFlags(),
- PixelFormat.TRANSLUCENT);
- mCoreLayoutParams.setTitle(TAG);
- // Overrides default, avoiding status bars during layout
- mCoreLayoutParams.setFitInsetsTypes(0);
- mCoreLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
- mCoreLayoutParams.layoutInDisplayCutoutMode =
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-
- DisplayMetrics displayMetrics = new DisplayMetrics();
- windowManager.getDefaultDisplay().getMetrics(displayMetrics);
- mDisplayHeight = displayMetrics.heightPixels;
- mDisplayWidth = displayMetrics.widthPixels;
-
- mFingerprintManager.setSidefpsController(mSidefpsControllerImpl);
- }
-
- private void show() {
- mView = (SidefpsView) mInflater.inflate(R.layout.sidefps_view, null, false);
- mView.setSensorProperties(mSensorProps);
- mWindowManager.addView(mView, computeLayoutParams());
-
- mOrientationListener.enable();
- }
-
- private void hide() {
- if (mView != null) {
- mWindowManager.removeView(mView);
- mView.setOnTouchListener(null);
- mView.setOnHoverListener(null);
- mView = null;
- } else {
- Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
- }
-
- mOrientationListener.disable();
- }
-
- private void onOrientationChanged() {
- // If mView is null or if view is hidden, then return.
- if (mView == null || !mIsVisible) {
- return;
- }
-
- // If the overlay needs to be displayed with a new configuration, destroy the current
- // overlay, and re-create and show the overlay with the updated LayoutParams.
- hide();
- show();
- }
-
- @Nullable
- private FingerprintSensorPropertiesInternal findFirstSidefps() {
- for (FingerprintSensorPropertiesInternal props :
- mFingerprintManager.getSensorPropertiesInternal()) {
- if (props.isAnySidefpsType()) {
- // TODO(b/188690214): L155-L173 can be removed once sensorLocationX,
- // sensorLocationY, and sensorRadius are defined in sensorProps by the HAL
- int sensorLocationX = 25;
- int sensorLocationY = 610;
- int sensorRadius = 112;
-
- FingerprintSensorPropertiesInternal tempProps =
- new FingerprintSensorPropertiesInternal(
- props.sensorId,
- props.sensorStrength,
- props.maxEnrollmentsPerUser,
- props.componentInfo,
- props.sensorType,
- props.resetLockoutRequiresHardwareAuthToken,
- sensorLocationX,
- sensorLocationY,
- sensorRadius
- );
- props = tempProps;
- return props;
- }
- }
- return null;
- }
-
- private int getCoreLayoutParamFlags() {
- return WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
- }
-
- /**
- * Computes layout params depending on orientation & folding configuration of device
- */
- private WindowManager.LayoutParams computeLayoutParams() {
- mCoreLayoutParams.flags = getCoreLayoutParamFlags();
- // Y value of top of affordance in portrait mode, X value of left of affordance in landscape
- int sfpsLocationY = mSensorProps.sensorLocationY - mSensorProps.sensorRadius;
- int sfpsAffordanceHeight = mSensorProps.sensorRadius * 2;
-
- // Calculate coordinates of drawable area for the fps affordance, accounting for orientation
- switch (mContext.getDisplay().getRotation()) {
- case Surface.ROTATION_90:
- mCoreLayoutParams.x = sfpsLocationY;
- mCoreLayoutParams.y = 0;
- mCoreLayoutParams.height = SFPS_AFFORDANCE_WIDTH;
- mCoreLayoutParams.width = sfpsAffordanceHeight;
- break;
- case Surface.ROTATION_270:
- mCoreLayoutParams.x = mDisplayHeight - sfpsLocationY - sfpsAffordanceHeight;
- mCoreLayoutParams.y = mDisplayWidth - SFPS_AFFORDANCE_WIDTH;
- mCoreLayoutParams.height = SFPS_AFFORDANCE_WIDTH;
- mCoreLayoutParams.width = sfpsAffordanceHeight;
- break;
- default: // Portrait
- mCoreLayoutParams.x = mDisplayWidth - SFPS_AFFORDANCE_WIDTH;
- mCoreLayoutParams.y = sfpsLocationY;
- mCoreLayoutParams.height = sfpsAffordanceHeight;
- mCoreLayoutParams.width = SFPS_AFFORDANCE_WIDTH;
- }
- return mCoreLayoutParams;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
new file mode 100644
index 000000000000..b7398d86c16e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
@@ -0,0 +1,219 @@
+/*
+ * 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.systemui.biometrics
+
+import android.content.Context
+import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.graphics.Rect
+import android.hardware.biometrics.BiometricOverlayConstants
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+import android.hardware.display.DisplayManager
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.hardware.fingerprint.ISidefpsController
+import android.os.Handler
+import android.util.Log
+import android.view.Display
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.Surface
+import android.view.View
+import android.view.WindowManager
+import androidx.annotation.RawRes
+import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.model.KeyPath
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.concurrency.DelayableExecutor
+import javax.inject.Inject
+
+private const val TAG = "SidefpsController"
+
+/**
+ * Shows and hides the side fingerprint sensor (side-fps) overlay and handles side fps touch events.
+ */
+@SysUISingleton
+class SidefpsController @Inject constructor(
+ private val context: Context,
+ private val layoutInflater: LayoutInflater,
+ fingerprintManager: FingerprintManager?,
+ private val windowManager: WindowManager,
+ @Main mainExecutor: DelayableExecutor,
+ displayManager: DisplayManager,
+ @Main handler: Handler
+) {
+ @VisibleForTesting
+ val sensorProps: FingerprintSensorPropertiesInternal = fingerprintManager
+ ?.sensorPropertiesInternal
+ ?.firstOrNull { it.isAnySidefpsType }
+ ?: throw IllegalStateException("no side fingerprint sensor")
+
+ @VisibleForTesting
+ val orientationListener = BiometricDisplayListener(
+ context,
+ displayManager,
+ handler,
+ BiometricDisplayListener.SensorType.SideFingerprint(sensorProps)
+ ) { onOrientationChanged() }
+
+ private var overlayView: View? = null
+ set(value) {
+ field?.let { oldView ->
+ windowManager.removeView(oldView)
+ orientationListener.disable()
+ }
+ field = value
+ field?.let { newView ->
+ windowManager.addView(newView, overlayViewParams)
+ orientationListener.enable()
+ }
+ }
+
+ private val overlayViewParams = WindowManager.LayoutParams(
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+ PixelFormat.TRANSLUCENT
+ ).apply {
+ title = TAG
+ fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
+ gravity = Gravity.TOP or Gravity.LEFT
+ layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+ }
+
+ init {
+ fingerprintManager?.setSidefpsController(object : ISidefpsController.Stub() {
+ override fun show(
+ sensorId: Int,
+ @BiometricOverlayConstants.ShowReason reason: Int
+ ) = if (reason.isReasonToShow()) doShow() else hide(sensorId)
+
+ private fun doShow() = mainExecutor.execute {
+ if (overlayView == null) {
+ overlayView = createOverlayForDisplay()
+ } else {
+ Log.v(TAG, "overlay already shown")
+ }
+ }
+
+ override fun hide(sensorId: Int) = mainExecutor.execute { overlayView = null }
+ })
+ }
+
+ private fun onOrientationChanged() {
+ if (overlayView != null) {
+ overlayView = createOverlayForDisplay()
+ }
+ }
+
+ private fun createOverlayForDisplay(): View {
+ val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
+ val display = context.display!!
+
+ val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
+ lottie.setAnimation(display.asSideFpsAnimation())
+ view.rotation = display.asSideFpsAnimationRotation()
+
+ updateOverlayParams(display, lottie.composition?.bounds ?: Rect())
+ lottie.addLottieOnCompositionLoadedListener {
+ if (overlayView == view) {
+ updateOverlayParams(display, it.bounds)
+ windowManager.updateViewLayout(overlayView, overlayViewParams)
+ }
+ }
+ lottie.addOverlayDynamicColor(context)
+
+ return view
+ }
+
+ private fun updateOverlayParams(display: Display, bounds: Rect) {
+ val isPortrait = display.isPortrait()
+ val size = windowManager.maximumWindowMetrics.bounds
+ val displayWidth = if (isPortrait) size.width() else size.height()
+ val displayHeight = if (isPortrait) size.height() else size.width()
+ val offsets = sensorProps.getLocation(display.uniqueId).let { location ->
+ if (location == null) {
+ Log.w(TAG, "No location specified for display: ${display.uniqueId}")
+ }
+ location ?: sensorProps.location
+ }
+
+ // ignore sensorLocationX and sensorRadius since it's assumed to be on the side
+ // of the device and centered at sensorLocationY
+ val (x, y) = when (display.rotation) {
+ Surface.ROTATION_90 ->
+ Pair(offsets.sensorLocationY, 0)
+ Surface.ROTATION_270 ->
+ Pair(displayHeight - offsets.sensorLocationY - bounds.width(), displayWidth)
+ Surface.ROTATION_180 ->
+ Pair(0, displayHeight - offsets.sensorLocationY - bounds.height())
+ else ->
+ Pair(displayWidth, offsets.sensorLocationY)
+ }
+ overlayViewParams.x = x
+ overlayViewParams.y = y
+ }
+}
+
+@BiometricOverlayConstants.ShowReason
+private fun Int.isReasonToShow(): Boolean = when (this) {
+ REASON_AUTH_KEYGUARD -> false
+ else -> true
+}
+
+@RawRes
+private fun Display.asSideFpsAnimation(): Int = when (rotation) {
+ Surface.ROTATION_0 -> R.raw.sfps_pulse
+ Surface.ROTATION_180 -> R.raw.sfps_pulse
+ else -> R.raw.sfps_pulse_landscape
+}
+
+private fun Display.asSideFpsAnimationRotation(): Float = when (rotation) {
+ Surface.ROTATION_180 -> 180f
+ Surface.ROTATION_270 -> 180f
+ else -> 0f
+}
+
+private fun Display.isPortrait(): Boolean =
+ rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
+
+private fun LottieAnimationView.addOverlayDynamicColor(context: Context) {
+ fun update() {
+ val c = context.getColor(R.color.biometric_dialog_accent)
+ for (key in listOf(".blue600", ".blue400")) {
+ addValueCallback(
+ KeyPath(key, "**"),
+ LottieProperty.COLOR_FILTER
+ ) { PorterDuffColorFilter(c, PorterDuff.Mode.SRC_ATOP) }
+ }
+ }
+
+ if (composition != null) {
+ update()
+ } else {
+ addLottieOnCompositionLoadedListener { update() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsView.java
deleted file mode 100644
index 4fc59d60ff62..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsView.java
+++ /dev/null
@@ -1,107 +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.systemui.biometrics;
-
-import static com.android.systemui.biometrics.SidefpsController.SFPS_AFFORDANCE_WIDTH;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.RectF;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.util.AttributeSet;
-import android.view.Surface;
-import android.widget.FrameLayout;
-
-/**
- * A view containing a normal drawable view for sidefps events.
- */
-public class SidefpsView extends FrameLayout {
- private static final String TAG = "SidefpsView";
- private static final int POINTER_SIZE_PX = 50;
- private static final int ROUND_RADIUS = 15;
-
- @NonNull private final RectF mSensorRect;
- @NonNull private final Paint mSensorRectPaint;
- @NonNull private final Paint mPointerText;
- @NonNull private final Context mContext;
-
- // Used to obtain the sensor location.
- @NonNull private FingerprintSensorPropertiesInternal mSensorProps;
- @Surface.Rotation private int mOrientation;
-
- public SidefpsView(Context context, AttributeSet attrs) {
- super(context, attrs);
- super.setWillNotDraw(false);
- mContext = context;
- mPointerText = new Paint(0 /* flags */);
- mPointerText.setAntiAlias(true);
- mPointerText.setColor(Color.WHITE);
- mPointerText.setTextSize(POINTER_SIZE_PX);
-
- mSensorRect = new RectF();
- mSensorRectPaint = new Paint(0 /* flags */);
- mSensorRectPaint.setAntiAlias(true);
- mSensorRectPaint.setColor(Color.BLUE); // TODO: Fix Color
- mSensorRectPaint.setStyle(Paint.Style.FILL);
- }
-
- void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) {
- mSensorProps = properties;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- canvas.drawRoundRect(mSensorRect, ROUND_RADIUS, ROUND_RADIUS, mSensorRectPaint);
- int x, y;
- if (mOrientation == Surface.ROTATION_90 || mOrientation == Surface.ROTATION_270) {
- x = mSensorProps.sensorRadius + 10;
- y = SFPS_AFFORDANCE_WIDTH / 2 + 15;
- } else {
- x = SFPS_AFFORDANCE_WIDTH / 2 - 10;
- y = mSensorProps.sensorRadius + 30;
- }
- canvas.drawText(
- ">",
- x,
- y,
- mPointerText
- );
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- mOrientation = mContext.getDisplay().getRotation();
- if (mOrientation == Surface.ROTATION_90 || mOrientation == Surface.ROTATION_270) {
- right = mSensorProps.sensorRadius * 2;
- bottom = SFPS_AFFORDANCE_WIDTH;
- } else {
- right = SFPS_AFFORDANCE_WIDTH;
- bottom = mSensorProps.sensorRadius * 2;
- }
-
- mSensorRect.set(
- 0,
- 0,
- right,
- bottom);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java
index b7344fbc6dda..a2e55c0f76e2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java
@@ -28,6 +28,7 @@ import com.android.systemui.util.ViewController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.Optional;
/**
* Handles:
@@ -42,7 +43,7 @@ import java.io.PrintWriter;
abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView>
extends ViewController<T> implements Dumpable {
@NonNull final StatusBarStateController mStatusBarStateController;
- @NonNull final StatusBar mStatusBar;
+ @NonNull final Optional<StatusBar> mStatusBarOptional;
@NonNull final DumpManager mDumpManger;
boolean mNotificationShadeExpanded;
@@ -50,11 +51,11 @@ abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView>
protected UdfpsAnimationViewController(
T view,
@NonNull StatusBarStateController statusBarStateController,
- @NonNull StatusBar statusBar,
+ @NonNull Optional<StatusBar> statusBarOptional,
@NonNull DumpManager dumpManager) {
super(view);
mStatusBarStateController = statusBarStateController;
- mStatusBar = statusBar;
+ mStatusBarOptional = statusBarOptional;
mDumpManger = dumpManager;
}
@@ -62,13 +63,17 @@ abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView>
@Override
protected void onViewAttached() {
- mStatusBar.addExpansionChangedListener(mStatusBarExpansionChangedListener);
+ mStatusBarOptional.ifPresent(
+ statusBar -> statusBar.addExpansionChangedListener(
+ mStatusBarExpansionChangedListener));
mDumpManger.registerDumpable(getDumpTag(), this);
}
@Override
protected void onViewDetached() {
- mStatusBar.removeExpansionChangedListener(mStatusBarExpansionChangedListener);
+ mStatusBarOptional.ifPresent(
+ statusBar -> statusBar.removeExpansionChangedListener(
+ mStatusBarExpansionChangedListener));
mDumpManger.unregisterDumpable(getDumpTag());
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java
index 93d80e29aded..85955e1b5d56 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java
@@ -22,6 +22,8 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.StatusBar;
+import java.util.Optional;
+
/**
* Class that coordinates non-HBM animations for biometric prompt.
*/
@@ -29,9 +31,9 @@ class UdfpsBpViewController extends UdfpsAnimationViewController<UdfpsBpView> {
protected UdfpsBpViewController(
@NonNull UdfpsBpView view,
@NonNull StatusBarStateController statusBarStateController,
- @NonNull StatusBar statusBar,
+ @NonNull Optional<StatusBar> statusBarOptional,
@NonNull DumpManager dumpManager) {
- super(view, statusBarStateController, statusBar, dumpManager);
+ super(view, statusBarStateController, statusBarOptional, dumpManager);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 420ae531b3dd..3a3f22a4fda8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -16,7 +16,7 @@
package com.android.systemui.biometrics;
-import static android.hardware.fingerprint.IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD;
+import static android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;
@@ -32,6 +32,8 @@ import android.content.IntentFilter;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.RectF;
+import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -110,7 +112,7 @@ public class UdfpsController implements DozeReceiver {
@NonNull private final LayoutInflater mInflater;
private final WindowManager mWindowManager;
private final DelayableExecutor mFgExecutor;
- @NonNull private final StatusBar mStatusBar;
+ @NonNull private final Optional<StatusBar> mStatusBarOptional;
@NonNull private final StatusBarStateController mStatusBarStateController;
@NonNull private final KeyguardStateController mKeyguardStateController;
@NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
@@ -125,9 +127,9 @@ public class UdfpsController implements DozeReceiver {
@NonNull private final KeyguardBypassController mKeyguardBypassController;
@NonNull private final ConfigurationController mConfigurationController;
@NonNull private final SystemClock mSystemClock;
+ @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
@NonNull private final UnlockedScreenOffAnimationController
mUnlockedScreenOffAnimationController;
- @VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
@@ -240,8 +242,8 @@ public class UdfpsController implements DozeReceiver {
@NonNull IUdfpsOverlayControllerCallback callback) {
mFgExecutor.execute(() -> {
final UdfpsEnrollHelper enrollHelper;
- if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR
- || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) {
+ if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
+ || reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING) {
enrollHelper = new UdfpsEnrollHelper(mContext, mFingerprintManager, reason);
} else {
enrollHelper = null;
@@ -254,6 +256,13 @@ public class UdfpsController implements DozeReceiver {
@Override
public void hideUdfpsOverlay(int sensorId) {
mFgExecutor.execute(() -> {
+ if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
+ // if we get here, we expect keyguardUpdateMonitor's fingerprintRunningState
+ // to be updated shortly afterwards
+ Log.d(TAG, "hiding udfps overlay when "
+ + "mKeyguardUpdateMonitor.isFingerprintDetectionRunning()=true");
+ }
+
mServerRequest = null;
updateOverlay();
});
@@ -319,7 +328,7 @@ public class UdfpsController implements DozeReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (mServerRequest != null
- && mServerRequest.mRequestReason != REASON_AUTH_FPM_KEYGUARD
+ && mServerRequest.mRequestReason != REASON_AUTH_KEYGUARD
&& Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, mRequestReason: "
+ mServerRequest.mRequestReason);
@@ -512,7 +521,7 @@ public class UdfpsController implements DozeReceiver {
@NonNull WindowManager windowManager,
@NonNull StatusBarStateController statusBarStateController,
@Main DelayableExecutor fgExecutor,
- @NonNull StatusBar statusBar,
+ @NonNull Optional<StatusBar> statusBarOptional,
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull DumpManager dumpManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -541,7 +550,7 @@ public class UdfpsController implements DozeReceiver {
mFingerprintManager = checkNotNull(fingerprintManager);
mWindowManager = windowManager;
mFgExecutor = fgExecutor;
- mStatusBar = statusBar;
+ mStatusBarOptional = statusBarOptional;
mStatusBarStateController = statusBarStateController;
mKeyguardStateController = keyguardStateController;
mKeyguardViewManager = statusBarKeyguardViewManager;
@@ -554,14 +563,6 @@ public class UdfpsController implements DozeReceiver {
mHbmProvider = hbmProvider.orElse(null);
screenLifecycle.addObserver(mScreenObserver);
mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON;
- mOrientationListener = new BiometricOrientationEventListener(
- context,
- () -> {
- onOrientationChanged();
- return Unit.INSTANCE;
- },
- displayManager,
- mainHandler);
mKeyguardBypassController = keyguardBypassController;
mConfigurationController = configurationController;
mSystemClock = systemClock;
@@ -570,6 +571,15 @@ public class UdfpsController implements DozeReceiver {
mSensorProps = findFirstUdfps();
// At least one UDFPS sensor exists
checkArgument(mSensorProps != null);
+ mOrientationListener = new BiometricDisplayListener(
+ context,
+ displayManager,
+ mainHandler,
+ new BiometricDisplayListener.SensorType.UnderDisplayFingerprint(mSensorProps),
+ () -> {
+ onOrientationChanged();
+ return Unit.INSTANCE;
+ });
mCoreLayoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
@@ -639,10 +649,11 @@ public class UdfpsController implements DozeReceiver {
// on lockscreen and for the udfps light reveal animation on keyguard.
// Keyguard is only shown in portrait mode for now, so this will need to
// be updated if that ever changes.
- return new RectF(mSensorProps.sensorLocationX - mSensorProps.sensorRadius,
- mSensorProps.sensorLocationY - mSensorProps.sensorRadius,
- mSensorProps.sensorLocationX + mSensorProps.sensorRadius,
- mSensorProps.sensorLocationY + mSensorProps.sensorRadius);
+ final SensorLocationInternal location = mSensorProps.getLocation();
+ return new RectF(location.sensorLocationX - location.sensorRadius,
+ location.sensorLocationY - location.sensorRadius,
+ location.sensorLocationX + location.sensorRadius,
+ location.sensorLocationY + location.sensorRadius);
}
private void updateOverlay() {
@@ -680,10 +691,11 @@ public class UdfpsController implements DozeReceiver {
}
// Default dimensions assume portrait mode.
- mCoreLayoutParams.x = mSensorProps.sensorLocationX - mSensorProps.sensorRadius - paddingX;
- mCoreLayoutParams.y = mSensorProps.sensorLocationY - mSensorProps.sensorRadius - paddingY;
- mCoreLayoutParams.height = 2 * mSensorProps.sensorRadius + 2 * paddingX;
- mCoreLayoutParams.width = 2 * mSensorProps.sensorRadius + 2 * paddingY;
+ final SensorLocationInternal location = mSensorProps.getLocation();
+ mCoreLayoutParams.x = location.sensorLocationX - location.sensorRadius - paddingX;
+ mCoreLayoutParams.y = location.sensorLocationY - location.sensorRadius - paddingY;
+ mCoreLayoutParams.height = 2 * location.sensorRadius + 2 * paddingX;
+ mCoreLayoutParams.width = 2 * location.sensorRadius + 2 * paddingY;
Point p = new Point();
// Gets the size based on the current rotation of the display.
@@ -698,9 +710,9 @@ public class UdfpsController implements DozeReceiver {
} else {
Log.v(TAG, "rotate udfps location ROTATION_90");
}
- mCoreLayoutParams.x = mSensorProps.sensorLocationY - mSensorProps.sensorRadius
+ mCoreLayoutParams.x = location.sensorLocationY - location.sensorRadius
- paddingX;
- mCoreLayoutParams.y = p.y - mSensorProps.sensorLocationX - mSensorProps.sensorRadius
+ mCoreLayoutParams.y = p.y - location.sensorLocationX - location.sensorRadius
- paddingY;
break;
@@ -711,9 +723,9 @@ public class UdfpsController implements DozeReceiver {
} else {
Log.v(TAG, "rotate udfps location ROTATION_270");
}
- mCoreLayoutParams.x = p.x - mSensorProps.sensorLocationY - mSensorProps.sensorRadius
+ mCoreLayoutParams.x = p.x - location.sensorLocationY - location.sensorRadius
- paddingX;
- mCoreLayoutParams.y = mSensorProps.sensorLocationX - mSensorProps.sensorRadius
+ mCoreLayoutParams.y = location.sensorLocationX - location.sensorRadius
- paddingY;
break;
@@ -763,9 +775,9 @@ public class UdfpsController implements DozeReceiver {
// This view overlaps the sensor area, so prevent it from being selectable
// during a11y.
- if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR
- || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING
- || reason == IUdfpsOverlayController.REASON_AUTH_BP) {
+ if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
+ || reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING
+ || reason == BiometricOverlayConstants.REASON_AUTH_BP) {
mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
}
@@ -783,8 +795,8 @@ public class UdfpsController implements DozeReceiver {
private UdfpsAnimationViewController inflateUdfpsAnimation(int reason) {
switch (reason) {
- case IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR:
- case IUdfpsOverlayController.REASON_ENROLL_ENROLLING:
+ case BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR:
+ case BiometricOverlayConstants.REASON_ENROLL_ENROLLING:
UdfpsEnrollView enrollView = (UdfpsEnrollView) mInflater.inflate(
R.layout.udfps_enroll_view, null);
mView.addView(enrollView);
@@ -793,17 +805,17 @@ public class UdfpsController implements DozeReceiver {
enrollView,
mServerRequest.mEnrollHelper,
mStatusBarStateController,
- mStatusBar,
+ mStatusBarOptional,
mDumpManager
);
- case IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD:
+ case BiometricOverlayConstants.REASON_AUTH_KEYGUARD:
UdfpsKeyguardView keyguardView = (UdfpsKeyguardView)
mInflater.inflate(R.layout.udfps_keyguard_view, null);
mView.addView(keyguardView);
return new UdfpsKeyguardViewController(
keyguardView,
mStatusBarStateController,
- mStatusBar,
+ mStatusBarOptional,
mKeyguardViewManager,
mKeyguardUpdateMonitor,
mDumpManager,
@@ -814,24 +826,24 @@ public class UdfpsController implements DozeReceiver {
mUnlockedScreenOffAnimationController,
this
);
- case IUdfpsOverlayController.REASON_AUTH_BP:
+ case BiometricOverlayConstants.REASON_AUTH_BP:
// note: empty controller, currently shows no visual affordance
UdfpsBpView bpView = (UdfpsBpView) mInflater.inflate(R.layout.udfps_bp_view, null);
mView.addView(bpView);
return new UdfpsBpViewController(
bpView,
mStatusBarStateController,
- mStatusBar,
+ mStatusBarOptional,
mDumpManager
);
- case IUdfpsOverlayController.REASON_AUTH_FPM_OTHER:
+ case BiometricOverlayConstants.REASON_AUTH_OTHER:
UdfpsFpmOtherView authOtherView = (UdfpsFpmOtherView)
mInflater.inflate(R.layout.udfps_fpm_other_view, null);
mView.addView(authOtherView);
return new UdfpsFpmOtherViewController(
authOtherView,
mStatusBarStateController,
- mStatusBar,
+ mStatusBarOptional,
mDumpManager
);
default:
@@ -877,6 +889,7 @@ public class UdfpsController implements DozeReceiver {
}
if (!mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
+ mKeyguardViewManager.showBouncer(true);
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
index 7ccfb865cd5a..6cc8acf07c50 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.util.Log;
import android.view.Surface;
@@ -93,7 +94,7 @@ public class UdfpsDialogMeasureAdapter {
// Go through each of the children and do the custom measurement.
int totalHeight = 0;
final int numChildren = mView.getChildCount();
- final int sensorDiameter = mSensorProps.sensorRadius * 2;
+ final int sensorDiameter = mSensorProps.getLocation().sensorRadius * 2;
for (int i = 0; i < numChildren; i++) {
final View child = mView.getChildAt(i);
if (child.getId() == R.id.biometric_icon_frame) {
@@ -160,7 +161,7 @@ public class UdfpsDialogMeasureAdapter {
final int horizontalSpacerWidth = calculateHorizontalSpacerWidthForLandscape(
mSensorProps, displayWidth, dialogMargin, horizontalInset);
- final int sensorDiameter = mSensorProps.sensorRadius * 2;
+ final int sensorDiameter = mSensorProps.getLocation().sensorRadius * 2;
final int remeasuredWidth = sensorDiameter + 2 * horizontalSpacerWidth;
int remeasuredHeight = 0;
@@ -257,10 +258,10 @@ public class UdfpsDialogMeasureAdapter {
@NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayHeightPx,
int textIndicatorHeightPx, int buttonBarHeightPx, int dialogMarginPx,
int navbarBottomInsetPx) {
-
+ final SensorLocationInternal location = sensorProperties.getLocation();
final int sensorDistanceFromBottom = displayHeightPx
- - sensorProperties.sensorLocationY
- - sensorProperties.sensorRadius;
+ - location.sensorLocationY
+ - location.sensorRadius;
final int spacerHeight = sensorDistanceFromBottom
- textIndicatorHeightPx
@@ -318,10 +319,10 @@ public class UdfpsDialogMeasureAdapter {
static int calculateHorizontalSpacerWidthForLandscape(
@NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayWidthPx,
int dialogMarginPx, int navbarHorizontalInsetPx) {
-
+ final SensorLocationInternal location = sensorProperties.getLocation();
final int sensorDistanceFromEdge = displayWidthPx
- - sensorProperties.sensorLocationY
- - sensorProperties.sensorRadius;
+ - location.sensorLocationY
+ - location.sensorRadius;
final int horizontalPadding = sensorDistanceFromEdge
- dialogMarginPx
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
index caec1d517761..d5c763d3b6e2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
@@ -20,8 +20,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.PointF;
+import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
@@ -131,7 +131,7 @@ public class UdfpsEnrollHelper {
}
boolean shouldShowProgressBar() {
- return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING;
+ return mEnrollReason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING;
}
void onEnrollmentProgress(int remaining) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index b2a54097539d..11addf0e906b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -40,7 +40,7 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
@Nullable private UdfpsEnrollHelper mEnrollHelper;
@NonNull private List<UdfpsEnrollProgressBarSegment> mSegments = new ArrayList<>();
- private int mTotalSteps = 1;
+ private int mTotalSteps = 0;
private int mProgressSteps = 0;
private boolean mIsShowingHelp = false;
@@ -67,22 +67,19 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
void onEnrollmentProgress(int remaining, int totalSteps) {
mTotalSteps = totalSteps;
- updateState(getProgressSteps(remaining, totalSteps), false /* isShowingHelp */);
+
+ // Show some progress for the initial touch.
+ updateState(Math.max(1, totalSteps - remaining), false /* isShowingHelp */);
}
void onEnrollmentHelp(int remaining, int totalSteps) {
- updateState(getProgressSteps(remaining, totalSteps), true /* isShowingHelp */);
+ updateState(Math.max(0, totalSteps - remaining), true /* isShowingHelp */);
}
void onLastStepAcquired() {
updateState(mTotalSteps, false /* isShowingHelp */);
}
- private static int getProgressSteps(int remaining, int totalSteps) {
- // Show some progress for the initial touch.
- return Math.max(1, totalSteps - remaining);
- }
-
private void updateState(int progressSteps, boolean isShowingHelp) {
updateProgress(progressSteps);
updateFillColor(isShowingHelp);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index 64b096867ec1..729838ed59aa 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -63,7 +63,7 @@ public class UdfpsEnrollView extends UdfpsAnimationView {
void updateSensorLocation(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
View fingerprintAccessibilityView = findViewById(R.id.udfps_enroll_accessibility_view);
- final int sensorHeight = sensorProps.sensorRadius * 2;
+ final int sensorHeight = sensorProps.getLocation().sensorRadius * 2;
final int sensorWidth = sensorHeight;
ViewGroup.LayoutParams params = fingerprintAccessibilityView.getLayoutParams();
params.width = sensorWidth;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index 6cdd1c8b0d4e..af7c3522dc23 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -24,6 +24,8 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.StatusBar;
+import java.util.Optional;
+
/**
* Class that coordinates non-HBM animations during enrollment.
*/
@@ -53,9 +55,9 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp
@NonNull UdfpsEnrollView view,
@NonNull UdfpsEnrollHelper enrollHelper,
@NonNull StatusBarStateController statusBarStateController,
- @NonNull StatusBar statusBar,
+ @NonNull Optional<StatusBar> statusBarOptional,
@NonNull DumpManager dumpManager) {
- super(view, statusBarStateController, statusBar, dumpManager);
+ super(view, statusBarStateController, statusBarOptional, dumpManager);
mEnrollProgressBarRadius = getContext().getResources()
.getInteger(R.integer.config_udfpsEnrollProgressBar);
mEnrollHelper = enrollHelper;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java
index 6e2e4baf492b..dcb5aefc8aa3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java
@@ -22,6 +22,8 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.StatusBar;
+import java.util.Optional;
+
/**
* Class that coordinates non-HBM animations for non keyguard, enrollment or biometric prompt
* states.
@@ -32,9 +34,9 @@ class UdfpsFpmOtherViewController extends UdfpsAnimationViewController<UdfpsFpmO
protected UdfpsFpmOtherViewController(
@NonNull UdfpsFpmOtherView view,
@NonNull StatusBarStateController statusBarStateController,
- @NonNull StatusBar statusBar,
+ @NonNull Optional<StatusBar> statusBarOptional,
@NonNull DumpManager dumpManager) {
- super(view, statusBarStateController, statusBar, dumpManager);
+ super(view, statusBarStateController, statusBarOptional, dumpManager);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
index ea2bbfad1b74..e23131069eab 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
@@ -58,9 +58,6 @@ class UdfpsHapticsSimulator @Inject constructor(
"start" -> {
udfpsController?.playStartHaptic()
}
- "acquired" -> {
- keyguardUpdateMonitor.playAcquiredHaptic()
- }
"success" -> {
// needs to be kept up to date with AcquisitionClient#SUCCESS_VIBRATION_EFFECT
vibrator?.vibrate(
@@ -82,7 +79,6 @@ class UdfpsHapticsSimulator @Inject constructor(
pw.println("Usage: adb shell cmd statusbar udfps-haptic <haptic>")
pw.println("Available commands:")
pw.println(" start")
- pw.println(" acquired")
pw.println(" success, always plays CLICK haptic")
pw.println(" error, always plays DOUBLE_CLICK haptic")
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index c128e5e2ab30..7a28c9d52260 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -39,6 +39,7 @@ import com.android.systemui.util.time.SystemClock;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.Optional;
/**
* Class that coordinates non-HBM animations during keyguard authentication.
@@ -76,7 +77,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
protected UdfpsKeyguardViewController(
@NonNull UdfpsKeyguardView view,
@NonNull StatusBarStateController statusBarStateController,
- @NonNull StatusBar statusBar,
+ @NonNull Optional<StatusBar> statusBarOptional,
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
@NonNull DumpManager dumpManager,
@@ -86,7 +87,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
@NonNull KeyguardStateController keyguardStateController,
@NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
@NonNull UdfpsController udfpsController) {
- super(view, statusBarStateController, statusBar, dumpManager);
+ super(view, statusBarStateController, statusBarOptional, dumpManager);
mKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockScreenShadeTransitionController = transitionController;
@@ -125,7 +126,9 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
mInputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN;
mIsBouncerVisible = mKeyguardViewManager.bouncerIsOrWillBeShowing();
mConfigurationController.addCallback(mConfigurationListener);
- mStatusBar.addExpansionChangedListener(mStatusBarExpansionChangedListener);
+ mStatusBarOptional.ifPresent(
+ statusBar -> statusBar.addExpansionChangedListener(
+ mStatusBarExpansionChangedListener));
updateAlpha();
updatePauseAuth();
@@ -144,7 +147,9 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor);
mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
mConfigurationController.removeCallback(mConfigurationListener);
- mStatusBar.removeExpansionChangedListener(mStatusBarExpansionChangedListener);
+ mStatusBarOptional.ifPresent(
+ statusBar -> statusBar.removeExpansionChangedListener(
+ mStatusBarExpansionChangedListener));
if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) {
mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null);
}
@@ -393,11 +398,6 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
}
@Override
- public void onOverlayChanged() {
- mView.updateColor();
- }
-
- @Override
public void onConfigChanged(Configuration newConfig) {
mView.updateColor();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
index 15f77ffc08fd..6d31ef0e7701 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
@@ -25,6 +25,7 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
+import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Build;
import android.os.UserHandle;
@@ -141,11 +142,12 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin
: mAnimationViewController.getPaddingX();
int paddingY = mAnimationViewController == null ? 0
: mAnimationViewController.getPaddingY();
+ final SensorLocationInternal location = mSensorProps.getLocation();
mSensorRect.set(
paddingX,
paddingY,
- 2 * mSensorProps.sensorRadius + paddingX,
- 2 * mSensorProps.sensorRadius + paddingY);
+ 2 * location.sensorRadius + paddingX,
+ 2 * location.sensorRadius + paddingY);
if (mAnimationViewController != null) {
mAnimationViewController.onSensorRectUpdated(new RectF(mSensorRect));
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 14e5991f35d2..be326da8d3bf 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -37,6 +37,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.sensors.ThresholdSensor;
+import com.android.systemui.util.sensors.ThresholdSensorEvent;
import com.android.systemui.util.time.SystemClock;
import java.util.Collections;
@@ -405,7 +406,7 @@ class FalsingCollectorImpl implements FalsingCollector {
mProximitySensor.unregister(mSensorEventListener);
}
- private void onProximityEvent(ThresholdSensor.ThresholdSensorEvent proximityEvent) {
+ private void onProximityEvent(ThresholdSensorEvent proximityEvent) {
// TODO: some of these classifiers might allow us to abort early, meaning we don't have to
// make these calls.
mFalsingManager.onProximityEvent(new ProximityEventImpl(proximityEvent));
@@ -423,9 +424,9 @@ class FalsingCollectorImpl implements FalsingCollector {
}
private static class ProximityEventImpl implements FalsingManager.ProximityEvent {
- private ThresholdSensor.ThresholdSensorEvent mThresholdSensorEvent;
+ private ThresholdSensorEvent mThresholdSensorEvent;
- ProximityEventImpl(ThresholdSensor.ThresholdSensorEvent thresholdSensorEvent) {
+ ProximityEventImpl(ThresholdSensorEvent thresholdSensorEvent) {
mThresholdSensorEvent = thresholdSensorEvent;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
index 5b33428ff5f1..7c281f942627 100644
--- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
@@ -29,6 +29,7 @@ import com.android.internal.colorextraction.types.Tonal;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import java.io.FileDescriptor;
@@ -50,19 +51,32 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable,
private final GradientColors mBackdropColors;
@Inject
- public SysuiColorExtractor(Context context, ConfigurationController configurationController) {
- this(context, new Tonal(context), configurationController,
- context.getSystemService(WallpaperManager.class), false /* immediately */);
+ public SysuiColorExtractor(
+ Context context,
+ ConfigurationController configurationController,
+ DumpManager dumpManager) {
+ this(
+ context,
+ new Tonal(context),
+ configurationController,
+ context.getSystemService(WallpaperManager.class),
+ dumpManager,
+ false /* immediately */);
}
@VisibleForTesting
- public SysuiColorExtractor(Context context, ExtractionType type,
+ public SysuiColorExtractor(
+ Context context,
+ ExtractionType type,
ConfigurationController configurationController,
- WallpaperManager wallpaperManager, boolean immediately) {
+ WallpaperManager wallpaperManager,
+ DumpManager dumpManager,
+ boolean immediately) {
super(context, type, immediately, wallpaperManager);
mTonal = type instanceof Tonal ? (Tonal) type : new Tonal(context);
mNeutralColorsLock = new GradientColors();
configurationController.addCallback(this);
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
mBackdropColors = new GradientColors();
mBackdropColors.setMainColor(Color.BLACK);
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 72da7f4f893a..5a52fd0516ea 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -120,7 +120,7 @@ class ControlsUiControllerImpl @Inject constructor (
private val onSeedingComplete = Consumer<Boolean> {
accepted ->
if (accepted) {
- selectedStructure = controlsController.get().getFavorites().maxBy {
+ selectedStructure = controlsController.get().getFavorites().maxByOrNull {
it.controls.size
} ?: EMPTY_STRUCTURE
updatePreferences(selectedStructure)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 79ee6a8e8830..fa2384268b15 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -34,11 +34,8 @@ import android.os.UserHandle;
import android.view.Choreographer;
import android.view.IWindowManager;
import android.view.LayoutInflater;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -49,9 +46,7 @@ import com.android.systemui.R;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
import com.android.systemui.accessibility.ModeSwitchesController;
-import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
-import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger;
import com.android.systemui.dagger.qualifiers.Background;
@@ -59,46 +54,25 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.navigationbar.NavigationBarOverlayController;
-import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.plugins.PluginInitializerImpl;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.ReduceBrightColorsController;
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.recents.Recents;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.shared.plugins.PluginManagerImpl;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.shared.system.WindowManagerWrapper;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.theme.ThemeOverlayApplier;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.pip.Pip;
-import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Named;
-import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
@@ -166,9 +140,8 @@ public class DependencyProvider {
/** */
@Provides
@SysUISingleton
- public LeakDetector provideLeakDetector() {
- return LeakDetector.create();
-
+ public LeakDetector provideLeakDetector(DumpManager dumpManager) {
+ return LeakDetector.create(dumpManager);
}
@SuppressLint("MissingPermission")
@@ -188,13 +161,6 @@ public class DependencyProvider {
}
/** */
- @Provides
- @SysUISingleton
- public PluginManager providePluginManager(Context context) {
- return new PluginManagerImpl(context, new PluginInitializerImpl());
- }
-
- /** */
@SysUISingleton
@Provides
static ThemeOverlayApplier provideThemeOverlayManager(Context context,
@@ -210,65 +176,6 @@ public class DependencyProvider {
/** */
@Provides
@SysUISingleton
- public NavigationBarController provideNavigationBarController(Context context,
- WindowManager windowManager,
- Lazy<AssistManager> assistManagerLazy,
- AccessibilityManager accessibilityManager,
- AccessibilityManagerWrapper accessibilityManagerWrapper,
- DeviceProvisionedController deviceProvisionedController,
- MetricsLogger metricsLogger,
- OverviewProxyService overviewProxyService,
- NavigationModeController navigationModeController,
- AccessibilityButtonModeObserver accessibilityButtonModeObserver,
- StatusBarStateController statusBarStateController,
- SysUiState sysUiFlagsContainer,
- BroadcastDispatcher broadcastDispatcher,
- CommandQueue commandQueue,
- Optional<Pip> pipOptional,
- Optional<LegacySplitScreen> splitScreenOptional,
- Optional<Recents> recentsOptional,
- Lazy<StatusBar> statusBarLazy,
- ShadeController shadeController,
- NotificationRemoteInputManager notificationRemoteInputManager,
- NotificationShadeDepthController notificationShadeDepthController,
- SystemActions systemActions,
- @Main Handler mainHandler,
- UiEventLogger uiEventLogger,
- NavigationBarOverlayController navBarOverlayController,
- ConfigurationController configurationController,
- UserTracker userTracker) {
- return new NavigationBarController(context,
- windowManager,
- assistManagerLazy,
- accessibilityManager,
- accessibilityManagerWrapper,
- deviceProvisionedController,
- metricsLogger,
- overviewProxyService,
- navigationModeController,
- accessibilityButtonModeObserver,
- statusBarStateController,
- sysUiFlagsContainer,
- broadcastDispatcher,
- commandQueue,
- pipOptional,
- splitScreenOptional,
- recentsOptional,
- statusBarLazy,
- shadeController,
- notificationRemoteInputManager,
- notificationShadeDepthController,
- systemActions,
- mainHandler,
- uiEventLogger,
- navBarOverlayController,
- configurationController,
- userTracker);
- }
-
- /** */
- @Provides
- @SysUISingleton
public AccessibilityFloatingMenuController provideAccessibilityFloatingMenuController(
Context context, AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
AccessibilityButtonModeObserver accessibilityButtonModeObserver,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 954ba797e164..79723188348e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -41,6 +41,7 @@ import android.content.pm.ShortcutManager;
import android.content.res.Resources;
import android.hardware.SensorManager;
import android.hardware.SensorPrivacyManager;
+import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.ColorDisplayManager;
import android.hardware.display.DisplayManager;
import android.hardware.face.FaceManager;
@@ -73,6 +74,7 @@ import android.view.inputmethod.InputMethodManager;
import com.android.internal.app.IBatteryStats;
import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.LatencyTracker;
import com.android.systemui.dagger.qualifiers.DisplayId;
@@ -159,6 +161,12 @@ public class FrameworkServicesModule {
@Provides
@Singleton
+ static DeviceStateManager provideDeviceStateManager(Context context) {
+ return context.getSystemService(DeviceStateManager.class);
+ }
+
+ @Provides
+ @Singleton
static IActivityManager provideIActivityManager() {
return ActivityManager.getService();
}
@@ -213,6 +221,12 @@ public class FrameworkServicesModule {
@Provides
@Singleton
+ static InteractionJankMonitor provideInteractionJankMonitor() {
+ return InteractionJankMonitor.getInstance();
+ }
+
+ @Provides
+ @Singleton
static InputMethodManager provideInputMethodManager(Context context) {
return context.getSystemService(InputMethodManager.class);
}
@@ -360,6 +374,12 @@ public class FrameworkServicesModule {
@Provides
@Singleton
+ static Optional<TelecomManager> provideOptionalTelecomManager(Context context) {
+ return Optional.ofNullable(context.getSystemService(TelecomManager.class));
+ }
+
+ @Provides
+ @Singleton
static TelephonyManager provideTelephonyManager(Context context) {
return context.getSystemService(TelephonyManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
index a89c7acea984..18f85196aa0b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
@@ -23,6 +23,8 @@ import android.util.DisplayMetrics;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.systemui.dagger.qualifiers.TestHarness;
+import com.android.systemui.plugins.PluginsModule;
+import com.android.systemui.unfold.UnfoldTransitionModule;
import com.android.systemui.util.concurrency.GlobalConcurrencyModule;
import javax.inject.Singleton;
@@ -47,7 +49,10 @@ import dagger.Provides;
*/
@Module(includes = {
FrameworkServicesModule.class,
- GlobalConcurrencyModule.class})
+ GlobalConcurrencyModule.class,
+ UnfoldTransitionModule.class,
+ PluginsModule.class,
+})
public class GlobalModule {
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index d648c949ffc5..a3a45fe7ae40 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -18,8 +18,6 @@ package com.android.systemui.dagger;
import android.content.Context;
-import com.android.systemui.util.concurrency.ThreadFactory;
-
import javax.inject.Singleton;
import dagger.BindsInstance;
@@ -55,9 +53,4 @@ public interface GlobalRootComponent {
* Builder for a SysUIComponent.
*/
SysUIComponent.Builder getSysUIComponent();
-
- /**
- * Build a {@link ThreadFactory}.
- */
- ThreadFactory createThreadFactory();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
index 406981d0c4ad..67ad3db74f8a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
@@ -24,6 +24,7 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.GlobalActions;
+import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
@@ -32,6 +33,7 @@ import com.android.systemui.volume.VolumeDialogControllerImpl;
import dagger.Binds;
import dagger.Module;
+import dagger.Provides;
/**
* Module for binding Plugin implementations.
@@ -39,36 +41,40 @@ import dagger.Module;
* TODO(b/166258224): Many of these should be moved closer to their implementations.
*/
@Module
-public interface PluginModule {
+public abstract class PluginModule {
/** */
- @Binds
- ActivityStarter provideActivityStarter(ActivityStarterDelegate delegate);
+ @Provides
+ static ActivityStarter provideActivityStarter(ActivityStarterDelegate delegate,
+ PluginDependencyProvider dependencyProvider) {
+ dependencyProvider.allowPluginDependency(ActivityStarter.class, delegate);
+ return delegate;
+ }
/** */
@Binds
- DarkIconDispatcher provideDarkIconDispatcher(DarkIconDispatcherImpl controllerImpl);
+ abstract DarkIconDispatcher provideDarkIconDispatcher(DarkIconDispatcherImpl controllerImpl);
/** */
@Binds
- FalsingManager provideFalsingManager(FalsingManagerProxy falsingManagerImpl);
+ abstract FalsingManager provideFalsingManager(FalsingManagerProxy falsingManagerImpl);
/** */
@Binds
- GlobalActions provideGlobalActions(GlobalActionsImpl controllerImpl);
+ abstract GlobalActions provideGlobalActions(GlobalActionsImpl controllerImpl);
/** */
@Binds
- GlobalActions.GlobalActionsManager provideGlobalActionsManager(
+ abstract GlobalActions.GlobalActionsManager provideGlobalActionsManager(
GlobalActionsComponent controllerImpl);
/** */
@Binds
- StatusBarStateController provideStatusBarStateController(
+ abstract StatusBarStateController provideStatusBarStateController(
StatusBarStateControllerImpl controllerImpl);
/** */
@Binds
- VolumeDialogController provideVolumeDialogController(VolumeDialogControllerImpl controllerImpl);
-
+ abstract VolumeDialogController provideVolumeDialogController(
+ VolumeDialogControllerImpl controllerImpl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 0fdc4d8e86a7..a9a4da8d94a2 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -25,11 +25,11 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.people.PeopleProvider;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.util.InjectionInflationController;
import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded;
@@ -96,6 +96,9 @@ public interface SysUIComponent {
Builder setStartingSurface(Optional<StartingSurface> s);
@BindsInstance
+ Builder setDisplayAreaHelper(Optional<DisplayAreaHelper> h);
+
+ @BindsInstance
Builder setTaskSurfaceHelper(Optional<TaskSurfaceHelper> t);
SysUIComponent build();
@@ -143,11 +146,6 @@ public interface SysUIComponent {
InitController getInitController();
/**
- * ViewInstanceCreator generates all Views that need injection.
- */
- InjectionInflationController.ViewInstanceCreator.Factory createViewInstanceCreatorFactory();
-
- /**
* Member injection into the supplied argument.
*/
void inject(SystemUIAppComponentFactory factory);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index 9cb9a362d460..50d2dd16b407 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -68,6 +68,7 @@ import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyControllerImpl;
import com.android.systemui.statusbar.policy.SensorPrivacyController;
import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
+import com.android.systemui.volume.dagger.VolumeModule;
import javax.inject.Named;
@@ -82,7 +83,8 @@ import dagger.Provides;
@Module(includes = {
MediaModule.class,
PowerModule.class,
- QSModule.class
+ QSModule.class,
+ VolumeModule.class
})
public abstract class SystemUIDefaultModule {
@@ -185,9 +187,13 @@ public abstract class SystemUIDefaultModule {
return new Recents(context, recentsImplementation, commandQueue);
}
- @Binds
- abstract DeviceProvisionedController bindDeviceProvisionedController(
- DeviceProvisionedControllerImpl deviceProvisionedController);
+ @SysUISingleton
+ @Provides
+ static DeviceProvisionedController bindDeviceProvisionedController(
+ DeviceProvisionedControllerImpl deviceProvisionedController) {
+ deviceProvisionedController.init();
+ return deviceProvisionedController;
+ }
@Binds
abstract KeyguardViewController bindKeyguardViewController(
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 46ab5f68abe9..a4e2572836f8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -18,6 +18,7 @@ package com.android.systemui.dagger;
import android.app.INotificationManager;
import android.content.Context;
+import android.view.LayoutInflater;
import androidx.annotation.Nullable;
@@ -26,6 +27,7 @@ import com.android.keyguard.clock.ClockModule;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.BootCompleteCache;
import com.android.systemui.BootCompleteCacheImpl;
+import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.appops.dagger.AppOpsModule;
import com.android.systemui.assist.AssistModule;
@@ -36,6 +38,10 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.dagger.DemoModeModule;
import com.android.systemui.doze.dagger.DozeComponent;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlagManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FlagReader;
+import com.android.systemui.flags.FlagWriter;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.model.SysUiState;
@@ -46,7 +52,6 @@ import com.android.systemui.screenshot.dagger.ScreenshotModule;
import com.android.systemui.settings.dagger.SettingsModule;
import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -61,6 +66,7 @@ import com.android.systemui.statusbar.notification.row.dagger.NotificationRowCom
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarWindowView;
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -75,7 +81,6 @@ import com.android.systemui.util.sensors.SensorModule;
import com.android.systemui.util.settings.SettingsUtilModule;
import com.android.systemui.util.time.SystemClock;
import com.android.systemui.util.time.SystemClockImpl;
-import com.android.systemui.volume.dagger.VolumeModule;
import com.android.systemui.wallet.dagger.WalletModule;
import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubbles;
@@ -112,7 +117,6 @@ import dagger.Provides;
TunerModule.class,
UserModule.class,
UtilModule.class,
- VolumeModule.class,
WalletModule.class
},
subcomponents = {
@@ -141,10 +145,18 @@ public abstract class SystemUIModule {
@SysUISingleton
@Provides
- static SysUiState provideSysUiState() {
- return new SysUiState();
+ static SysUiState provideSysUiState(DumpManager dumpManager) {
+ final SysUiState state = new SysUiState();
+ dumpManager.registerDumpable(state);
+ return state;
}
+ @Binds
+ abstract FlagReader provideFlagReader(FeatureFlagManager impl);
+
+ @Binds
+ abstract FlagWriter provideFlagWriter(FeatureFlagManager impl);
+
@BindsOptionalOf
abstract CommandQueue optionalCommandQueue();
@@ -200,4 +212,17 @@ public abstract class SystemUIModule {
groupManager, entryManager, notifPipeline, sysUiState, featureFlags, dumpManager,
sysuiMainExecutor));
}
+
+ @Provides
+ @SysUISingleton
+ static StatusBarWindowView providesStatusBarWindowView(LayoutInflater layoutInflater) {
+ StatusBarWindowView view =
+ (StatusBarWindowView) layoutInflater.inflate(R.layout.super_status_bar,
+ /* root= */ null);
+ if (view == null) {
+ throw new IllegalStateException(
+ "R.layout.super_status_bar could not be properly inflated");
+ }
+ return view;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 442d351729d7..618c26b89196 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -27,6 +27,7 @@ import com.android.wm.shell.ShellInit;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded;
@@ -105,5 +106,8 @@ public interface WMComponent {
Optional<StartingSurface> getStartingSurface();
@WMSingleton
+ Optional<DisplayAreaHelper> getDisplayAreaHelper();
+
+ @WMSingleton
Optional<TaskSurfaceHelper> getTaskSurfaceHelper();
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 845d7dc26ee5..669965bcbea5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -26,6 +26,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -268,6 +269,16 @@ public class DozeLog implements Dumpable {
}
/**
+ * Appends doze updates due to a posture change.
+ */
+ public void tracePostureChanged(
+ @DevicePostureController.DevicePostureInt int posture,
+ String partUpdated
+ ) {
+ mLogger.logPostureChanged(posture, partUpdated);
+ }
+
+ /**
* Appends pulse dropped event to logs
*/
public void tracePulseDropped(boolean pulsePending, DozeMachine.State state, boolean blocked) {
@@ -391,8 +402,8 @@ public class DozeLog implements Dumpable {
case REASON_SENSOR_DOUBLE_TAP: return "doubletap";
case PULSE_REASON_SENSOR_LONG_PRESS: return "longpress";
case PULSE_REASON_DOCKING: return "docking";
- case PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN: return "reach-wakelockscreen";
- case REASON_SENSOR_WAKE_UP: return "presence-wakeup";
+ case PULSE_REASON_SENSOR_WAKE_REACH: return "reach-wakelockscreen";
+ case REASON_SENSOR_WAKE_UP_PRESENCE: return "presence-wakeup";
case REASON_SENSOR_TAP: return "tap";
case REASON_SENSOR_UDFPS_LONG_PRESS: return "udfps";
case REASON_SENSOR_QUICK_PICKUP: return "quickPickup";
@@ -403,8 +414,8 @@ public class DozeLog implements Dumpable {
@Retention(RetentionPolicy.SOURCE)
@IntDef({PULSE_REASON_NONE, PULSE_REASON_INTENT, PULSE_REASON_NOTIFICATION,
PULSE_REASON_SENSOR_SIGMOTION, REASON_SENSOR_PICKUP, REASON_SENSOR_DOUBLE_TAP,
- PULSE_REASON_SENSOR_LONG_PRESS, PULSE_REASON_DOCKING, REASON_SENSOR_WAKE_UP,
- PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, REASON_SENSOR_TAP,
+ PULSE_REASON_SENSOR_LONG_PRESS, PULSE_REASON_DOCKING, REASON_SENSOR_WAKE_UP_PRESENCE,
+ PULSE_REASON_SENSOR_WAKE_REACH, REASON_SENSOR_TAP,
REASON_SENSOR_UDFPS_LONG_PRESS, REASON_SENSOR_QUICK_PICKUP})
public @interface Reason {}
public static final int PULSE_REASON_NONE = -1;
@@ -415,8 +426,8 @@ public class DozeLog implements Dumpable {
public static final int REASON_SENSOR_DOUBLE_TAP = 4;
public static final int PULSE_REASON_SENSOR_LONG_PRESS = 5;
public static final int PULSE_REASON_DOCKING = 6;
- public static final int REASON_SENSOR_WAKE_UP = 7;
- public static final int PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN = 8;
+ public static final int REASON_SENSOR_WAKE_UP_PRESENCE = 7;
+ public static final int PULSE_REASON_SENSOR_WAKE_REACH = 8;
public static final int REASON_SENSOR_TAP = 9;
public static final int REASON_SENSOR_UDFPS_LONG_PRESS = 10;
public static final int REASON_SENSOR_QUICK_PICKUP = 11;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index dc186182432f..d79bf22cced2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -24,6 +24,7 @@ import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.LogLevel.ERROR
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.DozeLog
+import com.android.systemui.statusbar.policy.DevicePostureController
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@@ -195,6 +196,16 @@ class DozeLogger @Inject constructor(
})
}
+ fun logPostureChanged(posture: Int, partUpdated: String) {
+ buffer.log(TAG, INFO, {
+ int1 = posture
+ str1 = partUpdated
+ }, {
+ "Posture changed, posture=${DevicePostureController.devicePostureToString(int1)}" +
+ " partUpdated=$str1"
+ })
+ }
+
fun logPulseDropped(pulsePending: Boolean, state: DozeMachine.State, blocked: Boolean) {
buffer.log(TAG, INFO, {
bool1 = pulsePending
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 058f37a2ef38..765c24507662 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -29,17 +29,18 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.IndentingPrintWriter;
-import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.dagger.BrightnessSensor;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.doze.dagger.WrappedService;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.sensors.AsyncSensorManager;
import java.io.PrintWriter;
+import java.util.Objects;
import java.util.Optional;
import javax.inject.Inject;
@@ -56,29 +57,22 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
"com.android.systemui.doze.AOD_BRIGHTNESS";
protected static final String BRIGHTNESS_BUCKET = "brightness_bucket";
- /**
- * Just before the screen times out from user inactivity, DisplayPowerController dims the screen
- * brightness to the lower of {@link #mScreenBrightnessDim}, or the current brightness minus
- * DisplayPowerController#SCREEN_DIM_MINIMUM_REDUCTION_FLOAT.
- *
- * This value is 0.04f * 255, which converts SCREEN_DIM_MINIMUM_REDUCTION_FLOAT to the integer
- * brightness values used by doze.
- */
- private static final int SCREEN_DIM_MINIMUM_REDUCTION_INT = 10;
-
private final Context mContext;
private final DozeMachine.Service mDozeService;
private final DozeHost mDozeHost;
private final Handler mHandler;
private final SensorManager mSensorManager;
- private final Optional<Sensor> mLightSensorOptional;
+ private final Optional<Sensor>[] mLightSensorOptional; // light sensors to use per posture
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final DozeParameters mDozeParameters;
- private final DockManager mDockManager;
+ private final DevicePostureController mDevicePostureController;
+ private final DozeLog mDozeLog;
private final int[] mSensorToBrightness;
private final int[] mSensorToScrimOpacity;
private final int mScreenBrightnessDim;
+ @DevicePostureController.DevicePostureInt
+ private int mDevicePosture;
private boolean mRegistered;
private int mDefaultDozeBrightness;
private boolean mPaused = false;
@@ -93,32 +87,37 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
*/
private int mDebugBrightnessBucket = -1;
- private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
-
@Inject
- public DozeScreenBrightness(Context context, @WrappedService DozeMachine.Service service,
+ public DozeScreenBrightness(
+ Context context,
+ @WrappedService DozeMachine.Service service,
AsyncSensorManager sensorManager,
- @BrightnessSensor Optional<Sensor> lightSensorOptional, DozeHost host, Handler handler,
+ @BrightnessSensor Optional<Sensor>[] lightSensorOptional,
+ DozeHost host, Handler handler,
AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
WakefulnessLifecycle wakefulnessLifecycle,
DozeParameters dozeParameters,
- DockManager dockManager,
- UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
+ DevicePostureController devicePostureController,
+ DozeLog dozeLog
+ ) {
mContext = context;
mDozeService = service;
mSensorManager = sensorManager;
mLightSensorOptional = lightSensorOptional;
+ mDevicePostureController = devicePostureController;
+ mDevicePosture = mDevicePostureController.getDevicePosture();
mWakefulnessLifecycle = wakefulnessLifecycle;
mDozeParameters = dozeParameters;
mDozeHost = host;
mHandler = handler;
- mDockManager = dockManager;
- mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+ mDozeLog = dozeLog;
mDefaultDozeBrightness = alwaysOnDisplayPolicy.defaultDozeBrightness;
mScreenBrightnessDim = alwaysOnDisplayPolicy.dimBrightness;
mSensorToBrightness = alwaysOnDisplayPolicy.screenBrightnessArray;
mSensorToScrimOpacity = alwaysOnDisplayPolicy.dimmingScrimArray;
+
+ mDevicePostureController.addCallback(mDevicePostureCallback);
}
@Override
@@ -148,6 +147,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
private void onDestroy() {
setLightSensorEnabled(false);
+ mDevicePostureController.removeCallback(mDevicePostureCallback);
}
@Override
@@ -163,19 +163,18 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
}
}
- public void updateBrightnessAndReady(boolean force) {
+ private void updateBrightnessAndReady(boolean force) {
if (force || mRegistered || mDebugBrightnessBucket != -1) {
int sensorValue = mDebugBrightnessBucket == -1
? mLastSensorValue : mDebugBrightnessBucket;
int brightness = computeBrightness(sensorValue);
boolean brightnessReady = brightness > 0;
if (brightnessReady) {
- mDozeService.setDozeScreenBrightness(
- clampToDimBrightnessForScreenOff(clampToUserSetting(brightness)));
+ mDozeService.setDozeScreenBrightness(clampToUserSetting(brightness));
}
int scrimOpacity = -1;
- if (!mLightSensorOptional.isPresent()) {
+ if (!isLightSensorPresent()) {
// No light sensor, scrims are always transparent.
scrimOpacity = 0;
} else if (brightnessReady) {
@@ -188,6 +187,27 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
}
}
+ private boolean lightSensorSupportsCurrentPosture() {
+ return mLightSensorOptional != null
+ && mDevicePosture < mLightSensorOptional.length;
+ }
+
+ private boolean isLightSensorPresent() {
+ if (!lightSensorSupportsCurrentPosture()) {
+ return mLightSensorOptional != null && mLightSensorOptional[0].isPresent();
+ }
+
+ return mLightSensorOptional[mDevicePosture].isPresent();
+ }
+
+ private Sensor getLightSensor() {
+ if (!lightSensorSupportsCurrentPosture()) {
+ return null;
+ }
+
+ return mLightSensorOptional[mDevicePosture].get();
+ }
+
private int computeScrimOpacity(int sensorValue) {
if (sensorValue < 0 || sensorValue >= mSensorToScrimOpacity.length) {
return -1;
@@ -223,30 +243,22 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
/**
* Clamp the brightness to the dim brightness value used by PowerManagerService just before the
* device times out and goes to sleep, if we are sleeping from a timeout. This ensures that we
- * don't raise the brightness back to the user setting before or during the screen off
- * animation.
+ * don't raise the brightness back to the user setting before playing the screen off animation.
*/
private int clampToDimBrightnessForScreenOff(int brightness) {
- if (mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
+ if (mDozeParameters.shouldControlUnlockedScreenOff()
&& mWakefulnessLifecycle.getLastSleepReason()
== PowerManager.GO_TO_SLEEP_REASON_TIMEOUT) {
- return Math.max(
- PowerManager.BRIGHTNESS_OFF,
- // Use the lower of either the dim brightness, or the current brightness reduced
- // by the minimum dim amount. This is the same logic used in
- // DisplayPowerController#updatePowerState to apply a minimum dim amount.
- Math.min(
- brightness - SCREEN_DIM_MINIMUM_REDUCTION_INT,
- mScreenBrightnessDim));
+ return Math.min(mScreenBrightnessDim, brightness);
} else {
return brightness;
}
}
private void setLightSensorEnabled(boolean enabled) {
- if (enabled && !mRegistered && mLightSensorOptional.isPresent()) {
+ if (enabled && !mRegistered && isLightSensorPresent()) {
// Wait until we get an event from the sensor until indicating ready.
- mRegistered = mSensorManager.registerListener(this, mLightSensorOptional.get(),
+ mRegistered = mSensorManager.registerListener(this, getLightSensor(),
SensorManager.SENSOR_DELAY_NORMAL, mHandler);
mLastSensorValue = -1;
} else if (!enabled && mRegistered) {
@@ -279,6 +291,41 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
/** Dump current state */
public void dump(PrintWriter pw) {
- pw.println("DozeScreenBrightnessSensorRegistered=" + mRegistered);
+ pw.println("DozeScreenBrightness:");
+ IndentingPrintWriter idpw = new IndentingPrintWriter(pw);
+ idpw.increaseIndent();
+ idpw.println("registered=" + mRegistered);
+ idpw.println("posture=" + DevicePostureController.devicePostureToString(mDevicePosture));
}
+
+ private final DevicePostureController.Callback mDevicePostureCallback =
+ new DevicePostureController.Callback() {
+ @Override
+ public void onPostureChanged(int posture) {
+ if (mDevicePosture == posture
+ || mLightSensorOptional.length < 2
+ || posture >= mLightSensorOptional.length) {
+ return;
+ }
+
+ final Sensor oldSensor = mLightSensorOptional[mDevicePosture].get();
+ final Sensor newSensor = mLightSensorOptional[posture].get();
+ if (Objects.equals(oldSensor, newSensor)) {
+ mDevicePosture = posture;
+ // uses the same sensor for the new posture
+ return;
+ }
+
+ // cancel the previous sensor:
+ if (mRegistered) {
+ setLightSensorEnabled(false);
+ mDevicePosture = posture;
+ setLightSensorEnabled(true);
+ } else {
+ mDevicePosture = posture;
+ }
+ mDozeLog.tracePostureChanged(mDevicePosture, "DozeScreenBrightness swap "
+ + "{" + oldSensor + "} => {" + newSensor + "}, mRegistered=" + mRegistered);
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 038be48b53ee..8f1486b0c7cb 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -77,7 +77,6 @@ public class DozeScreenState implements DozeMachine.Part {
private final Provider<UdfpsController> mUdfpsControllerProvider;
@Nullable private UdfpsController mUdfpsController;
private final DozeLog mDozeLog;
- private final DozeScreenBrightness mDozeScreenBrightness;
private int mPendingScreenState = Display.STATE_UNKNOWN;
private SettableWakeLock mWakeLock;
@@ -91,8 +90,7 @@ public class DozeScreenState implements DozeMachine.Part {
WakeLock wakeLock,
AuthController authController,
Provider<UdfpsController> udfpsControllerProvider,
- DozeLog dozeLog,
- DozeScreenBrightness dozeScreenBrightness) {
+ DozeLog dozeLog) {
mDozeService = service;
mHandler = handler;
mParameters = parameters;
@@ -101,7 +99,6 @@ public class DozeScreenState implements DozeMachine.Part {
mAuthController = authController;
mUdfpsControllerProvider = udfpsControllerProvider;
mDozeLog = dozeLog;
- mDozeScreenBrightness = dozeScreenBrightness;
updateUdfpsController();
if (mUdfpsController == null) {
@@ -207,12 +204,6 @@ public class DozeScreenState implements DozeMachine.Part {
if (screenState != Display.STATE_UNKNOWN) {
if (DEBUG) Log.d(TAG, "setDozeScreenState(" + screenState + ")");
mDozeService.setDozeScreenState(screenState);
- if (screenState == Display.STATE_DOZE) {
- // If we're entering doze, update the doze screen brightness. We might have been
- // clamping it to the dim brightness during the screen off animation, and we should
- // now change it to the brightness we actually want according to the sensor.
- mDozeScreenBrightness.updateBrightnessAndReady(false /* force */);
- }
mPendingScreenState = Display.STATE_UNKNOWN;
mWakeLock.setAcquired(false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 55e6154b829d..5c3e07fbaea1 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -38,6 +38,7 @@ import android.util.IndentingPrintWriter;
import android.util.Log;
import android.view.Display;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.UiEvent;
@@ -47,16 +48,43 @@ import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.plugins.SensorManagerPlugin;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.wakelock.WakeLock;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.function.Consumer;
+/**
+ * Tracks and registers/unregisters sensors while the device is dozing based on the config
+ * provided by {@link AmbientDisplayConfiguration} and parameters provided by {@link DozeParameters}
+ *
+ * Sensors registration depends on:
+ * - sensor existence/availability
+ * - user configuration (some can be toggled on/off via settings)
+ * - use of the proximity sensor (sometimes prox cannot be registered in certain display states)
+ * - touch state
+ * - device posture
+ *
+ * Sensors will trigger the provided Callback's {@link Callback#onSensorPulse} method.
+ * These sensors include:
+ * - pickup gesture
+ * - single and double tap gestures
+ * - udfps long-press gesture
+ * - reach and presence gestures
+ * - quick pickup gesture (low-threshold pickup gesture)
+ *
+ * This class also registers a ProximitySensor that reports near/far events and will
+ * trigger callbacks on the provided {@link mProxCallback}.
+ */
public class DozeSensors {
private static final boolean DEBUG = DozeService.DEBUG;
@@ -67,21 +95,30 @@ public class DozeSensors {
private final AsyncSensorManager mSensorManager;
private final AmbientDisplayConfiguration mConfig;
private final WakeLock mWakeLock;
- private final Consumer<Boolean> mProxCallback;
+ private final DozeLog mDozeLog;
private final SecureSettings mSecureSettings;
- private final Callback mCallback;
+ private final DevicePostureController mDevicePostureController;
private final boolean mScreenOffUdfpsEnabled;
+
+ // Sensors
@VisibleForTesting
- protected TriggerSensor[] mSensors;
+ protected TriggerSensor[] mTriggerSensors;
+ private final ProximitySensor mProximitySensor;
+
+ // Sensor callbacks
+ private final Callback mSensorCallback; // receives callbacks on registered sensor events
+ private final Consumer<Boolean> mProxCallback; // receives callbacks on near/far updates
private final Handler mHandler = new Handler();
- private final ProximitySensor mProximitySensor;
private long mDebounceFrom;
private boolean mSettingRegistered;
private boolean mListening;
private boolean mListeningTouchScreenSensors;
private boolean mListeningProxSensors;
+ @DevicePostureController.DevicePostureInt
+ private int mDevicePosture;
+
// whether to only register sensors that use prox when the display state is dozing or off
private boolean mSelectivelyRegisterProxSensors;
@@ -102,35 +139,47 @@ public class DozeSensors {
}
}
- DozeSensors(Context context, AsyncSensorManager sensorManager,
- DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
- Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog,
- ProximitySensor proximitySensor, SecureSettings secureSettings,
- AuthController authController) {
+ DozeSensors(
+ Context context,
+ AsyncSensorManager sensorManager,
+ DozeParameters dozeParameters,
+ AmbientDisplayConfiguration config,
+ WakeLock wakeLock,
+ Callback sensorCallback,
+ Consumer<Boolean> proxCallback,
+ DozeLog dozeLog,
+ ProximitySensor proximitySensor,
+ SecureSettings secureSettings,
+ AuthController authController,
+ DevicePostureController devicePostureController
+ ) {
mContext = context;
mSensorManager = sensorManager;
mConfig = config;
mWakeLock = wakeLock;
mProxCallback = proxCallback;
mSecureSettings = secureSettings;
- mCallback = callback;
+ mSensorCallback = sensorCallback;
+ mDozeLog = dozeLog;
mProximitySensor = proximitySensor;
mProximitySensor.setTag(TAG);
mSelectivelyRegisterProxSensors = dozeParameters.getSelectivelyRegisterSensorsUsingProx();
mListeningProxSensors = !mSelectivelyRegisterProxSensors;
mScreenOffUdfpsEnabled =
config.screenOffUdfpsEnabled(KeyguardUpdateMonitor.getCurrentUser());
+ mDevicePostureController = devicePostureController;
+ mDevicePosture = mDevicePostureController.getDevicePosture();
boolean udfpsEnrolled =
authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser());
boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT);
- mSensors = new TriggerSensor[] {
+ mTriggerSensors = new TriggerSensor[] {
new TriggerSensor(
mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION),
null /* setting */,
dozeParameters.getPulseOnSigMotion(),
DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */,
- false /* touchscreen */, dozeLog),
+ false /* touchscreen */),
new TriggerSensor(
mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
Settings.Secure.DOZE_PICK_UP_GESTURE,
@@ -139,18 +188,16 @@ public class DozeSensors {
DozeLog.REASON_SENSOR_PICKUP, false /* touchCoords */,
false /* touchscreen */,
false /* ignoresSetting */,
- false /* requires prox */,
- dozeLog),
+ false /* requires prox */),
new TriggerSensor(
- findSensorWithType(config.doubleTapSensorType()),
+ findSensor(config.doubleTapSensorType()),
Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
true /* configured */,
DozeLog.REASON_SENSOR_DOUBLE_TAP,
dozeParameters.doubleTapReportsTouchCoordinates(),
- true /* touchscreen */,
- dozeLog),
+ true /* touchscreen */),
new TriggerSensor(
- findSensorWithType(config.tapSensorType()),
+ findSensors(config.tapSensorTypeMapping()),
Settings.Secure.DOZE_TAP_SCREEN_GESTURE,
true /* settingDef */,
true /* configured */,
@@ -158,10 +205,10 @@ public class DozeSensors {
false /* reports touch coordinates */,
true /* touchscreen */,
false /* ignoresSetting */,
- dozeParameters.singleTapUsesProx() /* requiresProx */,
- dozeLog),
+ dozeParameters.singleTapUsesProx(mDevicePosture) /* requiresProx */,
+ mDevicePosture),
new TriggerSensor(
- findSensorWithType(config.longPressSensorType()),
+ findSensor(config.longPressSensorType()),
Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
false /* settingDef */,
true /* configured */,
@@ -169,10 +216,9 @@ public class DozeSensors {
true /* reports touch coordinates */,
true /* touchscreen */,
false /* ignoresSetting */,
- dozeParameters.longPressUsesProx() /* requiresProx */,
- dozeLog),
+ dozeParameters.longPressUsesProx() /* requiresProx */),
new TriggerSensor(
- findSensorWithType(config.udfpsLongPressSensorType()),
+ findSensor(config.udfpsLongPressSensorType()),
"doze_pulse_on_auth",
true /* settingDef */,
udfpsEnrolled && (alwaysOn || mScreenOffUdfpsEnabled),
@@ -180,36 +226,34 @@ public class DozeSensors {
true /* reports touch coordinates */,
true /* touchscreen */,
false /* ignoresSetting */,
- dozeParameters.longPressUsesProx(),
- dozeLog),
+ dozeParameters.longPressUsesProx()),
new PluginSensor(
new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY),
Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
mConfig.wakeScreenGestureAvailable() && alwaysOn,
- DozeLog.REASON_SENSOR_WAKE_UP,
+ DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE,
false /* reports touch coordinates */,
- false /* touchscreen */,
- dozeLog),
+ false /* touchscreen */),
new PluginSensor(
new SensorManagerPlugin.Sensor(TYPE_WAKE_LOCK_SCREEN),
Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE,
mConfig.wakeScreenGestureAvailable(),
- DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN,
+ DozeLog.PULSE_REASON_SENSOR_WAKE_REACH,
false /* reports touch coordinates */,
false /* touchscreen */,
- mConfig.getWakeLockScreenDebounce(),
- dozeLog),
+ mConfig.getWakeLockScreenDebounce()),
new TriggerSensor(
- findSensorWithType(config.quickPickupSensorType()),
+ findSensor(config.quickPickupSensorType()),
Settings.Secure.DOZE_QUICK_PICKUP_GESTURE,
true /* setting default */,
config.quickPickupSensorEnabled(KeyguardUpdateMonitor.getCurrentUser())
&& udfpsEnrolled,
DozeLog.REASON_SENSOR_QUICK_PICKUP,
- false /* touchCoords */,
- false /* touchscreen */, dozeLog),
+ false /* requiresTouchCoordinates */,
+ false /* requiresTouchscreen */,
+ false /* ignoresSetting */,
+ false /* requiresProx */),
};
-
setProxListening(false); // Don't immediately start listening when we register.
mProximitySensor.register(
proximityEvent -> {
@@ -217,17 +261,21 @@ public class DozeSensors {
mProxCallback.accept(!proximityEvent.getBelow());
}
});
+
+ mDevicePostureController.addCallback(mDevicePostureCallback);
}
/**
- * Unregister any sensors.
+ * Unregister all sensors and callbacks.
*/
public void destroy() {
// Unregisters everything, which is enough to allow gc.
- for (TriggerSensor triggerSensor : mSensors) {
+ for (TriggerSensor triggerSensor : mTriggerSensors) {
triggerSensor.setListening(false);
}
mProximitySensor.pause();
+
+ mDevicePostureController.removeCallback(mDevicePostureCallback);
}
/**
@@ -238,21 +286,47 @@ public class DozeSensors {
mDebounceFrom = SystemClock.uptimeMillis();
}
- private Sensor findSensorWithType(String type) {
- return findSensorWithType(mSensorManager, type);
+ private Sensor findSensor(String type) {
+ return findSensor(mSensorManager, type, null);
+ }
+
+ @NonNull
+ private Sensor[] findSensors(@NonNull String[] types) {
+ Sensor[] sensorMap = new Sensor[DevicePostureController.SUPPORTED_POSTURES_SIZE];
+
+ // Map of sensorType => Sensor, so we reuse the same sensor if it's the same between
+ // postures
+ Map<String, Sensor> typeToSensorMap = new HashMap<>();
+ for (int i = 0; i < types.length; i++) {
+ String sensorType = types[i];
+ if (!typeToSensorMap.containsKey(sensorType)) {
+ typeToSensorMap.put(sensorType, findSensor(sensorType));
+ }
+ sensorMap[i] = typeToSensorMap.get(sensorType);
+ }
+
+ return sensorMap;
}
/**
- * Utility method to find a {@link Sensor} for the supplied string type.
+ * Utility method to find a {@link Sensor} for the supplied string type and string name.
+ *
+ * Return the first sensor in the list that matches the specified inputs. Ignores type or name
+ * if the input is null or empty.
+ *
+ * @param type sensorType
+ * @parm name sensorName, to differentiate between sensors with the same type
*/
- public static Sensor findSensorWithType(SensorManager sensorManager, String type) {
- if (TextUtils.isEmpty(type)) {
- return null;
- }
- List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
- for (Sensor s : sensorList) {
- if (type.equals(s.getStringType())) {
- return s;
+ public static Sensor findSensor(SensorManager sensorManager, String type, String name) {
+ final boolean isNameSpecified = !TextUtils.isEmpty(name);
+ final boolean isTypeSpecified = !TextUtils.isEmpty(type);
+ if (isNameSpecified || isTypeSpecified) {
+ final List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
+ for (Sensor sensor : sensors) {
+ if ((!isNameSpecified || name.equals(sensor.getName()))
+ && (!isTypeSpecified || type.equals(sensor.getStringType()))) {
+ return sensor;
+ }
}
}
return null;
@@ -292,7 +366,7 @@ public class DozeSensors {
*/
private void updateListening() {
boolean anyListening = false;
- for (TriggerSensor s : mSensors) {
+ for (TriggerSensor s : mTriggerSensors) {
boolean listen = mListening
&& (!s.mRequiresTouchscreen || mListeningTouchScreenSensors)
&& (!s.mRequiresProx || mListeningProxSensors);
@@ -305,7 +379,7 @@ public class DozeSensors {
if (!anyListening) {
mSecureSettings.unregisterContentObserver(mSettingsObserver);
} else if (!mSettingRegistered) {
- for (TriggerSensor s : mSensors) {
+ for (TriggerSensor s : mTriggerSensors) {
s.registerSettingsObserver(mSettingsObserver);
}
}
@@ -314,7 +388,7 @@ public class DozeSensors {
/** Set the listening state of only the sensors that require the touchscreen. */
public void setTouchscreenSensorsListening(boolean listening) {
- for (TriggerSensor sensor : mSensors) {
+ for (TriggerSensor sensor : mTriggerSensors) {
if (sensor.mRequiresTouchscreen) {
sensor.setListening(listening);
}
@@ -322,7 +396,7 @@ public class DozeSensors {
}
public void onUserSwitched() {
- for (TriggerSensor s : mSensors) {
+ for (TriggerSensor s : mTriggerSensors) {
s.updateListening();
}
}
@@ -352,7 +426,7 @@ public class DozeSensors {
if (userId != ActivityManager.getCurrentUser()) {
return;
}
- for (TriggerSensor s : mSensors) {
+ for (TriggerSensor s : mTriggerSensors) {
s.updateListening();
}
}
@@ -360,7 +434,7 @@ public class DozeSensors {
/** Ignore the setting value of only the sensors that require the touchscreen. */
public void ignoreTouchScreenSensorsSettingInterferingWithDocking(boolean ignore) {
- for (TriggerSensor sensor : mSensors) {
+ for (TriggerSensor sensor : mTriggerSensors) {
if (sensor.mRequiresTouchscreen) {
sensor.ignoreSetting(ignore);
}
@@ -370,13 +444,15 @@ public class DozeSensors {
/** Dump current state */
public void dump(PrintWriter pw) {
pw.println("mListening=" + mListening);
+ pw.println("mDevicePosture="
+ + DevicePostureController.devicePostureToString(mDevicePosture));
pw.println("mListeningTouchScreenSensors=" + mListeningTouchScreenSensors);
pw.println("mSelectivelyRegisterProxSensors=" + mSelectivelyRegisterProxSensors);
pw.println("mListeningProxSensors=" + mListeningProxSensors);
pw.println("mScreenOffUdfpsEnabled=" + mScreenOffUdfpsEnabled);
IndentingPrintWriter idpw = new IndentingPrintWriter(pw);
idpw.increaseIndent();
- for (TriggerSensor s : mSensors) {
+ for (TriggerSensor s : mTriggerSensors) {
idpw.println("Sensor: " + s.toString());
}
idpw.println("ProxSensor: " + mProximitySensor.toString());
@@ -391,7 +467,7 @@ public class DozeSensors {
@VisibleForTesting
class TriggerSensor extends TriggerEventListener {
- final Sensor mSensor;
+ @NonNull final Sensor[] mSensors; // index = posture, value = sensor
final boolean mConfigured;
final int mPulseReason;
private final String mSetting;
@@ -404,27 +480,67 @@ public class DozeSensors {
protected boolean mRegistered;
protected boolean mDisabled;
protected boolean mIgnoresSetting;
- protected final DozeLog mDozeLog;
-
- public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason,
- boolean reportsTouchCoordinates, boolean requiresTouchscreen, DozeLog dozeLog) {
- this(sensor, setting, true /* settingDef */, configured, pulseReason,
- reportsTouchCoordinates, requiresTouchscreen, dozeLog);
- }
-
- public TriggerSensor(Sensor sensor, String setting, boolean settingDef,
- boolean configured, int pulseReason, boolean reportsTouchCoordinates,
- boolean requiresTouchscreen, DozeLog dozeLog) {
- this(sensor, setting, settingDef, configured, pulseReason, reportsTouchCoordinates,
- requiresTouchscreen, false /* ignoresSetting */,
- false /* requiresProx */, dozeLog);
- }
-
- private TriggerSensor(Sensor sensor, String setting, boolean settingDef,
- boolean configured, int pulseReason, boolean reportsTouchCoordinates,
- boolean requiresTouchscreen, boolean ignoresSetting, boolean requiresProx,
- DozeLog dozeLog) {
- mSensor = sensor;
+ private @DevicePostureController.DevicePostureInt int mPosture;
+
+ TriggerSensor(
+ Sensor sensor,
+ String setting,
+ boolean configured,
+ int pulseReason,
+ boolean reportsTouchCoordinates,
+ boolean requiresTouchscreen
+ ) {
+ this(
+ sensor,
+ setting,
+ true /* settingDef */,
+ configured,
+ pulseReason,
+ false /* ignoresSetting */,
+ false /* requiresProx */,
+ reportsTouchCoordinates,
+ requiresTouchscreen
+ );
+ }
+
+ TriggerSensor(
+ Sensor sensor,
+ String setting,
+ boolean settingDef,
+ boolean configured,
+ int pulseReason,
+ boolean reportsTouchCoordinates,
+ boolean requiresTouchscreen,
+ boolean ignoresSetting,
+ boolean requiresProx
+ ) {
+ this(
+ new Sensor[]{ sensor },
+ setting,
+ settingDef,
+ configured,
+ pulseReason,
+ reportsTouchCoordinates,
+ requiresTouchscreen,
+ ignoresSetting,
+ requiresProx,
+ DevicePostureController.DEVICE_POSTURE_UNKNOWN
+ );
+ }
+
+ TriggerSensor(
+ @NonNull Sensor[] sensors,
+ String setting,
+ boolean settingDef,
+ boolean configured,
+ int pulseReason,
+ boolean reportsTouchCoordinates,
+ boolean requiresTouchscreen,
+ boolean ignoresSetting,
+ boolean requiresProx,
+ @DevicePostureController.DevicePostureInt int posture
+ ) {
+ mSensors = sensors;
mSetting = setting;
mSettingDefault = settingDef;
mConfigured = configured;
@@ -433,7 +549,43 @@ public class DozeSensors {
mRequiresTouchscreen = requiresTouchscreen;
mIgnoresSetting = ignoresSetting;
mRequiresProx = requiresProx;
- mDozeLog = dozeLog;
+ mPosture = posture;
+ }
+
+ /**
+ * @return true if the sensor changed based for the new posture
+ */
+ public boolean setPosture(@DevicePostureController.DevicePostureInt int posture) {
+ if (mPosture == posture
+ || mSensors.length < 2
+ || posture >= mSensors.length) {
+ return false;
+ }
+
+ Sensor oldSensor = mSensors[mPosture];
+ Sensor newSensor = mSensors[posture];
+ if (Objects.equals(oldSensor, newSensor)) {
+ mPosture = posture;
+ // uses the same sensor for the new posture
+ return false;
+ }
+
+ // cancel the previous sensor:
+ if (mRegistered) {
+ final boolean rt = mSensorManager.cancelTriggerSensor(this, oldSensor);
+ if (DEBUG) {
+ Log.d(TAG, "posture changed, cancelTriggerSensor[" + oldSensor + "] "
+ + rt);
+ }
+ mRegistered = false;
+ }
+
+ // update the new sensor:
+ mPosture = posture;
+ updateListening();
+ mDozeLog.tracePostureChanged(mPosture, "DozeSensors swap "
+ + "{" + oldSensor + "} => {" + newSensor + "}, mRegistered=" + mRegistered);
+ return true;
}
public void setListening(boolean listen) {
@@ -455,24 +607,23 @@ public class DozeSensors {
}
public void updateListening() {
- if (!mConfigured || mSensor == null) return;
+ final Sensor sensor = mSensors[mPosture];
+ if (!mConfigured || sensor == null) return;
if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)) {
if (!mRegistered) {
- mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
+ mRegistered = mSensorManager.requestTriggerSensor(this, sensor);
if (DEBUG) {
- Log.d(TAG, "requestTriggerSensor[" + mSensor
- + "] " + mRegistered);
+ Log.d(TAG, "requestTriggerSensor[" + sensor + "] " + mRegistered);
}
} else {
if (DEBUG) {
- Log.d(TAG, "requestTriggerSensor[" + mSensor
- + "] already registered");
+ Log.d(TAG, "requestTriggerSensor[" + sensor + "] already registered");
}
}
} else if (mRegistered) {
- final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor);
+ final boolean rt = mSensorManager.cancelTriggerSensor(this, sensor);
if (DEBUG) {
- Log.d(TAG, "cancelTriggerSensor[" + mSensor + "] " + rt);
+ Log.d(TAG, "cancelTriggerSensor[" + sensor + "] " + rt);
}
mRegistered = false;
}
@@ -490,21 +641,29 @@ public class DozeSensors {
@Override
public String toString() {
- return new StringBuilder("{mRegistered=").append(mRegistered)
+ StringBuilder sb = new StringBuilder();
+ sb.append("{")
+ .append("mRegistered=").append(mRegistered)
.append(", mRequested=").append(mRequested)
.append(", mDisabled=").append(mDisabled)
.append(", mConfigured=").append(mConfigured)
.append(", mIgnoresSetting=").append(mIgnoresSetting)
- .append(", mSensor=").append(mSensor).append("}").toString();
+ .append(", mSensors=").append(Arrays.toString(mSensors));
+ if (mSensors.length > 2) {
+ sb.append(", mPosture=")
+ .append(DevicePostureController.devicePostureToString(mDevicePosture));
+ }
+ return sb.append("}").toString();
}
@Override
@AnyThread
public void onTrigger(TriggerEvent event) {
+ final Sensor sensor = mSensors[mPosture];
mDozeLog.traceSensor(mPulseReason);
mHandler.post(mWakeLock.wrap(() -> {
if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event));
- if (mSensor != null && mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
+ if (sensor != null && sensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
UI_EVENT_LOGGER.log(DozeSensorsUiEvent.ACTION_AMBIENT_GESTURE_PICKUP);
}
@@ -515,7 +674,7 @@ public class DozeSensors {
screenX = event.values[0];
screenY = event.values[1];
}
- mCallback.onSensorPulse(mPulseReason, screenX, screenY, event.values);
+ mSensorCallback.onSensorPulse(mPulseReason, screenX, screenY, event.values);
if (!mRegistered) {
updateListening(); // reregister, this sensor only fires once
}
@@ -553,17 +712,16 @@ public class DozeSensors {
private long mDebounce;
PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured,
- int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen,
- DozeLog dozeLog) {
+ int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen) {
this(sensor, setting, configured, pulseReason, reportsTouchCoordinates,
- requiresTouchscreen, 0L /* debounce */, dozeLog);
+ requiresTouchscreen, 0L /* debounce */);
}
PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured,
int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen,
- long debounce, DozeLog dozeLog) {
+ long debounce) {
super(null, setting, configured, pulseReason, reportsTouchCoordinates,
- requiresTouchscreen, dozeLog);
+ requiresTouchscreen);
mPluginSensor = sensor;
mDebounce = debounce;
}
@@ -617,11 +775,22 @@ public class DozeSensors {
return;
}
if (DEBUG) Log.d(TAG, "onSensorEvent: " + triggerEventToString(event));
- mCallback.onSensorPulse(mPulseReason, -1, -1, event.getValues());
+ mSensorCallback.onSensorPulse(mPulseReason, -1, -1, event.getValues());
}));
}
}
+ private final DevicePostureController.Callback mDevicePostureCallback = posture -> {
+ if (mDevicePosture == posture) {
+ return;
+ }
+ mDevicePosture = posture;
+
+ for (TriggerSensor triggerSensor : mTriggerSensors) {
+ triggerSensor.setPosture(mDevicePosture);
+ }
+ };
+
public interface Callback {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 756adca724e9..20cd5b987c1c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -41,10 +41,12 @@ import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeMachine.State;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.Assert;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.ProximityCheck;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.wakelock.WakeLock;
@@ -89,12 +91,13 @@ public class DozeTriggers implements DozeMachine.Part {
private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver();
private final DockEventListener mDockEventListener = new DockEventListener();
private final DockManager mDockManager;
- private final ProximitySensor.ProximityCheck mProxCheck;
+ private final ProximityCheck mProxCheck;
private final BroadcastDispatcher mBroadcastDispatcher;
private final AuthController mAuthController;
private final DelayableExecutor mMainExecutor;
private final KeyguardStateController mKeyguardStateController;
private final UiEventLogger mUiEventLogger;
+ private final DevicePostureController mDevicePostureController;
private long mNotificationPulseTime;
private boolean mPulsePending;
@@ -177,12 +180,14 @@ public class DozeTriggers implements DozeMachine.Part {
AmbientDisplayConfiguration config,
DozeParameters dozeParameters, AsyncSensorManager sensorManager,
WakeLock wakeLock, DockManager dockManager,
- ProximitySensor proximitySensor, ProximitySensor.ProximityCheck proxCheck,
+ ProximitySensor proximitySensor,
+ ProximityCheck proxCheck,
DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher,
SecureSettings secureSettings, AuthController authController,
@Main DelayableExecutor mainExecutor,
UiEventLogger uiEventLogger,
- KeyguardStateController keyguardStateController) {
+ KeyguardStateController keyguardStateController,
+ DevicePostureController devicePostureController) {
mContext = context;
mDozeHost = dozeHost;
mConfig = config;
@@ -190,9 +195,12 @@ public class DozeTriggers implements DozeMachine.Part {
mSensorManager = sensorManager;
mWakeLock = wakeLock;
mAllowPulseTriggers = true;
+
+ mDevicePostureController = devicePostureController;
mDozeSensors = new DozeSensors(context, mSensorManager, dozeParameters,
config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
- secureSettings, authController);
+ secureSettings, authController, devicePostureController);
+
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mDockManager = dockManager;
mProxCheck = proxCheck;
@@ -203,6 +211,10 @@ public class DozeTriggers implements DozeMachine.Part {
mUiEventLogger = uiEventLogger;
mKeyguardStateController = keyguardStateController;
}
+ private final DevicePostureController.Callback mDevicePostureCallback =
+ posture -> {
+
+ };
@Override
public void setDozeMachine(DozeMachine dozeMachine) {
@@ -275,8 +287,8 @@ public class DozeTriggers implements DozeMachine.Part {
boolean isTap = pulseReason == DozeLog.REASON_SENSOR_TAP;
boolean isPickup = pulseReason == DozeLog.REASON_SENSOR_PICKUP;
boolean isLongPress = pulseReason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS;
- boolean isWakeOnPresence = pulseReason == DozeLog.REASON_SENSOR_WAKE_UP;
- boolean isWakeOnReach = pulseReason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN;
+ boolean isWakeOnPresence = pulseReason == DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE;
+ boolean isWakeOnReach = pulseReason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH;
boolean isUdfpsLongPress = pulseReason == DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
boolean isQuickPickup = pulseReason == DozeLog.REASON_SENSOR_QUICK_PICKUP;
boolean isWakeDisplayEvent = isQuickPickup || ((isWakeOnPresence || isWakeOnReach)
@@ -446,7 +458,7 @@ public class DozeTriggers implements DozeMachine.Part {
mWantSensors = true;
mWantTouchScreenSensors = true;
if (newState == DozeMachine.State.DOZE_AOD && !sWakeDisplaySensorState) {
- onWakeScreen(false, newState, DozeLog.REASON_SENSOR_WAKE_UP);
+ onWakeScreen(false, newState, DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE);
}
break;
case DOZE_AOD_PAUSED:
@@ -515,7 +527,7 @@ public class DozeTriggers implements DozeMachine.Part {
// When already pulsing we're allowed to show the wallpaper directly without
// requesting a new pulse.
if (dozeState == DozeMachine.State.DOZE_PULSING
- && reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+ && reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH) {
mMachine.requestState(DozeMachine.State.DOZE_PULSING_BRIGHT);
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index fbe06b02d955..374bed31eb7d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -155,7 +155,7 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
public void onPulseStarted() {
try {
mMachine.requestState(
- reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN
+ reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH
? DozeMachine.State.DOZE_PULSING_BRIGHT
: DozeMachine.State.DOZE_PULSING);
} catch (IllegalStateException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index 9c6e02a7924e..32b7658b6e09 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -38,10 +38,14 @@ import com.android.systemui.doze.DozeTriggers;
import com.android.systemui.doze.DozeUi;
import com.android.systemui.doze.DozeWallpaperState;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Optional;
import dagger.Module;
@@ -93,9 +97,43 @@ public abstract class DozeModule {
@Provides
@BrightnessSensor
- static Optional<Sensor> providesBrightnessSensor(
- AsyncSensorManager sensorManager, Context context) {
- return Optional.ofNullable(DozeSensors.findSensorWithType(sensorManager,
- context.getString(R.string.doze_brightness_sensor_type)));
+ static Optional<Sensor>[] providesBrightnessSensors(
+ AsyncSensorManager sensorManager,
+ Context context,
+ DozeParameters dozeParameters) {
+ String[] sensorNames = dozeParameters.brightnessNames();
+ if (sensorNames.length == 0 || sensorNames == null) {
+ // if no brightness names are specified, just use the brightness sensor type
+ return new Optional[]{
+ Optional.ofNullable(DozeSensors.findSensor(
+ sensorManager,
+ context.getString(R.string.doze_brightness_sensor_type),
+ null
+ ))
+ };
+ }
+
+ // length and index of brightnessMap correspond to DevicePostureController.DevicePostureInt:
+ final Optional<Sensor>[] brightnessSensorMap =
+ new Optional[DevicePostureController.SUPPORTED_POSTURES_SIZE];
+ Arrays.fill(brightnessSensorMap, Optional.empty());
+
+ // Map of sensorName => Sensor, so we reuse the same sensor if it's the same between
+ // postures
+ Map<String, Optional<Sensor>> nameToSensorMap = new HashMap<>();
+ for (int i = 0; i < sensorNames.length; i++) {
+ final String sensorName = sensorNames[i];
+ if (!nameToSensorMap.containsKey(sensorName)) {
+ nameToSensorMap.put(sensorName,
+ Optional.ofNullable(
+ DozeSensors.findSensor(
+ sensorManager,
+ context.getString(R.string.doze_brightness_sensor_type),
+ sensorNames[i]
+ )));
+ }
+ brightnessSensorMap[i] = nameToSensorMap.get(sensorName);
+ }
+ return brightnessSensorMap;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index 5b327bd5e4c1..21a1b75224b2 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -18,11 +18,11 @@ package com.android.systemui.dump
import android.util.ArrayMap
import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import java.io.FileDescriptor
import java.io.PrintWriter
import javax.inject.Inject
+import javax.inject.Singleton
/**
* Maintains a registry of things that should be dumped when a bug report is taken
@@ -33,7 +33,7 @@ import javax.inject.Inject
*
* See [DumpHandler] for more information on how and when this information is dumped.
*/
-@SysUISingleton
+@Singleton
open class DumpManager @Inject constructor() {
private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
@@ -56,6 +56,15 @@ open class DumpManager @Inject constructor() {
}
/**
+ * Same as the above override, but automatically uses the simple class name as the dumpable
+ * name.
+ */
+ @Synchronized
+ fun registerDumpable(module: Dumpable) {
+ registerDumpable(module::class.java.simpleName, module)
+ }
+
+ /**
* Unregisters a previously-registered dumpable.
*/
@Synchronized
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
index 6fbf81c53c90..e78646a4839d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
@@ -20,19 +20,25 @@ import android.content.res.Resources;
import android.util.SparseArray;
import androidx.annotation.BoolRes;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.wrapper.BuildInfo;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
import javax.inject.Inject;
/**
* Reads and caches feature flags for quick access
*
- * Feature flags must be defined as boolean resources. For example:
+ * Feature flags must be defined as boolean resources. For example:t
*
* {@code
* <bool name="flag_foo_bar_baz">false</bool>
@@ -52,21 +58,39 @@ import javax.inject.Inject;
* Calls to this class should probably be wrapped by a method in {@link FeatureFlags}.
*/
@SysUISingleton
-public class FeatureFlagReader {
+public class FeatureFlagReader implements Dumpable {
private final Resources mResources;
private final boolean mAreFlagsOverrideable;
private final SystemPropertiesHelper mSystemPropertiesHelper;
private final SparseArray<CachedFlag> mCachedFlags = new SparseArray<>();
+ private final FlagReader mFlagReader;
+
@Inject
public FeatureFlagReader(
@Main Resources resources,
BuildInfo build,
- SystemPropertiesHelper systemPropertiesHelper) {
+ DumpManager dumpManager,
+ SystemPropertiesHelper systemPropertiesHelper,
+ FlagReader reader) {
mResources = resources;
+ mFlagReader = reader;
mSystemPropertiesHelper = systemPropertiesHelper;
mAreFlagsOverrideable =
build.isDebuggable() && mResources.getBoolean(R.bool.are_flags_overrideable);
+ dumpManager.registerDumpable("FeatureFlags", this);
+ }
+
+ boolean isEnabled(BooleanFlag flag) {
+ return mFlagReader.isEnabled(flag.getId(), flag.getDefault());
+ }
+
+ void addListener(FlagReader.Listener listener) {
+ mFlagReader.addListener(listener);
+ }
+
+ void removeListener(FlagReader.Listener listener) {
+ mFlagReader.removeListener(listener);
}
/**
@@ -119,6 +143,23 @@ public class FeatureFlagReader {
}
}
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ ArrayList<String> flagStrings = new ArrayList<>(mCachedFlags.size());
+ for (int i = 0; i < mCachedFlags.size(); i++) {
+ int key = mCachedFlags.keyAt(i);
+ // get the object by the key.
+ CachedFlag flag = mCachedFlags.get(key);
+ flagStrings.add(" " + RESNAME_PREFIX + flag.name + ": " + flag.value + "\n");
+ }
+ flagStrings.sort(String.CASE_INSENSITIVE_ORDER);
+ pw.println("AreFlagsOverrideable: " + mAreFlagsOverrideable);
+ pw.println("Cached FeatureFlags:");
+ for (String flagString : flagStrings) {
+ pw.print(flagString);
+ }
+ }
+
private static class CachedFlag {
public final String name;
public final boolean value;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
new file mode 100644
index 000000000000..48bb281429c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -0,0 +1,196 @@
+/*
+ * 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.systemui.flags;
+
+import android.content.Context;
+import android.util.FeatureFlagUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+/**
+ * Class to manage simple DeviceConfig-based feature flags.
+ *
+ * See {@link Flags} for instructions on defining new flags.
+ */
+@SysUISingleton
+public class FeatureFlags {
+ private final FeatureFlagReader mFlagReader;
+ private final Context mContext;
+ private final Map<Integer, Flag<?>> mFlagMap = new HashMap<>();
+ private final Map<Integer, List<Listener>> mListeners = new HashMap<>();
+
+ @Inject
+ public FeatureFlags(FeatureFlagReader flagReader, Context context) {
+ mFlagReader = flagReader;
+ mContext = context;
+
+ flagReader.addListener(mListener);
+ }
+
+ private final FlagReader.Listener mListener = id -> {
+ if (mListeners.containsKey(id) && mFlagMap.containsKey(id)) {
+ mListeners.get(id).forEach(listener -> listener.onFlagChanged(mFlagMap.get(id)));
+ }
+ };
+
+ @VisibleForTesting
+ void addFlag(Flag flag) {
+ mFlagMap.put(flag.getId(), flag);
+ }
+
+ /**
+ * @param flag The {@link BooleanFlag} of interest.
+ * @return The value of the flag.
+ */
+ public boolean isEnabled(BooleanFlag flag) {
+ return mFlagReader.isEnabled(flag);
+ }
+
+ /**
+ * @param flag The {@link IntFlag} of interest.
+
+ /** Add a listener for a specific flag. */
+ public void addFlagListener(Flag<?> flag, Listener listener) {
+ mListeners.putIfAbsent(flag.getId(), new ArrayList<>());
+ mListeners.get(flag.getId()).add(listener);
+ mFlagMap.putIfAbsent(flag.getId(), flag);
+ }
+
+ /** Remove a listener for a specific flag. */
+ public void removeFlagListener(Flag<?> flag, Listener listener) {
+ if (mListeners.containsKey(flag.getId())) {
+ mListeners.get(flag.getId()).remove(listener);
+ }
+ }
+
+ public void assertLegacyPipelineEnabled() {
+ if (isNewNotifPipelineRenderingEnabled()) {
+ throw new IllegalStateException("Old pipeline code running w/ new pipeline enabled");
+ }
+ }
+
+ public boolean checkLegacyPipelineEnabled() {
+ if (!isNewNotifPipelineRenderingEnabled()) {
+ return true;
+ }
+ Log.d("NotifPipeline", "Old pipeline code running w/ new pipeline enabled",
+ new Exception());
+ Toast.makeText(mContext, "Old pipeline code running!", Toast.LENGTH_SHORT).show();
+ return false;
+ }
+
+ public boolean isNewNotifPipelineEnabled() {
+ return isEnabled(Flags.NEW_NOTIFICATION_PIPELINE);
+ }
+
+ public boolean isNewNotifPipelineRenderingEnabled() {
+ return isEnabled(Flags.NEW_NOTIFICATION_PIPELINE_RENDERING);
+ }
+
+ /** */
+ public boolean useNewLockscreenAnimations() {
+ return isEnabled(Flags.LOCKSCREEN_ANIMATIONS);
+ }
+
+ public boolean isPeopleTileEnabled() {
+ // TODO(b/202860494): different resource overlays have different values.
+ return mFlagReader.isEnabled(R.bool.flag_conversations);
+ }
+
+ public boolean isMonetEnabled() {
+ // TODO(b/202860494): used in wallpaper picker. Always true, maybe delete.
+ return mFlagReader.isEnabled(R.bool.flag_monet);
+ }
+
+ public boolean isPMLiteEnabled() {
+ return isEnabled(Flags.POWER_MENU_LITE);
+ }
+
+ public boolean isChargingRippleEnabled() {
+ // TODO(b/202860494): different resource overlays have different values.
+ return mFlagReader.isEnabled(R.bool.flag_charging_ripple);
+ }
+
+ public boolean isOngoingCallStatusBarChipEnabled() {
+ return isEnabled(Flags.ONGOING_CALL_STATUS_BAR_CHIP);
+ }
+
+ public boolean isOngoingCallInImmersiveEnabled() {
+ return isOngoingCallStatusBarChipEnabled() && isEnabled(Flags.ONGOING_CALL_IN_IMMERSIVE);
+ }
+
+ public boolean isOngoingCallInImmersiveChipTapEnabled() {
+ return isOngoingCallInImmersiveEnabled()
+ && isEnabled(Flags.ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP);
+ }
+
+ public boolean isSmartspaceEnabled() {
+ // TODO(b/202860494): different resource overlays have different values.
+ return mFlagReader.isEnabled(R.bool.flag_smartspace);
+ }
+
+ public boolean isSmartspaceDedupingEnabled() {
+ return isSmartspaceEnabled() && isEnabled(Flags.SMARTSPACE_DEDUPING);
+ }
+
+ public boolean isNewKeyguardSwipeAnimationEnabled() {
+ return isEnabled(Flags.NEW_UNLOCK_SWIPE_ANIMATION);
+ }
+
+ public boolean isSmartSpaceSharedElementTransitionEnabled() {
+ return isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED);
+ }
+
+ /** Whether or not to use the provider model behavior for the status bar icons */
+ public boolean isCombinedStatusBarSignalIconsEnabled() {
+ return isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS);
+ }
+
+ /** System setting for provider model behavior */
+ public boolean isProviderModelSettingEnabled() {
+ return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
+ }
+
+ /**
+ * Use the new version of the user switcher
+ */
+ public boolean useNewUserSwitcher() {
+ return isEnabled(Flags.NEW_USER_SWITCHER);
+ }
+
+ /** static method for the system setting */
+ public static boolean isProviderModelSettingEnabled(Context context) {
+ return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
+ }
+
+ /** Simple interface for beinga alerted when a specific flag changes value. */
+ public interface Listener {
+ /** */
+ void onFlagChanged(Flag<?> flag);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagReader.java b/packages/SystemUI/src/com/android/systemui/flags/FlagReader.java
new file mode 100644
index 000000000000..1ae8c1f2a712
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagReader.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.systemui.flags;
+
+
+/**
+ * Plugin for loading flag values
+ */
+public interface FlagReader {
+ /** Returns a boolean value for the given flag. */
+ default boolean isEnabled(int id, boolean def) {
+ return def;
+ }
+
+ /** Add a listener to be alerted when any flag changes. */
+ default void addListener(Listener listener) {}
+
+ /** Remove a listener to be alerted when any flag changes. */
+ default void removeListener(Listener listener) {}
+
+ /** A simple listener to be alerted when a flag changes. */
+ interface Listener {
+ /** */
+ void onFlagChanged(int id);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagWriter.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagWriter.kt
new file mode 100644
index 000000000000..bacc66b39c58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagWriter.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.systemui.flags
+
+interface FlagWriter {
+ fun setEnabled(key: Int, value: Boolean) {}
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
index 6561bd5a5323..1dc5a9f3adc5 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
@@ -26,11 +26,19 @@ import javax.inject.Inject
*/
@SysUISingleton
open class SystemPropertiesHelper @Inject constructor() {
+ fun get(name: String): String {
+ return SystemProperties.get(name)
+ }
+
fun getBoolean(name: String, default: Boolean): Boolean {
return SystemProperties.getBoolean(name, default)
}
+ fun set(name: String, value: String) {
+ SystemProperties.set(name, value)
+ }
+
fun set(name: String, value: Int) {
- SystemProperties.set(name, value.toString())
+ set(name, value.toString())
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
index 37b8a2c953fe..4f5a969c9791 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
@@ -22,6 +22,7 @@ import android.view.View;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -60,11 +61,15 @@ public class FragmentService implements Dumpable {
};
@Inject
- public FragmentService(FragmentCreator.Factory fragmentCreatorFactory,
- ConfigurationController configurationController) {
+ public FragmentService(
+ FragmentCreator.Factory fragmentCreatorFactory,
+ ConfigurationController configurationController,
+ DumpManager dumpManager) {
mFragmentCreator = fragmentCreatorFactory.build();
initInjectionMap();
configurationController.addCallback(mConfigurationListener);
+
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
ArrayMap<String, Method> getInjectionMap() {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
deleted file mode 100644
index 1b4a47e4bf39..000000000000
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ /dev/null
@@ -1,599 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.globalactions;
-
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.annotation.Nullable;
-import android.app.IActivityManager;
-import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
-import android.app.trust.TrustManager;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.graphics.drawable.Drawable;
-import android.media.AudioManager;
-import android.os.Handler;
-import android.os.UserManager;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.service.dreams.IDreamManager;
-import android.telecom.TelecomManager;
-import android.transition.AutoTransition;
-import android.transition.TransitionManager;
-import android.transition.TransitionSet;
-import android.view.IWindowManager;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import androidx.lifecycle.LifecycleOwner;
-
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.view.RotationPolicy;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
-import com.android.systemui.plugins.GlobalActionsPanelPlugin;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.util.RingerModeTracker;
-import com.android.systemui.util.leak.RotationUtils;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-import javax.inject.Provider;
-
-/**
- * Helper to show the global actions dialog. Each item is an {@link Action} that may show depending
- * on whether the keyguard is showing, and whether the device is provisioned.
- * This version includes wallet.
- */
-public class GlobalActionsDialog extends GlobalActionsDialogLite
- implements DialogInterface.OnDismissListener,
- DialogInterface.OnShowListener,
- ConfigurationController.ConfigurationListener,
- GlobalActionsPanelPlugin.Callbacks,
- LifecycleOwner {
-
- private static final String TAG = "GlobalActionsDialog";
-
- private final LockPatternUtils mLockPatternUtils;
- private final KeyguardStateController mKeyguardStateController;
- private final SysUiState mSysUiState;
- private final ActivityStarter mActivityStarter;
- private final SysuiColorExtractor mSysuiColorExtractor;
- private final IStatusBarService mStatusBarService;
- private final NotificationShadeWindowController mNotificationShadeWindowController;
- private GlobalActionsPanelPlugin mWalletPlugin;
-
- @VisibleForTesting
- boolean mShowLockScreenCards = false;
-
- private final KeyguardStateController.Callback mKeyguardStateControllerListener =
- new KeyguardStateController.Callback() {
- @Override
- public void onUnlockedChanged() {
- if (mDialog != null) {
- ActionsDialog dialog = (ActionsDialog) mDialog;
- boolean unlocked = mKeyguardStateController.isUnlocked();
- if (dialog.mWalletViewController != null) {
- dialog.mWalletViewController.onDeviceLockStateChanged(!unlocked);
- }
-
- if (unlocked) {
- dialog.hideLockMessage();
- }
- }
- }
- };
-
- private final ContentObserver mSettingsObserver = new ContentObserver(mMainHandler) {
- @Override
- public void onChange(boolean selfChange) {
- onPowerMenuLockScreenSettingsChanged();
- }
- };
-
- /**
- * @param context everything needs a context :(
- */
- @Inject
- public GlobalActionsDialog(
- Context context,
- GlobalActionsManager windowManagerFuncs,
- AudioManager audioManager,
- IDreamManager iDreamManager,
- DevicePolicyManager devicePolicyManager,
- LockPatternUtils lockPatternUtils,
- BroadcastDispatcher broadcastDispatcher,
- TelephonyListenerManager telephonyListenerManager,
- GlobalSettings globalSettings,
- SecureSettings secureSettings,
- @Nullable Vibrator vibrator,
- @Main Resources resources,
- ConfigurationController configurationController,
- ActivityStarter activityStarter,
- KeyguardStateController keyguardStateController,
- UserManager userManager,
- TrustManager trustManager,
- IActivityManager iActivityManager,
- @Nullable TelecomManager telecomManager,
- MetricsLogger metricsLogger,
- SysuiColorExtractor colorExtractor,
- IStatusBarService statusBarService,
- NotificationShadeWindowController notificationShadeWindowController,
- IWindowManager iWindowManager,
- @Background Executor backgroundExecutor,
- UiEventLogger uiEventLogger,
- RingerModeTracker ringerModeTracker,
- SysUiState sysUiState,
- @Main Handler handler,
- PackageManager packageManager,
- StatusBar statusBar,
- KeyguardUpdateMonitor keyguardUpdateMonitor) {
-
- super(context,
- windowManagerFuncs,
- audioManager,
- iDreamManager,
- devicePolicyManager,
- lockPatternUtils,
- broadcastDispatcher,
- telephonyListenerManager,
- globalSettings,
- secureSettings,
- vibrator,
- resources,
- configurationController,
- keyguardStateController,
- userManager,
- trustManager,
- iActivityManager,
- telecomManager,
- metricsLogger,
- colorExtractor,
- statusBarService,
- notificationShadeWindowController,
- iWindowManager,
- backgroundExecutor,
- uiEventLogger,
- null,
- ringerModeTracker,
- sysUiState,
- handler,
- packageManager,
- statusBar,
- keyguardUpdateMonitor);
-
- mLockPatternUtils = lockPatternUtils;
- mKeyguardStateController = keyguardStateController;
- mSysuiColorExtractor = colorExtractor;
- mStatusBarService = statusBarService;
- mNotificationShadeWindowController = notificationShadeWindowController;
- mSysUiState = sysUiState;
- mActivityStarter = activityStarter;
-
- mKeyguardStateController.addCallback(mKeyguardStateControllerListener);
-
- // Listen for changes to show pay on the power menu while locked
- onPowerMenuLockScreenSettingsChanged();
- mGlobalSettings.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT),
- false /* notifyForDescendants */,
- mSettingsObserver);
- }
-
- @Override
- public void destroy() {
- super.destroy();
- mKeyguardStateController.removeCallback(mKeyguardStateControllerListener);
- mGlobalSettings.unregisterContentObserver(mSettingsObserver);
- }
-
- /**
- * Show the global actions dialog (creating if necessary)
- *
- * @param keyguardShowing True if keyguard is showing
- */
- public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
- GlobalActionsPanelPlugin walletPlugin) {
- mWalletPlugin = walletPlugin;
- super.showOrHideDialog(keyguardShowing, isDeviceProvisioned);
- }
-
- /**
- * Returns the maximum number of power menu items to show based on which GlobalActions
- * layout is being used.
- */
- @VisibleForTesting
- @Override
- protected int getMaxShownPowerItems() {
- return getContext().getResources().getInteger(
- com.android.systemui.R.integer.power_menu_max_columns);
- }
-
- /**
- * Create the global actions dialog.
- *
- * @return A new dialog.
- */
- @Override
- protected ActionsDialogLite createDialog() {
- initDialogItems();
-
- ActionsDialog dialog = new ActionsDialog(getContext(), mAdapter, mOverflowAdapter,
- this::getWalletViewController, mSysuiColorExtractor,
- mStatusBarService, mNotificationShadeWindowController,
- mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger(),
- getStatusBar(), getKeyguardUpdateMonitor(), mLockPatternUtils);
-
- if (shouldShowLockMessage(dialog)) {
- dialog.showLockMessage();
- }
- dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
- dialog.setOnDismissListener(this);
- dialog.setOnShowListener(this);
-
- return dialog;
- }
-
- @Nullable
- private GlobalActionsPanelPlugin.PanelViewController getWalletViewController() {
- if (mWalletPlugin == null) {
- return null;
- }
- return mWalletPlugin.onPanelShown(this, !mKeyguardStateController.isUnlocked());
- }
-
- /**
- * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is
- * called when the quick access wallet requests that an intent be started (with lock screen
- * shown first if needed).
- */
- @Override
- public void startPendingIntentDismissingKeyguard(PendingIntent pendingIntent) {
- mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent);
- }
-
- @Override
- protected int getEmergencyTextColor(Context context) {
- return context.getResources().getColor(
- com.android.systemui.R.color.global_actions_emergency_text);
- }
-
- @Override
- protected int getEmergencyIconColor(Context context) {
- return getContext().getResources().getColor(
- com.android.systemui.R.color.global_actions_emergency_text);
- }
-
- @Override
- protected int getEmergencyBackgroundColor(Context context) {
- return getContext().getResources().getColor(
- com.android.systemui.R.color.global_actions_emergency_background);
- }
-
- @Override
- protected int getGridItemLayoutResource() {
- return com.android.systemui.R.layout.global_actions_grid_item_v2;
- }
-
- @VisibleForTesting
- static class ActionsDialog extends ActionsDialogLite {
-
- private final Provider<GlobalActionsPanelPlugin.PanelViewController> mWalletFactory;
- @Nullable private GlobalActionsPanelPlugin.PanelViewController mWalletViewController;
- private ResetOrientationData mResetOrientationData;
- @VisibleForTesting ViewGroup mLockMessageContainer;
- private TextView mLockMessage;
-
- ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter,
- Provider<GlobalActionsPanelPlugin.PanelViewController> walletFactory,
- SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
- NotificationShadeWindowController notificationShadeWindowController,
- SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
- MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
- StatusBar statusBar, KeyguardUpdateMonitor keyguardUpdateMonitor,
- LockPatternUtils lockPatternUtils) {
- super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions,
- adapter, overflowAdapter, sysuiColorExtractor, statusBarService,
- notificationShadeWindowController, sysuiState, onRotateCallback,
- keyguardShowing, powerAdapter, uiEventLogger, null,
- statusBar, keyguardUpdateMonitor, lockPatternUtils);
- mWalletFactory = walletFactory;
-
- // Update window attributes
- Window window = getWindow();
- window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
- window.setLayout(MATCH_PARENT, MATCH_PARENT);
- window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
- window.addFlags(
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
- setTitle(R.string.global_actions);
- initializeLayout();
- }
-
- private boolean isWalletViewAvailable() {
- return mWalletViewController != null && mWalletViewController.getPanelContent() != null;
- }
-
- private void initializeWalletView() {
- if (mWalletFactory == null) {
- return;
- }
- mWalletViewController = mWalletFactory.get();
- if (!isWalletViewAvailable()) {
- return;
- }
-
- boolean isLandscapeWalletViewShown = mContext.getResources().getBoolean(
- com.android.systemui.R.bool.global_actions_show_landscape_wallet_view);
-
- int rotation = RotationUtils.getRotation(mContext);
- boolean rotationLocked = RotationPolicy.isRotationLocked(mContext);
- if (rotation != RotationUtils.ROTATION_NONE) {
- if (rotationLocked) {
- if (mResetOrientationData == null) {
- mResetOrientationData = new ResetOrientationData();
- mResetOrientationData.locked = true;
- mResetOrientationData.rotation = rotation;
- }
-
- // Unlock rotation, so user can choose to rotate to portrait to see the panel.
- // This call is posted so that the rotation does not change until post-layout,
- // otherwise onConfigurationChanged() may not get invoked.
- mGlobalActionsLayout.post(() ->
- RotationPolicy.setRotationLockAtAngle(
- mContext, false, RotationUtils.ROTATION_NONE));
-
- if (!isLandscapeWalletViewShown) {
- return;
- }
- }
- } else {
- if (!rotationLocked) {
- if (mResetOrientationData == null) {
- mResetOrientationData = new ResetOrientationData();
- mResetOrientationData.locked = false;
- }
- }
-
- boolean shouldLockRotation = !isLandscapeWalletViewShown;
- if (rotationLocked != shouldLockRotation) {
- // Locks the screen to portrait if the landscape / seascape orientation does not
- // show the wallet view, so the user doesn't accidentally hide the panel.
- // This call is posted so that the rotation does not change until post-layout,
- // otherwise onConfigurationChanged() may not get invoked.
- mGlobalActionsLayout.post(() ->
- RotationPolicy.setRotationLockAtAngle(
- mContext, shouldLockRotation, RotationUtils.ROTATION_NONE));
- }
- }
-
- // Disable rotation suggestions, if enabled
- setRotationSuggestionsEnabled(false);
-
- FrameLayout panelContainer =
- findViewById(com.android.systemui.R.id.global_actions_wallet);
- FrameLayout.LayoutParams panelParams =
- new FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.MATCH_PARENT);
- panelParams.topMargin = mContext.getResources().getDimensionPixelSize(
- com.android.systemui.R.dimen.global_actions_wallet_top_margin);
- View walletView = mWalletViewController.getPanelContent();
- panelContainer.addView(walletView, panelParams);
- // Smooth transitions when wallet is resized, which can happen when a card is added
- ViewGroup root = findViewById(com.android.systemui.R.id.global_actions_grid_root);
- if (root != null) {
- walletView.addOnLayoutChangeListener((v, l, t, r, b, ol, ot, or, ob) -> {
- int oldHeight = ob - ot;
- int newHeight = b - t;
- if (oldHeight > 0 && oldHeight != newHeight) {
- TransitionSet transition = new AutoTransition()
- .setDuration(250)
- .setOrdering(TransitionSet.ORDERING_TOGETHER);
- TransitionManager.beginDelayedTransition(root, transition);
- }
- });
- }
- }
-
- @Override
- protected int getLayoutResource() {
- return com.android.systemui.R.layout.global_actions_grid_v2;
- }
-
- @Override
- protected void initializeLayout() {
- super.initializeLayout();
- mLockMessageContainer = requireViewById(
- com.android.systemui.R.id.global_actions_lock_message_container);
- mLockMessage = requireViewById(com.android.systemui.R.id.global_actions_lock_message);
- initializeWalletView();
- getWindow().setBackgroundDrawable(mBackgroundDrawable);
- }
-
- @Override
- protected void showDialog() {
- mShowing = true;
- mNotificationShadeWindowController.setRequestTopUi(true, TAG);
- mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true)
- .commitUpdate(mContext.getDisplayId());
-
- ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView();
- root.setOnApplyWindowInsetsListener((v, windowInsets) -> {
- root.setPadding(windowInsets.getStableInsetLeft(),
- windowInsets.getStableInsetTop(),
- windowInsets.getStableInsetRight(),
- windowInsets.getStableInsetBottom());
- return WindowInsets.CONSUMED;
- });
-
- mBackgroundDrawable.setAlpha(0);
- float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
- ObjectAnimator alphaAnimator =
- ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f);
- alphaAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- alphaAnimator.setDuration(183);
- alphaAnimator.addUpdateListener((animation) -> {
- float animatedValue = animation.getAnimatedFraction();
- int alpha = (int) (animatedValue * mScrimAlpha * 255);
- mBackgroundDrawable.setAlpha(alpha);
- });
-
- ObjectAnimator xAnimator =
- ObjectAnimator.ofFloat(mContainer, "translationX", xOffset, 0f);
- xAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- xAnimator.setDuration(350);
-
- AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(alphaAnimator, xAnimator);
- animatorSet.start();
- }
-
- @Override
- protected void dismissInternal() {
- super.dismissInternal();
- }
-
- @Override
- protected void completeDismiss() {
- dismissWallet();
- resetOrientation();
- super.completeDismiss();
- }
-
- private void dismissWallet() {
- if (mWalletViewController != null) {
- mWalletViewController.onDismissed();
- // The wallet controller should not be re-used after being dismissed.
- mWalletViewController = null;
- }
- }
-
- private void resetOrientation() {
- if (mResetOrientationData != null) {
- RotationPolicy.setRotationLockAtAngle(mContext, mResetOrientationData.locked,
- mResetOrientationData.rotation);
- }
- setRotationSuggestionsEnabled(true);
- }
-
- @Override
- public void refreshDialog() {
- // ensure dropdown menus are dismissed before re-initializing the dialog
- dismissWallet();
- super.refreshDialog();
- }
-
- void hideLockMessage() {
- if (mLockMessageContainer.getVisibility() == View.VISIBLE) {
- mLockMessageContainer.animate().alpha(0).setDuration(150).setListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mLockMessageContainer.setVisibility(View.GONE);
- }
- }).start();
- }
- }
-
- void showLockMessage() {
- Drawable lockIcon = mContext.getDrawable(com.android.internal.R.drawable.ic_lock);
- lockIcon.setTint(mContext.getColor(com.android.systemui.R.color.control_primary_text));
- mLockMessage.setCompoundDrawablesWithIntrinsicBounds(null, lockIcon, null, null);
- mLockMessageContainer.setVisibility(View.VISIBLE);
- }
-
- private static class ResetOrientationData {
- public boolean locked;
- public int rotation;
- }
- }
-
- /**
- * Determines whether or not debug mode has been activated for the Global Actions Panel.
- */
- private static boolean isPanelDebugModeEnabled(Context context) {
- return Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.GLOBAL_ACTIONS_PANEL_DEBUG_ENABLED, 0) == 1;
- }
-
- /**
- * Determines whether or not the Global Actions menu should be forced to use the newer
- * grid-style layout.
- */
- private static boolean isForceGridEnabled(Context context) {
- return isPanelDebugModeEnabled(context);
- }
-
- private boolean shouldShowLockMessage(ActionsDialog dialog) {
- return isWalletAvailableAfterUnlock(dialog);
- }
-
- // Temporary while we move items out of the power menu
- private boolean isWalletAvailableAfterUnlock(ActionsDialog dialog) {
- boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id)
- == STRONG_AUTH_REQUIRED_AFTER_BOOT;
- return !mKeyguardStateController.isUnlocked()
- && (!mShowLockScreenCards || isLockedAfterBoot)
- && dialog.isWalletViewAvailable();
- }
-
- private void onPowerMenuLockScreenSettingsChanged() {
- mShowLockScreenCards = mSecureSettings.getInt(
- Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, 0) != 0;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 9ada54b9ef6f..b06b024a63a4 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -133,6 +133,7 @@ import com.android.systemui.util.settings.SecureSettings;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -197,7 +198,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
private final MetricsLogger mMetricsLogger;
private final UiEventLogger mUiEventLogger;
private final SysUiState mSysUiState;
- private final GlobalActionsInfoProvider mInfoProvider;
// Used for RingerModeTracker
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
@@ -237,7 +237,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
protected Handler mMainHandler;
private int mSmallestScreenWidthDp;
- private final StatusBar mStatusBar;
+ private final Optional<StatusBar> mStatusBarOptional;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@VisibleForTesting
@@ -341,12 +341,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
IWindowManager iWindowManager,
@Background Executor backgroundExecutor,
UiEventLogger uiEventLogger,
- GlobalActionsInfoProvider infoProvider,
RingerModeTracker ringerModeTracker,
SysUiState sysUiState,
- @Main Handler handler,
+ @Main Handler handler,
PackageManager packageManager,
- StatusBar statusBar,
+ Optional<StatusBar> statusBarOptional,
KeyguardUpdateMonitor keyguardUpdateMonitor) {
mContext = context;
mWindowManagerFuncs = windowManagerFuncs;
@@ -367,7 +366,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mTelecomManager = telecomManager;
mMetricsLogger = metricsLogger;
mUiEventLogger = uiEventLogger;
- mInfoProvider = infoProvider;
mSysuiColorExtractor = colorExtractor;
mStatusBarService = statusBarService;
mNotificationShadeWindowController = notificationShadeWindowController;
@@ -377,7 +375,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mSysUiState = sysUiState;
mMainHandler = handler;
mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp;
- mStatusBar = statusBar;
+ mStatusBarOptional = statusBarOptional;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
// receive broadcasts
@@ -428,8 +426,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
return mUiEventLogger;
}
- protected StatusBar getStatusBar() {
- return mStatusBar;
+ protected Optional<StatusBar> getStatusBar() {
+ return mStatusBarOptional;
}
protected KeyguardUpdateMonitor getKeyguardUpdateMonitor() {
@@ -667,7 +665,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mAdapter, mOverflowAdapter, mSysuiColorExtractor,
mStatusBarService, mNotificationShadeWindowController,
mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter, mUiEventLogger,
- mInfoProvider, mStatusBar, mKeyguardUpdateMonitor, mLockPatternUtils);
+ mStatusBarOptional, mKeyguardUpdateMonitor, mLockPatternUtils);
dialog.setOnDismissListener(this);
dialog.setOnShowListener(this);
@@ -864,7 +862,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mUiEventLogger.log(GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS);
if (mTelecomManager != null) {
// Close shade so user sees the activity
- mStatusBar.collapseShade();
+ mStatusBarOptional.ifPresent(StatusBar::collapseShade);
Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent(
null /* number */);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
@@ -996,7 +994,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mIActivityManager.requestInteractiveBugReport();
}
// Close shade so user sees the activity
- mStatusBar.collapseShade();
+ mStatusBarOptional.ifPresent(StatusBar::collapseShade);
} catch (RemoteException e) {
}
}
@@ -1016,7 +1014,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS);
mIActivityManager.requestFullBugReport();
// Close shade so user sees the activity
- mStatusBar.collapseShade();
+ mStatusBarOptional.ifPresent(StatusBar::collapseShade);
} catch (RemoteException e) {
}
return false;
@@ -2133,9 +2131,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
private Dialog mPowerOptionsDialog;
protected final Runnable mOnRotateCallback;
private UiEventLogger mUiEventLogger;
- private GlobalActionsInfoProvider mInfoProvider;
private GestureDetector mGestureDetector;
- private StatusBar mStatusBar;
+ private Optional<StatusBar> mStatusBarOptional;
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private LockPatternUtils mLockPatternUtils;
@@ -2162,7 +2159,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
if (distanceY < 0 && distanceY > distanceX
- && e1.getY() <= mStatusBar.getStatusBarHeight()) {
+ && e1.getY() <= mStatusBarOptional.map(
+ StatusBar::getStatusBarHeight).orElse(0)) {
// Downwards scroll from top
openShadeAndDismiss();
return true;
@@ -2174,7 +2172,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
if (velocityY > 0 && Math.abs(velocityY) > Math.abs(velocityX)
- && e1.getY() <= mStatusBar.getStatusBarHeight()) {
+ && e1.getY() <= mStatusBarOptional.map(
+ StatusBar::getStatusBarHeight).orElse(0)) {
// Downwards fling from top
openShadeAndDismiss();
return true;
@@ -2189,7 +2188,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
NotificationShadeWindowController notificationShadeWindowController,
SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
- @Nullable GlobalActionsInfoProvider infoProvider, StatusBar statusBar,
+ Optional<StatusBar> statusBarOptional,
KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils) {
super(context, themeRes);
mContext = context;
@@ -2203,8 +2202,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mOnRotateCallback = onRotateCallback;
mKeyguardShowing = keyguardShowing;
mUiEventLogger = uiEventLogger;
- mInfoProvider = infoProvider;
- mStatusBar = statusBar;
+ mStatusBarOptional = statusBarOptional;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
@@ -2237,12 +2235,14 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
private void openShadeAndDismiss() {
mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
- if (mStatusBar.isKeyguardShowing()) {
+ if (mStatusBarOptional.map(StatusBar::isKeyguardShowing).orElse(false)) {
// match existing lockscreen behavior to open QS when swiping from status bar
- mStatusBar.animateExpandSettingsPanel(null);
+ mStatusBarOptional.ifPresent(
+ statusBar -> statusBar.animateExpandSettingsPanel(null));
} else {
// otherwise, swiping down should expand notification shade
- mStatusBar.animateExpandNotificationsPanel();
+ mStatusBarOptional.ifPresent(
+ statusBar -> statusBar.animateExpandNotificationsPanel());
}
dismiss();
}
@@ -2324,10 +2324,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mScrimAlpha = 1.0f;
}
- if (mInfoProvider != null && mInfoProvider.shouldShowMessage()) {
- mInfoProvider.addPanel(mContext, mContainer, mAdapter.getCount(), () -> dismiss());
- }
-
// If user entered from the lock screen and smart lock was enabled, disable it
int user = KeyguardUpdateMonitor.getCurrentUser();
boolean userHasTrust = mKeyguardUpdateMonitor.getUserHasTrust(user);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt
deleted file mode 100644
index 25837e3aacdf..000000000000
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt
+++ /dev/null
@@ -1,120 +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.systemui.globalactions
-
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.content.res.Configuration
-import android.net.Uri
-import android.service.quickaccesswallet.QuickAccessWalletClient
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import android.widget.TextView
-import com.android.systemui.R
-import com.android.systemui.controls.controller.ControlsController
-import com.android.systemui.plugins.ActivityStarter
-import javax.inject.Inject
-
-private const val TAG = "GlobalActionsInfo"
-
-/** Maximum number of times to show change info message */
-private const val MAX_VIEW_COUNT = 3
-
-/** Maximum number of buttons allowed in landscape before this panel does not fit */
-private const val MAX_BUTTONS_LANDSCAPE = 4
-
-private const val PREFERENCE = "global_actions_info_prefs"
-private const val KEY_VIEW_COUNT = "view_count"
-
-class GlobalActionsInfoProvider @Inject constructor(
- private val context: Context,
- private val walletClient: QuickAccessWalletClient,
- private val controlsController: ControlsController,
- private val activityStarter: ActivityStarter
-) {
-
- private var pendingIntent: PendingIntent
-
- init {
- val url = context.resources.getString(R.string.global_actions_change_url)
- val intent = Intent(Intent.ACTION_VIEW).apply {
- setData(Uri.parse(url))
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- }
- pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
- }
-
- fun addPanel(context: Context, parent: ViewGroup, nActions: Int, dismissParent: Runnable) {
- // This panel does not fit on landscape with two rows of buttons showing,
- // so skip adding the panel (and incrementing view counT) in that case
- val isLandscape =
- context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
- if (isLandscape && nActions > MAX_BUTTONS_LANDSCAPE) {
- return
- }
-
- val view = LayoutInflater.from(context).inflate(R.layout.global_actions_change_panel,
- parent, false)
-
- val walletTitle = walletClient.serviceLabel ?: context.getString(R.string.wallet_title)
- val message = view.findViewById<TextView>(R.id.global_actions_change_message)
- message?.setText(context.getString(R.string.global_actions_change_description, walletTitle))
-
- view.setOnClickListener { _ ->
- dismissParent.run()
- activityStarter.postStartActivityDismissingKeyguard(pendingIntent)
- }
- parent.addView(view, 0) // Add to top
- incrementViewCount()
- }
-
- fun shouldShowMessage(): Boolean {
- // This is only relevant for some devices
- val isEligible = context.resources.getBoolean(
- R.bool.global_actions_show_change_info)
- if (!isEligible) {
- return false
- }
-
- val sharedPrefs = context.getSharedPreferences(PREFERENCE, Context.MODE_PRIVATE)
-
- // Only show to users who previously had these items set up
- val viewCount = if (sharedPrefs.contains(KEY_VIEW_COUNT) || hadContent()) {
- sharedPrefs.getInt(KEY_VIEW_COUNT, 0)
- } else {
- -1
- }
-
- // Limit number of times this is displayed
- return viewCount > -1 && viewCount < MAX_VIEW_COUNT
- }
-
- private fun hadContent(): Boolean {
- // Check whether user would have seen content in the power menu that has now moved
- val hadControls = controlsController.getFavorites().size > 0
- val hadCards = walletClient.isWalletFeatureAvailable
- Log.d(TAG, "Previously had controls $hadControls, cards $hadCards")
- return hadControls || hadCards
- }
-
- private fun incrementViewCount() {
- val sharedPrefs = context.getSharedPreferences(PREFERENCE, Context.MODE_PRIVATE)
- val count = sharedPrefs.getInt(KEY_VIEW_COUNT, 0)
- sharedPrefs.edit().putInt(KEY_VIEW_COUNT, count + 1).apply()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index a51ec54ebcce..729730cdf6f1 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -66,6 +66,13 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer {
mOnBitmapUpdated = c;
}
+ /**
+ * @hide
+ */
+ public void use(Consumer<Bitmap> c) {
+ mTexture.use(c);
+ }
+
@Override
public boolean isWcgContent() {
return mTexture.isWcgContent();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessController.kt
deleted file mode 100644
index b4137fa80f19..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessController.kt
+++ /dev/null
@@ -1,195 +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.systemui.keyguard
-
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.ValueAnimator
-import android.content.res.Resources
-import android.database.ContentObserver
-import android.graphics.Bitmap
-import android.graphics.BitmapFactory
-import android.graphics.Color
-import android.graphics.drawable.ColorDrawable
-import android.hardware.biometrics.BiometricSourceType
-import android.os.Handler
-import android.provider.Settings.System.SCREEN_BRIGHTNESS_FLOAT
-import android.util.MathUtils
-import android.view.View
-import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
-import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-import com.android.internal.annotations.VisibleForTesting
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.Dumpable
-import com.android.systemui.R
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.settings.SystemSettings
-import java.io.FileDescriptor
-import java.io.PrintWriter
-import java.lang.Float.max
-import java.util.concurrent.TimeUnit
-
-val DEFAULT_ANIMATION_DURATION = TimeUnit.SECONDS.toMillis(4)
-val MAX_SCREEN_BRIGHTNESS = 100 // 0..100
-val MAX_SCRIM_OPACTY = 50 // 0..100
-val DEFAULT_USE_FACE_WALLPAPER = false
-
-/**
- * This class is responsible for ramping up the display brightness (and white overlay) in order
- * to mitigate low light conditions when running face auth without an IR camera.
- */
-@SysUISingleton
-open class FaceAuthScreenBrightnessController(
- private val notificationShadeWindowController: NotificationShadeWindowController,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val resources: Resources,
- private val globalSettings: GlobalSettings,
- private val systemSettings: SystemSettings,
- private val mainHandler: Handler,
- private val dumpManager: DumpManager,
- private val enabled: Boolean
-) : Dumpable {
-
- private var userDefinedBrightness: Float = 1f
- @VisibleForTesting
- var useFaceAuthWallpaper = globalSettings
- .getInt("sysui.use_face_auth_wallpaper", if (DEFAULT_USE_FACE_WALLPAPER) 1 else 0) == 1
- private val brightnessAnimationDuration = globalSettings
- .getLong("sysui.face_brightness_anim_duration", DEFAULT_ANIMATION_DURATION)
- private val maxScreenBrightness = globalSettings
- .getInt("sysui.face_max_brightness", MAX_SCREEN_BRIGHTNESS) / 100f
- private val maxScrimOpacity = globalSettings
- .getInt("sysui.face_max_scrim_opacity", MAX_SCRIM_OPACTY) / 100f
- private val keyguardUpdateCallback = object : KeyguardUpdateMonitorCallback() {
- override fun onBiometricRunningStateChanged(
- running: Boolean,
- biometricSourceType: BiometricSourceType?
- ) {
- if (biometricSourceType != BiometricSourceType.FACE) {
- return
- }
- // TODO enable only when receiving a low-light error
- overridingBrightness = if (enabled) running else false
- }
- }
- private lateinit var whiteOverlay: View
- private var brightnessAnimator: ValueAnimator? = null
- private var overridingBrightness = false
- set(value) {
- if (field == value) {
- return
- }
- field = value
- brightnessAnimator?.cancel()
-
- if (!value) {
- notificationShadeWindowController.setFaceAuthDisplayBrightness(BRIGHTNESS_OVERRIDE_NONE)
- if (whiteOverlay.alpha > 0) {
- brightnessAnimator = createAnimator(whiteOverlay.alpha, 0f).apply {
- duration = 200
- addUpdateListener {
- whiteOverlay.alpha = it.animatedValue as Float
- }
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- whiteOverlay.visibility = View.INVISIBLE
- brightnessAnimator = null
- }
- })
- start()
- }
- }
- return
- }
-
- val targetBrightness = max(maxScreenBrightness, userDefinedBrightness)
- whiteOverlay.visibility = View.VISIBLE
- brightnessAnimator = createAnimator(0f, 1f).apply {
- duration = brightnessAnimationDuration
- addUpdateListener {
- val progress = it.animatedValue as Float
- val brightnessProgress = MathUtils.constrainedMap(
- userDefinedBrightness, targetBrightness, 0f, 0.5f, progress)
- val scrimProgress = MathUtils.constrainedMap(
- 0f, maxScrimOpacity, 0.5f, 1f, progress)
- notificationShadeWindowController.setFaceAuthDisplayBrightness(brightnessProgress)
- whiteOverlay.alpha = scrimProgress
- }
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- brightnessAnimator = null
- }
- })
- start()
- }
- }
-
- @VisibleForTesting
- open fun createAnimator(start: Float, end: Float) = ValueAnimator.ofFloat(start, end)
-
- /**
- * Returns a bitmap that should be used by the lock screen as a wallpaper, if face auth requires
- * a secure wallpaper.
- */
- var faceAuthWallpaper: Bitmap? = null
- get() {
- val user = KeyguardUpdateMonitor.getCurrentUser()
- if (useFaceAuthWallpaper && keyguardUpdateMonitor.isFaceAuthEnabledForUser(user)) {
- val options = BitmapFactory.Options().apply {
- inScaled = false
- }
- return BitmapFactory.decodeResource(resources, R.drawable.face_auth_wallpaper, options)
- }
- return null
- }
- private set
-
- fun attach(overlayView: View) {
- whiteOverlay = overlayView
- whiteOverlay.focusable = FLAG_NOT_FOCUSABLE
- whiteOverlay.background = ColorDrawable(Color.WHITE)
- whiteOverlay.isEnabled = false
- whiteOverlay.alpha = 0f
- whiteOverlay.visibility = View.INVISIBLE
-
- dumpManager.registerDumpable(this.javaClass.name, this)
- keyguardUpdateMonitor.registerCallback(keyguardUpdateCallback)
- systemSettings.registerContentObserver(SCREEN_BRIGHTNESS_FLOAT,
- object : ContentObserver(mainHandler) {
- override fun onChange(selfChange: Boolean) {
- userDefinedBrightness = systemSettings.getFloat(SCREEN_BRIGHTNESS_FLOAT)
- }
- })
- userDefinedBrightness = systemSettings.getFloat(SCREEN_BRIGHTNESS_FLOAT, 1f)
- }
-
- override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
- pw.apply {
- println("overridingBrightness: $overridingBrightness")
- println("useFaceAuthWallpaper: $useFaceAuthWallpaper")
- println("brightnessAnimator: $brightnessAnimator")
- println("brightnessAnimationDuration: $brightnessAnimationDuration")
- println("maxScreenBrightness: $maxScreenBrightness")
- println("userDefinedBrightness: $userDefinedBrightness")
- println("enabled: $enabled")
- }
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 62b92cb33f5c..01a0f271e5b9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -18,14 +18,34 @@ package com.android.systemui.keyguard;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+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_OLD_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
-
+import static android.view.WindowManager.TRANSIT_OLD_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.TransitionOldType;
+import static android.view.WindowManager.TransitionType;
+import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
+
+import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.Service;
+import android.app.WindowConfiguration;
import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
@@ -42,8 +62,14 @@ import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.RemoteTransition;
+import android.window.TransitionFilter;
+import android.window.TransitionInfo;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.policy.IKeyguardDrawnCallback;
@@ -51,8 +77,11 @@ import com.android.internal.policy.IKeyguardExitCallback;
import com.android.internal.policy.IKeyguardService;
import com.android.internal.policy.IKeyguardStateCallback;
import com.android.systemui.SystemUIApplication;
+import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
+import java.util.ArrayList;
+
import javax.inject.Inject;
public class KeyguardService extends Service {
@@ -73,46 +102,204 @@ public class KeyguardService extends Service {
"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 boolean sEnableRemoteKeyguardGoingAwayAnimation =
- !Transitions.ENABLE_SHELL_TRANSITIONS && sEnableRemoteKeyguardAnimation >= 1;
+ sEnableRemoteKeyguardAnimation >= 1;
/**
* @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
*/
public static boolean sEnableRemoteKeyguardOccludeAnimation =
- !Transitions.ENABLE_SHELL_TRANSITIONS && sEnableRemoteKeyguardAnimation >= 2;
+ sEnableRemoteKeyguardAnimation >= 2;
private final KeyguardViewMediator mKeyguardViewMediator;
private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
+ private static int newModeToLegacyMode(int newMode) {
+ switch (newMode) {
+ case WindowManager.TRANSIT_OPEN:
+ case WindowManager.TRANSIT_TO_FRONT:
+ return MODE_OPENING;
+ case WindowManager.TRANSIT_CLOSE:
+ case WindowManager.TRANSIT_TO_BACK:
+ return MODE_CLOSING;
+ default:
+ return 2; // MODE_CHANGING
+ }
+ }
+
+ private static RemoteAnimationTarget[] wrap(TransitionInfo info, boolean wallpapers) {
+ final ArrayList<RemoteAnimationTarget> out = new ArrayList<>();
+ for (int i = 0; i < info.getChanges().size(); i++) {
+ boolean changeIsWallpaper =
+ (info.getChanges().get(i).getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0;
+ if (wallpapers != changeIsWallpaper) continue;
+
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ final int taskId = taskInfo != null ? change.getTaskInfo().taskId : -1;
+ boolean isNotInRecents;
+ WindowConfiguration windowConfiguration = null;
+ if (taskInfo != null) {
+ if (taskInfo.getConfiguration() != null) {
+ windowConfiguration =
+ change.getTaskInfo().getConfiguration().windowConfiguration;
+ }
+ isNotInRecents = !change.getTaskInfo().isRunning;
+ } else {
+ isNotInRecents = true;
+ }
+ Rect localBounds = new Rect(change.getEndAbsBounds());
+ localBounds.offsetTo(change.getEndRelOffset().x, change.getEndRelOffset().y);
+
+ out.add(new RemoteAnimationTarget(
+ taskId,
+ newModeToLegacyMode(change.getMode()),
+ change.getLeash(),
+ (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0
+ || (change.getFlags() & TransitionInfo.FLAG_SHOW_WALLPAPER) != 0,
+ null /* clipRect */,
+ new Rect(0, 0, 0, 0) /* contentInsets */,
+ info.getChanges().size() - i,
+ new Point(), localBounds, new Rect(change.getEndAbsBounds()),
+ windowConfiguration, isNotInRecents, null /* startLeash */,
+ change.getStartAbsBounds(), taskInfo, false /* allowEnterPip */));
+ }
+ return out.toArray(new RemoteAnimationTarget[out.size()]);
+ }
+
+ private static @TransitionOldType int getTransitionOldType(@TransitionType int type,
+ @TransitionFlags int flags, RemoteAnimationTarget[] apps) {
+ if (type == TRANSIT_KEYGUARD_GOING_AWAY
+ || (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) {
+ return apps.length == 0 ? TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER
+ : TRANSIT_OLD_KEYGUARD_GOING_AWAY;
+ } else if (type == TRANSIT_KEYGUARD_OCCLUDE) {
+ return TRANSIT_OLD_KEYGUARD_OCCLUDE;
+ } else if (type == TRANSIT_KEYGUARD_UNOCCLUDE) {
+ return TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
+ } else {
+ Slog.d(TAG, "Unexpected transit type: " + type);
+ return TRANSIT_OLD_NONE;
+ }
+ }
+
+ private static IRemoteTransition wrap(IRemoteAnimationRunner runner) {
+ return new IRemoteTransition.Stub() {
+ @Override
+ public void startAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback)
+ throws RemoteException {
+ Slog.d(TAG, "Starts IRemoteAnimationRunner: info=" + info);
+ final RemoteAnimationTarget[] apps = wrap(info, false /* wallpapers */);
+ final RemoteAnimationTarget[] wallpapers = wrap(info, true /* wallpapers */);
+ final RemoteAnimationTarget[] nonApps = new RemoteAnimationTarget[0];
+
+ // TODO: Remove this, and update alpha value in the IAnimationRunner.
+ for (TransitionInfo.Change change : info.getChanges()) {
+ t.setAlpha(change.getLeash(), 1.0f);
+ }
+ t.apply();
+ runner.onAnimationStart(getTransitionOldType(info.getType(), info.getFlags(), apps),
+ apps, wallpapers, nonApps,
+ new IRemoteAnimationFinishedCallback.Stub() {
+ @Override
+ public void onAnimationFinished() throws RemoteException {
+ Slog.d(TAG, "Finish IRemoteAnimationRunner.");
+ finishCallback.onTransitionFinished(null /* wct */, null /* t */);
+ }
+ }
+ );
+ }
+
+ public void mergeAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction t, IBinder mergeTarget,
+ IRemoteTransitionFinishedCallback finishCallback) {
+
+ }
+ };
+ }
+
@Inject
public KeyguardService(KeyguardViewMediator keyguardViewMediator,
- KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher) {
+ KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher,
+ ShellTransitions shellTransitions) {
super();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
- RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
- if (sEnableRemoteKeyguardGoingAwayAnimation) {
- final RemoteAnimationAdapter exitAnimationAdapter =
- new RemoteAnimationAdapter(mExitAnimationRunner, 0, 0);
- definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, exitAnimationAdapter);
- definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
- exitAnimationAdapter);
- }
- if (sEnableRemoteKeyguardOccludeAnimation) {
- final RemoteAnimationAdapter occludeAnimationAdapter =
- new RemoteAnimationAdapter(mOccludeAnimationRunner, 0, 0);
- definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_OCCLUDE, occludeAnimationAdapter);
- definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_UNOCCLUDE, occludeAnimationAdapter);
- }
- ActivityTaskManager.getInstance().registerRemoteAnimationsForDisplay(
- DEFAULT_DISPLAY, definition);
+ if (shellTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS) {
+ if (sEnableRemoteKeyguardGoingAwayAnimation) {
+ Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_GOING_AWAY");
+ TransitionFilter f = new TransitionFilter();
+ f.mFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+ shellTransitions.registerRemote(f,
+ new RemoteTransition(wrap(mExitAnimationRunner)));
+ }
+ if (sEnableRemoteKeyguardOccludeAnimation) {
+ Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_(UN)OCCLUDE");
+ // Register for occluding
+ TransitionFilter f = new TransitionFilter();
+ f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
+ f.mRequirements = new TransitionFilter.Requirement[]{
+ new TransitionFilter.Requirement(), new TransitionFilter.Requirement()};
+ // First require at-least one app showing that occludes.
+ f.mRequirements[0].mMustBeIndependent = false;
+ f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD;
+ f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+ // Then require that we aren't closing any occludes (because this would mean a
+ // regular task->task or activity->activity animation not involving keyguard).
+ f.mRequirements[1].mNot = true;
+ f.mRequirements[1].mMustBeIndependent = false;
+ f.mRequirements[1].mFlags = FLAG_OCCLUDES_KEYGUARD;
+ f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
+ shellTransitions.registerRemote(f, new RemoteTransition(mOccludeAnimation));
+
+ // Now register for un-occlude.
+ f = new TransitionFilter();
+ f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
+ f.mRequirements = new TransitionFilter.Requirement[]{
+ new TransitionFilter.Requirement(), new TransitionFilter.Requirement()};
+ // First require at-least one app going-away (doesn't need occlude flag
+ // as that is implicit by it having been visible and we don't want to exclude
+ // cases where we are un-occluding because the app removed its showWhenLocked
+ // capability at runtime).
+ f.mRequirements[1].mMustBeIndependent = false;
+ f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
+ f.mRequirements[1].mMustBeTask = true;
+ // Then require that we aren't opening any occludes (otherwise we'd remain
+ // occluded).
+ f.mRequirements[0].mNot = true;
+ f.mRequirements[0].mMustBeIndependent = false;
+ f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD;
+ f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+ shellTransitions.registerRemote(f, new RemoteTransition(mUnoccludeAnimation));
+ }
+ } else {
+ RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
+ if (sEnableRemoteKeyguardGoingAwayAnimation) {
+ final RemoteAnimationAdapter exitAnimationAdapter =
+ new RemoteAnimationAdapter(mExitAnimationRunner, 0, 0);
+ definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
+ exitAnimationAdapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+ exitAnimationAdapter);
+ }
+ if (sEnableRemoteKeyguardOccludeAnimation) {
+ final RemoteAnimationAdapter occludeAnimationAdapter =
+ new RemoteAnimationAdapter(mOccludeAnimationRunner, 0, 0);
+ definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_OCCLUDE,
+ occludeAnimationAdapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_UNOCCLUDE,
+ occludeAnimationAdapter);
+ }
+ ActivityTaskManager.getInstance().registerRemoteAnimationsForDisplay(
+ DEFAULT_DISPLAY, definition);
+ }
}
@Override
@@ -145,10 +332,10 @@ public class KeyguardService extends Service {
RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) {
- Trace.beginSection("KeyguardService.mBinder#startKeyguardExitAnimation");
+ Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
checkPermission();
mKeyguardViewMediator.startKeyguardExitAnimation(transit, apps, wallpapers,
- null /* nonApps */, finishedCallback);
+ nonApps, finishedCallback);
Trace.endSection();
}
@@ -166,14 +353,14 @@ public class KeyguardService extends Service {
RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) {
+ Slog.d(TAG, "mOccludeAnimationRunner.onAnimationStart: transit=" + transit);
try {
if (transit == TRANSIT_OLD_KEYGUARD_OCCLUDE) {
mBinder.setOccluded(true /* isOccluded */, true /* animate */);
} else if (transit == TRANSIT_OLD_KEYGUARD_UNOCCLUDE) {
- mBinder.setOccluded(false /* isOccluded */, true /* animate */);
+ mBinder.setOccluded(false /* isOccluded */, false /* animate */);
}
- // TODO(bc-unlock): Implement occlude/unocclude animation applied on apps,
- // wallpapers and nonApps.
+ // TODO(bc-unlock): Implement (un)occlude animation.
finishedCallback.onAnimationFinished();
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
@@ -185,6 +372,40 @@ public class KeyguardService extends Service {
}
};
+ final IRemoteTransition mOccludeAnimation = new IRemoteTransition.Stub() {
+ @Override
+ public void startAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback)
+ throws RemoteException {
+ t.apply();
+ mBinder.setOccluded(true /* isOccluded */, true /* animate */);
+ finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ }
+
+ @Override
+ public void mergeAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction t, IBinder mergeTarget,
+ IRemoteTransitionFinishedCallback finishCallback) {
+ }
+ };
+
+ final IRemoteTransition mUnoccludeAnimation = new IRemoteTransition.Stub() {
+ @Override
+ public void startAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback)
+ throws RemoteException {
+ t.apply();
+ mBinder.setOccluded(false /* isOccluded */, true /* animate */);
+ finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ }
+
+ @Override
+ public void mergeAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction t, IBinder mergeTarget,
+ IRemoteTransitionFinishedCallback finishCallback) {
+ }
+ };
+
private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() {
@Override // Binder interface
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 941f2c6f4282..2cc564bf8452 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -31,7 +31,7 @@ import com.android.keyguard.KeyguardViewController
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController
-import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
import javax.inject.Inject
@@ -62,7 +62,7 @@ const val SURFACE_BEHIND_SCALE_PIVOT_Y = 0.66f
* The dismiss amount is the inverse of the notification panel expansion, which decreases as the
* lock screen is swiped away.
*/
-const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.1f
+const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.25f
/**
* Dismiss amount at which to complete the keyguard exit animation and hide the keyguard.
@@ -70,7 +70,7 @@ const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.1f
* The dismiss amount is the inverse of the notification panel expansion, which decreases as the
* lock screen is swiped away.
*/
-const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.3f
+const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.4f
/**
* Initiates, controls, and ends the keyguard unlock animation.
@@ -222,6 +222,11 @@ class KeyguardUnlockAnimationController @Inject constructor(
keyguardViewController.hide(startTime, 350)
surfaceBehindEntryAnimator.start()
}
+
+ // Finish the keyguard remote animation if the dismiss amount has crossed the threshold.
+ // Check it here in case there is no more change to the dismiss amount after the last change
+ // that starts the keyguard animation. @see #updateKeyguardViewMediatorIfThresholdsReached()
+ finishKeyguardExitRemoteAnimationIfReachThreshold()
}
fun notifyCancelKeyguardExitAnimation() {
@@ -353,16 +358,6 @@ class KeyguardUnlockAnimationController @Inject constructor(
}
val dismissAmount = keyguardStateController.dismissAmount
-
- // Hide the keyguard if we're fully dismissed, or if we're swiping to dismiss and have
- // crossed the threshold to finish the dismissal.
- val reachedHideKeyguardThreshold = (dismissAmount >= 1f ||
- (keyguardStateController.isDismissingFromSwipe &&
- // Don't hide if we're flinging during a swipe, since we need to finish
- // animating it out. This will be called again after the fling ends.
- !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
- dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD))
-
if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
!keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
// We passed the threshold, and we're not yet showing the surface behind the
@@ -375,9 +370,35 @@ class KeyguardUnlockAnimationController @Inject constructor(
// out.
keyguardViewMediator.get().hideSurfaceBehindKeyguard()
fadeOutSurfaceBehind()
- } else if (keyguardViewMediator.get()
- .isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe &&
- reachedHideKeyguardThreshold) {
+ } else {
+ finishKeyguardExitRemoteAnimationIfReachThreshold()
+ }
+ }
+
+ /**
+ * Hides the keyguard if we're fully dismissed, or if we're swiping to dismiss and have crossed
+ * the threshold to finish the dismissal.
+ */
+ private fun finishKeyguardExitRemoteAnimationIfReachThreshold() {
+ // no-op if keyguard is not showing or animation is not enabled.
+ if (!KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation ||
+ !keyguardViewController.isShowing) {
+ return
+ }
+
+ // no-op if animation is not requested yet.
+ if (!keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
+ !keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) {
+ return
+ }
+
+ val dismissAmount = keyguardStateController.dismissAmount
+ if (dismissAmount >= 1f ||
+ (keyguardStateController.isDismissingFromSwipe &&
+ // Don't hide if we're flinging during a swipe, since we need to finish
+ // animating it out. This will be called again after the fling ends.
+ !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
+ dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)) {
keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(false /* cancelled */)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 1a8af3bb650b..19ee50ac99cd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -77,7 +77,6 @@ import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SyncRtSurfaceTransactionApplier;
import android.view.View;
-import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants;
import android.view.animation.Animation;
@@ -120,12 +119,15 @@ import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
+import com.android.systemui.unfold.config.UnfoldTransitionConfig;
import com.android.systemui.util.DeviceConfigProxy;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
import dagger.Lazy;
@@ -671,13 +673,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
}
}
}
-
- @Override
- public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) {
- synchronized (KeyguardViewMediator.this) {
- notifyHasLockscreenWallpaperChanged(hasLockscreenWallpaper);
- }
- }
};
ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() {
@@ -816,6 +811,10 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
private DeviceConfigProxy mDeviceConfig;
private DozeParameters mDozeParameters;
+ private final UnfoldTransitionConfig mUnfoldTransitionConfig;
+ private final Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealAnimation;
+ private final AtomicInteger mPendingDrawnTasks = new AtomicInteger();
+
private final KeyguardStateController mKeyguardStateController;
private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
private boolean mWallpaperSupportsAmbientMode;
@@ -838,6 +837,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
NavigationModeController navigationModeController,
KeyguardDisplayManager keyguardDisplayManager,
DozeParameters dozeParameters,
+ UnfoldTransitionConfig unfoldTransitionConfig,
+ Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
SysuiStatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationControllerLazy,
@@ -871,6 +872,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
}));
mDozeParameters = dozeParameters;
+ mUnfoldTransitionConfig = unfoldTransitionConfig;
+ mUnfoldLightRevealAnimation = unfoldLightRevealOverlayAnimation;
mStatusBarStateController = statusBarStateController;
statusBarStateController.addCallback(this);
@@ -2349,8 +2352,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
}
private Configuration.Builder createInteractionJankMonitorConf(String tag) {
- return new Configuration.Builder(CUJ_LOCKSCREEN_UNLOCK_ANIMATION)
- .setView(mKeyguardViewControllerLazy.get().getViewRootImpl().getView())
+ return Configuration.Builder.withView(CUJ_LOCKSCREEN_UNLOCK_ANIMATION,
+ mKeyguardViewControllerLazy.get().getViewRootImpl().getView())
.setTag(tag);
}
@@ -2394,7 +2397,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
final boolean wasShowing = mShowing;
onKeyguardExitFinished();
- if (mKeyguardStateController.isDismissingFromSwipe() || !wasShowing) {
+ if (mKeyguardStateController.isDismissingFromSwipe() || wasShowing) {
mKeyguardUnlockAnimationControllerLazy.get().hideKeyguardViewAfterRemoteAnimation();
}
@@ -2553,6 +2556,24 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
Trace.beginSection("KeyguardViewMediator#handleNotifyScreenTurningOn");
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleNotifyScreenTurningOn");
+
+ if (mUnfoldTransitionConfig.isEnabled()) {
+ mPendingDrawnTasks.set(2); // unfold overlay and keyguard drawn
+
+ mUnfoldLightRevealAnimation.get()
+ .onScreenTurningOn(() -> {
+ if (mPendingDrawnTasks.decrementAndGet() == 0) {
+ try {
+ callback.onDrawn();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception calling onDrawn():", e);
+ }
+ }
+ });
+ } else {
+ mPendingDrawnTasks.set(1); // only keyguard drawn
+ }
+
mKeyguardViewControllerLazy.get().onScreenTurningOn();
if (callback != null) {
if (mWakeAndUnlocking) {
@@ -2567,9 +2588,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
private void handleNotifyScreenTurnedOn() {
Trace.beginSection("KeyguardViewMediator#handleNotifyScreenTurnedOn");
- if (LatencyTracker.isEnabled(mContext)) {
- LatencyTracker.getInstance(mContext).onActionEnd(LatencyTracker.ACTION_TURN_ON_SCREEN);
- }
synchronized (this) {
if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOn");
mKeyguardViewControllerLazy.get().onScreenTurnedOn();
@@ -2586,10 +2604,12 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
private void notifyDrawn(final IKeyguardDrawnCallback callback) {
Trace.beginSection("KeyguardViewMediator#notifyDrawn");
- try {
- callback.onDrawn();
- } catch (RemoteException e) {
- Slog.w(TAG, "Exception calling onDrawn():", e);
+ if (mPendingDrawnTasks.decrementAndGet() == 0) {
+ try {
+ callback.onDrawn();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception calling onDrawn():", e);
+ }
}
Trace.endSection();
}
@@ -2626,7 +2646,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
* Registers the StatusBar to which the Keyguard View is mounted.
*
* @param statusBar
- * @param container
* @param panelView
* @param biometricUnlockController
* @param notificationContainer
@@ -2634,10 +2653,10 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
* @return the View Controller for the Keyguard View this class is mediating.
*/
public KeyguardViewController registerStatusBar(StatusBar statusBar,
- ViewGroup container, NotificationPanelViewController panelView,
+ NotificationPanelViewController panelView,
BiometricUnlockController biometricUnlockController,
View notificationContainer, KeyguardBypassController bypassController) {
- mKeyguardViewControllerLazy.get().registerStatusBar(statusBar, container, panelView,
+ mKeyguardViewControllerLazy.get().registerStatusBar(statusBar, panelView,
biometricUnlockController, notificationContainer, bypassController);
return mKeyguardViewControllerLazy.get();
}
@@ -2743,6 +2762,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
pw.print(" mHideAnimationRun: "); pw.println(mHideAnimationRun);
pw.print(" mPendingReset: "); pw.println(mPendingReset);
pw.print(" mPendingLock: "); pw.println(mPendingLock);
+ pw.print(" mPendingDrawnTasks: "); pw.println(mPendingDrawnTasks.get());
pw.print(" mWakeAndUnlocking: "); pw.println(mWakeAndUnlocking);
pw.print(" mDrawnCallback: "); pw.println(mDrawnCallback);
}
@@ -2872,21 +2892,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
}
}
- private void notifyHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) {
- int size = mKeyguardStateCallbacks.size();
- for (int i = size - 1; i >= 0; i--) {
- try {
- mKeyguardStateCallbacks.get(i).onHasLockscreenWallpaperChanged(
- hasLockscreenWallpaper);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onHasLockscreenWallpaperChanged", e);
- if (e instanceof DeadObjectException) {
- mKeyguardStateCallbacks.remove(i);
- }
- }
- }
- }
-
public void addStateMonitorCallback(IKeyguardStateCallback callback) {
synchronized (this) {
mKeyguardStateCallbacks.add(callback);
@@ -2896,7 +2901,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
callback.onInputRestrictedStateChanged(mInputRestricted);
callback.onTrustedChanged(mUpdateMonitor.getUserHasTrust(
KeyguardUpdateMonitor.getCurrentUser()));
- callback.onHasLockscreenWallpaperChanged(mUpdateMonitor.hasLockscreenWallpaper());
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call to IKeyguardStateCallback", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
new file mode 100644
index 000000000000..044a57ced3fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.systemui.keyguard
+
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class LifecycleScreenStatusProvider @Inject constructor(screenLifecycle: ScreenLifecycle) :
+ ScreenStatusProvider, ScreenLifecycle.Observer {
+
+ init {
+ screenLifecycle.addObserver(this)
+ }
+
+ private val listeners: MutableList<ScreenListener> = mutableListOf()
+
+ override fun removeCallback(listener: ScreenListener) {
+ listeners.remove(listener)
+ }
+
+ override fun addCallback(listener: ScreenListener) {
+ listeners.add(listener)
+ }
+
+ override fun onScreenTurnedOn() {
+ listeners.forEach(ScreenListener::onScreenTurnedOn)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
index 084e84a7a77a..d17c39a81dae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
@@ -19,17 +19,18 @@ package com.android.systemui.keyguard;
import android.os.Trace;
import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import javax.inject.Inject;
+import javax.inject.Singleton;
/**
* Tracks the screen lifecycle.
*/
-@SysUISingleton
+@Singleton
public class ScreenLifecycle extends Lifecycle<ScreenLifecycle.Observer> implements Dumpable {
public static final int SCREEN_OFF = 0;
@@ -40,7 +41,8 @@ public class ScreenLifecycle extends Lifecycle<ScreenLifecycle.Observer> impleme
private int mScreenState = SCREEN_OFF;
@Inject
- public ScreenLifecycle() {
+ public ScreenLifecycle(DumpManager dumpManager) {
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
public int getScreenState() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
index 6f878d19aa4b..2e1c9faf8848 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
@@ -32,6 +32,7 @@ import androidx.annotation.Nullable;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -83,10 +84,13 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe
@Inject
public WakefulnessLifecycle(
Context context,
- @Nullable IWallpaperManager wallpaperManagerService) {
+ @Nullable IWallpaperManager wallpaperManagerService,
+ DumpManager dumpManager) {
mContext = context;
mDisplayMetrics = context.getResources().getDisplayMetrics();
mWallpaperManagerService = wallpaperManagerService;
+
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
public @Wakefulness int getWakefulness() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 8a383b974d77..9b0d69b38374 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -20,9 +20,6 @@ import android.annotation.Nullable;
import android.app.trust.TrustManager;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.hardware.face.FaceManager;
-import android.os.Handler;
import android.os.PowerManager;
import com.android.internal.widget.LockPatternUtils;
@@ -37,17 +34,14 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardLiftController;
@@ -55,12 +49,11 @@ import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
+import com.android.systemui.unfold.config.UnfoldTransitionConfig;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.sensors.AsyncSensorManager;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SystemSettings;
-import java.util.Optional;
import java.util.concurrent.Executor;
import dagger.Lazy;
@@ -99,6 +92,8 @@ public class KeyguardModule {
NavigationModeController navigationModeController,
KeyguardDisplayManager keyguardDisplayManager,
DozeParameters dozeParameters,
+ UnfoldTransitionConfig unfoldTransitionConfig,
+ Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
SysuiStatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
@@ -121,6 +116,8 @@ public class KeyguardModule {
navigationModeController,
keyguardDisplayManager,
dozeParameters,
+ unfoldTransitionConfig,
+ unfoldLightRevealOverlayAnimation,
statusBarStateController,
keyguardStateController,
keyguardUnlockAnimationController,
@@ -144,35 +141,4 @@ public class KeyguardModule {
return new KeyguardLiftController(statusBarStateController, asyncSensorManager,
keyguardUpdateMonitor, dumpManager);
}
-
- @SysUISingleton
- @Provides
- static Optional<FaceAuthScreenBrightnessController> provideFaceAuthScreenBrightnessController(
- Context context,
- NotificationShadeWindowController notificationShadeWindowController,
- @Main Resources resources,
- Handler handler,
- @Nullable FaceManager faceManager,
- PackageManager packageManager,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- GlobalSettings globalSetting,
- SystemSettings systemSettings,
- DumpManager dumpManager) {
- if (faceManager == null || !packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
- return Optional.empty();
- }
-
- // Cameras that support "self illumination," via IR for example, don't need low light
- // environment mitigation.
- boolean needsLowLightMitigation = faceManager.getSensorPropertiesInternal().stream()
- .anyMatch((properties) -> !properties.supportsSelfIllumination);
- if (!needsLowLightMitigation) {
- return Optional.empty();
- }
-
- // currently disabled (doesn't ramp up brightness or use scrim) see b/175918367
- return Optional.of(new FaceAuthScreenBrightnessController(
- notificationShadeWindowController, keyguardUpdateMonitor, resources,
- globalSetting, systemSettings, handler, dumpManager, false));
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
new file mode 100644
index 000000000000..c8afd72d1dde
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for
+ * {@link com.android.systemui.statusbar.phone.CollapsedStatusBarFragment}-related messages.
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface CollapsedSbFragmentLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 19193f9eceb2..72601e9816f9 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -100,6 +100,28 @@ public class LogModule {
return factory.create("PrivacyLog", 100);
}
+ /**
+ * Provides a logging buffer for
+ * {@link com.android.systemui.statusbar.phone.CollapsedStatusBarFragment}.
+ */
+ @Provides
+ @SysUISingleton
+ @CollapsedSbFragmentLog
+ public static LogBuffer provideCollapsedSbFragmentLogBuffer(LogBufferFactory factory) {
+ return factory.create("CollapsedSbFragmentLog", 20);
+ }
+
+ /**
+ * Provides a logging buffer for logs related to swiping away the status bar while in immersive
+ * mode. See {@link com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureLogger}.
+ */
+ @Provides
+ @SysUISingleton
+ @SwipeStatusBarAwayLog
+ public static LogBuffer provideSwipeAwayGestureLogBuffer(LogBufferFactory factory) {
+ return factory.create("SwipeStatusBarAwayLog", 30);
+ }
+
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@Provides
@SysUISingleton
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
index 01e34d9f8f97..dd6837563a74 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
@@ -14,16 +14,23 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.launch
+package com.android.systemui.log.dagger;
-import android.platform.helpers.IAppHelper
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.HOME_WINDOW_TITLE
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
-fun FlickerTestParameter.appWindowReplacesLauncherAsTopWindow(testApp: IAppHelper) {
- assertWm {
- this.showsAppWindowOnTop(*HOME_WINDOW_TITLE)
- .then()
- .showsAppWindowOnTop("Snapshot", testApp.getPackage())
- }
-} \ No newline at end of file
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for
+ * {@link com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureLogger}.
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface SwipeStatusBarAwayLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
index 2bf102f724f4..5ff624db33c7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
@@ -24,7 +24,6 @@ import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.dagger.MediaModule.KEYGUARD
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -45,7 +44,6 @@ class KeyguardMediaController @Inject constructor(
private val bypassController: KeyguardBypassController,
private val statusBarStateController: SysuiStatusBarStateController,
private val notifLockscreenUserManager: NotificationLockscreenUserManager,
- private val featureFlags: FeatureFlags,
private val context: Context,
configurationController: ConfigurationController
) {
@@ -73,7 +71,7 @@ class KeyguardMediaController @Inject constructor(
}
private fun updateResources() {
- useSplitShade = Utils.shouldUseSplitNotificationShade(featureFlags, context.resources)
+ useSplitShade = Utils.shouldUseSplitNotificationShade(context.resources)
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index c743fe125cf7..e87558ebee27 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -148,7 +148,7 @@ class MediaCarouselController @Inject constructor(
inflateSettingsButton()
}
- override fun onOverlayChanged() {
+ override fun onThemeChanged() {
recreatePlayers()
inflateSettingsButton()
}
@@ -833,11 +833,12 @@ internal object MediaPlayerData {
)
private val comparator =
- compareByDescending<MediaSortKey> { it.data.isPlaying }
+ compareByDescending<MediaSortKey> { it.data.isPlaying == true && it.data.isLocalSession }
+ .thenByDescending { it.data.isPlaying }
.thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
- .thenByDescending { it.data.isLocalSession }
.thenByDescending { !it.data.resumption }
.thenByDescending { it.updateTime }
+ .thenByDescending { !it.data.isLocalSession }
private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index e7445f920ffe..e73eb66a51bf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -55,6 +55,7 @@ import com.android.systemui.animation.GhostedViewLaunchAnimatorController;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.util.animation.TransitionLayout;
@@ -119,6 +120,7 @@ public class MediaControlPanel {
private int mSmartspaceMediaItemsCount;
private MediaCarouselController mMediaCarouselController;
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final FalsingManager mFalsingManager;
/**
* Initialize a new control panel
@@ -131,7 +133,8 @@ public class MediaControlPanel {
ActivityStarter activityStarter, MediaViewController mediaViewController,
SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory
- mediaOutputDialogFactory, MediaCarouselController mediaCarouselController) {
+ mediaOutputDialogFactory, MediaCarouselController mediaCarouselController,
+ FalsingManager falsingManager) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter;
@@ -141,6 +144,7 @@ public class MediaControlPanel {
mKeyguardDismissUtil = keyguardDismissUtil;
mMediaOutputDialogFactory = mediaOutputDialogFactory;
mMediaCarouselController = mediaCarouselController;
+ mFalsingManager = falsingManager;
loadDimens();
mSeekBarViewModel.setLogSmartspaceClick(() -> {
@@ -235,10 +239,14 @@ public class MediaControlPanel {
}
});
mPlayerViewHolder.getCancel().setOnClickListener(v -> {
- closeGuts();
+ if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ closeGuts();
+ }
});
mPlayerViewHolder.getSettings().setOnClickListener(v -> {
- mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */);
+ if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */);
+ }
});
}
@@ -259,10 +267,14 @@ public class MediaControlPanel {
}
});
mRecommendationViewHolder.getCancel().setOnClickListener(v -> {
- closeGuts();
+ if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ closeGuts();
+ }
});
mRecommendationViewHolder.getSettings().setOnClickListener(v -> {
- mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */);
+ if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */);
+ }
});
}
@@ -299,6 +311,7 @@ public class MediaControlPanel {
PendingIntent clickIntent = data.getClickIntent();
if (clickIntent != null) {
mPlayerViewHolder.getPlayer().setOnClickListener(v -> {
+ if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
if (mMediaViewController.isGutsVisible()) return;
logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
@@ -364,9 +377,13 @@ public class MediaControlPanel {
seamlessView.setVisibility(View.VISIBLE);
setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */);
setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */);
- seamlessView.setOnClickListener(v -> {
- mMediaOutputDialogFactory.create(data.getPackageName(), true);
- });
+ seamlessView.setOnClickListener(
+ v -> {
+ if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ mMediaOutputDialogFactory.create(data.getPackageName(), true,
+ mPlayerViewHolder.getSeamlessButton());
+ }
+ });
ImageView iconView = mPlayerViewHolder.getSeamlessIcon();
TextView deviceName = mPlayerViewHolder.getSeamlessText();
@@ -417,9 +434,11 @@ public class MediaControlPanel {
} else {
button.setEnabled(true);
button.setOnClickListener(v -> {
- logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
- /* isRecommendationCard */ false);
- action.run();
+ if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
+ /* isRecommendationCard */ false);
+ action.run();
+ }
});
}
boolean visibleInCompat = actionsWhenCollapsed.contains(i);
@@ -451,6 +470,8 @@ public class MediaControlPanel {
mPlayerViewHolder.getDismissLabel().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
mPlayerViewHolder.getDismiss().setEnabled(isDismissible);
mPlayerViewHolder.getDismiss().setOnClickListener(v -> {
+ if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
+
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
/* isRecommendationCard */ false);
@@ -633,6 +654,8 @@ public class MediaControlPanel {
mSmartspaceMediaItemsCount = uiComponentIndex;
// Set up long press to show guts setting panel.
mRecommendationViewHolder.getDismiss().setOnClickListener(v -> {
+ if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
+
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
/* isRecommendationCard */ true);
closeGuts();
@@ -788,6 +811,8 @@ public class MediaControlPanel {
}
view.setOnClickListener(v -> {
+ if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
+
logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
/* isRecommendationCard */ true,
interactedSubcardRank,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index eacdab6e537d..3631d2fa04fb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -768,7 +768,7 @@ class MediaDataManager(
val resumeAction = getResumeMediaAction(removed.resumeAction!!)
val updated = removed.copy(token = null, actions = listOf(resumeAction),
actionsToShowInCompact = listOf(0), active = false, resumption = true,
- isClearable = true)
+ isPlaying = false, isClearable = true)
val pkg = removed.packageName
val migrate = mediaEntries.put(pkg, updated) == null
// Notify listeners of "new" controls when migrating or removed and update when not
@@ -889,7 +889,7 @@ class MediaDataManager(
dismissIntent = target
.baseAction
.extras
- .getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY) as Intent
+ .getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY) as Intent?
}
packageName(target)?.let {
return SmartspaceMediaData(target.smartspaceTargetId, isActive, true, it,
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 35603b6ef6cc..042a337322e9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -43,6 +43,7 @@ class PlayerViewHolder private constructor(itemView: View) {
val seamless = itemView.requireViewById<ViewGroup>(R.id.media_seamless)
val seamlessIcon = itemView.requireViewById<ImageView>(R.id.media_seamless_image)
val seamlessText = itemView.requireViewById<TextView>(R.id.media_seamless_text)
+ val seamlessButton = itemView.requireViewById<View>(R.id.media_seamless_button)
// Seek bar
val seekBar = itemView.requireViewById<SeekBar>(R.id.media_progress_bar)
@@ -105,6 +106,7 @@ class PlayerViewHolder private constructor(itemView: View) {
*/
@JvmStatic fun create(inflater: LayoutInflater, parent: ViewGroup): PlayerViewHolder {
val mediaView = inflater.inflate(R.layout.media_view, parent, false)
+ mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
// Because this media view (a TransitionLayout) is used to measure and layout the views
// in various states before being attached to its parent, we can't depend on the default
// LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
index f17ad6f09128..33ef19ad040f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
@@ -50,6 +50,7 @@ class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarVi
holder.seekBar.setProgress(0)
holder.elapsedTimeView.setText("")
holder.totalTimeView.setText("")
+ holder.seekBar.contentDescription = ""
return
}
@@ -61,16 +62,22 @@ class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarVi
setVerticalPadding(seekBarEnabledVerticalPadding)
}
- data.duration?.let {
- holder.seekBar.setMax(it)
- holder.totalTimeView.setText(DateUtils.formatElapsedTime(
- it / DateUtils.SECOND_IN_MILLIS))
- }
+ holder.seekBar.setMax(data.duration)
+ val totalTimeString = DateUtils.formatElapsedTime(
+ data.duration / DateUtils.SECOND_IN_MILLIS)
+ holder.totalTimeView.setText(totalTimeString)
data.elapsedTime?.let {
holder.seekBar.setProgress(it)
- holder.elapsedTimeView.setText(DateUtils.formatElapsedTime(
- it / DateUtils.SECOND_IN_MILLIS))
+ val elapsedTimeString = DateUtils.formatElapsedTime(
+ it / DateUtils.SECOND_IN_MILLIS)
+ holder.elapsedTimeView.setText(elapsedTimeString)
+
+ holder.seekBar.contentDescription = holder.seekBar.context.getString(
+ R.string.controls_media_seekbar_description,
+ elapsedTimeString,
+ totalTimeString
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 391dff634dab..113ba59cd514 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,14 +16,10 @@
package com.android.systemui.media.dialog;
-import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
-
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
-import android.text.SpannableString;
import android.text.TextUtils;
-import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -45,11 +41,14 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
private static final String TAG = "MediaOutputAdapter";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private final MediaOutputDialog mMediaOutputDialog;
private ViewGroup mConnectedItem;
private boolean mIncludeDynamicGroup;
- public MediaOutputAdapter(MediaOutputController controller) {
+ public MediaOutputAdapter(MediaOutputController controller,
+ MediaOutputDialog mediaOutputDialog) {
super(controller);
+ mMediaOutputDialog = mediaOutputDialog;
}
@Override
@@ -96,23 +95,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
return mController.getMediaDevices().size();
}
- @Override
- CharSequence getItemTitle(MediaDevice device) {
- if (device.getDeviceType() == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE
- && !device.isConnected()) {
- final CharSequence deviceName = device.getName();
- // Append status to title only for the disconnected Bluetooth device.
- final SpannableString spannableTitle = new SpannableString(
- mContext.getString(R.string.media_output_dialog_disconnected, deviceName));
- spannableTitle.setSpan(new ForegroundColorSpan(
- Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorSecondary)),
- deviceName.length(),
- spannableTitle.length(), SPAN_EXCLUSIVE_EXCLUSIVE);
- return spannableTitle;
- }
- return super.getItemTitle(device);
- }
-
class MediaDeviceViewHolder extends MediaDeviceBaseViewHolder {
MediaDeviceViewHolder(View view) {
@@ -132,14 +114,11 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
if (currentlyConnected && mController.isActiveRemoteDevice(device)
&& mController.getSelectableMediaDevice().size() > 0) {
// Init active device layout
- mDivider.setVisibility(View.VISIBLE);
- mDivider.setTransitionAlpha(1);
mAddIcon.setVisibility(View.VISIBLE);
mAddIcon.setTransitionAlpha(1);
- mAddIcon.setOnClickListener(v -> onEndItemClick());
+ mAddIcon.setOnClickListener(this::onEndItemClick);
} else {
// Init non-active device layout
- mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
}
if (mCurrentActivePosition == position) {
@@ -166,6 +145,14 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
false /* showProgressBar */, false /* showSubtitle */);
initSeekbar(device);
mCurrentActivePosition = position;
+ } else if (
+ device.getDeviceType() == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE
+ && !device.isConnected()) {
+ setTwoLineLayout(device, false /* bFocused */,
+ false /* showSeekBar */, false /* showProgressBar */,
+ true /* showSubtitle */);
+ mSubTitleText.setText(R.string.media_output_dialog_disconnected);
+ mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
} else {
setSingleLineLayout(getItemTitle(device), false /* bFocused */);
mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
@@ -175,10 +162,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
@Override
void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
- super.onBind(customizedItem, topMargin, bottomMargin);
if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) {
mCheckBox.setVisibility(View.GONE);
- mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
mBottomDivider.setVisibility(View.GONE);
setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new),
@@ -193,13 +178,10 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
mBottomDivider.setVisibility(View.GONE);
mCheckBox.setVisibility(View.GONE);
if (mController.getSelectableMediaDevice().size() > 0) {
- mDivider.setVisibility(View.VISIBLE);
- mDivider.setTransitionAlpha(1);
mAddIcon.setVisibility(View.VISIBLE);
mAddIcon.setTransitionAlpha(1);
- mAddIcon.setOnClickListener(v -> onEndItemClick());
+ mAddIcon.setOnClickListener(this::onEndItemClick);
} else {
- mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
}
mTitleIcon.setImageDrawable(getSpeakerDrawable());
@@ -232,8 +214,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
}
- private void onEndItemClick() {
- mController.launchMediaOutputGroupDialog();
+ private void onEndItemClick(View view) {
+ mController.launchMediaOutputGroupDialog(mMediaOutputDialog.getDialogView());
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 0890841eb4fb..868193b44704 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -121,7 +121,6 @@ public abstract class MediaOutputBaseAdapter extends
final ProgressBar mProgressBar;
final SeekBar mSeekBar;
final RelativeLayout mTwoLineLayout;
- final View mDivider;
final View mBottomDivider;
final CheckBox mCheckBox;
private String mDeviceId;
@@ -136,7 +135,6 @@ public abstract class MediaOutputBaseAdapter extends
mTitleIcon = view.requireViewById(R.id.title_icon);
mProgressBar = view.requireViewById(R.id.volume_indeterminate_progress);
mSeekBar = view.requireViewById(R.id.volume_seekbar);
- mDivider = view.requireViewById(R.id.end_divider);
mBottomDivider = view.requireViewById(R.id.bottom_divider);
mAddIcon = view.requireViewById(R.id.add_icon);
mCheckBox = view.requireViewById(R.id.check_box);
@@ -151,22 +149,11 @@ public abstract class MediaOutputBaseAdapter extends
return;
}
mTitleIcon.setImageIcon(icon);
- setMargin(topMargin, bottomMargin);
});
});
}
- void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
- setMargin(topMargin, bottomMargin);
- }
-
- private void setMargin(boolean topMargin, boolean bottomMargin) {
- ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mContainerLayout
- .getLayoutParams();
- params.topMargin = topMargin ? mMargin : 0;
- params.bottomMargin = bottomMargin ? mMargin : 0;
- mContainerLayout.setLayoutParams(params);
- }
+ abstract void onBind(int customizedItem, boolean topMargin, boolean bottomMargin);
void setSingleLineLayout(CharSequence title, boolean bFocused) {
mTwoLineLayout.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index cdcdf9a1d4de..26ce645eefc5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -82,7 +82,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
};
public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController) {
- super(context, R.style.Theme_SystemUI_Dialog_MediaOutput);
+ super(context);
mContext = context;
mMediaOutputController = mediaOutputController;
mLayoutManager = new LinearLayoutManager(mContext);
@@ -97,15 +97,13 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
mDialogView = LayoutInflater.from(mContext).inflate(R.layout.media_output_dialog, null);
final Window window = getWindow();
final WindowManager.LayoutParams lp = window.getAttributes();
- lp.gravity = Gravity.BOTTOM;
+ lp.gravity = Gravity.CENTER;
// Config insets to make sure the layout is above the navigation bar
lp.setFitInsetsTypes(statusBars() | navigationBars());
lp.setFitInsetsSides(WindowInsets.Side.all());
lp.setFitInsetsIgnoringVisibility(true);
window.setAttributes(lp);
window.setContentView(mDialogView);
- window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- window.setWindowAnimations(R.style.Animation_MediaOutputDialog);
mHeaderTitle = mDialogView.requireViewById(R.id.header_title);
mHeaderSubtitle = mDialogView.requireViewById(R.id.header_subtitle);
@@ -175,7 +173,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
}
if (!mAdapter.isDragging() && !mAdapter.isAnimating()) {
int currentActivePosition = mAdapter.getCurrentActivePosition();
- if (currentActivePosition >= 0) {
+ if (currentActivePosition >= 0 && currentActivePosition < mAdapter.getItemCount()) {
mAdapter.notifyItemChanged(currentActivePosition);
} else {
mAdapter.notifyDataSetChanged();
@@ -229,4 +227,8 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
void onHeaderIconClick() {
}
+
+ View getDialogView() {
+ return mDialogView;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index b2def7a8596a..42dd8862576b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -32,6 +32,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
+import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -48,6 +49,7 @@ import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputConstants;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.R;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -71,8 +73,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback {
private final String mPackageName;
private final Context mContext;
private final MediaSessionManager mMediaSessionManager;
+ private final LocalBluetoothManager mLocalBluetoothManager;
private final ShadeController mShadeController;
private final ActivityStarter mActivityStarter;
+ private final DialogLaunchAnimator mDialogLaunchAnimator;
private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
private final boolean mAboveStatusbar;
private final NotificationEntryManager mNotificationEntryManager;
@@ -92,10 +96,12 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback {
public MediaOutputController(@NonNull Context context, String packageName,
boolean aboveStatusbar, MediaSessionManager mediaSessionManager, LocalBluetoothManager
lbm, ShadeController shadeController, ActivityStarter starter,
- NotificationEntryManager notificationEntryManager, UiEventLogger uiEventLogger) {
+ NotificationEntryManager notificationEntryManager, UiEventLogger uiEventLogger,
+ DialogLaunchAnimator dialogLaunchAnimator) {
mContext = context;
mPackageName = packageName;
mMediaSessionManager = mediaSessionManager;
+ mLocalBluetoothManager = lbm;
mShadeController = shadeController;
mActivityStarter = starter;
mAboveStatusbar = aboveStatusbar;
@@ -104,6 +110,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback {
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
mUiEventLogger = uiEventLogger;
+ mDialogLaunchAnimator = dialogLaunchAnimator;
}
void start(@NonNull Callback cb) {
@@ -436,6 +443,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback {
}
void launchBluetoothPairing() {
+ // Dismissing a dialog into its touch surface and starting an activity at the same time
+ // looks bad, so let's make sure the dialog just fades out quickly.
+ mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
+
mCallback.dismissDialog();
final ActivityStarter.OnDismissAction postKeyguardAction = () -> {
mContext.sendBroadcast(new Intent()
@@ -447,14 +458,14 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback {
mActivityStarter.dismissKeyguardThenExecute(postKeyguardAction, null, true);
}
- void launchMediaOutputDialog() {
- mCallback.dismissDialog();
- new MediaOutputDialog(mContext, mAboveStatusbar, this, mUiEventLogger);
- }
-
- void launchMediaOutputGroupDialog() {
- mCallback.dismissDialog();
- new MediaOutputGroupDialog(mContext, mAboveStatusbar, this);
+ void launchMediaOutputGroupDialog(View mediaOutputDialog) {
+ // We show the output group dialog from the output dialog.
+ MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
+ mAboveStatusbar, mMediaSessionManager, mLocalBluetoothManager, mShadeController,
+ mActivityStarter, mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
+ MediaOutputGroupDialog dialog = new MediaOutputGroupDialog(mContext, mAboveStatusbar,
+ controller);
+ mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog);
}
boolean isActiveRemoteDevice(@NonNull MediaDevice device) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 53029bd04ef6..eca8ac90427b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -40,11 +40,10 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
mediaOutputController, UiEventLogger uiEventLogger) {
super(context, mediaOutputController);
mUiEventLogger = uiEventLogger;
- mAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mAdapter = new MediaOutputAdapter(mMediaOutputController, this);
if (!aboveStatusbar) {
getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
}
- show();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 0f340a5cedaa..b91901de5af3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -18,8 +18,10 @@ package com.android.systemui.media.dialog
import android.content.Context
import android.media.session.MediaSessionManager
+import android.view.View
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.notification.NotificationEntryManager
import com.android.systemui.statusbar.phone.ShadeController
@@ -35,19 +37,29 @@ class MediaOutputDialogFactory @Inject constructor(
private val shadeController: ShadeController,
private val starter: ActivityStarter,
private val notificationEntryManager: NotificationEntryManager,
- private val uiEventLogger: UiEventLogger
+ private val uiEventLogger: UiEventLogger,
+ private val dialogLaunchAnimator: DialogLaunchAnimator
) {
companion object {
var mediaOutputDialog: MediaOutputDialog? = null
}
/** Creates a [MediaOutputDialog] for the given package. */
- fun create(packageName: String, aboveStatusBar: Boolean) {
+ fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+ // Dismiss the previous dialog, if any.
mediaOutputDialog?.dismiss()
- mediaOutputDialog = MediaOutputController(context, packageName, aboveStatusBar,
- mediaSessionManager, lbm, shadeController, starter, notificationEntryManager,
- uiEventLogger).run {
- MediaOutputDialog(context, aboveStatusBar, this, uiEventLogger)
+
+ val controller = MediaOutputController(context, packageName, aboveStatusBar,
+ mediaSessionManager, lbm, shadeController, starter, notificationEntryManager,
+ uiEventLogger, dialogLaunchAnimator)
+ val dialog = MediaOutputDialog(context, aboveStatusBar, controller, uiEventLogger)
+ mediaOutputDialog = dialog
+
+ // Show the dialog.
+ if (view != null) {
+ dialogLaunchAnimator.showFromView(dialog, view)
+ } else {
+ dialog.show()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
index 968c3506f39f..a201c071bbbe 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
@@ -96,7 +96,6 @@ public class MediaOutputGroupAdapter extends MediaOutputBaseAdapter {
@Override
void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
super.onBind(device, topMargin, bottomMargin, position);
- mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
mBottomDivider.setVisibility(View.GONE);
mCheckBox.setVisibility(View.VISIBLE);
@@ -127,7 +126,6 @@ public class MediaOutputGroupAdapter extends MediaOutputBaseAdapter {
@Override
void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
- super.onBind(customizedItem, topMargin, bottomMargin);
if (customizedItem == CUSTOMIZED_ITEM_GROUP) {
setTwoLineLayout(mContext.getText(R.string.media_output_dialog_group),
true /* bFocused */, true /* showSeekBar */, false /* showProgressBar */,
@@ -135,7 +133,6 @@ public class MediaOutputGroupAdapter extends MediaOutputBaseAdapter {
mTitleIcon.setImageDrawable(getSpeakerDrawable());
mBottomDivider.setVisibility(View.VISIBLE);
mCheckBox.setVisibility(View.GONE);
- mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
initSessionSeekbar();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
index 407930492fbe..1300400f3b66 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
@@ -38,7 +38,6 @@ public class MediaOutputGroupDialog extends MediaOutputBaseDialog {
if (!aboveStatusbar) {
getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
}
- show();
}
@Override
@@ -83,6 +82,8 @@ public class MediaOutputGroupDialog extends MediaOutputBaseDialog {
@Override
void onHeaderIconClick() {
- mMediaOutputController.launchMediaOutputDialog();
+ // Given that we launched the media output group dialog from the media output dialog,
+ // dismissing this dialog will show the media output dialog again.
+ dismiss();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 8e6eb02bb6f6..6a1eae75f9a9 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -23,10 +23,14 @@ import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.WindowType;
import static android.app.StatusBarManager.WindowVisibleState;
import static android.app.StatusBarManager.windowStateToString;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.containsType;
+import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
@@ -40,10 +44,12 @@ import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSE
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_FORCE_OPAQUE;
import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
+import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
@@ -54,9 +60,7 @@ import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode
import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_WINDOW_STATE;
import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions;
-import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IdRes;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
@@ -68,6 +72,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.database.ContentObserver;
+import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -88,8 +93,8 @@ import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
-import android.view.IWindowManager;
import android.view.InsetsState.InternalInsetsType;
+import android.view.InsetsVisibilities;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -101,7 +106,6 @@ import android.view.WindowInsetsController.Behavior;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.VisibleForTesting;
@@ -113,7 +117,6 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.LatencyTracker;
import com.android.internal.view.AppearanceRegion;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.SystemActions;
@@ -125,11 +128,13 @@ import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.navigationbar.buttons.RotationContextButton;
import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle;
-import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
+import com.android.systemui.shared.rotation.RotationButton;
+import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.AutoHideUiElement;
@@ -144,26 +149,24 @@ import com.android.systemui.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.pip.Pip;
import java.io.PrintWriter;
-import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Consumer;
+import javax.inject.Inject;
+
import dagger.Lazy;
/**
* Contains logic for a navigation bar view.
*/
public class NavigationBar implements View.OnAttachStateChangeListener,
- Callbacks, NavigationModeController.ModeChangedListener,
- AccessibilityButtonModeObserver.ModeChangedListener {
+ Callbacks, NavigationModeController.ModeChangedListener {
public static final String TAG = "NavigationBar";
private static final boolean DEBUG = false;
@@ -180,13 +183,12 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
private final Context mContext;
private final WindowManager mWindowManager;
private final AccessibilityManager mAccessibilityManager;
- private final AccessibilityManagerWrapper mAccessibilityManagerWrapper;
private final DeviceProvisionedController mDeviceProvisionedController;
private final StatusBarStateController mStatusBarStateController;
private final MetricsLogger mMetricsLogger;
private final Lazy<AssistManager> mAssistManagerLazy;
private final SysUiState mSysUiFlagsContainer;
- private final Lazy<StatusBar> mStatusBarLazy;
+ private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
private final ShadeController mShadeController;
private final NotificationRemoteInputManager mNotificationRemoteInputManager;
private final OverviewProxyService mOverviewProxyService;
@@ -201,11 +203,13 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
private final Handler mHandler;
private final NavigationBarOverlayController mNavbarOverlayController;
private final UiEventLogger mUiEventLogger;
+ private final NavigationBarA11yHelper mNavigationBarA11yHelper;
private final UserTracker mUserTracker;
private final NotificationShadeDepthController mNotificationShadeDepthController;
private Bundle mSavedState;
private NavigationBarView mNavigationBarView;
+ private NavigationBarFrame mFrame;
private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
@@ -237,7 +241,13 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
private boolean mTransientShown;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
private LightBarController mLightBarController;
+ private final LightBarController mMainLightBarController;
+ private final LightBarController.Factory mLightBarControllerFactory;
private AutoHideController mAutoHideController;
+ private final AutoHideController mMainAutoHideController;
+ private final AutoHideController.Factory mAutoHideControllerFactory;
+ private final Optional<TelecomManager> mTelecomManagerOptional;
+ private final InputMethodManager mInputMethodManager;
@VisibleForTesting
public int mDisplayId;
@@ -261,6 +271,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
private ViewTreeObserver.OnGlobalLayoutListener mOrientationHandleGlobalLayoutListener;
private boolean mShowOrientedHandleForImmersiveMode;
+
@com.android.internal.annotations.VisibleForTesting
public enum NavBarActionEvent implements UiEventLogger.UiEventEnum {
@@ -287,7 +298,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
@Override
public boolean shouldHideOnTouch() {
- return !mNotificationRemoteInputManager.getController().isRemoteInputActive();
+ return !mNotificationRemoteInputManager.isRemoteInputActive();
}
@Override
@@ -381,6 +392,13 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
}
@Override
+ public void onTaskbarStatusUpdated(boolean visible, boolean stashed) {
+ mNavigationBarView
+ .getFloatingRotationButton()
+ .onTaskbarStateChanged(visible, stashed);
+ }
+
+ @Override
public void onToggleRecentApps() {
// The same case as onOverviewShown but only for 3-button navigation.
mNavigationBarView.getRotationButtonController().setSkipOverrideUserLockPrefsOnce();
@@ -465,11 +483,10 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
}
};
- public NavigationBar(Context context,
+ private NavigationBar(Context context,
WindowManager windowManager,
Lazy<AssistManager> assistManagerLazy,
AccessibilityManager accessibilityManager,
- AccessibilityManagerWrapper accessibilityManagerWrapper,
DeviceProvisionedController deviceProvisionedController,
MetricsLogger metricsLogger,
OverviewProxyService overviewProxyService,
@@ -481,7 +498,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
CommandQueue commandQueue,
Optional<Pip> pipOptional,
Optional<LegacySplitScreen> splitScreenOptional,
- Optional<Recents> recentsOptional, Lazy<StatusBar> statusBarLazy,
+ Optional<Recents> recentsOptional,
+ Lazy<Optional<StatusBar>> statusBarOptionalLazy,
ShadeController shadeController,
NotificationRemoteInputManager notificationRemoteInputManager,
NotificationShadeDepthController notificationShadeDepthController,
@@ -489,17 +507,23 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
@Main Handler mainHandler,
NavigationBarOverlayController navbarOverlayController,
UiEventLogger uiEventLogger,
- UserTracker userTracker) {
+ NavigationBarA11yHelper navigationBarA11yHelper,
+ UserTracker userTracker,
+ LightBarController mainLightBarController,
+ LightBarController.Factory lightBarControllerFactory,
+ AutoHideController mainAutoHideController,
+ AutoHideController.Factory autoHideControllerFactory,
+ Optional<TelecomManager> telecomManagerOptional,
+ InputMethodManager inputMethodManager) {
mContext = context;
mWindowManager = windowManager;
mAccessibilityManager = accessibilityManager;
- mAccessibilityManagerWrapper = accessibilityManagerWrapper;
mDeviceProvisionedController = deviceProvisionedController;
mStatusBarStateController = statusBarStateController;
mMetricsLogger = metricsLogger;
mAssistManagerLazy = assistManagerLazy;
mSysUiFlagsContainer = sysUiFlagsContainer;
- mStatusBarLazy = statusBarLazy;
+ mStatusBarOptionalLazy = statusBarOptionalLazy;
mShadeController = shadeController;
mNotificationRemoteInputManager = notificationRemoteInputManager;
mOverviewProxyService = overviewProxyService;
@@ -514,11 +538,17 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
mHandler = mainHandler;
mNavbarOverlayController = navbarOverlayController;
mUiEventLogger = uiEventLogger;
+ mNavigationBarA11yHelper = navigationBarA11yHelper;
mUserTracker = userTracker;
mNotificationShadeDepthController = notificationShadeDepthController;
+ mMainLightBarController = mainLightBarController;
+ mLightBarControllerFactory = lightBarControllerFactory;
+ mMainAutoHideController = mainAutoHideController;
+ mAutoHideControllerFactory = autoHideControllerFactory;
+ mTelecomManagerOptional = telecomManagerOptional;
+ mInputMethodManager = inputMethodManager;
mNavBarMode = mNavigationModeController.addListener(this);
- mAccessibilityButtonModeObserver.addListener(this);
}
public NavigationBarView getView() {
@@ -526,34 +556,17 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
}
public View createView(Bundle savedState) {
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
- WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
- | WindowManager.LayoutParams.FLAG_SLIPPERY,
- PixelFormat.TRANSLUCENT);
- lp.token = new Binder();
- lp.accessibilityTitle = mContext.getString(R.string.nav_bar);
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
- lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- lp.windowAnimations = 0;
- lp.setTitle("NavigationBar" + mContext.getDisplayId());
- lp.setFitInsetsTypes(0 /* types */);
- lp.setTrustedOverlay();
-
- NavigationBarFrame frame = (NavigationBarFrame) LayoutInflater.from(mContext).inflate(
+ mFrame = (NavigationBarFrame) LayoutInflater.from(mContext).inflate(
R.layout.navigation_bar_window, null);
- View barView = LayoutInflater.from(frame.getContext()).inflate(
- R.layout.navigation_bar, frame);
+ View barView = LayoutInflater.from(mFrame.getContext()).inflate(
+ R.layout.navigation_bar, mFrame);
barView.addOnAttachStateChangeListener(this);
mNavigationBarView = barView.findViewById(R.id.navigation_bar_view);
if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + barView);
- mContext.getSystemService(WindowManager.class).addView(frame, lp);
+ mWindowManager.addView(mFrame,
+ getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration
+ .getRotation()));
mDisplayId = mContext.getDisplayId();
mIsOnDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
@@ -601,7 +614,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
mDeviceProvisionedController.addCallback(mUserSetupListener);
mNotificationShadeDepthController.addListener(mDepthListener);
- setAccessibilityFloatingMenuModeIfNeeded();
+ updateAccessibilityButtonModeIfNeeded();
return barView;
}
@@ -609,12 +622,10 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
public void destroyView() {
setAutoHideController(/* autoHideController */ null);
mCommandQueue.removeCallback(this);
- mContext.getSystemService(WindowManager.class).removeViewImmediate(
- mNavigationBarView.getRootView());
+ mWindowManager.removeViewImmediate(mNavigationBarView.getRootView());
mNavigationModeController.removeListener(this);
- mAccessibilityButtonModeObserver.removeListener(this);
- mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
+ mNavigationBarA11yHelper.removeA11yEventListener(mAccessibilityListener);
mContentResolver.unregisterContentObserver(mAssistContentObserver);
mDeviceProvisionedController.removeCallback(mUserSetupListener);
mNotificationShadeDepthController.removeListener(mDepthListener);
@@ -625,7 +636,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
@Override
public void onViewAttachedToWindow(View v) {
final Display display = v.getDisplay();
- mNavigationBarView.setComponents(mStatusBarLazy.get().getPanelController());
+ mNavigationBarView.setComponents(mRecentsOptional);
+ mNavigationBarView.setComponents(mStatusBarOptionalLazy.get().get().getPanelController());
mNavigationBarView.setDisabledFlags(mDisabledFlags1);
mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
@@ -636,7 +648,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
mNavigationBarView.setBehavior(mBehavior);
- mAccessibilityManagerWrapper.addCallback(mAccessibilityListener);
+ mNavigationBarA11yHelper.registerA11yEventListener(mAccessibilityListener);
mSplitScreenOptional.ifPresent(mNavigationBarView::registerDockedListener);
mPipOptional.ifPresent(mNavigationBarView::registerPipExclusionBoundsChangeListener);
@@ -676,21 +688,16 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
// before notifications creation. We cannot directly use getLightBarController()
// from NavigationBarFragment directly.
LightBarController lightBarController = mIsOnDefaultDisplay
- ? Dependency.get(LightBarController.class)
- : new LightBarController(mContext,
- Dependency.get(DarkIconDispatcher.class),
- Dependency.get(BatteryController.class),
- Dependency.get(NavigationModeController.class));
+ ? mMainLightBarController : mLightBarControllerFactory.create(mContext);
setLightBarController(lightBarController);
// TODO(b/118592525): to support multi-display, we start to add something which is
// per-display, while others may be global. I think it's time to
// add a new class maybe named DisplayDependency to solve
// per-display Dependency problem.
+ // Alternative: this is a good case for a Dagger subcomponent. Same with LightBarController.
AutoHideController autoHideController = mIsOnDefaultDisplay
- ? Dependency.get(AutoHideController.class)
- : new AutoHideController(mContext, mHandler,
- Dependency.get(IWindowManager.class));
+ ? mMainAutoHideController : mAutoHideControllerFactory.create(mContext);
setAutoHideController(autoHideController);
restoreAppearanceAndTransientState();
}
@@ -714,6 +721,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
mHandler.removeCallbacks(mAutoDim);
mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
mHandler.removeCallbacks(mEnableLayoutTransitions);
+ mNavigationBarA11yHelper.removeA11yEventListener(mAccessibilityListener);
+ mFrame = null;
mNavigationBarView = null;
mOrientationHandle = null;
}
@@ -732,6 +741,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
* Called when a non-reloading configuration change happens and we need to update.
*/
public void onConfigurationChanged(Configuration newConfig) {
+ final int rotation = newConfig.windowConfiguration.getRotation();
final Locale locale = mContext.getResources().getConfiguration().locale;
final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
if (!locale.equals(mLocale) || ld != mLayoutDirection) {
@@ -745,9 +755,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
refreshLayout(ld);
}
- repositionNavigationBar();
+ repositionNavigationBar(rotation);
if (canShowSecondaryHandle()) {
- int rotation = newConfig.windowConfiguration.getRotation();
if (rotation != mCurrentRotation) {
mCurrentRotation = rotation;
orientSecondaryHomeHandle();
@@ -899,30 +908,15 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
return;
}
boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;
- int hints = mNavigationIconHints;
- switch (backDisposition) {
- case InputMethodService.BACK_DISPOSITION_DEFAULT:
- case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
- case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
- if (imeShown) {
- hints |= NAVIGATION_HINT_BACK_ALT;
- } else {
- hints &= ~NAVIGATION_HINT_BACK_ALT;
- }
- break;
- case InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING:
- hints &= ~NAVIGATION_HINT_BACK_ALT;
- break;
- }
- if (showImeSwitcher) {
- hints |= NAVIGATION_HINT_IME_SHOWN;
- } else {
- hints &= ~NAVIGATION_HINT_IME_SHOWN;
- }
+ int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition,
+ imeShown, showImeSwitcher);
if (hints == mNavigationIconHints) return;
mNavigationIconHints = hints;
- mNavigationBarView.setNavigationIconHints(hints);
+ if (!isTablet(mContext)) {
+ // All IME functions handled by launcher via Sysui flags for large screen
+ mNavigationBarView.setNavigationIconHints(hints);
+ }
checkBarModes();
updateSystemUiStateFlags(-1);
}
@@ -952,7 +946,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
// not valid. Just ignore the rotation in this case.
if (!mNavigationBarView.isAttachedToWindow()) return;
- final int winRotation = mNavigationBarView.getDisplay().getRotation();
final boolean rotateSuggestionsDisabled = RotationButtonController
.hasDisable2RotateSuggestionFlag(mDisabledFlags2);
final RotationButtonController rotationButtonController =
@@ -961,7 +954,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
if (RotationContextButton.DEBUG_ROTATION) {
Log.v(TAG, "onRotationProposal proposedRotation=" + Surface.rotationToString(rotation)
- + ", winRotation=" + Surface.rotationToString(winRotation)
+ ", isValid=" + isValid + ", mNavBarWindowState="
+ StatusBarManager.windowStateToString(mNavigationBarWindowState)
+ ", rotateSuggestionsDisabled=" + rotateSuggestionsDisabled
@@ -971,7 +963,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
// Respect the disabled flag, no need for action as flag change callback will handle hiding
if (rotateSuggestionsDisabled) return;
- rotationButtonController.onRotationProposal(rotation, winRotation, isValid);
+ rotationButtonController.onRotationProposal(rotation, isValid);
}
@Override
@@ -999,7 +991,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
@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) {
if (displayId != mDisplayId) {
return;
}
@@ -1131,13 +1123,12 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
|| (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0;
}
- private void repositionNavigationBar() {
- if (!mNavigationBarView.isAttachedToWindow()) return;
+ private void repositionNavigationBar(int rotation) {
+ if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return;
prepareNavigationBarView();
- mWindowManager.updateViewLayout((View) mNavigationBarView.getParent(),
- ((View) mNavigationBarView.getParent()).getLayoutParams());
+ mWindowManager.updateViewLayout(mFrame, getBarLayoutParams(rotation));
}
private void updateScreenPinningGestures() {
@@ -1179,7 +1170,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
accessibilityButton.setOnClickListener(this::onAccessibilityClick);
accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
- updateAccessibilityServicesState(mAccessibilityManager);
+ updateAccessibilityServicesState();
ButtonDispatcher imeSwitcherButton = mNavigationBarView.getImeSwitchButton();
imeSwitcherButton.setOnClickListener(this::onImeSwitcherClick);
@@ -1195,13 +1186,13 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
// If an incoming call is ringing, HOME is totally disabled.
// (The user is already on the InCallUI at this point,
// and their ONLY options are to answer or reject the call.)
+ final Optional<StatusBar> statusBarOptional = mStatusBarOptionalLazy.get();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mHomeBlockedThisTouch = false;
- TelecomManager telecomManager =
- mContext.getSystemService(TelecomManager.class);
- if (telecomManager != null && telecomManager.isRinging()) {
- if (mStatusBarLazy.get().isKeyguardShowing()) {
+ if (mTelecomManagerOptional.isPresent()
+ && mTelecomManagerOptional.get().isRinging()) {
+ if (statusBarOptional.map(StatusBar::isKeyguardShowing).orElse(false)) {
Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " +
"No heads up");
mHomeBlockedThisTouch = true;
@@ -1217,14 +1208,15 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
- mStatusBarLazy.get().awakenDreams();
+ statusBarOptional.ifPresent(StatusBar::awakenDreams);
break;
}
return false;
}
private void onVerticalChanged(boolean isVertical) {
- mStatusBarLazy.get().setQsScrimEnabled(!isVertical);
+ mStatusBarOptionalLazy.get().ifPresent(
+ statusBar -> statusBar.setQsScrimEnabled(!isVertical));
}
private boolean onNavigationTouch(View v, MotionEvent event) {
@@ -1250,7 +1242,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
AssistManager.INVOCATION_TYPE_KEY,
AssistManager.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
mAssistManagerLazy.get().startAssist(args);
- mStatusBarLazy.get().awakenDreams();
+ mStatusBarOptionalLazy.get().ifPresent(StatusBar::awakenDreams);
mNavigationBarView.abortCurrentGesture();
return true;
}
@@ -1276,12 +1268,12 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
LatencyTracker.getInstance(mContext).onActionStart(
LatencyTracker.ACTION_TOGGLE_RECENTS);
}
- mStatusBarLazy.get().awakenDreams();
+ mStatusBarOptionalLazy.get().ifPresent(StatusBar::awakenDreams);
mCommandQueue.toggleRecentApps();
}
private void onImeSwitcherClick(View v) {
- mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem(
+ mInputMethodManager.showInputMethodPickerFromSystem(
true /* showAuxiliarySubtypes */, mDisplayId);
};
@@ -1381,8 +1373,11 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
return false;
}
- return mStatusBarLazy.get().toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
- MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
+ return mStatusBarOptionalLazy.get().map(
+ statusBar -> statusBar.toggleSplitScreenMode(
+ MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
+ MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS))
+ .orElse(false);
}
private void onAccessibilityClick(View v) {
@@ -1400,28 +1395,48 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
return true;
}
- void updateAccessibilityServicesState(AccessibilityManager accessibilityManager) {
- boolean[] feedbackEnabled = new boolean[1];
- int a11yFlags = getA11yButtonState(feedbackEnabled);
-
- boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
- boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
- mNavigationBarView.setAccessibilityButtonState(clickable, longClickable);
+ void updateAccessibilityServicesState() {
+ int a11yFlags = mNavigationBarA11yHelper.getA11yButtonState();
+ if (mNavigationBarView != null) {
+ boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
+ boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
+ mNavigationBarView.setAccessibilityButtonState(clickable, longClickable);
+ }
updateSystemUiStateFlags(a11yFlags);
}
- private void setAccessibilityFloatingMenuModeIfNeeded() {
- if (QuickStepContract.isGesturalMode(mNavBarMode)) {
+ private void updateAccessibilityButtonModeIfNeeded() {
+ final int mode = Settings.Secure.getIntForUser(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
+ ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
+
+ // ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU is compatible under gestural or non-gestural
+ // mode, so we don't need to update it.
+ if (mode == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
+ return;
+ }
+
+ // ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR is incompatible under gestural mode. Need to
+ // force update to ACCESSIBILITY_BUTTON_MODE_GESTURE.
+ if (QuickStepContract.isGesturalMode(mNavBarMode)
+ && mode == ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR) {
+ Settings.Secure.putIntForUser(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_GESTURE,
+ UserHandle.USER_CURRENT);
+ // ACCESSIBILITY_BUTTON_MODE_GESTURE is incompatible under non gestural mode. Need to
+ // force update to ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR.
+ } else if (!QuickStepContract.isGesturalMode(mNavBarMode)
+ && mode == ACCESSIBILITY_BUTTON_MODE_GESTURE) {
Settings.Secure.putIntForUser(mContentResolver,
Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
- ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU, UserHandle.USER_CURRENT);
+ ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
}
}
public void updateSystemUiStateFlags(int a11yFlags) {
if (a11yFlags < 0) {
- a11yFlags = getA11yButtonState(null);
+ a11yFlags = mNavigationBarA11yHelper.getA11yButtonState();
}
boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
@@ -1431,6 +1446,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
.setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isNavBarWindowVisible())
.setFlag(SYSUI_STATE_IME_SHOWING,
(mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0)
+ .setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING,
+ (mNavigationIconHints & NAVIGATION_HINT_IME_SHOWN) != 0)
.setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
allowSystemGestureIgnoringBarVisibility())
.commitUpdate(mDisplayId);
@@ -1446,45 +1463,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
}
}
- /**
- * Returns the system UI flags corresponding the the current accessibility button state
- *
- * @param outFeedbackEnabled if non-null, sets it to true if accessibility feedback is enabled.
- */
- public int getA11yButtonState(@Nullable boolean[] outFeedbackEnabled) {
- boolean feedbackEnabled = false;
- // AccessibilityManagerService resolves services for the current user since the local
- // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
- final List<AccessibilityServiceInfo> services =
- mAccessibilityManager.getEnabledAccessibilityServiceList(
- AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
- final List<String> a11yButtonTargets =
- mAccessibilityManager.getAccessibilityShortcutTargets(
- AccessibilityManager.ACCESSIBILITY_BUTTON);
- final int requestingServices = a11yButtonTargets.size();
- for (int i = services.size() - 1; i >= 0; --i) {
- AccessibilityServiceInfo info = services.get(i);
- if (info.feedbackType != 0 && info.feedbackType !=
- AccessibilityServiceInfo.FEEDBACK_GENERIC) {
- feedbackEnabled = true;
- }
- }
-
- if (outFeedbackEnabled != null) {
- outFeedbackEnabled[0] = feedbackEnabled;
- }
-
- // If accessibility button is floating menu mode, click and long click state should be
- // disabled.
- if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()
- == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
- return 0;
- }
-
- return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0)
- | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
- }
-
private void updateAssistantEntrypoints() {
mAssistantAvailable = mAssistManagerLazy.get()
.getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
@@ -1546,7 +1524,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
private void checkBarModes() {
// We only have status bar on default display now.
if (mIsOnDefaultDisplay) {
- mStatusBarLazy.get().checkBarModes();
+ mStatusBarOptionalLazy.get().ifPresent(StatusBar::checkBarModes);
} else {
checkNavBarModes();
}
@@ -1564,7 +1542,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
* Checks current navigation bar mode and make transitions.
*/
public void checkNavBarModes() {
- final boolean anim = mStatusBarLazy.get().isDeviceInteractive()
+ final boolean anim =
+ mStatusBarOptionalLazy.get().map(StatusBar::isDeviceInteractive).orElse(false)
&& mNavigationBarWindowState != WINDOW_STATE_HIDDEN;
mNavigationBarView.getBarTransitions().transitionTo(mNavigationBarMode, anim);
}
@@ -1579,18 +1558,13 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
}
}
updateScreenPinningGestures();
- setAccessibilityFloatingMenuModeIfNeeded();
+ updateAccessibilityButtonModeIfNeeded();
if (!canShowSecondaryHandle()) {
resetSecondaryHandle();
}
}
- @Override
- public void onAccessibilityButtonModeChanged(int mode) {
- updateAccessibilityServicesState(mAccessibilityManager);
- }
-
public void disableAnimationsDuringHide(long delay) {
mNavigationBarView.setLayoutTransitionsEnabled(false);
mHandler.postDelayed(mEnableLayoutTransitions,
@@ -1615,16 +1589,97 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
mNavigationBarView.getBarTransitions().finishAnimations();
}
- private final AccessibilityServicesStateChangeListener mAccessibilityListener =
+ private final NavigationBarA11yHelper.NavA11yEventListener mAccessibilityListener =
this::updateAccessibilityServicesState;
+ private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
+ WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation);
+ lp.paramsForRotation = new WindowManager.LayoutParams[4];
+ for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+ lp.paramsForRotation[rot] = getBarLayoutParamsForRotation(rot);
+ }
+ return lp;
+ }
+
+ private WindowManager.LayoutParams getBarLayoutParamsForRotation(int rotation) {
+ int width = WindowManager.LayoutParams.MATCH_PARENT;
+ int height = WindowManager.LayoutParams.MATCH_PARENT;
+ int insetsHeight = -1;
+ int gravity = Gravity.BOTTOM;
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ boolean navBarCanMove = true;
+ if (mWindowManager != null && mWindowManager.getCurrentWindowMetrics() != null) {
+ Rect displaySize = mWindowManager.getCurrentWindowMetrics().getBounds();
+ navBarCanMove = displaySize.width() != displaySize.height()
+ && mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_navBarCanMove);
+ }
+ if (!navBarCanMove) {
+ height = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_frame_height);
+ insetsHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_height);
+ } else {
+ switch (rotation) {
+ case ROTATION_UNDEFINED:
+ case Surface.ROTATION_0:
+ case Surface.ROTATION_180:
+ height = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_frame_height);
+ insetsHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_height);
+ break;
+ case Surface.ROTATION_90:
+ gravity = Gravity.RIGHT;
+ width = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_width);
+ break;
+ case Surface.ROTATION_270:
+ gravity = Gravity.LEFT;
+ width = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_width);
+ break;
+ }
+ }
+ }
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ width,
+ height,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
+ WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+ | WindowManager.LayoutParams.FLAG_SLIPPERY,
+ PixelFormat.TRANSLUCENT);
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ lp.gravity = gravity;
+ if (insetsHeight != -1) {
+ lp.providedInternalInsets = Insets.of(0, height - insetsHeight, 0, 0);
+ } else {
+ lp.providedInternalInsets = Insets.NONE;
+ }
+ }
+ lp.token = new Binder();
+ lp.accessibilityTitle = mContext.getString(R.string.nav_bar);
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
+ lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ lp.windowAnimations = 0;
+ lp.setTitle("NavigationBar" + mContext.getDisplayId());
+ lp.setFitInsetsTypes(0 /* types */);
+ lp.setTrustedOverlay();
+ return lp;
+ }
+
private boolean canShowSecondaryHandle() {
return mNavBarMode == NAV_BAR_MODE_GESTURAL && mOrientationHandle != null;
}
private final Consumer<Integer> mRotationWatcher = rotation -> {
- if (mNavigationBarView.needsReorient(rotation)) {
- repositionNavigationBar();
+ if (mNavigationBarView != null
+ && mNavigationBarView.needsReorient(rotation)) {
+ repositionNavigationBar(rotation);
}
};
@@ -1644,7 +1699,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
}
if (Intent.ACTION_USER_SWITCHED.equals(action)) {
// The accessibility settings may be different for the new user
- updateAccessibilityServicesState(mAccessibilityManager);
+ updateAccessibilityServicesState();
}
}
};
@@ -1653,4 +1708,121 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
int getNavigationIconHints() {
return mNavigationIconHints;
}
-}
+
+ /**
+ * Injectable factory for construction a {@link NavigationBar}.
+ */
+ public static class Factory {
+ private final Lazy<AssistManager> mAssistManagerLazy;
+ private final AccessibilityManager mAccessibilityManager;
+ private final DeviceProvisionedController mDeviceProvisionedController;
+ private final MetricsLogger mMetricsLogger;
+ private final OverviewProxyService mOverviewProxyService;
+ private final NavigationModeController mNavigationModeController;
+ private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
+ private final StatusBarStateController mStatusBarStateController;
+ private final SysUiState mSysUiFlagsContainer;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final CommandQueue mCommandQueue;
+ private final Optional<Pip> mPipOptional;
+ private final Optional<LegacySplitScreen> mSplitScreenOptional;
+ private final Optional<Recents> mRecentsOptional;
+ private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
+ private final ShadeController mShadeController;
+ private final NotificationRemoteInputManager mNotificationRemoteInputManager;
+ private final NotificationShadeDepthController mNotificationShadeDepthController;
+ private final SystemActions mSystemActions;
+ private final Handler mMainHandler;
+ private final NavigationBarOverlayController mNavbarOverlayController;
+ private final UiEventLogger mUiEventLogger;
+ private final NavigationBarA11yHelper mNavigationBarA11yHelper;
+ private final UserTracker mUserTracker;
+ private final LightBarController mMainLightBarController;
+ private final LightBarController.Factory mLightBarControllerFactory;
+ private final AutoHideController mMainAutoHideController;
+ private final AutoHideController.Factory mAutoHideControllerFactory;
+ private final Optional<TelecomManager> mTelecomManagerOptional;
+ private final InputMethodManager mInputMethodManager;
+
+ @Inject
+ public Factory(
+ Lazy<AssistManager> assistManagerLazy,
+ AccessibilityManager accessibilityManager,
+ DeviceProvisionedController deviceProvisionedController,
+ MetricsLogger metricsLogger,
+ OverviewProxyService overviewProxyService,
+ NavigationModeController navigationModeController,
+ AccessibilityButtonModeObserver accessibilityButtonModeObserver,
+ StatusBarStateController statusBarStateController,
+ SysUiState sysUiFlagsContainer,
+ BroadcastDispatcher broadcastDispatcher,
+ CommandQueue commandQueue,
+ Optional<Pip> pipOptional,
+ Optional<LegacySplitScreen> splitScreenOptional,
+ Optional<Recents> recentsOptional,
+ Lazy<Optional<StatusBar>> statusBarOptionalLazy,
+ ShadeController shadeController,
+ NotificationRemoteInputManager notificationRemoteInputManager,
+ NotificationShadeDepthController notificationShadeDepthController,
+ SystemActions systemActions,
+ @Main Handler mainHandler,
+ NavigationBarOverlayController navbarOverlayController,
+ UiEventLogger uiEventLogger,
+ NavigationBarA11yHelper navigationBarA11yHelper,
+ UserTracker userTracker,
+ LightBarController mainLightBarController,
+ LightBarController.Factory lightBarControllerFactory,
+ AutoHideController mainAutoHideController,
+ AutoHideController.Factory autoHideControllerFactory,
+ Optional<TelecomManager> telecomManagerOptional,
+ InputMethodManager inputMethodManager) {
+ mAssistManagerLazy = assistManagerLazy;
+ mAccessibilityManager = accessibilityManager;
+ mDeviceProvisionedController = deviceProvisionedController;
+ mMetricsLogger = metricsLogger;
+ mOverviewProxyService = overviewProxyService;
+ mNavigationModeController = navigationModeController;
+ mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
+ mStatusBarStateController = statusBarStateController;
+ mSysUiFlagsContainer = sysUiFlagsContainer;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mCommandQueue = commandQueue;
+ mPipOptional = pipOptional;
+ mSplitScreenOptional = splitScreenOptional;
+ mRecentsOptional = recentsOptional;
+ mStatusBarOptionalLazy = statusBarOptionalLazy;
+ mShadeController = shadeController;
+ mNotificationRemoteInputManager = notificationRemoteInputManager;
+ mNotificationShadeDepthController = notificationShadeDepthController;
+ mSystemActions = systemActions;
+ mMainHandler = mainHandler;
+ mNavbarOverlayController = navbarOverlayController;
+ mUiEventLogger = uiEventLogger;
+ mNavigationBarA11yHelper = navigationBarA11yHelper;
+ mUserTracker = userTracker;
+ mMainLightBarController = mainLightBarController;
+ mLightBarControllerFactory = lightBarControllerFactory;
+ mMainAutoHideController = mainAutoHideController;
+ mAutoHideControllerFactory = autoHideControllerFactory;
+ mTelecomManagerOptional = telecomManagerOptional;
+ mInputMethodManager = inputMethodManager;
+ }
+
+ /** Construct a {@link NavigationBar} */
+ public NavigationBar create(Context context) {
+ final WindowManager wm = context.getSystemService(WindowManager.class);
+ return new NavigationBar(context, wm, mAssistManagerLazy,
+ mAccessibilityManager, mDeviceProvisionedController, mMetricsLogger,
+ mOverviewProxyService, mNavigationModeController,
+ mAccessibilityButtonModeObserver, mStatusBarStateController,
+ mSysUiFlagsContainer, mBroadcastDispatcher, mCommandQueue, mPipOptional,
+ mSplitScreenOptional, mRecentsOptional, mStatusBarOptionalLazy,
+ mShadeController, mNotificationRemoteInputManager,
+ mNotificationShadeDepthController, mSystemActions, mMainHandler,
+ mNavbarOverlayController, mUiEventLogger, mNavigationBarA11yHelper,
+ mUserTracker, mMainLightBarController, mLightBarControllerFactory,
+ mMainAutoHideController, mAutoHideControllerFactory, mTelecomManagerOptional,
+ mInputMethodManager);
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java
new file mode 100644
index 000000000000..13e6d8b410d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java
@@ -0,0 +1,90 @@
+package com.android.systemui.navigationbar;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * Extracts shared elements of a11y necessary between navbar and taskbar delegate
+ */
+@SysUISingleton
+public final class NavigationBarA11yHelper implements
+ AccessibilityButtonModeObserver.ModeChangedListener {
+ private final AccessibilityManager mAccessibilityManager;
+ private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
+ private final List<NavA11yEventListener> mA11yEventListeners = new ArrayList<>();
+
+ @Inject
+ public NavigationBarA11yHelper(AccessibilityManager accessibilityManager,
+ AccessibilityManagerWrapper accessibilityManagerWrapper,
+ AccessibilityButtonModeObserver accessibilityButtonModeObserver) {
+ mAccessibilityManager = accessibilityManager;
+ accessibilityManagerWrapper.addCallback(
+ accessibilityManager1 -> NavigationBarA11yHelper.this.dispatchEventUpdate());
+ mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
+
+ mAccessibilityButtonModeObserver.addListener(this);
+ }
+
+ public void registerA11yEventListener(NavA11yEventListener listener) {
+ mA11yEventListeners.add(listener);
+ }
+
+ public void removeA11yEventListener(NavA11yEventListener listener) {
+ mA11yEventListeners.remove(listener);
+ }
+
+ private void dispatchEventUpdate() {
+ for (NavA11yEventListener listener : mA11yEventListeners) {
+ listener.updateAccessibilityServicesState();
+ }
+ }
+
+ @Override
+ public void onAccessibilityButtonModeChanged(int mode) {
+ dispatchEventUpdate();
+ }
+
+ /**
+ * See {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and
+ * {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE}
+ *
+ * @return the a11y button clickable and long_clickable states, or 0 if there is no
+ * a11y button in the navbar
+ */
+ public int getA11yButtonState() {
+ // AccessibilityManagerService resolves services for the current user since the local
+ // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
+ final List<String> a11yButtonTargets =
+ mAccessibilityManager.getAccessibilityShortcutTargets(
+ AccessibilityManager.ACCESSIBILITY_BUTTON);
+ final int requestingServices = a11yButtonTargets.size();
+
+ // If accessibility button is floating menu mode, click and long click state should be
+ // disabled.
+ if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()
+ == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
+ return 0;
+ }
+
+ return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0)
+ | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
+ }
+
+ public interface NavA11yEventListener {
+ void updateAccessibilityServicesState();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 1628c71ae005..97bcb00eddbd 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -17,109 +17,64 @@
package com.android.systemui.navigationbar;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
+
+import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
import android.view.Display;
import android.view.IWindowManager;
import android.view.View;
-import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.Dumpable;
-import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
-import com.android.systemui.accessibility.SystemActions;
-import com.android.systemui.assist.AssistManager;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.pip.Pip;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.Optional;
import javax.inject.Inject;
-import dagger.Lazy;
-
/** A controller to handle navigation bars. */
@SysUISingleton
-public class NavigationBarController implements Callbacks,
+public class NavigationBarController implements
+ Callbacks,
ConfigurationController.ConfigurationListener,
- NavigationModeController.ModeChangedListener, Dumpable {
-
- private static final float TABLET_MIN_DPS = 600;
+ NavigationModeController.ModeChangedListener,
+ Dumpable {
private static final String TAG = NavigationBarController.class.getSimpleName();
private final Context mContext;
- private final WindowManager mWindowManager;
- private final Lazy<AssistManager> mAssistManagerLazy;
- private final AccessibilityManager mAccessibilityManager;
- private final AccessibilityManagerWrapper mAccessibilityManagerWrapper;
- private final DeviceProvisionedController mDeviceProvisionedController;
- private final MetricsLogger mMetricsLogger;
- private final OverviewProxyService mOverviewProxyService;
- private final NavigationModeController mNavigationModeController;
- private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
- private final StatusBarStateController mStatusBarStateController;
- private final SysUiState mSysUiFlagsContainer;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final CommandQueue mCommandQueue;
- private final Optional<Pip> mPipOptional;
- private final Optional<LegacySplitScreen> mSplitScreenOptional;
- private final Optional<Recents> mRecentsOptional;
- private final Lazy<StatusBar> mStatusBarLazy;
- private final ShadeController mShadeController;
- private final NotificationRemoteInputManager mNotificationRemoteInputManager;
- private final SystemActions mSystemActions;
- private final UiEventLogger mUiEventLogger;
private final Handler mHandler;
+ private final NavigationBar.Factory mNavigationBarFactory;
private final DisplayManager mDisplayManager;
- private final NavigationBarOverlayController mNavBarOverlayController;
private final TaskbarDelegate mTaskbarDelegate;
- private final NotificationShadeDepthController mNotificationShadeDepthController;
private int mNavMode;
- private boolean mIsTablet;
- private final UserTracker mUserTracker;
+ @VisibleForTesting boolean mIsTablet;
/** A displayId - nav bar maps. */
@VisibleForTesting
@@ -132,72 +87,37 @@ public class NavigationBarController implements Callbacks,
@Inject
public NavigationBarController(Context context,
- WindowManager windowManager,
- Lazy<AssistManager> assistManagerLazy,
- AccessibilityManager accessibilityManager,
- AccessibilityManagerWrapper accessibilityManagerWrapper,
- DeviceProvisionedController deviceProvisionedController,
- MetricsLogger metricsLogger,
OverviewProxyService overviewProxyService,
NavigationModeController navigationModeController,
- AccessibilityButtonModeObserver accessibilityButtonModeObserver,
- StatusBarStateController statusBarStateController,
SysUiState sysUiFlagsContainer,
- BroadcastDispatcher broadcastDispatcher,
CommandQueue commandQueue,
- Optional<Pip> pipOptional,
- Optional<LegacySplitScreen> splitScreenOptional,
- Optional<Recents> recentsOptional,
- Lazy<StatusBar> statusBarLazy,
- ShadeController shadeController,
- NotificationRemoteInputManager notificationRemoteInputManager,
- NotificationShadeDepthController notificationShadeDepthController,
- SystemActions systemActions,
@Main Handler mainHandler,
- UiEventLogger uiEventLogger,
- NavigationBarOverlayController navBarOverlayController,
ConfigurationController configurationController,
- UserTracker userTracker) {
+ NavigationBarA11yHelper navigationBarA11yHelper,
+ TaskbarDelegate taskbarDelegate,
+ NavigationBar.Factory navigationBarFactory,
+ DumpManager dumpManager,
+ AutoHideController autoHideController) {
mContext = context;
- mWindowManager = windowManager;
- mAssistManagerLazy = assistManagerLazy;
- mAccessibilityManager = accessibilityManager;
- mAccessibilityManagerWrapper = accessibilityManagerWrapper;
- mDeviceProvisionedController = deviceProvisionedController;
- mMetricsLogger = metricsLogger;
- mOverviewProxyService = overviewProxyService;
- mNavigationModeController = navigationModeController;
- mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
- mStatusBarStateController = statusBarStateController;
- mSysUiFlagsContainer = sysUiFlagsContainer;
- mBroadcastDispatcher = broadcastDispatcher;
- mCommandQueue = commandQueue;
- mPipOptional = pipOptional;
- mSplitScreenOptional = splitScreenOptional;
- mRecentsOptional = recentsOptional;
- mStatusBarLazy = statusBarLazy;
- mShadeController = shadeController;
- mNotificationRemoteInputManager = notificationRemoteInputManager;
- mNotificationShadeDepthController = notificationShadeDepthController;
- mSystemActions = systemActions;
- mUiEventLogger = uiEventLogger;
mHandler = mainHandler;
+ mNavigationBarFactory = navigationBarFactory;
mDisplayManager = mContext.getSystemService(DisplayManager.class);
commandQueue.addCallback(this);
configurationController.addCallback(this);
mConfigChanges.applyNewConfig(mContext.getResources());
- mNavBarOverlayController = navBarOverlayController;
- mNavMode = mNavigationModeController.addListener(this);
- mNavigationModeController.addListener(this);
- mTaskbarDelegate = new TaskbarDelegate(mOverviewProxyService);
- mIsTablet = isTablet(mContext.getResources().getConfiguration());
- mUserTracker = userTracker;
+ mNavMode = navigationModeController.addListener(this);
+ mTaskbarDelegate = taskbarDelegate;
+ mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService,
+ navigationBarA11yHelper, navigationModeController, sysUiFlagsContainer,
+ dumpManager, autoHideController);
+ mIsTablet = isTablet(mContext);
+ dumpManager.registerDumpable(this);
}
@Override
public void onConfigChanged(Configuration newConfig) {
boolean isOldConfigTablet = mIsTablet;
- mIsTablet = isTablet(newConfig);
+ mIsTablet = isTablet(mContext);
boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
// If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded
if (largeScreenChanged && updateNavbarForTaskbar()) {
@@ -237,25 +157,25 @@ public class NavigationBarController implements Callbacks,
});
}
- /**
- * @return {@code true} if navbar was added/removed, false otherwise
- */
- public boolean updateNavbarForTaskbar() {
- if (!isThreeButtonTaskbarFlagEnabled()) {
- return false;
+ /** @see #initializeTaskbarIfNecessary() */
+ private boolean updateNavbarForTaskbar() {
+ boolean taskbarShown = initializeTaskbarIfNecessary();
+ if (!taskbarShown && mNavigationBars.get(mContext.getDisplayId()) == null) {
+ createNavigationBar(mContext.getDisplay(), null, null);
}
+ return taskbarShown;
+ }
- if (mIsTablet && mNavMode == NAV_BAR_MODE_3BUTTON) {
- // Remove navigation bar when taskbar is showing, currently only for 3 button mode
+ /** @return {@code true} if taskbar is enabled, false otherwise */
+ private boolean initializeTaskbarIfNecessary() {
+ if (mIsTablet) {
+ // Remove navigation bar when taskbar is showing
removeNavigationBar(mContext.getDisplayId());
- mCommandQueue.addCallback(mTaskbarDelegate);
- } else if (mNavigationBars.get(mContext.getDisplayId()) == null) {
- // Add navigation bar after taskbar goes away
- createNavigationBar(mContext.getDisplay(), null, null);
- mCommandQueue.removeCallback(mTaskbarDelegate);
+ mTaskbarDelegate.init(mContext.getDisplayId());
+ } else {
+ mTaskbarDelegate.destroy();
}
-
- return true;
+ return mIsTablet;
}
@Override
@@ -266,7 +186,7 @@ public class NavigationBarController implements Callbacks,
@Override
public void onDisplayReady(int displayId) {
Display display = mDisplayManager.getDisplay(displayId);
- mIsTablet = isTablet(mContext.getResources().getConfiguration());
+ mIsTablet = isTablet(mContext);
createNavigationBar(display, null /* savedState */, null /* result */);
}
@@ -302,7 +222,7 @@ public class NavigationBarController implements Callbacks,
*/
public void createNavigationBars(final boolean includeDefaultDisplay,
RegisterStatusBarResult result) {
- if (updateNavbarForTaskbar()) {
+ if (initializeTaskbarIfNecessary()) {
return;
}
@@ -326,7 +246,7 @@ public class NavigationBarController implements Callbacks,
return;
}
- if (isThreeButtonTaskbarEnabled()) {
+ if (mIsTablet) {
return;
}
@@ -346,32 +266,8 @@ public class NavigationBarController implements Callbacks,
final Context context = isOnDefaultDisplay
? mContext
: mContext.createDisplayContext(display);
- NavigationBar navBar = new NavigationBar(context,
- mWindowManager,
- mAssistManagerLazy,
- mAccessibilityManager,
- mAccessibilityManagerWrapper,
- mDeviceProvisionedController,
- mMetricsLogger,
- mOverviewProxyService,
- mNavigationModeController,
- mAccessibilityButtonModeObserver,
- mStatusBarStateController,
- mSysUiFlagsContainer,
- mBroadcastDispatcher,
- mCommandQueue,
- mPipOptional,
- mSplitScreenOptional,
- mRecentsOptional,
- mStatusBarLazy,
- mShadeController,
- mNotificationRemoteInputManager,
- mNotificationShadeDepthController,
- mSystemActions,
- mHandler,
- mNavBarOverlayController,
- mUiEventLogger,
- mUserTracker);
+ NavigationBar navBar = mNavigationBarFactory.create(context);
+
mNavigationBars.put(displayId, navBar);
View navigationBarView = navBar.createView(savedState);
@@ -461,24 +357,6 @@ public class NavigationBarController implements Callbacks,
return mNavigationBars.get(DEFAULT_DISPLAY);
}
- private boolean isThreeButtonTaskbarEnabled() {
- return mIsTablet && mNavMode == NAV_BAR_MODE_3BUTTON &&
- isThreeButtonTaskbarFlagEnabled();
- }
-
- private boolean isThreeButtonTaskbarFlagEnabled() {
- return SystemProperties.getBoolean("persist.debug.taskbar_three_button", false);
- }
-
- private boolean isTablet(Configuration newConfig) {
- float density = Resources.getSystem().getDisplayMetrics().density;
- int size = Math.min((int) (density * newConfig.screenWidthDp),
- (int) (density* newConfig.screenHeightDp));
- DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
- float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
- return (size / densityRatio) >= TABLET_MIN_DPS;
- }
-
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
for (int i = 0; i < mNavigationBars.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 70c21e43b79a..680cc5cb5cba 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -19,9 +19,7 @@ package com.android.systemui.navigationbar;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SEARCH_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
@@ -77,10 +75,12 @@ import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
import com.android.systemui.navigationbar.buttons.NearestTouchFrame;
import com.android.systemui.navigationbar.buttons.RotationContextButton;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
-import com.android.systemui.navigationbar.gestural.FloatingRotationButton;
-import com.android.systemui.navigationbar.gestural.RegionSamplingHelper;
+import com.android.systemui.shared.rotation.FloatingRotationButton;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
+import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
+import com.android.systemui.shared.rotation.RotationButtonController;
+import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
@@ -96,6 +96,8 @@ import com.android.wm.shell.pip.Pip;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
public class NavigationBarView extends FrameLayout implements
@@ -159,6 +161,7 @@ public class NavigationBarView extends FrameLayout implements
private Configuration mTmpLastConfiguration;
private NavigationBarInflaterView mNavigationInflaterView;
+ private Optional<Recents> mRecentsOptional = Optional.empty();
private NotificationPanelViewController mPanelView;
private RotationContextButton mRotationContextButton;
private FloatingRotationButton mFloatingRotationButton;
@@ -247,7 +250,7 @@ public class NavigationBarView extends FrameLayout implements
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
if (action == R.id.action_toggle_overview) {
- Dependency.get(Recents.class).toggleRecentApps();
+ mRecentsOptional.ifPresent(Recents::toggleRecentApps);
} else {
return super.performAccessibilityAction(host, action, args);
}
@@ -270,14 +273,23 @@ public class NavigationBarView extends FrameLayout implements
false /* inScreen */, false /* useNearestRegion */));
};
- private final Consumer<Boolean> mRotationButtonListener = (visible) -> {
- if (visible && mAutoHideController != null) {
- // If the button will actually become visible and the navbar is about to hide,
- // tell the statusbar to keep it around for longer
- mAutoHideController.touchAutoHide();
- }
- notifyActiveTouchRegions();
- };
+ private final RotationButtonUpdatesCallback mRotationButtonListener =
+ new RotationButtonUpdatesCallback() {
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ if (visible && mAutoHideController != null) {
+ // If the button will actually become visible and the navbar is about
+ // to hide, tell the statusbar to keep it around for longer
+ mAutoHideController.touchAutoHide();
+ }
+ notifyActiveTouchRegions();
+ }
+
+ @Override
+ public void onPositionChanged() {
+ notifyActiveTouchRegions();
+ }
+ };
private final Consumer<Boolean> mNavbarOverlayVisibilityChangeCallback = (visible) -> {
if (visible) {
@@ -311,9 +323,23 @@ public class NavigationBarView extends FrameLayout implements
mContextualButtonGroup.addButton(accessibilityButton);
mRotationContextButton = new RotationContextButton(R.id.rotate_suggestion,
mLightContext, R.drawable.ic_sysbar_rotate_button_ccw_start_0);
- mFloatingRotationButton = new FloatingRotationButton(context);
- mRotationButtonController = new RotationButtonController(mLightContext,
- mLightIconColor, mDarkIconColor);
+ mFloatingRotationButton = new FloatingRotationButton(mContext,
+ R.string.accessibility_rotate_button,
+ R.layout.rotate_suggestion,
+ R.id.rotate_suggestion,
+ R.dimen.floating_rotation_button_min_margin,
+ R.dimen.rounded_corner_content_padding,
+ R.dimen.floating_rotation_button_taskbar_left_margin,
+ R.dimen.floating_rotation_button_taskbar_bottom_margin,
+ R.dimen.floating_rotation_button_diameter,
+ R.dimen.key_button_ripple_max_width);
+ mRotationButtonController = new RotationButtonController(mLightContext, mLightIconColor,
+ mDarkIconColor, R.drawable.ic_sysbar_rotate_button_ccw_start_0,
+ R.drawable.ic_sysbar_rotate_button_ccw_start_90,
+ R.drawable.ic_sysbar_rotate_button_cw_start_0,
+ R.drawable.ic_sysbar_rotate_button_cw_start_90,
+ () -> getDisplay().getRotation());
+
updateRotationButton();
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
@@ -339,6 +365,7 @@ public class NavigationBarView extends FrameLayout implements
mEdgeBackGestureHandler = Dependency.get(EdgeBackGestureHandler.Factory.class)
.create(mContext);
mEdgeBackGestureHandler.setStateChangeCallback(this::updateStates);
+ Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
mRegionSamplingHelper = new RegionSamplingHelper(this,
new RegionSamplingHelper.SamplingCallback() {
@Override
@@ -361,7 +388,7 @@ public class NavigationBarView extends FrameLayout implements
public boolean isSamplingEnabled() {
return isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode);
}
- });
+ }, backgroundExecutor);
mNavBarOverlayController = Dependency.get(NavigationBarOverlayController.class);
if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) {
@@ -388,6 +415,10 @@ public class NavigationBarView extends FrameLayout implements
return mBarTransitions.getLightTransitionsController();
}
+ public void setComponents(Optional<Recents> recentsOptional) {
+ mRecentsOptional = recentsOptional;
+ }
+
public void setComponents(NotificationPanelViewController panel) {
mPanelView = panel;
updatePanelSystemUiStateFlags();
@@ -645,7 +676,7 @@ public class NavigationBarView extends FrameLayout implements
}
public void setBehavior(@Behavior int behavior) {
- mRotationButtonController.onBehaviorChanged(behavior);
+ mRotationButtonController.onBehaviorChanged(Display.DEFAULT_DISPLAY, behavior);
}
@Override
@@ -826,7 +857,6 @@ public class NavigationBarView extends FrameLayout implements
public void onStatusBarPanelStateChanged() {
updateSlippery();
- updatePanelSystemUiStateFlags();
}
public void updateDisabledSystemUiStateFlags() {
@@ -843,21 +873,12 @@ public class NavigationBarView extends FrameLayout implements
.commitUpdate(displayId);
}
- public void updatePanelSystemUiStateFlags() {
- int displayId = mContext.getDisplayId();
+ private void updatePanelSystemUiStateFlags() {
if (SysUiState.DEBUG) {
Log.d(TAG, "Updating panel sysui state flags: panelView=" + mPanelView);
}
if (mPanelView != null) {
- if (SysUiState.DEBUG) {
- Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
- + mPanelView.isFullyExpanded() + " inQs=" + mPanelView.isInSettings());
- }
- mSysUiFlagContainer.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
- mPanelView.isFullyExpanded() && !mPanelView.isInSettings())
- .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
- mPanelView.isInSettings())
- .commitUpdate(displayId);
+ mPanelView.updateSystemUiStateFlags();
}
}
@@ -1271,6 +1292,7 @@ public class NavigationBarView extends FrameLayout implements
mButtonDispatchers.valueAt(i).onDestroy();
}
if (mRotationButtonController != null) {
+ mFloatingRotationButton.hide();
mRotationButtonController.unregisterListeners();
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
index 422ffd524aa8..73a0c542fb09 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
@@ -36,6 +36,7 @@ import android.util.Log;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -99,12 +100,15 @@ public class NavigationModeController implements Dumpable {
public NavigationModeController(Context context,
DeviceProvisionedController deviceProvisionedController,
ConfigurationController configurationController,
- @UiBackground Executor uiBgExecutor) {
+ @UiBackground Executor uiBgExecutor,
+ DumpManager dumpManager) {
mContext = context;
mCurrentUserContext = context;
mOverlayManager = IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE));
mUiBgExecutor = uiBgExecutor;
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
+
deviceProvisionedController.addCallback(mDeviceProvisionedCallback);
IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
@@ -114,7 +118,7 @@ public class NavigationModeController implements Dumpable {
configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
if (DEBUG) {
Log.d(TAG, "onOverlayChanged");
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButton.java
deleted file mode 100644
index e48785844347..000000000000
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButton.java
+++ /dev/null
@@ -1,42 +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.systemui.navigationbar;
-
-import android.view.View;
-
-import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
-
-import java.util.function.Consumer;
-
-/** Interface of a rotation button that interacts {@link RotationButtonController}. */
-public interface RotationButton {
- void setRotationButtonController(RotationButtonController rotationButtonController);
- void setVisibilityChangedCallback(Consumer<Boolean> visibilityChangedCallback);
- View getCurrentView();
- boolean show();
- boolean hide();
- boolean isVisible();
- void updateIcon(int lightIconColor, int darkIconColor);
- void setOnClickListener(View.OnClickListener onClickListener);
- void setOnHoverListener(View.OnHoverListener onHoverListener);
- KeyButtonDrawable getImageDrawable();
- void setDarkIntensity(float darkIntensity);
- default void setCanShowRotationButton(boolean canShow) {}
- default boolean acceptRotationProposal() {
- return getCurrentView() != null;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 03147d8f1085..d707dbdf5cba 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -16,23 +16,290 @@
package com.android.systemui.navigationbar;
+import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
+import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
+import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.containsType;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+
+import android.app.StatusBarManager;
+import android.app.StatusBarManager.WindowVisibleState;
+import android.content.ComponentCallbacks;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.hardware.display.DisplayManager;
+import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
+import android.view.Display;
+import android.view.InsetsVisibilities;
+import android.view.View;
+import android.view.WindowInsetsController.Behavior;
+import androidx.annotation.NonNull;
+
+import com.android.internal.view.AppearanceRegion;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.AutoHideController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Singleton
+public class TaskbarDelegate implements CommandQueue.Callbacks,
+ OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
+ ComponentCallbacks, Dumpable {
+
+ private final EdgeBackGestureHandler mEdgeBackGestureHandler;
-public class TaskbarDelegate implements CommandQueue.Callbacks {
+ private CommandQueue mCommandQueue;
+ private OverviewProxyService mOverviewProxyService;
+ private NavigationBarA11yHelper mNavigationBarA11yHelper;
+ private NavigationModeController mNavigationModeController;
+ private SysUiState mSysUiState;
+ private AutoHideController mAutoHideController;
+ private int mDisplayId;
+ private int mNavigationIconHints;
+ private final NavigationBarA11yHelper.NavA11yEventListener mNavA11yEventListener =
+ this::updateSysuiFlags;
+ private int mDisabledFlags;
+ private @WindowVisibleState int mTaskBarWindowState = WINDOW_STATE_SHOWING;
+ private @Behavior int mBehavior;
+ private final Context mContext;
+ private final DisplayManager mDisplayManager;
+ private Context mWindowContext;
+ /**
+ * Tracks the system calls for when taskbar should transiently show or hide so we can return
+ * this value in {@link AutoHideUiElement#isVisible()} below.
+ *
+ * This also gets set by {@link #onTaskbarAutohideSuspend(boolean)} to force show the transient
+ * taskbar if launcher has requested to suspend auto-hide behavior.
+ */
+ private boolean mTaskbarTransientShowing;
+ private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() {
+ @Override
+ public void synchronizeState() {
+ }
- private final OverviewProxyService mOverviewProxyService;
+ @Override
+ public boolean isVisible() {
+ return mTaskbarTransientShowing;
+ }
- public TaskbarDelegate(OverviewProxyService overviewProxyService) {
+ @Override
+ public void hide() {
+ }
+ };
+
+ @Inject
+ public TaskbarDelegate(Context context) {
+ mEdgeBackGestureHandler = Dependency.get(EdgeBackGestureHandler.Factory.class)
+ .create(context);
+ mContext = context;
+ mDisplayManager = mContext.getSystemService(DisplayManager.class);
+ }
+
+ public void setDependencies(CommandQueue commandQueue,
+ OverviewProxyService overviewProxyService,
+ NavigationBarA11yHelper navigationBarA11yHelper,
+ NavigationModeController navigationModeController,
+ SysUiState sysUiState, DumpManager dumpManager,
+ AutoHideController autoHideController) {
+ // TODO: adding this in the ctor results in a dagger dependency cycle :(
+ mCommandQueue = commandQueue;
mOverviewProxyService = overviewProxyService;
+ mNavigationBarA11yHelper = navigationBarA11yHelper;
+ mNavigationModeController = navigationModeController;
+ mSysUiState = sysUiState;
+ dumpManager.registerDumpable(this);
+ mAutoHideController = autoHideController;
+ }
+
+ public void init(int displayId) {
+ mDisplayId = displayId;
+ mCommandQueue.addCallback(this);
+ mOverviewProxyService.addCallback(this);
+ mEdgeBackGestureHandler.onNavigationModeChanged(
+ mNavigationModeController.addListener(this));
+ mNavigationBarA11yHelper.registerA11yEventListener(mNavA11yEventListener);
+ mEdgeBackGestureHandler.onNavBarAttached();
+ // Initialize component callback
+ Display display = mDisplayManager.getDisplay(displayId);
+ mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
+ mWindowContext.registerComponentCallbacks(this);
+ // Set initial state for any listeners
+ updateSysuiFlags();
+ mAutoHideController.setNavigationBar(mAutoHideUiElement);
+ }
+
+ public void destroy() {
+ mCommandQueue.removeCallback(this);
+ mOverviewProxyService.removeCallback(this);
+ mNavigationModeController.removeListener(this);
+ mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener);
+ mEdgeBackGestureHandler.onNavBarDetached();
+ if (mWindowContext != null) {
+ mWindowContext.unregisterComponentCallbacks(this);
+ mWindowContext = null;
+ }
+ mAutoHideController.setNavigationBar(null);
+ }
+
+ private void updateSysuiFlags() {
+ int a11yFlags = mNavigationBarA11yHelper.getA11yButtonState();
+ boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
+ boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
+
+ mSysUiState.setFlag(SYSUI_STATE_A11Y_BUTTON_CLICKABLE, clickable)
+ .setFlag(SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE, longClickable)
+ .setFlag(SYSUI_STATE_IME_SHOWING,
+ (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0)
+ .setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING,
+ (mNavigationIconHints & NAVIGATION_HINT_IME_SHOWN) != 0)
+ .setFlag(SYSUI_STATE_OVERVIEW_DISABLED,
+ (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0)
+ .setFlag(SYSUI_STATE_HOME_DISABLED,
+ (mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0)
+ .setFlag(SYSUI_STATE_BACK_DISABLED,
+ (mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
+ .setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isWindowVisible())
+ .setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
+ allowSystemGestureIgnoringBarVisibility())
+ .setFlag(SYSUI_STATE_SCREEN_PINNING,
+ ActivityManagerWrapper.getInstance().isScreenPinningActive())
+ .commitUpdate(mDisplayId);
}
@Override
public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
boolean showImeSwitcher) {
- mOverviewProxyService.notifyImeWindowStatus(displayId, token, vis, backDisposition,
- showImeSwitcher);
+ boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;
+ int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition,
+ imeShown, showImeSwitcher);
+ if (hints != mNavigationIconHints) {
+ mNavigationIconHints = hints;
+ updateSysuiFlags();
+ }
+ }
+
+ @Override
+ public void setWindowState(int displayId, int window, int state) {
+ if (displayId == mDisplayId
+ && window == StatusBarManager.WINDOW_NAVIGATION_BAR
+ && mTaskBarWindowState != state) {
+ mTaskBarWindowState = state;
+ updateSysuiFlags();
+ }
+ }
+
+ @Override
+ public void onRotationProposal(int rotation, boolean isValid) {
+ mOverviewProxyService.onRotationProposal(rotation, isValid);
+ }
+
+ @Override
+ public void disable(int displayId, int state1, int state2, boolean animate) {
+ mDisabledFlags = state1;
+ updateSysuiFlags();
+ mOverviewProxyService.disable(displayId, state1, state2, animate);
+ }
+
+ @Override
+ public void onSystemBarAttributesChanged(int displayId, int appearance,
+ AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior,
+ InsetsVisibilities requestedVisibilities, String packageName) {
+ mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior);
+ if (mBehavior != behavior) {
+ mBehavior = behavior;
+ updateSysuiFlags();
+ }
+ }
+
+ @Override
+ public void showTransient(int displayId, int[] types) {
+ if (displayId != mDisplayId) {
+ return;
+ }
+ if (!containsType(types, ITYPE_NAVIGATION_BAR)) {
+ return;
+ }
+ mTaskbarTransientShowing = true;
+ }
+
+ @Override
+ public void abortTransient(int displayId, int[] types) {
+ if (displayId != mDisplayId) {
+ return;
+ }
+ if (!containsType(types, ITYPE_NAVIGATION_BAR)) {
+ return;
+ }
+ mTaskbarTransientShowing = false;
+ }
+
+ @Override
+ public void onTaskbarAutohideSuspend(boolean suspend) {
+ mTaskbarTransientShowing = suspend;
+ if (suspend) {
+ mAutoHideController.suspendAutoHide();
+ } else {
+ mAutoHideController.resumeSuspendedAutoHide();
+ }
+ }
+
+ @Override
+ public void onNavigationModeChanged(int mode) {
+ mEdgeBackGestureHandler.onNavigationModeChanged(mode);
+ }
+
+ private boolean isWindowVisible() {
+ return mTaskBarWindowState == WINDOW_STATE_SHOWING;
+ }
+
+ private boolean allowSystemGestureIgnoringBarVisibility() {
+ return mBehavior != BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration configuration) {
+ mEdgeBackGestureHandler.onConfigurationChanged(configuration);
+ }
+
+ @Override
+ public void onLowMemory() {}
+
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("TaskbarDelegate (displayId=" + mDisplayId + "):");
+ pw.println(" mNavigationIconHints=" + mNavigationIconHints);
+ pw.println(" mDisabledFlags=" + mDisabledFlags);
+ pw.println(" mTaskBarWindowState=" + mTaskBarWindowState);
+ pw.println(" mBehavior=" + mBehavior);
+ pw.println(" mTaskbarTransientShowing=" + mTaskbarTransientShowing);
+ mEdgeBackGestureHandler.dump(pw);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index 9ea938325659..debd2ebb51be 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -168,7 +168,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
setClickable(true);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- mRipple = new KeyButtonRipple(context, this);
+ mRipple = new KeyButtonRipple(context, this, R.dimen.key_button_ripple_max_width);
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
mInputManager = manager;
setBackground(mRipple);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java
index 6a97a3379939..ac014b5b4a64 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/RotationContextButton.java
@@ -21,12 +21,8 @@ import android.annotation.IdRes;
import android.content.Context;
import android.view.View;
-import com.android.systemui.navigationbar.RotationButton;
-import com.android.systemui.navigationbar.RotationButtonController;
-import com.android.systemui.navigationbar.buttons.ContextualButton;
-import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
-
-import java.util.function.Consumer;
+import com.android.systemui.shared.rotation.RotationButton;
+import com.android.systemui.shared.rotation.RotationButtonController;
/** Containing logic for the rotation button in nav bar. */
public class RotationContextButton extends ContextualButton implements RotationButton {
@@ -48,13 +44,10 @@ public class RotationContextButton extends ContextualButton implements RotationB
}
@Override
- public void setVisibilityChangedCallback(Consumer<Boolean> visibilityChangedCallback) {
- setListener(new ContextButtonListener() {
- @Override
- public void onVisibilityChanged(ContextualButton button, boolean visible) {
- if (visibilityChangedCallback != null) {
- visibilityChangedCallback.accept(visible);
- }
+ public void setUpdatesCallback(RotationButtonUpdatesCallback updatesCallback) {
+ setListener((button, visible) -> {
+ if (updatesCallback != null) {
+ updatesCallback.onVisibilityChanged(visible);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index aaa3bf0f40ee..c6da3420ffe9 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -101,8 +101,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private static final int MAX_NUM_LOGGED_PREDICTIONS = 10;
private static final int MAX_NUM_LOGGED_GESTURES = 10;
- // Temporary log until b/176302696 is resolved
- static final boolean DEBUG_MISSING_GESTURE = false;
+ // Temporary log until b/201642126 is resolved
+ static final boolean DEBUG_MISSING_GESTURE = true;
static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture";
private static final boolean ENABLE_PER_WINDOW_INPUT_ROTATION =
@@ -384,7 +384,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private void onNavigationSettingsChanged() {
boolean wasBackAllowed = isHandlingGestures();
updateCurrentUserResources();
- if (wasBackAllowed != isHandlingGestures()) {
+ if (mStateChangeCallback != null && wasBackAllowed != isHandlingGestures()) {
mStateChangeCallback.run();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButton.java
deleted file mode 100644
index 61118c5d26ac..000000000000
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/FloatingRotationButton.java
+++ /dev/null
@@ -1,186 +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.systemui.navigationbar.gestural;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.Surface;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.systemui.R;
-import com.android.systemui.navigationbar.RotationButton;
-import com.android.systemui.navigationbar.RotationButtonController;
-import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
-import com.android.systemui.navigationbar.buttons.KeyButtonView;
-
-import java.util.function.Consumer;
-
-/** Containing logic for the rotation button on the physical left bottom corner of the screen. */
-public class FloatingRotationButton implements RotationButton {
-
- private static final float BACKGROUND_ALPHA = 0.92f;
-
- private final Context mContext;
- private final WindowManager mWindowManager;
- private final KeyButtonView mKeyButtonView;
- private final int mDiameter;
- private final int mMargin;
- private KeyButtonDrawable mKeyButtonDrawable;
- private boolean mIsShowing;
- private boolean mCanShow = true;
-
- private RotationButtonController mRotationButtonController;
- private Consumer<Boolean> mVisibilityChangedCallback;
-
- public FloatingRotationButton(Context context) {
- mContext = context;
- mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- mKeyButtonView = (KeyButtonView) LayoutInflater.from(mContext).inflate(
- R.layout.rotate_suggestion, null);
- mKeyButtonView.setVisibility(View.VISIBLE);
-
- Resources res = mContext.getResources();
- mDiameter = res.getDimensionPixelSize(R.dimen.floating_rotation_button_diameter);
- mMargin = Math.max(res.getDimensionPixelSize(R.dimen.floating_rotation_button_min_margin),
- res.getDimensionPixelSize(R.dimen.rounded_corner_content_padding));
- }
-
- @Override
- public void setRotationButtonController(RotationButtonController rotationButtonController) {
- mRotationButtonController = rotationButtonController;
- updateIcon(mRotationButtonController.getLightIconColor(),
- mRotationButtonController.getDarkIconColor());
- }
-
- @Override
- public void setVisibilityChangedCallback(Consumer<Boolean> visibilityChangedCallback) {
- mVisibilityChangedCallback = visibilityChangedCallback;
- }
-
- @Override
- public View getCurrentView() {
- return mKeyButtonView;
- }
-
- @Override
- public boolean show() {
- if (!mCanShow || mIsShowing) {
- return false;
- }
- mIsShowing = true;
- int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(mDiameter, mDiameter,
- mMargin, mMargin, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, flags,
- PixelFormat.TRANSLUCENT);
- lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- lp.setTitle("FloatingRotationButton");
- lp.setFitInsetsTypes(0 /*types */);
- switch (mWindowManager.getDefaultDisplay().getRotation()) {
- case Surface.ROTATION_0:
- lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
- break;
- case Surface.ROTATION_90:
- lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
- break;
- case Surface.ROTATION_180:
- lp.gravity = Gravity.TOP | Gravity.RIGHT;
- break;
- case Surface.ROTATION_270:
- lp.gravity = Gravity.TOP | Gravity.LEFT;
- break;
- default:
- break;
- }
- mWindowManager.addView(mKeyButtonView, lp);
- if (mKeyButtonDrawable != null && mKeyButtonDrawable.canAnimate()) {
- mKeyButtonDrawable.resetAnimation();
- mKeyButtonDrawable.startAnimation();
- }
- mKeyButtonView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5,
- int i6, int i7) {
- if (mIsShowing && mVisibilityChangedCallback != null) {
- mVisibilityChangedCallback.accept(true);
- }
- mKeyButtonView.removeOnLayoutChangeListener(this);
- }
- });
- return true;
- }
-
- @Override
- public boolean hide() {
- if (!mIsShowing) {
- return false;
- }
- mWindowManager.removeViewImmediate(mKeyButtonView);
- mIsShowing = false;
- if (mVisibilityChangedCallback != null) {
- mVisibilityChangedCallback.accept(false);
- }
- return true;
- }
-
- @Override
- public boolean isVisible() {
- return mIsShowing;
- }
-
- @Override
- public void updateIcon(int lightIconColor, int darkIconColor) {
- Color ovalBackgroundColor = Color.valueOf(Color.red(darkIconColor),
- Color.green(darkIconColor), Color.blue(darkIconColor), BACKGROUND_ALPHA);
- mKeyButtonDrawable = KeyButtonDrawable.create(mRotationButtonController.getContext(),
- lightIconColor, darkIconColor, mRotationButtonController.getIconResId(),
- false /* shadow */, ovalBackgroundColor);
- mKeyButtonView.setImageDrawable(mKeyButtonDrawable);
- }
-
- @Override
- public void setOnClickListener(View.OnClickListener onClickListener) {
- mKeyButtonView.setOnClickListener(onClickListener);
- }
-
- @Override
- public void setOnHoverListener(View.OnHoverListener onHoverListener) {
- mKeyButtonView.setOnHoverListener(onHoverListener);
- }
-
- @Override
- public KeyButtonDrawable getImageDrawable() {
- return mKeyButtonDrawable;
- }
-
- @Override
- public void setDarkIntensity(float darkIntensity) {
- mKeyButtonView.setDarkIntensity(darkIntensity);
- }
-
- @Override
- public void setCanShowRotationButton(boolean canShow) {
- mCanShow = canShow;
- if (!mCanShow) {
- hide();
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 7fdb79eae2a9..8d1dfc842fba 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -55,9 +55,11 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.NavigationEdgeBackPlugin;
+import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.statusbar.VibratorHelper;
import java.io.PrintWriter;
+import java.util.concurrent.Executor;
public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPlugin {
@@ -349,6 +351,7 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
.getDimension(R.dimen.navigation_edge_action_drag_threshold);
setVisibility(GONE);
+ Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
boolean isPrimaryDisplay = mContext.getDisplayId() == DEFAULT_DISPLAY;
mRegionSamplingHelper = new RegionSamplingHelper(this,
new RegionSamplingHelper.SamplingCallback() {
@@ -366,7 +369,7 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
public boolean isSamplingEnabled() {
return isPrimaryDisplay;
}
- });
+ }, backgroundExecutor);
mRegionSamplingHelper.setWindowVisible(true);
mShowProtection = !isPrimaryDisplay;
}
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
index ad1e21d7cc45..0b565ea25911 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
@@ -17,25 +17,27 @@ package com.android.systemui.plugins;
import android.util.ArrayMap;
import com.android.systemui.Dependency;
-import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.PluginDependency.DependencyProvider;
import com.android.systemui.shared.plugins.PluginManager;
import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
/**
*/
-@SysUISingleton
+@Singleton
public class PluginDependencyProvider extends DependencyProvider {
private final ArrayMap<Class<?>, Object> mDependencies = new ArrayMap<>();
- private final PluginManager mManager;
+ private final Lazy<PluginManager> mManagerLazy;
/**
*/
@Inject
- public PluginDependencyProvider(PluginManager manager) {
- mManager = manager;
+ public PluginDependencyProvider(Lazy<PluginManager> managerLazy) {
+ mManagerLazy = managerLazy;
PluginDependency.sProvider = this;
}
@@ -51,7 +53,7 @@ public class PluginDependencyProvider extends DependencyProvider {
@Override
<T> T get(Plugin p, Class<T> cls) {
- if (!mManager.dependsOn(p, cls)) {
+ if (!mManagerLazy.get().dependsOn(p, cls)) {
throw new IllegalArgumentException(p.getClass() + " does not depend on " + cls);
}
synchronized (mDependencies) {
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java
index 63374150adaa..40f59744e038 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java
@@ -22,16 +22,22 @@ import android.content.pm.PackageManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.shared.plugins.PluginEnabler;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/** */
+@Singleton
public class PluginEnablerImpl implements PluginEnabler {
private static final String CRASH_DISABLED_PLUGINS_PREF_FILE = "auto_disabled_plugins_prefs";
- private PackageManager mPm;
+ private final PackageManager mPm;
private final SharedPreferences mAutoDisabledPrefs;
public PluginEnablerImpl(Context context) {
this(context, context.getPackageManager());
}
+ @Inject
@VisibleForTesting public PluginEnablerImpl(Context context, PackageManager pm) {
mAutoDisabledPrefs = context.getSharedPreferences(
CRASH_DISABLED_PLUGINS_PREF_FILE, Context.MODE_PRIVATE);
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java
deleted file mode 100644
index 7f01d6f1ffa3..000000000000
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.plugins;
-
-import android.content.Context;
-import android.os.Build;
-import android.os.Looper;
-import android.util.Log;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.shared.plugins.PluginEnabler;
-import com.android.systemui.shared.plugins.PluginInitializer;
-import com.android.systemui.shared.plugins.PluginManagerImpl;
-
-public class PluginInitializerImpl implements PluginInitializer {
-
- /**
- * True if WTFs should lead to crashes
- */
- private static final boolean WTFS_SHOULD_CRASH = false;
- private boolean mWtfsSet;
-
- @Override
- public Looper getBgLooper() {
- return Dependency.get(Dependency.BG_LOOPER);
- }
-
- @Override
- public void onPluginManagerInit() {
- // Plugin dependencies that don't have another good home can go here, but
- // dependencies that have better places to init can happen elsewhere.
- Dependency.get(PluginDependencyProvider.class)
- .allowPluginDependency(ActivityStarter.class);
- }
-
- @Override
- public String[] getWhitelistedPlugins(Context context) {
- return context.getResources().getStringArray(R.array.config_pluginWhitelist);
- }
-
- public PluginEnabler getPluginEnabler(Context context) {
- return new PluginEnablerImpl(context);
- }
-
- @Override
- public void handleWtfs() {
- if (WTFS_SHOULD_CRASH && !mWtfsSet) {
- mWtfsSet = true;
- Log.setWtfHandler(new Log.TerribleFailureHandler() {
- @Override
- public void onTerribleFailure(String tag, Log.TerribleFailure what,
- boolean system) {
- throw new PluginManagerImpl.CrashWhilePluginActiveException(what);
- }
- });
- }
- }
-
- @Override
- public boolean isDebuggable() {
- return Build.IS_DEBUGGABLE;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
new file mode 100644
index 000000000000..16b971f876bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
@@ -0,0 +1,130 @@
+/*
+ * 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.systemui.plugins;
+
+import static com.android.systemui.util.concurrency.GlobalConcurrencyModule.PRE_HANDLER;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.PluginModule;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.shared.plugins.PluginActionManager;
+import com.android.systemui.shared.plugins.PluginEnabler;
+import com.android.systemui.shared.plugins.PluginInstance;
+import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.shared.plugins.PluginManagerImpl;
+import com.android.systemui.shared.plugins.PluginPrefs;
+import com.android.systemui.util.concurrency.GlobalConcurrencyModule;
+import com.android.systemui.util.concurrency.ThreadFactory;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger Module for code related to plugins.
+ *
+ * Covers code both in com.android.systemui.plugins and code in
+ * com.android.systemui.shared.plugins.
+ */
+@Module(includes = {GlobalConcurrencyModule.class})
+public abstract class PluginsModule {
+ public static final String PLUGIN_THREAD = "plugin_thread";
+ public static final String PLUGIN_DEBUG = "plugin_debug";
+ public static final String PLUGIN_PRIVILEGED = "plugin_privileged";
+
+ @Provides
+ @Named(PLUGIN_DEBUG)
+ static boolean providesPluginDebug() {
+ return Build.IS_DEBUGGABLE;
+ }
+
+ @Binds
+ abstract PluginEnabler bindsPluginEnablerImpl(PluginEnablerImpl impl);
+
+ @Provides
+ @Singleton
+ static PluginInstance.Factory providesPluginInstanceFactory(
+ @Named(PLUGIN_PRIVILEGED) List<String> privilegedPlugins,
+ @Named(PLUGIN_DEBUG) boolean isDebug) {
+ return new PluginInstance.Factory(
+ PluginModule.class.getClassLoader(),
+ new PluginInstance.InstanceFactory<>(),
+ new PluginInstance.VersionChecker(),
+ privilegedPlugins,
+ isDebug);
+ }
+
+ @Provides
+ @Singleton
+ static PluginActionManager.Factory providePluginInstanceManagerFactory(Context context,
+ PackageManager packageManager, @Main Executor mainExecutor,
+ @Named(PLUGIN_THREAD) Executor pluginExecutor,
+ NotificationManager notificationManager, PluginEnabler pluginEnabler,
+ @Named(PLUGIN_PRIVILEGED) List<String> privilegedPlugins,
+ PluginInstance.Factory pluginInstanceFactory) {
+ return new PluginActionManager.Factory(
+ context, packageManager, mainExecutor, pluginExecutor,
+ notificationManager, pluginEnabler, privilegedPlugins, pluginInstanceFactory);
+ }
+
+ @Provides
+ @Singleton
+ @Named(PLUGIN_THREAD)
+ static Executor providesPluginExecutor(ThreadFactory threadFactory) {
+ return threadFactory.buildExecutorOnNewThread("plugin");
+ }
+
+ @Provides
+ @Singleton
+ static PluginManager providesPluginManager(
+ Context context,
+ PluginActionManager.Factory instanceManagerFactory,
+ @Named(PLUGIN_DEBUG) boolean debug,
+ @Named(PRE_HANDLER)
+ Optional<Thread.UncaughtExceptionHandler> uncaughtExceptionHandlerOptional,
+ PluginEnabler pluginEnabler,
+ PluginPrefs pluginPrefs,
+ @Named(PLUGIN_PRIVILEGED) List<String> privilegedPlugins) {
+ return new PluginManagerImpl(context, instanceManagerFactory, debug,
+ uncaughtExceptionHandlerOptional, pluginEnabler, pluginPrefs,
+ privilegedPlugins);
+ }
+
+ @Provides
+ static PluginPrefs providesPluginPrefs(Context context) {
+ return new PluginPrefs(context);
+ }
+
+ @Provides
+ @Named(PLUGIN_PRIVILEGED)
+ static List<String> providesPrivilegedPlugins(Context context) {
+ return Arrays.asList(context.getResources().getStringArray(R.array.config_pluginWhitelist));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index a888305cc83d..625265485f6d 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -54,6 +54,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.time.Duration;
import java.util.Arrays;
+import java.util.Optional;
import java.util.concurrent.Future;
import javax.inject.Inject;
@@ -108,15 +109,15 @@ public class PowerUI extends SystemUI implements CommandQueue.Callbacks {
private IThermalEventListener mUsbThermalEventListener;
private final BroadcastDispatcher mBroadcastDispatcher;
private final CommandQueue mCommandQueue;
- private final Lazy<StatusBar> mStatusBarLazy;
+ private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
@Inject
public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher,
- CommandQueue commandQueue, Lazy<StatusBar> statusBarLazy) {
+ CommandQueue commandQueue, Lazy<Optional<StatusBar>> statusBarOptionalLazy) {
super(context);
mBroadcastDispatcher = broadcastDispatcher;
mCommandQueue = commandQueue;
- mStatusBarLazy = statusBarLazy;
+ mStatusBarOptionalLazy = statusBarOptionalLazy;
}
public void start() {
@@ -710,7 +711,8 @@ public class PowerUI extends SystemUI implements CommandQueue.Callbacks {
int status = temp.getStatus();
if (status >= Temperature.THROTTLING_EMERGENCY) {
- if (!mStatusBarLazy.get().isDeviceInVrMode()) {
+ final Optional<StatusBar> statusBarOptional = mStatusBarOptionalLazy.get();
+ if (!statusBarOptional.map(StatusBar::isDeviceInVrMode).orElse(false)) {
mWarnings.showHighTemperatureWarning();
Slog.d(TAG, "SkinThermalEventListener: notifyThrottling was called "
+ ", current skin status = " + status
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt
index 1d2e74703b42..eec69f98b9be 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt
@@ -28,7 +28,7 @@ class PrivacyChipBuilder(private val context: Context, itemsList: List<PrivacyIt
appsAndTypes = itemsList.groupBy({ it.application }, { it.privacyType })
.toList()
.sortedWith(compareBy({ -it.second.size }, // Sort by number of AppOps
- { it.second.min() })) // Sort by "smallest" AppOpp (Location is largest)
+ { it.second.minOrNull() })) // Sort by "smallest" AppOpp (Location is largest)
types = itemsList.map { it.privacyType }.distinct().sorted()
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
index 9e8f6b82c182..23482677038c 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
@@ -24,7 +24,6 @@ import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.WindowInsets
import android.widget.ImageView
import android.widget.TextView
@@ -65,7 +64,6 @@ class PrivacyDialog(
window?.apply {
attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
attributes.receiveInsetsIgnoringZOrder = true
- setLayout(context.resources.getDimensionPixelSize(R.dimen.qs_panel_width), WRAP_CONTENT)
setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
new file mode 100644
index 000000000000..98b914672112
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -0,0 +1,218 @@
+/*
+ * 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.systemui.qs
+
+import android.content.Intent
+import android.os.UserManager
+import android.provider.Settings
+import android.view.View
+import android.widget.Toast
+import androidx.annotation.VisibleForTesting
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.nano.MetricsProto
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.globalactions.GlobalActionsDialogLite
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.FooterActionsController.ExpansionState.COLLAPSED
+import com.android.systemui.qs.FooterActionsController.ExpansionState.EXPANDED
+import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
+import com.android.systemui.statusbar.phone.MultiUserSwitchController
+import com.android.systemui.statusbar.phone.SettingsButton
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.UserInfoController
+import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
+import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.ViewController
+import javax.inject.Inject
+import javax.inject.Named
+
+/**
+ * Manages [FooterActionsView] behaviour, both when it's placed in QS or QQS (split shade).
+ * Main difference between QS and QQS behaviour is condition when buttons should be visible,
+ * determined by [buttonsVisibleState]
+ */
+class FooterActionsController @Inject constructor(
+ view: FooterActionsView,
+ private val qsPanelController: QSPanelController,
+ private val activityStarter: ActivityStarter,
+ private val userManager: UserManager,
+ private val userInfoController: UserInfoController,
+ private val multiUserSwitchController: MultiUserSwitchController,
+ private val deviceProvisionedController: DeviceProvisionedController,
+ private val falsingManager: FalsingManager,
+ private val metricsLogger: MetricsLogger,
+ private val tunerService: TunerService,
+ private val globalActionsDialog: GlobalActionsDialogLite,
+ private val uiEventLogger: UiEventLogger,
+ @Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
+ private val buttonsVisibleState: ExpansionState
+) : ViewController<FooterActionsView>(view) {
+
+ enum class ExpansionState { COLLAPSED, EXPANDED }
+
+ private var listening: Boolean = false
+
+ var expanded = false
+
+ private val settingsButton: SettingsButton = view.findViewById(R.id.settings_button)
+ private val settingsButtonContainer: View? = view.findViewById(R.id.settings_button_container)
+ private val editButton: View = view.findViewById(android.R.id.edit)
+ private val powerMenuLite: View = view.findViewById(R.id.pm_lite)
+
+ private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ ->
+ val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
+ mView.onUserInfoChanged(picture, isGuestUser)
+ }
+
+ private val onClickListener = View.OnClickListener { v ->
+ // Don't do anything until views are unhidden. Don't do anything if the tap looks
+ // suspicious.
+ if (!buttonsVisible() || falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ return@OnClickListener
+ }
+ if (v === settingsButton) {
+ if (!deviceProvisionedController.isCurrentUserSetup) {
+ // If user isn't setup just unlock the device and dump them back at SUW.
+ activityStarter.postQSRunnableDismissingKeyguard {}
+ return@OnClickListener
+ }
+ metricsLogger.action(
+ if (expanded) MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
+ else MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH)
+ if (settingsButton.isTunerClick) {
+ activityStarter.postQSRunnableDismissingKeyguard {
+ if (isTunerEnabled()) {
+ tunerService.showResetRequest {
+ // Relaunch settings so that the tuner disappears.
+ startSettingsActivity()
+ }
+ } else {
+ Toast.makeText(context, R.string.tuner_toast, Toast.LENGTH_LONG).show()
+ tunerService.isTunerEnabled = true
+ }
+ startSettingsActivity()
+ }
+ } else {
+ startSettingsActivity()
+ }
+ } else if (v === powerMenuLite) {
+ uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
+ globalActionsDialog.showOrHideDialog(false, true)
+ }
+ }
+
+ private fun buttonsVisible(): Boolean {
+ return when (buttonsVisibleState) {
+ EXPANDED -> expanded
+ COLLAPSED -> !expanded
+ }
+ }
+
+ override fun onInit() {
+ multiUserSwitchController.init()
+ }
+
+ fun hideFooter() {
+ mView.visibility = View.GONE
+ }
+
+ fun showFooter() {
+ mView.visibility = View.VISIBLE
+ updateView()
+ }
+
+ private fun startSettingsActivity() {
+ val animationController = settingsButtonContainer?.let {
+ ActivityLaunchAnimator.Controller.fromView(
+ it,
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON)
+ }
+ activityStarter.startActivity(Intent(Settings.ACTION_SETTINGS),
+ true /* dismissShade */, animationController)
+ }
+
+ @VisibleForTesting
+ public override fun onViewAttached() {
+ if (showPMLiteButton) {
+ powerMenuLite.visibility = View.VISIBLE
+ powerMenuLite.setOnClickListener(onClickListener)
+ } else {
+ powerMenuLite.visibility = View.GONE
+ }
+ settingsButton.setOnClickListener(onClickListener)
+ editButton.setOnClickListener(View.OnClickListener { view: View? ->
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ return@OnClickListener
+ }
+ activityStarter.postQSRunnableDismissingKeyguard { qsPanelController.showEdit(view) }
+ })
+
+ updateView()
+ }
+
+ private fun updateView() {
+ mView.updateEverything(isTunerEnabled(), multiUserSwitchController.isMultiUserEnabled)
+ }
+
+ override fun onViewDetached() {
+ setListening(false)
+ }
+
+ fun setListening(listening: Boolean) {
+ if (this.listening == listening) {
+ return
+ }
+ this.listening = listening
+ if (this.listening) {
+ userInfoController.addCallback(onUserInfoChangedListener)
+ updateView()
+ } else {
+ userInfoController.removeCallback(onUserInfoChangedListener)
+ }
+ }
+
+ fun disable(state2: Int) {
+ mView.disable(state2, isTunerEnabled(), multiUserSwitchController.isMultiUserEnabled)
+ }
+
+ fun setExpansion(headerExpansionFraction: Float) {
+ mView.setExpansion(headerExpansionFraction)
+ }
+
+ fun updateAnimator(width: Int, numTiles: Int) {
+ mView.updateAnimator(width, numTiles)
+ }
+
+ fun setKeyguardShowing() {
+ mView.setKeyguardShowing()
+ }
+
+ fun refreshVisibility(shouldBeVisible: Boolean) {
+ if (shouldBeVisible) {
+ showFooter()
+ } else {
+ hideFooter()
+ }
+ }
+
+ private fun isTunerEnabled() = tunerService.isTunerEnabled
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
new file mode 100644
index 000000000000..f6c89a9c66a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.systemui.qs
+
+import android.os.UserManager
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.globalactions.GlobalActionsDialogLite
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.FooterActionsController.ExpansionState
+import com.android.systemui.qs.dagger.QSFlagsModule
+import com.android.systemui.statusbar.phone.MultiUserSwitchController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.UserInfoController
+import com.android.systemui.tuner.TunerService
+import javax.inject.Inject
+import javax.inject.Named
+
+class FooterActionsControllerBuilder @Inject constructor(
+ private val qsPanelController: QSPanelController,
+ private val activityStarter: ActivityStarter,
+ private val userManager: UserManager,
+ private val userInfoController: UserInfoController,
+ private val multiUserSwitchControllerFactory: MultiUserSwitchController.Factory,
+ private val deviceProvisionedController: DeviceProvisionedController,
+ private val falsingManager: FalsingManager,
+ private val metricsLogger: MetricsLogger,
+ private val tunerService: TunerService,
+ private val globalActionsDialog: GlobalActionsDialogLite,
+ private val uiEventLogger: UiEventLogger,
+ @Named(QSFlagsModule.PM_LITE_ENABLED) private val showPMLiteButton: Boolean
+) {
+ private lateinit var view: FooterActionsView
+ private lateinit var buttonsVisibleState: ExpansionState
+
+ fun withView(view: FooterActionsView): FooterActionsControllerBuilder {
+ this.view = view
+ return this
+ }
+
+ fun withButtonsVisibleWhen(state: ExpansionState): FooterActionsControllerBuilder {
+ buttonsVisibleState = state
+ return this
+ }
+
+ fun build(): FooterActionsController {
+ return FooterActionsController(view, qsPanelController, activityStarter, userManager,
+ userInfoController, multiUserSwitchControllerFactory.create(view),
+ deviceProvisionedController, falsingManager, metricsLogger, tunerService,
+ globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
new file mode 100644
index 000000000000..f81f7bf73f64
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
@@ -0,0 +1,158 @@
+/*
+ * 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.systemui.qs
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.PorterDuff
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.RippleDrawable
+import android.os.UserManager
+import android.util.AttributeSet
+import android.view.View
+import android.widget.ImageView
+import android.widget.LinearLayout
+import com.android.settingslib.Utils
+import com.android.settingslib.drawable.UserIconDrawable
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.MultiUserSwitch
+import com.android.systemui.statusbar.phone.SettingsButton
+
+/**
+ * Quick Settings bottom buttons placed in footer (aka utility bar) - always visible in expanded QS,
+ * in split shade mode visible also in collapsed state. May contain up to 5 buttons: settings,
+ * edit tiles, power off and conditionally: user switch and tuner
+ */
+class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) {
+ private lateinit var settingsContainer: View
+ private lateinit var settingsButton: SettingsButton
+ private lateinit var multiUserSwitch: MultiUserSwitch
+ private lateinit var multiUserAvatar: ImageView
+ private lateinit var tunerIcon: View
+ private lateinit var editTilesButton: View
+
+ private var settingsCogAnimator: TouchAnimator? = null
+
+ private var qsDisabled = false
+ private var expansionAmount = 0f
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ editTilesButton = requireViewById(android.R.id.edit)
+ settingsButton = findViewById(R.id.settings_button)
+ settingsContainer = findViewById(R.id.settings_button_container)
+ multiUserSwitch = findViewById(R.id.multi_user_switch)
+ multiUserAvatar = multiUserSwitch.findViewById(R.id.multi_user_avatar)
+ tunerIcon = requireViewById(R.id.tuner_icon)
+
+ // RenderThread is doing more harm than good when touching the header (to expand quick
+ // settings), so disable it for this view
+ if (settingsButton.background is RippleDrawable) {
+ (settingsButton.background as RippleDrawable).setForceSoftware(true)
+ }
+ updateResources()
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ }
+
+ fun updateAnimator(width: Int, numTiles: Int) {
+ val size = (mContext.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size) -
+ mContext.resources.getDimensionPixelSize(R.dimen.qs_tile_padding))
+ val remaining = (width - numTiles * size) / (numTiles - 1)
+ val defSpace = mContext.resources.getDimensionPixelOffset(R.dimen.default_gear_space)
+ val translation = if (isLayoutRtl) (remaining - defSpace) else -(remaining - defSpace)
+ settingsCogAnimator = TouchAnimator.Builder()
+ .addFloat(settingsButton, "translationX", translation.toFloat(), 0f)
+ .addFloat(settingsButton, "rotation", -120f, 0f)
+ .build()
+ setExpansion(expansionAmount)
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+ updateResources()
+ }
+
+ override fun onRtlPropertiesChanged(layoutDirection: Int) {
+ super.onRtlPropertiesChanged(layoutDirection)
+ updateResources()
+ }
+
+ private fun updateResources() {
+ val tunerIconTranslation = mContext.resources
+ .getDimensionPixelOffset(R.dimen.qs_footer_tuner_icon_translation).toFloat()
+ tunerIcon.translationX = if (isLayoutRtl) (-tunerIconTranslation) else tunerIconTranslation
+ }
+
+ fun setKeyguardShowing() {
+ setExpansion(expansionAmount)
+ }
+
+ fun setExpansion(headerExpansionFraction: Float) {
+ expansionAmount = headerExpansionFraction
+ if (settingsCogAnimator != null) settingsCogAnimator!!.setPosition(headerExpansionFraction)
+ }
+
+ fun disable(
+ state2: Int,
+ isTunerEnabled: Boolean,
+ multiUserEnabled: Boolean
+ ) {
+ val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
+ if (disabled == qsDisabled) return
+ qsDisabled = disabled
+ updateEverything(isTunerEnabled, multiUserEnabled)
+ }
+
+ fun updateEverything(
+ isTunerEnabled: Boolean,
+ multiUserEnabled: Boolean
+ ) {
+ post {
+ updateVisibilities(isTunerEnabled, multiUserEnabled)
+ updateClickabilities()
+ isClickable = false
+ }
+ }
+
+ private fun updateClickabilities() {
+ multiUserSwitch.isClickable = multiUserSwitch.visibility == VISIBLE
+ editTilesButton.isClickable = editTilesButton.visibility == VISIBLE
+ settingsButton.isClickable = settingsButton.visibility == VISIBLE
+ }
+
+ private fun updateVisibilities(
+ isTunerEnabled: Boolean,
+ multiUserEnabled: Boolean
+ ) {
+ settingsContainer.visibility = if (qsDisabled) GONE else VISIBLE
+ tunerIcon.visibility = if (isTunerEnabled) VISIBLE else INVISIBLE
+ multiUserSwitch.visibility = if (multiUserEnabled) VISIBLE else GONE
+ val isDemo = UserManager.isDeviceInDemoMode(context)
+ settingsButton.visibility = if (isDemo) INVISIBLE else VISIBLE
+ }
+
+ fun onUserInfoChanged(picture: Drawable?, isGuestUser: Boolean) {
+ var pictureToSet = picture
+ if (picture != null && isGuestUser && picture !is UserIconDrawable) {
+ pictureToSet = picture.constantState.newDrawable(resources).mutate()
+ pictureToSet.setColorFilter(
+ Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorForeground),
+ PorterDuff.Mode.SRC_IN)
+ }
+ multiUserAvatar.setImageDrawable(pictureToSet)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 1bd36644bbc5..1784f73e1f53 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -112,8 +112,19 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+ // Pass configuration change to non-attached pages as well. Some config changes will cause
+ // QS to recreate itself (as determined in FragmentHostManager), but in order to minimize
+ // those, make sure that all get passed to all pages.
+ int numPages = mPages.size();
+ for (int i = 0; i < numPages; i++) {
+ View page = mPages.get(i);
+ if (page.getParent() == null) {
+ page.dispatchConfigurationChanged(newConfig);
+ }
+ }
if (mLayoutOrientation != newConfig.orientation) {
mLayoutOrientation = newConfig.orientation;
+ mDistributeTiles = true;
setCurrentItem(0, false);
mPageToRestore = 0;
}
@@ -165,6 +176,11 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
updateListening();
}
+ @Override
+ public void setSquishinessFraction(float squishinessFraction) {
+ // No-op, paged layouts are not squishy.
+ }
+
private void updateListening() {
for (TileLayout tilePage : mPages) {
tilePage.setListening(tilePage.getParent() != null && mListening);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
index 87c64c78edc8..2f189beb7780 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
@@ -38,6 +38,7 @@ public class PseudoGridView extends ViewGroup {
private int mNumColumns = 3;
private int mVerticalSpacing;
private int mHorizontalSpacing;
+ private int mFixedChildWidth = -1;
public PseudoGridView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -53,6 +54,8 @@ public class PseudoGridView extends ViewGroup {
mVerticalSpacing = a.getDimensionPixelSize(attr, 0);
} else if (attr == R.styleable.PseudoGridView_horizontalSpacing) {
mHorizontalSpacing = a.getDimensionPixelSize(attr, 0);
+ } else if (attr == R.styleable.PseudoGridView_fixedChildWidth) {
+ mFixedChildWidth = a.getDimensionPixelSize(attr, -1);
}
}
@@ -65,8 +68,15 @@ public class PseudoGridView extends ViewGroup {
throw new UnsupportedOperationException("Needs a maximum width");
}
int width = MeasureSpec.getSize(widthMeasureSpec);
-
- int childWidth = (width - (mNumColumns - 1) * mHorizontalSpacing) / mNumColumns;
+ int childWidth;
+ int necessarySpaceForChildWidth =
+ mFixedChildWidth * mNumColumns + mHorizontalSpacing * (mNumColumns - 1);
+ if (mFixedChildWidth != -1 && necessarySpaceForChildWidth <= width) {
+ childWidth = mFixedChildWidth;
+ width = mFixedChildWidth * mNumColumns + mHorizontalSpacing * (mNumColumns - 1);
+ } else {
+ childWidth = (width - (mNumColumns - 1) * mHorizontalSpacing) / mNumColumns;
+ }
int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
int childHeightSpec = MeasureSpec.UNSPECIFIED;
int totalHeight = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 4fcd46c96fe3..44d5e21e6354 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -14,6 +14,9 @@
package com.android.systemui.qs;
+import static com.android.systemui.qs.dagger.QSFragmentModule.QQS_FOOTER;
+import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FOOTER;
+
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.util.Log;
@@ -32,7 +35,6 @@ import com.android.systemui.qs.TouchAnimator.Builder;
import com.android.systemui.qs.TouchAnimator.Listener;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.tileimpl.HeightOverrideable;
-import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.wm.shell.animation.Interpolators;
@@ -43,6 +45,7 @@ import java.util.List;
import java.util.concurrent.Executor;
import javax.inject.Inject;
+import javax.inject.Named;
/** */
@QSScope
@@ -60,20 +63,25 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
// Fade out faster than fade in to finish before QQS hides.
private static final long QQS_FADE_OUT_DURATION = 50L;
-
+ /**
+ * List of all views that will be reset when clearing animation state
+ * see {@link #clearAnimationState()} }
+ */
private final ArrayList<View> mAllViews = new ArrayList<>();
/**
* List of {@link View}s representing Quick Settings that are being animated from the quick QS
* position to the normal QS panel. These views will only show once the animation is complete,
* to prevent overlapping of semi transparent views
*/
- private final ArrayList<View> mQuickQsViews = new ArrayList<>();
+ private final ArrayList<View> mAnimatedQsViews = new ArrayList<>();
private final QuickQSPanel mQuickQsPanel;
private final QSPanelController mQsPanelController;
private final QuickQSPanelController mQuickQSPanelController;
private final QuickStatusBarHeader mQuickStatusBarHeader;
private final QSSecurityFooter mSecurityFooter;
private final QS mQs;
+ private final View mQSFooterActions;
+ private final View mQQSFooterActions;
private PagedTileLayout mPagedLayout;
@@ -88,6 +96,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
// This animates fading of SecurityFooter and media divider
private TouchAnimator mAllPagesDelayedAnimator;
private TouchAnimator mBrightnessAnimator;
+ private TouchAnimator mQQSFooterActionsAnimator;
private HeightExpansionAnimator mQQSTileHeightAnimator;
private HeightExpansionAnimator mOtherTilesExpandAnimator;
@@ -110,12 +119,16 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
QSPanelController qsPanelController,
QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService,
- QSExpansionPathInterpolator qsExpansionPathInterpolator) {
+ QSExpansionPathInterpolator qsExpansionPathInterpolator,
+ @Named(QS_FOOTER) FooterActionsView qsFooterActionsView,
+ @Named(QQS_FOOTER) FooterActionsView qqsFooterActionsView) {
mQs = qs;
mQuickQsPanel = quickPanel;
mQsPanelController = qsPanelController;
mQuickQSPanelController = quickQSPanelController;
mQuickStatusBarHeader = quickStatusBarHeader;
+ mQQSFooterActions = qqsFooterActionsView;
+ mQSFooterActions = qsFooterActionsView;
mSecurityFooter = securityFooter;
mHost = qsTileHost;
mExecutor = executor;
@@ -156,19 +169,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
}
}
- void startAlphaAnimation(boolean show) {
- if (show == mToShowing) {
- return;
- }
- mToShowing = show;
- if (show) {
- CrossFadeHelper.fadeIn(mQs.getView(), QQS_FADE_IN_DURATION, 0 /* delay */);
- } else {
- CrossFadeHelper.fadeOut(mQs.getView(), QQS_FADE_OUT_DURATION, 0 /* delay */,
- null /* endRunnable */);
- }
- }
-
/**
* Sets whether or not the keyguard is currently being shown with a collapsed header.
*/
@@ -262,7 +262,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
clearAnimationState();
mAllViews.clear();
- mQuickQsViews.clear();
+ mAnimatedQsViews.clear();
mQQSTileHeightAnimator = null;
mOtherTilesExpandAnimator = null;
@@ -316,8 +316,8 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
if (mQQSTileHeightAnimator == null) {
mQQSTileHeightAnimator = new HeightExpansionAnimator(this,
- quickTileView.getHeight(), tileView.getHeight());
- qqsTileHeight = quickTileView.getHeight();
+ quickTileView.getMeasuredHeight(), tileView.getMeasuredHeight());
+ qqsTileHeight = quickTileView.getMeasuredHeight();
}
mQQSTileHeightAnimator.addView(quickTileView);
@@ -360,7 +360,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
firstPageBuilder.addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 1);
- mQuickQsViews.add(tileView);
+ mAnimatedQsViews.add(tileView);
mAllViews.add(quickTileView);
mAllViews.add(quickTileView.getSecondaryLabel());
} else if (mFullRows && isIconInAnimatedRow(count)) {
@@ -380,12 +380,13 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
if (mOtherTilesExpandAnimator == null) {
mOtherTilesExpandAnimator =
new HeightExpansionAnimator(
- this, qqsTileHeight, tileView.getHeight());
+ this, qqsTileHeight, tileView.getMeasuredHeight());
}
mOtherTilesExpandAnimator.addView(tileView);
tileView.setClipChildren(true);
tileView.setClipToPadding(true);
firstPageBuilder.addFloat(tileView.getSecondaryLabel(), "alpha", 0, 1);
+ mAllViews.add(tileView.getSecondaryLabel());
}
mAllViews.add(tileView);
@@ -394,21 +395,8 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
}
if (mAllowFancy) {
- // Make brightness appear static position and alpha in through second half.
- View brightness = mQsPanelController.getBrightnessView();
- if (brightness != null) {
- firstPageBuilder.addFloat(brightness, "translationY",
- brightness.getMeasuredHeight() * 0.5f, 0);
- mBrightnessAnimator = new TouchAnimator.Builder()
- .addFloat(brightness, "alpha", 0, 1)
- .addFloat(brightness, "sliderScaleY", 0.3f, 1)
- .setInterpolator(Interpolators.ALPHA_IN)
- .setStartDelay(0.3f)
- .build();
- mAllViews.add(brightness);
- } else {
- mBrightnessAnimator = null;
- }
+ animateBrightnessSlider(firstPageBuilder);
+
mFirstPageAnimator = firstPageBuilder
.setListener(this)
.build();
@@ -417,6 +405,13 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
.addFloat(tileLayout, "alpha", 0, 1);
mFirstPageDelayedAnimator = builder.build();
+ if (mQQSFooterActions.getVisibility() != View.GONE) {
+ // only when qqs footer is present (which means split shade mode) it needs to
+ // be animated
+ updateQQSFooterAnimation();
+ }
+
+
// Fade in the security footer and the divider as we reach the final position
builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY);
builder.addFloat(mSecurityFooter.getView(), "alpha", 0, 1);
@@ -452,6 +447,53 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
.addFloat(tileLayout, "alpha", 0, 1).build();
}
+ private void animateBrightnessSlider(Builder firstPageBuilder) {
+ View qsBrightness = mQsPanelController.getBrightnessView();
+ View qqsBrightness = mQuickQSPanelController.getBrightnessView();
+ if (qqsBrightness != null && qqsBrightness.getVisibility() == View.VISIBLE) {
+ // animating in split shade mode
+ mAnimatedQsViews.add(qsBrightness);
+ mAllViews.add(qqsBrightness);
+ int translationY = getRelativeTranslationY(qsBrightness, qqsBrightness);
+ mBrightnessAnimator = new Builder()
+ // we need to animate qs brightness even if animation will not be visible,
+ // as we might start from sliderScaleY set to 0.3 if device was in collapsed QS
+ // portrait orientation before
+ .addFloat(qsBrightness, "sliderScaleY", 0.3f, 1)
+ .addFloat(qqsBrightness, "translationY", 0, translationY)
+ .build();
+ } else if (qsBrightness != null) {
+ firstPageBuilder.addFloat(qsBrightness, "translationY",
+ qsBrightness.getMeasuredHeight() * 0.5f, 0);
+ mBrightnessAnimator = new Builder()
+ .addFloat(qsBrightness, "alpha", 0, 1)
+ .addFloat(qsBrightness, "sliderScaleY", 0.3f, 1)
+ .setInterpolator(Interpolators.ALPHA_IN)
+ .setStartDelay(0.3f)
+ .build();
+ mAllViews.add(qsBrightness);
+ } else {
+ mBrightnessAnimator = null;
+ }
+ }
+
+ private void updateQQSFooterAnimation() {
+ int translationY = getRelativeTranslationY(mQSFooterActions, mQQSFooterActions);
+ mQQSFooterActionsAnimator = new TouchAnimator.Builder()
+ .addFloat(mQQSFooterActions, "translationY", 0, translationY)
+ .build();
+ mAnimatedQsViews.add(mQSFooterActions);
+ }
+
+ private int getRelativeTranslationY(View view1, View view2) {
+ int[] qsPosition = new int[2];
+ int[] qqsPosition = new int[2];
+ View commonView = mQs.getView();
+ getRelativePositionInt(qsPosition, view1, commonView);
+ getRelativePositionInt(qqsPosition, view2, commonView);
+ return (qsPosition[1] - qqsPosition[1]) - mQuickStatusBarHeader.getOffsetTranslation();
+ }
+
private boolean isIconInAnimatedRow(int count) {
if (mPagedLayout == null) {
return false;
@@ -521,6 +563,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
if (mBrightnessAnimator != null) {
mBrightnessAnimator.setPosition(position);
}
+ if (mQQSFooterActionsAnimator != null) {
+ mQQSFooterActionsAnimator.setPosition(position);
+ }
}
}
@@ -532,9 +577,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
@Override
public void onAnimationAtEnd() {
mQuickQsPanel.setVisibility(View.INVISIBLE);
- final int N = mQuickQsViews.size();
+ final int N = mAnimatedQsViews.size();
for (int i = 0; i < N; i++) {
- mQuickQsViews.get(i).setVisibility(View.VISIBLE);
+ mAnimatedQsViews.get(i).setVisibility(View.VISIBLE);
}
}
@@ -542,9 +587,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
public void onAnimationStarted() {
updateQQSVisibility();
if (mOnFirstPage) {
- final int N = mQuickQsViews.size();
+ final int N = mAnimatedQsViews.size();
for (int i = 0; i < N; i++) {
- mQuickQsViews.get(i).setVisibility(View.INVISIBLE);
+ mAnimatedQsViews.get(i).setVisibility(View.INVISIBLE);
}
}
}
@@ -569,9 +614,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
if (mOtherTilesExpandAnimator != null) {
mOtherTilesExpandAnimator.resetViewsHeights();
}
- final int N2 = mQuickQsViews.size();
+ final int N2 = mAnimatedQsViews.size();
for (int i = 0; i < N2; i++) {
- mQuickQsViews.get(i).setVisibility(View.VISIBLE);
+ mAnimatedQsViews.get(i).setVisibility(View.VISIBLE);
}
}
@@ -613,7 +658,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
mTranslateWhileExpanding = shouldTranslate;
}
- static class HeightExpansionAnimator {
+ private static class HeightExpansionAnimator {
private final List<View> mViews = new ArrayList<>();
private final ValueAnimator mAnimator;
private final TouchAnimator.Listener mListener;
@@ -628,9 +673,10 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
int height = (Integer) valueAnimator.getAnimatedValue();
for (int i = 0; i < viewCount; i++) {
View v = mViews.get(i);
- v.setBottom(v.getTop() + height);
if (v instanceof HeightOverrideable) {
((HeightOverrideable) v).setHeightOverride(height);
+ } else {
+ v.setBottom(v.getTop() + height);
}
}
if (t == 0f) {
@@ -668,9 +714,10 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
final int viewsCount = mViews.size();
for (int i = 0; i < viewsCount; i++) {
View v = mViews.get(i);
- v.setBottom(v.getTop() + v.getMeasuredHeight());
if (v instanceof HeightOverrideable) {
((HeightOverrideable) v).resetOverride();
+ } else {
+ v.setBottom(v.getTop() + v.getMeasuredHeight());
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 6f12e467291a..8588ddfcfa63 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -26,12 +26,13 @@ import android.graphics.Point;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.View;
-import android.view.WindowInsets;
import android.widget.FrameLayout;
+import android.widget.ImageView;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.util.Utils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -52,11 +53,11 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
private float mQsExpansion;
private QSCustomizer mQSCustomizer;
private NonInterceptingScrollView mQSPanelContainer;
+ private ImageView mDragHandle;
private int mSideMargins;
private boolean mQsDisabled;
private int mContentPadding = -1;
- private int mNavBarInset = 0;
private boolean mClippingEnabled;
public QSContainerImpl(Context context, AttributeSet attrs) {
@@ -70,6 +71,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
mQSDetail = findViewById(R.id.qs_detail);
mHeader = findViewById(R.id.header);
mQSCustomizer = findViewById(R.id.qs_customize);
+ mDragHandle = findViewById(R.id.qs_drag_handle);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
@@ -92,24 +94,13 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
}
@Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- mNavBarInset = insets.getInsets(WindowInsets.Type.navigationBars()).bottom;
- mQSPanelContainer.setPaddingRelative(
- mQSPanelContainer.getPaddingStart(),
- mQSPanelContainer.getPaddingTop(),
- mQSPanelContainer.getPaddingEnd(),
- mNavBarInset
- );
- return super.onApplyWindowInsets(insets);
- }
-
- @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// QSPanel will show as many rows as it can (up to TileLayout.MAX_ROWS) such that the
// bottom and footer are inside the screen.
MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams();
- int maxQs = getDisplayHeight() - layoutParams.topMargin - layoutParams.bottomMargin
+ int availableHeight = View.MeasureSpec.getSize(heightMeasureSpec);
+ int maxQs = availableHeight - layoutParams.topMargin - layoutParams.bottomMargin
- getPaddingBottom();
int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin
+ layoutParams.rightMargin;
@@ -119,11 +110,11 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST));
int width = mQSPanelContainer.getMeasuredWidth() + padding;
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(getDisplayHeight(), MeasureSpec.EXACTLY));
+ MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY));
// QSCustomizer will always be the height of the screen, but do this after
// other measuring to avoid changing the height of the QS.
mQSCustomizer.measure(widthMeasureSpec,
- MeasureSpec.makeMeasureSpec(getDisplayHeight(), MeasureSpec.EXACTLY));
+ MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY));
}
@Override
@@ -165,8 +156,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
QuickStatusBarHeaderController quickStatusBarHeaderController) {
mQSPanelContainer.setPaddingRelative(
getPaddingStart(),
- mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.quick_qs_offset_height),
+ Utils.getQsHeaderSystemIconsAreaHeight(mContext),
getPaddingEnd(),
getPaddingBottom()
);
@@ -200,13 +190,23 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
mQSDetail.setBottom(getTop() + scrollBottom);
int qsDetailBottomMargin = ((MarginLayoutParams) mQSDetail.getLayoutParams()).bottomMargin;
mQSDetail.setBottom(getTop() + scrollBottom - qsDetailBottomMargin);
+ // Pin the drag handle to the bottom of the panel.
+ mDragHandle.setTranslationY(scrollBottom - mDragHandle.getHeight());
}
protected int calculateContainerHeight() {
int heightOverride = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
+ // Need to add the dragHandle height so touches will be intercepted by it.
+ int dragHandleHeight;
+ if (mDragHandle.getVisibility() == VISIBLE) {
+ dragHandleHeight = Math.round((1 - mQsExpansion) * mDragHandle.getHeight());
+ } else {
+ dragHandleHeight = 0;
+ }
return mQSCustomizer.isCustomizing() ? mQSCustomizer.getHeight()
: Math.round(mQsExpansion * (heightOverride - mHeader.getHeight()))
- + mHeader.getHeight();
+ + mHeader.getHeight()
+ + dragHandleHeight;
}
int calculateContainerBottom() {
@@ -221,6 +221,8 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
public void setExpansion(float expansion) {
mQsExpansion = expansion;
mQSPanelContainer.setScrollingEnabled(expansion > 0f);
+ mDragHandle.setAlpha(1.0f - expansion);
+ mDragHandle.setClickable(expansion == 0f); // Only clickable when fully collapsed
updateExpansion();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 929927e5d4e4..d43404b8781c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -40,14 +40,15 @@ import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.Dependency;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.DetailAdapter;
+import com.android.systemui.plugins.qs.QSContainerController;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
public class QSDetail extends LinearLayout {
@@ -86,7 +87,7 @@ public class QSDetail extends LinearLayout {
private boolean mSwitchState;
private QSFooter mFooter;
- private NotificationsQuickSettingsContainer mContainer;
+ private QSContainerController mQsContainerController;
public QSDetail(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -120,8 +121,8 @@ public class QSDetail extends LinearLayout {
mClipper = new QSDetailClipper(this);
}
- public void setContainer(NotificationsQuickSettingsContainer container) {
- mContainer = container;
+ public void setContainerController(QSContainerController controller) {
+ mQsContainerController = controller;
}
/** */
@@ -164,8 +165,7 @@ public class QSDetail extends LinearLayout {
public void updateResources() {
updateDetailText();
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
- lp.topMargin = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.quick_qs_offset_height);
+ lp.topMargin = SystemBarUtils.getQuickQsOffsetHeight(mContext);
setLayoutParams(lp);
}
@@ -262,8 +262,8 @@ public class QSDetail extends LinearLayout {
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
animateDetailVisibleDiff(x, y, visibleDiff, listener);
- if (mContainer != null) {
- mContainer.setDetailShowing(showingDetail);
+ if (mQsContainerController != null) {
+ mQsContainerController.setDetailShowing(showingDetail);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index e38bd4bd9a38..0e0681b94c62 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -35,11 +35,6 @@ public interface QSFooter {
void setExpanded(boolean expanded);
/**
- * Returns the full height of the footer.
- */
- int getHeight();
-
- /**
* Sets the percentage amount that the quick settings has been expanded.
*
* @param expansion A value from 1 to 0 that indicates how much the quick settings have been
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
index 57438d189b22..4d23958d56ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
@@ -21,60 +21,40 @@ import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
import android.content.Context;
import android.content.res.Configuration;
import android.database.ContentObserver;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.RippleDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
-import android.os.UserManager;
import android.provider.Settings;
import android.util.AttributeSet;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
-import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.settingslib.Utils;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
-import com.android.settingslib.drawable.UserIconDrawable;
import com.android.systemui.R;
-import com.android.systemui.qs.TouchAnimator.Builder;
-import com.android.systemui.statusbar.phone.MultiUserSwitch;
-import com.android.systemui.statusbar.phone.SettingsButton;
-/** */
+/**
+ * Footer of expanded Quick Settings, tiles page indicator, (optionally) build number and
+ * {@link FooterActionsView}
+ */
public class QSFooterView extends FrameLayout {
- private SettingsButton mSettingsButton;
- protected View mSettingsContainer;
private PageIndicator mPageIndicator;
private TextView mBuildText;
- private boolean mShouldShowBuildText;
+ private View mActionsContainer;
- private boolean mQsDisabled;
+ protected TouchAnimator mFooterAnimator;
+ private boolean mQsDisabled;
private boolean mExpanded;
-
- private boolean mListening;
-
- protected MultiUserSwitch mMultiUserSwitch;
- private ImageView mMultiUserAvatar;
-
- protected TouchAnimator mFooterAnimator;
private float mExpansionAmount;
- protected View mEdit;
- private TouchAnimator mSettingsCogAnimator;
-
- private View mActionsContainer;
- private View mTunerIcon;
- private int mTunerIconTranslation;
+ private boolean mShouldShowBuildText;
private OnClickListener mExpandClickListener;
@@ -94,27 +74,11 @@ public class QSFooterView extends FrameLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mEdit = requireViewById(android.R.id.edit);
-
mPageIndicator = findViewById(R.id.footer_page_indicator);
-
- mSettingsButton = findViewById(R.id.settings_button);
- mSettingsContainer = findViewById(R.id.settings_button_container);
-
- mMultiUserSwitch = findViewById(R.id.multi_user_switch);
- mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
-
- mActionsContainer = requireViewById(R.id.qs_footer_actions_container);
+ mActionsContainer = requireViewById(R.id.qs_footer_actions);
mBuildText = findViewById(R.id.build);
- mTunerIcon = requireViewById(R.id.tuner_icon);
- // RenderThread is doing more harm than good when touching the header (to expand quick
- // settings), so disable it for this view
- if (mSettingsButton.getBackground() instanceof RippleDrawable) {
- ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true);
- }
updateResources();
-
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
setBuildText();
}
@@ -137,18 +101,7 @@ public class QSFooterView extends FrameLayout {
}
}
- void updateAnimator(int width, int numTiles) {
- int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size)
- - mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_padding);
- int remaining = (width - numTiles * size) / (numTiles - 1);
- int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space);
-
- mSettingsCogAnimator = new Builder()
- .addFloat(mSettingsButton, "translationX",
- isLayoutRtl() ? (remaining - defSpace) : -(remaining - defSpace), 0)
- .addFloat(mSettingsButton, "rotation", -120, 0)
- .build();
-
+ void updateExpansion() {
setExpansion(mExpansionAmount);
}
@@ -158,20 +111,11 @@ public class QSFooterView extends FrameLayout {
updateResources();
}
- @Override
- public void onRtlPropertiesChanged(int layoutDirection) {
- super.onRtlPropertiesChanged(layoutDirection);
- updateResources();
- }
-
private void updateResources() {
updateFooterAnimator();
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
lp.bottomMargin = getResources().getDimensionPixelSize(R.dimen.qs_footers_margin_bottom);
setLayoutParams(lp);
- mTunerIconTranslation = mContext.getResources()
- .getDimensionPixelOffset(R.dimen.qs_footer_tuner_icon_translation);
- mTunerIcon.setTranslationX(isLayoutRtl() ? -mTunerIconTranslation : mTunerIconTranslation);
}
private void updateFooterAnimator() {
@@ -197,17 +141,15 @@ public class QSFooterView extends FrameLayout {
mExpandClickListener = onClickListener;
}
- void setExpanded(boolean expanded, boolean isTunerEnabled, boolean multiUserEnabled) {
+ void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
- updateEverything(isTunerEnabled, multiUserEnabled);
+ updateEverything();
}
/** */
public void setExpansion(float headerExpansionFraction) {
mExpansionAmount = headerExpansionFraction;
- if (mSettingsCogAnimator != null) mSettingsCogAnimator.setPosition(headerExpansionFraction);
-
if (mFooterAnimator != null) {
mFooterAnimator.setPosition(headerExpansionFraction);
}
@@ -228,14 +170,6 @@ public class QSFooterView extends FrameLayout {
super.onDetachedFromWindow();
}
- /** */
- public void setListening(boolean listening) {
- if (listening == mListening) {
- return;
- }
- mListening = listening;
- }
-
@Override
public boolean performAccessibilityAction(int action, Bundle arguments) {
if (action == AccessibilityNodeInfo.ACTION_EXPAND) {
@@ -253,50 +187,26 @@ public class QSFooterView extends FrameLayout {
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
}
- void disable(int state2, boolean isTunerEnabled, boolean multiUserEnabled) {
+ void disable(int state2) {
final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
if (disabled == mQsDisabled) return;
mQsDisabled = disabled;
- updateEverything(isTunerEnabled, multiUserEnabled);
+ updateEverything();
}
- void updateEverything(boolean isTunerEnabled, boolean multiUserEnabled) {
+ void updateEverything() {
post(() -> {
- updateVisibilities(isTunerEnabled, multiUserEnabled);
+ updateVisibilities();
updateClickabilities();
setClickable(false);
});
}
private void updateClickabilities() {
- mMultiUserSwitch.setClickable(mMultiUserSwitch.getVisibility() == View.VISIBLE);
- mEdit.setClickable(mEdit.getVisibility() == View.VISIBLE);
- mSettingsButton.setClickable(mSettingsButton.getVisibility() == View.VISIBLE);
mBuildText.setLongClickable(mBuildText.getVisibility() == View.VISIBLE);
}
- private void updateVisibilities(boolean isTunerEnabled, boolean multiUserEnabled) {
- mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
- mTunerIcon.setVisibility(isTunerEnabled ? View.VISIBLE : View.INVISIBLE);
- final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
- mMultiUserSwitch.setVisibility(
- showUserSwitcher(multiUserEnabled) ? View.VISIBLE : View.GONE);
- mSettingsButton.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
-
+ private void updateVisibilities() {
mBuildText.setVisibility(mExpanded && mShouldShowBuildText ? View.VISIBLE : View.INVISIBLE);
}
-
- private boolean showUserSwitcher(boolean multiUserEnabled) {
- return mExpanded && multiUserEnabled;
- }
-
- void onUserInfoChanged(Drawable picture, boolean isGuestUser) {
- if (picture != null && isGuestUser && !(picture instanceof UserIconDrawable)) {
- picture = picture.getConstantState().newDrawable(getResources()).mutate();
- picture.setColorFilter(
- Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorForeground),
- Mode.SRC_IN);
- }
- mMultiUserAvatar.setImageDrawable(picture);
- }
-}
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index 929aedae6706..e7c06e3c7ede 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -16,35 +16,18 @@
package com.android.systemui.qs;
-import static com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED;
+import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FOOTER;
import android.content.ClipData;
import android.content.ClipboardManager;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.os.UserManager;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.nano.MetricsProto;
-import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.globalactions.GlobalActionsDialogLite;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.MultiUserSwitchController;
-import com.android.systemui.statusbar.phone.SettingsButton;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
@@ -56,137 +39,45 @@ import javax.inject.Named;
@QSScope
public class QSFooterViewController extends ViewController<QSFooterView> implements QSFooter {
- private final UserManager mUserManager;
- private final UserInfoController mUserInfoController;
- private final ActivityStarter mActivityStarter;
- private final DeviceProvisionedController mDeviceProvisionedController;
private final UserTracker mUserTracker;
private final QSPanelController mQsPanelController;
private final QuickQSPanelController mQuickQSPanelController;
- private final TunerService mTunerService;
- private final MetricsLogger mMetricsLogger;
- private final FalsingManager mFalsingManager;
- private final MultiUserSwitchController mMultiUserSwitchController;
- private final SettingsButton mSettingsButton;
- private final View mSettingsButtonContainer;
+ private final FooterActionsController mFooterActionsController;
private final TextView mBuildText;
- private final View mEdit;
private final PageIndicator mPageIndicator;
- private final View mPowerMenuLite;
- private final boolean mShowPMLiteButton;
- private final GlobalActionsDialogLite mGlobalActionsDialog;
- private final UiEventLogger mUiEventLogger;
-
- private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener =
- new UserInfoController.OnUserInfoChangedListener() {
- @Override
- public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
- boolean isGuestUser = mUserManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser());
- mView.onUserInfoChanged(picture, isGuestUser);
- }
- };
-
- private final View.OnClickListener mSettingsOnClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Don't do anything until views are unhidden. Don't do anything if the tap looks
- // suspicious.
- if (!mExpanded || mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- return;
- }
-
- if (v == mSettingsButton) {
- if (!mDeviceProvisionedController.isCurrentUserSetup()) {
- // If user isn't setup just unlock the device and dump them back at SUW.
- mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
- });
- return;
- }
- mMetricsLogger.action(
- mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
- : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
- if (mSettingsButton.isTunerClick()) {
- mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
- if (isTunerEnabled()) {
- mTunerService.showResetRequest(
- () -> {
- // Relaunch settings so that the tuner disappears.
- startSettingsActivity();
- });
- } else {
- Toast.makeText(getContext(), R.string.tuner_toast,
- Toast.LENGTH_LONG).show();
- mTunerService.setTunerEnabled(true);
- }
- startSettingsActivity();
-
- });
- } else {
- startSettingsActivity();
- }
- } else if (v == mPowerMenuLite) {
- mUiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS);
- mGlobalActionsDialog.showOrHideDialog(false, true);
- }
- }
- };
-
- private boolean mListening;
- private boolean mExpanded;
@Inject
- QSFooterViewController(QSFooterView view, UserManager userManager,
- UserInfoController userInfoController, ActivityStarter activityStarter,
- DeviceProvisionedController deviceProvisionedController, UserTracker userTracker,
+ QSFooterViewController(QSFooterView view,
+ UserTracker userTracker,
QSPanelController qsPanelController,
- MultiUserSwitchController multiUserSwitchController,
QuickQSPanelController quickQSPanelController,
- TunerService tunerService, MetricsLogger metricsLogger, FalsingManager falsingManager,
- @Named(PM_LITE_ENABLED) boolean showPMLiteButton,
- GlobalActionsDialogLite globalActionsDialog, UiEventLogger uiEventLogger) {
+ @Named(QS_FOOTER) FooterActionsController footerActionsController) {
super(view);
- mUserManager = userManager;
- mUserInfoController = userInfoController;
- mActivityStarter = activityStarter;
- mDeviceProvisionedController = deviceProvisionedController;
mUserTracker = userTracker;
mQsPanelController = qsPanelController;
mQuickQSPanelController = quickQSPanelController;
- mTunerService = tunerService;
- mMetricsLogger = metricsLogger;
- mFalsingManager = falsingManager;
- mMultiUserSwitchController = multiUserSwitchController;
+ mFooterActionsController = footerActionsController;
- mSettingsButton = mView.findViewById(R.id.settings_button);
- mSettingsButtonContainer = mView.findViewById(R.id.settings_button_container);
mBuildText = mView.findViewById(R.id.build);
- mEdit = mView.findViewById(android.R.id.edit);
mPageIndicator = mView.findViewById(R.id.footer_page_indicator);
- mPowerMenuLite = mView.findViewById(R.id.pm_lite);
- mShowPMLiteButton = showPMLiteButton;
- mGlobalActionsDialog = globalActionsDialog;
- mUiEventLogger = uiEventLogger;
}
@Override
protected void onInit() {
super.onInit();
- mMultiUserSwitchController.init();
+ mFooterActionsController.init();
}
@Override
protected void onViewAttached() {
- if (mShowPMLiteButton) {
- mPowerMenuLite.setVisibility(View.VISIBLE);
- mPowerMenuLite.setOnClickListener(mSettingsOnClickListener);
- } else {
- mPowerMenuLite.setVisibility(View.GONE);
- }
mView.addOnLayoutChangeListener(
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
- mView.updateAnimator(
- right - left, mQuickQSPanelController.getNumQuickTiles()));
- mSettingsButton.setOnClickListener(mSettingsOnClickListener);
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ mView.updateExpansion();
+ mFooterActionsController.updateAnimator(right - left,
+ mQuickQSPanelController.getNumQuickTiles());
+ }
+ );
+
mBuildText.setOnLongClickListener(view -> {
CharSequence buildText = mBuildText.getText();
if (!TextUtils.isEmpty(buildText)) {
@@ -200,17 +91,8 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme
}
return false;
});
-
- mEdit.setOnClickListener(view -> {
- if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- return;
- }
- mActivityStarter.postQSRunnableDismissingKeyguard(() ->
- mQsPanelController.showEdit(view));
- });
-
mQsPanelController.setFooterPageIndicator(mPageIndicator);
- mView.updateEverything(isTunerEnabled(), mMultiUserSwitchController.isMultiUserEnabled());
+ mView.updateEverything();
}
@Override
@@ -225,38 +107,25 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme
@Override
public void setExpanded(boolean expanded) {
- mExpanded = expanded;
- mView.setExpanded(
- expanded, isTunerEnabled(), mMultiUserSwitchController.isMultiUserEnabled());
- }
-
- @Override
- public int getHeight() {
- return mView.getHeight();
+ mFooterActionsController.setExpanded(expanded);
+ mView.setExpanded(expanded);
}
@Override
public void setExpansion(float expansion) {
mView.setExpansion(expansion);
+ mFooterActionsController.setExpansion(expansion);
}
@Override
public void setListening(boolean listening) {
- if (mListening == listening) {
- return;
- }
-
- mListening = listening;
- if (mListening) {
- mUserInfoController.addCallback(mOnUserInfoChangedListener);
- } else {
- mUserInfoController.removeCallback(mOnUserInfoChangedListener);
- }
+ mFooterActionsController.setListening(listening);
}
@Override
public void setKeyguardShowing(boolean keyguardShowing) {
mView.setKeyguardShowing();
+ mFooterActionsController.setKeyguardShowing();
}
/** */
@@ -267,19 +136,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme
@Override
public void disable(int state1, int state2, boolean animate) {
- mView.disable(state2, isTunerEnabled(), mMultiUserSwitchController.isMultiUserEnabled());
- }
-
- private void startSettingsActivity() {
- ActivityLaunchAnimator.Controller animationController =
- mSettingsButtonContainer != null ? ActivityLaunchAnimator.Controller.fromView(
- mSettingsButtonContainer,
- InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON) : null;
- mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
- true /* dismissShade */, animationController);
- }
-
- private boolean isTunerEnabled() {
- return mTunerService.isTunerEnabled();
+ mView.disable(state2);
+ mFooterActionsController.disable(state2);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 1c841eca65f3..eeca239b3caa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -32,28 +32,28 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout.LayoutParams;
+import android.widget.ImageView;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.plugins.qs.QSContainerController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
+import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.util.InjectionInflationController;
import com.android.systemui.util.LifecycleFragment;
import com.android.systemui.util.Utils;
@@ -81,6 +81,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private QSAnimator mQSAnimator;
private HeightListener mPanelView;
+ private QSSquishinessController mQSSquishinessController;
protected QuickStatusBarHeader mHeader;
protected NonInterceptingScrollView mQSPanelScrollView;
private QSDetail mQSDetail;
@@ -89,10 +90,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private int mLayoutDirection;
private QSFooter mFooter;
private float mLastQSExpansion = -1;
+ private float mLastPanelFraction;
+ private float mSquishinessFraction = 1;
private boolean mQsDisabled;
+ private ImageView mQsDragHandler;
private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
- private final InjectionInflationController mInjectionInflater;
private final CommandQueue mCommandQueue;
private final QSDetailDisplayer mQsDetailDisplayer;
private final MediaHost mQsMediaHost;
@@ -115,12 +118,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private QuickQSPanelController mQuickQSPanelController;
private QSCustomizerController mQSCustomizerController;
private ScrollListener mScrollListener;
- private FeatureFlags mFeatureFlags;
/**
* When true, QS will translate from outside the screen. It will be clipped with parallax
* otherwise.
*/
- private boolean mTranslateWhileExpanding;
+ private boolean mInSplitShade;
private boolean mPulseExpanding;
/**
@@ -135,17 +137,22 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private DumpManager mDumpManager;
+ /**
+ * Progress of pull down from the center of the lock screen.
+ * @see com.android.systemui.statusbar.LockscreenShadeTransitionController
+ */
+ private float mFullShadeProgress;
+
@Inject
public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
- InjectionInflationController injectionInflater, QSTileHost qsTileHost,
+ QSTileHost qsTileHost,
StatusBarStateController statusBarStateController, CommandQueue commandQueue,
QSDetailDisplayer qsDetailDisplayer, @Named(QS_PANEL) MediaHost qsMediaHost,
@Named(QUICK_QS_PANEL) MediaHost qqsMediaHost,
KeyguardBypassController keyguardBypassController,
- QSFragmentComponent.Factory qsComponentFactory, FeatureFlags featureFlags,
+ QSFragmentComponent.Factory qsComponentFactory,
FalsingManager falsingManager, DumpManager dumpManager) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
- mInjectionInflater = injectionInflater;
mCommandQueue = commandQueue;
mQsDetailDisplayer = qsDetailDisplayer;
mQsMediaHost = qsMediaHost;
@@ -153,7 +160,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mQsComponentFactory = qsComponentFactory;
commandQueue.observe(getLifecycle(), this);
mHost = qsTileHost;
- mFeatureFlags = featureFlags;
mFalsingManager = falsingManager;
mBypassController = keyguardBypassController;
mStatusBarStateController = statusBarStateController;
@@ -163,9 +169,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
- inflater = mInjectionInflater.injectable(
- inflater.cloneInContext(new ContextThemeWrapper(getContext(),
- R.style.Theme_SystemUI_QuickSettings)));
+ inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(),
+ R.style.Theme_SystemUI_QuickSettings));
return inflater.inflate(R.layout.qs_panel, container, false);
}
@@ -196,6 +201,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mHeader = view.findViewById(R.id.header);
mQSPanelController.setHeaderContainer(view.findViewById(R.id.header_text_container));
mFooter = qsFragmentComponent.getQSFooter();
+ mQsDragHandler = view.findViewById(R.id.qs_drag_handle);
mQsDetailDisplayer.setQsPanelController(mQSPanelController);
@@ -206,6 +212,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mQSDetail.setQsPanel(mQSPanelController, mHeader, mFooter, mFalsingManager);
mQSAnimator = qsFragmentComponent.getQSAnimator();
+ mQSSquishinessController = qsFragmentComponent.getQSSquishinessController();
mQSCustomizerController = qsFragmentComponent.getQSCustomizerController();
mQSCustomizerController.init();
@@ -226,7 +233,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
boolean sizeChanged = (oldTop - oldBottom) != (top - bottom);
if (sizeChanged) {
- setQsExpansion(mLastQSExpansion, mLastHeaderTranslation);
+ setQsExpansion(mLastQSExpansion, mLastPanelFraction,
+ mLastHeaderTranslation, mSquishinessFraction);
}
});
mQSPanelController.setUsingHorizontalLayoutChangeListener(
@@ -237,6 +245,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mQSPanelController.getMediaHost().getHostView().setAlpha(1.0f);
mQSAnimator.requestAnimatorUpdate();
});
+
+ mQsDragHandler.setOnClickListener(v -> {
+ Log.d(TAG, "drag handler clicked");
+ mCommandQueue.animateExpandSettingsPanel(null);
+ });
}
@Override
@@ -302,6 +315,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mQSAnimator.onRtlChanged();
}
}
+ updateQsState();
}
@Override
@@ -330,11 +344,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
}
@Override
- public void setContainer(ViewGroup container) {
- if (container instanceof NotificationsQuickSettingsContainer) {
- mQSCustomizerController.setContainer((NotificationsQuickSettingsContainer) container);
- mQSDetail.setContainer((NotificationsQuickSettingsContainer) container);
- }
+ public void setContainerController(QSContainerController controller) {
+ mQSCustomizerController.setContainerController(controller);
+ mQSDetail.setContainerController(controller);
}
@Override
@@ -374,15 +386,19 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
: View.INVISIBLE);
mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
|| (mQsExpanded && !mStackScrollerOverscrolling), mQuickQSPanelController);
- mFooter.setVisibility(
- !mQsDisabled && (mQsExpanded || !keyguardShowing || mHeaderAnimating
- || mShowCollapsedOnKeyguard)
+ mFooter.setVisibility(!mQsDisabled && (mQsExpanded || !keyguardShowing || mHeaderAnimating
+ || mShowCollapsedOnKeyguard)
? View.VISIBLE
: View.INVISIBLE);
mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
|| (mQsExpanded && !mStackScrollerOverscrolling));
mQSPanelController.setVisibility(
!mQsDisabled && expandVisually ? View.VISIBLE : View.INVISIBLE);
+ mQsDragHandler.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating
+ || mShowCollapsedOnKeyguard)
+ && Utils.shouldUseSplitNotificationShade(getResources())
+ ? View.VISIBLE
+ : View.GONE);
}
private boolean isKeyguardState() {
@@ -400,7 +416,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mQSAnimator.setShowCollapsedOnKeyguard(showCollapsed);
}
if (!showCollapsed && isKeyguardState()) {
- setQsExpansion(mLastQSExpansion, 0);
+ setQsExpansion(mLastQSExpansion, mLastPanelFraction, 0,
+ mSquishinessFraction);
}
}
}
@@ -409,6 +426,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
return mQSPanelController;
}
+ public void setBrightnessMirrorController(
+ BrightnessMirrorController brightnessMirrorController) {
+ mQSPanelController.setBrightnessMirror(brightnessMirrorController);
+ mQuickQSPanelController.setBrightnessMirror(brightnessMirrorController);
+ }
+
@Override
public boolean isShowingDetail() {
return mQSCustomizerController.isCustomizing() || mQSDetail.isShowingDetail();
@@ -462,33 +485,31 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
}
@Override
- public void setTranslateWhileExpanding(boolean shouldTranslate) {
- mTranslateWhileExpanding = shouldTranslate;
- mQSAnimator.setTranslateWhileExpanding(shouldTranslate);
+ public void setInSplitShade(boolean inSplitShade) {
+ mInSplitShade = inSplitShade;
+ mQSAnimator.setTranslateWhileExpanding(inSplitShade);
}
@Override
- public void setTransitionToFullShadeAmount(float pxAmount, boolean animated) {
+ public void setTransitionToFullShadeAmount(float pxAmount, float progress) {
boolean isTransitioningToFullShade = pxAmount > 0;
if (isTransitioningToFullShade != mTransitioningToFullShade) {
mTransitioningToFullShade = isTransitioningToFullShade;
updateShowCollapsedOnKeyguard();
- setQsExpansion(mLastQSExpansion, mLastHeaderTranslation);
}
+ mFullShadeProgress = progress;
+ setQsExpansion(mLastQSExpansion, mLastPanelFraction, mLastHeaderTranslation,
+ isTransitioningToFullShade ? progress : mSquishinessFraction);
}
@Override
- public void setQsExpansion(float expansion, float proposedTranslation) {
- if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + proposedTranslation);
+ public void setQsExpansion(float expansion, float panelExpansionFraction,
+ float proposedTranslation, float squishinessFraction) {
float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation;
- if (mQSAnimator != null) {
- final boolean showQSOnLockscreen = expansion > 0;
- final boolean showQSUnlocked = headerTranslation == 0 || !mTranslateWhileExpanding;
- mQSAnimator.startAlphaAnimation(showQSOnLockscreen || showQSUnlocked
- || mTransitioningToFullShade);
- }
+ float progress = mTransitioningToFullShade ? mFullShadeProgress : panelExpansionFraction;
+ setAlphaAnimationProgress(mInSplitShade ? progress : 1);
mContainer.setExpansion(expansion);
- final float translationScaleY = (mTranslateWhileExpanding
+ final float translationScaleY = (mInSplitShade
? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1);
boolean onKeyguardAndExpanded = isKeyguardState() && !mShowCollapsedOnKeyguard;
if (!mHeaderAnimating && !headerWillBeAnimating()) {
@@ -501,10 +522,13 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
if (expansion == mLastQSExpansion
&& mLastKeyguardAndExpanded == onKeyguardAndExpanded
&& mLastViewHeight == currentHeight
- && mLastHeaderTranslation == headerTranslation) {
+ && mLastHeaderTranslation == headerTranslation
+ && mSquishinessFraction == squishinessFraction) {
return;
}
mLastHeaderTranslation = headerTranslation;
+ mLastPanelFraction = panelExpansionFraction;
+ mSquishinessFraction = squishinessFraction;
mLastQSExpansion = expansion;
mLastKeyguardAndExpanded = onKeyguardAndExpanded;
mLastViewHeight = currentHeight;
@@ -540,12 +564,26 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
}
updateQsBounds();
+ if (mQSSquishinessController != null) {
+ mQSSquishinessController.setSquishiness(mSquishinessFraction);
+ }
if (mQSAnimator != null) {
mQSAnimator.setPosition(expansion);
}
updateMediaPositions();
}
+ private void setAlphaAnimationProgress(float progress) {
+ final View view = getView();
+ if (progress == 0 && view.getVisibility() != View.INVISIBLE) {
+ view.setVisibility(View.INVISIBLE);
+ } else if (progress > 0 && view.getVisibility() != View.VISIBLE) {
+ view.setVisibility((View.VISIBLE));
+ }
+ float alpha = ShadeInterpolation.getContentAlpha(progress);
+ view.setAlpha(alpha);
+ }
+
private void updateQsBounds() {
if (mLastQSExpansion == 1.0f) {
// Fully expanded, let's set the layout bounds as clip bounds. This is necessary because
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index cde80e66ba26..71eb4a2e6cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -41,11 +41,10 @@ import com.android.internal.widget.RemeasuringLinearLayout;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.animation.UniqueObjectHostView;
import java.util.ArrayList;
import java.util.List;
@@ -70,7 +69,7 @@ public class QSPanel extends LinearLayout implements Tunable {
@Nullable
protected View mBrightnessView;
@Nullable
- protected BrightnessSlider mToggleSliderController;
+ protected BrightnessSliderController mToggleSliderController;
private final H mHandler = new H();
/** Whether or not the QS media player feature is enabled. */
@@ -322,7 +321,6 @@ public class QSPanel extends LinearLayout implements Tunable {
super.onConfigurationChanged(newConfig);
mOnConfigurationChangedListeners.forEach(
listener -> listener.onConfigurationChange(newConfig));
- switchSecurityFooter();
}
@Override
@@ -372,30 +370,21 @@ public class QSPanel extends LinearLayout implements Tunable {
switchToParent(mFooter, parent, index);
index++;
}
-
- // The security footer is switched on orientation changes
}
- private void switchSecurityFooter() {
- if (mSecurityFooter != null) {
- if (mContext.getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE && mHeaderContainer != null) {
- // Adding the security view to the header, that enables us to avoid scrolling
- switchToParent(mSecurityFooter, mHeaderContainer, 0);
- } else {
- // Where should this go? If there's media, right before it. Otherwise, at the end.
- View mediaView = findViewByPredicate(v -> v instanceof UniqueObjectHostView);
- int index = -1;
- if (mediaView != null) {
- index = indexOfChild(mediaView);
- }
- if (mSecurityFooter.getParent() == this && indexOfChild(mSecurityFooter) < index) {
- // When we remove the securityFooter to rearrange, the index of media will go
- // down by one, so we correct it
- index--;
- }
- switchToParent(mSecurityFooter, this, index);
- }
+ /** Switch the security footer between top and bottom of QS depending on orientation. */
+ public void switchSecurityFooter(boolean shouldUseSplitNotificationShade) {
+ if (mSecurityFooter == null) return;
+
+ if (!shouldUseSplitNotificationShade
+ && mContext.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE && mHeaderContainer != null) {
+ // Adding the security view to the header, that enables us to avoid scrolling
+ switchToParent(mSecurityFooter, mHeaderContainer, 0);
+ } else {
+ // Add after the footer
+ int index = indexOfChild(mFooter);
+ switchToParent(mSecurityFooter, this, index + 1);
}
}
@@ -666,9 +655,14 @@ public class QSPanel extends LinearLayout implements Tunable {
return mListening;
}
- public void setSecurityFooter(View view) {
+ /**
+ * Set the security footer view and switch it into the right place
+ * @param view the view in question
+ * @param shouldUseSplitNotificationShade if QS is in split shade mode
+ */
+ public void setSecurityFooter(View view, boolean shouldUseSplitNotificationShade) {
mSecurityFooter = view;
- switchSecurityFooter();
+ switchSecurityFooter(shouldUseSplitNotificationShade);
}
protected void setPageMargin(int pageMargin) {
@@ -742,6 +736,11 @@ public class QSPanel extends LinearLayout implements Tunable {
void setListening(boolean listening, UiEventLogger uiEventLogger);
/**
+ * Sets a size modifier for the tile. Where 0 means collapsed, and 1 expanded.
+ */
+ void setSquishinessFraction(float squishinessFraction);
+
+ /**
* Sets the minimum number of rows to show
*
* @param minRows the minimum.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index ae0f5104d20f..6794d5b0cee4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -40,8 +40,9 @@ import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.brightness.BrightnessController;
-import com.android.systemui.settings.brightness.BrightnessSlider;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
+import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.tuner.TunerService;
@@ -60,11 +61,11 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
private final QSCustomizerController mQsCustomizerController;
private final QSTileRevealController.Factory mQsTileRevealControllerFactory;
private final FalsingManager mFalsingManager;
+ private final CommandQueue mCommandQueue;
private final BrightnessController mBrightnessController;
- private final BrightnessSlider.Factory mBrightnessSliderFactory;
- private final BrightnessSlider mBrightnessSlider;
+ private final BrightnessSliderController mBrightnessSliderController;
+ private final BrightnessMirrorHandler mBrightnessMirrorHandler;
- private BrightnessMirrorController mBrightnessMirrorController;
private boolean mGridContentVisible = true;
private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
@@ -76,13 +77,10 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
if (mView.isListening()) {
refreshAllTiles();
}
- updateBrightnessMirror();
+ mView.switchSecurityFooter(mShouldUseSplitNotificationShade);
}
};
- private final BrightnessMirrorController.BrightnessMirrorListener mBrightnessMirrorListener =
- mirror -> updateBrightnessMirror();
-
private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
@@ -101,22 +99,23 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
QSTileRevealController.Factory qsTileRevealControllerFactory,
DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
- BrightnessSlider.Factory brightnessSliderFactory, FalsingManager falsingManager,
- FeatureFlags featureFlags) {
+ BrightnessSliderController.Factory brightnessSliderFactory,
+ FalsingManager falsingManager, CommandQueue commandQueue) {
super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
- metricsLogger, uiEventLogger, qsLogger, dumpManager, featureFlags);
+ metricsLogger, uiEventLogger, qsLogger, dumpManager);
mQsSecurityFooter = qsSecurityFooter;
mTunerService = tunerService;
mQsCustomizerController = qsCustomizerController;
mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
mFalsingManager = falsingManager;
+ mCommandQueue = commandQueue;
mQsSecurityFooter.setHostEnvironment(qstileHost);
- mBrightnessSliderFactory = brightnessSliderFactory;
- mBrightnessSlider = mBrightnessSliderFactory.create(getContext(), mView);
- mView.setBrightnessView(mBrightnessSlider.getRootView());
+ mBrightnessSliderController = brightnessSliderFactory.create(getContext(), mView);
+ mView.setBrightnessView(mBrightnessSliderController.getRootView());
- mBrightnessController = brightnessControllerFactory.create(mBrightnessSlider);
+ mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
+ mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
}
@Override
@@ -126,7 +125,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
mMediaHost.setShowsOnlyActiveMedia(false);
mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
mQsCustomizerController.init();
- mBrightnessSlider.init();
+ mBrightnessSliderController.init();
}
@Override
@@ -141,11 +140,9 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
refreshAllTiles();
}
mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
- mView.setSecurityFooter(mQsSecurityFooter.getView());
+ mView.setSecurityFooter(mQsSecurityFooter.getView(), mShouldUseSplitNotificationShade);
switchTileLayout(true);
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.addCallback(mBrightnessMirrorListener);
- }
+ mBrightnessMirrorHandler.onQsPanelAttached();
((PagedTileLayout) mView.getOrCreateTileLayout())
.setOnTouchListener(mTileLayoutTouchListener);
@@ -161,9 +158,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
protected void onViewDetached() {
mTunerService.removeTunable(mView);
mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.removeCallback(mBrightnessMirrorListener);
- }
+ mBrightnessMirrorHandler.onQsPanelDettached();
super.onViewDetached();
}
@@ -197,23 +192,8 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
}
}
- /** */
public void setBrightnessMirror(BrightnessMirrorController brightnessMirrorController) {
- mBrightnessMirrorController = brightnessMirrorController;
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.removeCallback(mBrightnessMirrorListener);
- }
- mBrightnessMirrorController = brightnessMirrorController;
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.addCallback(mBrightnessMirrorListener);
- }
- updateBrightnessMirror();
- }
-
- private void updateBrightnessMirror() {
- if (mBrightnessMirrorController != null) {
- mBrightnessSlider.setMirrorControllerAndMirror(mBrightnessMirrorController);
- }
+ mBrightnessMirrorHandler.setController(brightnessMirrorController);
}
/** Get the QSTileHost this panel uses. */
@@ -278,10 +258,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
return mView.isLayoutRtl();
}
- public View getBrightnessView() {
- return mView.getBrightnessView();
- }
-
/** */
public void setPageListener(PagedTileLayout.PageListener listener) {
mView.setPageListener(listener);
@@ -298,6 +274,14 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
/** */
public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) {
+ // TODO(b/199296365)
+ // Workaround for opening detail from QQS, when there might not be enough space to
+ // display e.g. in case of multiuser detail from split shade. Currently showing detail works
+ // only for QS (mView below) and that's why expanding panel (thus showing QS instead of QQS)
+ // makes it displayed correctly.
+ if (!isExpanded()) {
+ mCommandQueue.animateExpandSettingsPanel(null);
+ }
mView.showDetailAdapter(true, detailAdapter, new int[]{x, y});
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 08cb4a9d44b6..f7d1b1e2f5eb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -24,6 +24,7 @@ import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.res.Configuration;
import android.metrics.LogMaker;
+import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
@@ -36,7 +37,6 @@ import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.util.Utils;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.animation.DisappearParameters;
@@ -68,9 +68,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
private final UiEventLogger mUiEventLogger;
private final QSLogger mQSLogger;
private final DumpManager mDumpManager;
- private final FeatureFlags mFeatureFlags;
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
- private boolean mShouldUseSplitNotificationShade;
+ protected boolean mShouldUseSplitNotificationShade;
@Nullable
private Consumer<Boolean> mMediaVisibilityChangedListener;
@@ -87,7 +86,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
@Override
public void onConfigurationChange(Configuration newConfig) {
mShouldUseSplitNotificationShade =
- Utils.shouldUseSplitNotificationShade(mFeatureFlags, getResources());
+ Utils.shouldUseSplitNotificationShade(getResources());
+ onConfigurationChanged();
if (newConfig.orientation != mLastOrientation) {
mLastOrientation = newConfig.orientation;
switchTileLayout(false);
@@ -95,6 +95,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
}
};
+ protected void onConfigurationChanged() { }
+
private final Function1<Boolean, Unit> mMediaHostVisibilityListener = (visible) -> {
if (mMediaVisibilityChangedListener != null) {
mMediaVisibilityChangedListener.accept(visible);
@@ -117,8 +119,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
MetricsLogger metricsLogger,
UiEventLogger uiEventLogger,
QSLogger qsLogger,
- DumpManager dumpManager,
- FeatureFlags featureFlags
+ DumpManager dumpManager
) {
super(view);
mHost = host;
@@ -129,9 +130,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
mUiEventLogger = uiEventLogger;
mQSLogger = qsLogger;
mDumpManager = dumpManager;
- mFeatureFlags = featureFlags;
mShouldUseSplitNotificationShade =
- Utils.shouldUseSplitNotificationShade(mFeatureFlags, getResources());
+ Utils.shouldUseSplitNotificationShade(getResources());
}
@Override
@@ -408,6 +408,10 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
mUsingHorizontalLayoutChangedListener = listener;
}
+ public View getBrightnessView() {
+ return mView.getBrightnessView();
+ }
+
/** */
public static final class TileRecord extends QSPanel.Record {
public QSTile tile;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt b/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt
new file mode 100644
index 000000000000..4854600994aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt
@@ -0,0 +1,64 @@
+package com.android.systemui.qs
+
+import android.view.ViewGroup
+import com.android.systemui.qs.dagger.QSFragmentModule.QQS_FOOTER
+import com.android.systemui.qs.dagger.QSScope
+import com.android.systemui.qs.tileimpl.HeightOverrideable
+import javax.inject.Inject
+import javax.inject.Named
+
+@QSScope
+class QSSquishinessController @Inject constructor(
+ private val qsTileHost: QSTileHost,
+ @Named(QQS_FOOTER) private val qqsFooterActionsView: FooterActionsView,
+ private val qsAnimator: QSAnimator,
+ private val quickQSPanelController: QuickQSPanelController
+) {
+
+ /**
+ * Fraction from 0 to 1, where 0 is collapsed and 1 expanded.
+ */
+ var squishiness: Float = 1f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ if ((field != 1f && value == 1f) || (field != 0f && value == 0f)) {
+ qsAnimator.requestAnimatorUpdate()
+ }
+ field = value
+ updateSquishiness()
+ }
+
+ /**
+ * Change the height of all tiles and repositions their siblings.
+ */
+ private fun updateSquishiness() {
+ // Update tile positions in the layout
+ val tileLayout = quickQSPanelController.tileLayout as TileLayout
+ tileLayout.setSquishinessFraction(squishiness)
+
+ // Adjust their heights as well
+ for (tile in qsTileHost.tiles) {
+ val tileView = quickQSPanelController.getTileView(tile)
+ (tileView as? HeightOverrideable)?.let {
+ it.squishinessFraction = squishiness
+ }
+ }
+
+ // Calculate how much we should move the footer
+ val tileHeightOffset = tileLayout.height - tileLayout.tilesHeight
+ val footerTopMargin = (qqsFooterActionsView.layoutParams as ViewGroup.MarginLayoutParams)
+ .topMargin
+ val nextTop = tileLayout.bottom - tileHeightOffset + footerTopMargin
+ val amountMoved = nextTop - qqsFooterActionsView.top
+
+ // Move the footer and other siblings (MediaPlayer)
+ (qqsFooterActionsView.parent as ViewGroup?)?.let { parent ->
+ val index = parent.indexOfChild(qqsFooterActionsView)
+ for (i in index until parent.childCount) {
+ parent.getChildAt(i).top += amountMoved
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index e60fb494e82b..4e9b0f1e369d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -39,6 +39,7 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
@@ -51,7 +52,6 @@ import com.android.systemui.qs.external.TileServices;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt
new file mode 100644
index 000000000000..65889d792769
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSBrightnessController.kt
@@ -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.systemui.qs
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.settings.brightness.BrightnessController
+import com.android.systemui.settings.brightness.BrightnessSliderController
+import com.android.systemui.settings.brightness.MirroredBrightnessController
+import com.android.systemui.statusbar.policy.BrightnessMirrorController
+import javax.inject.Inject
+
+/**
+ * Controls brightness slider in QQS, which is visible only in split shade. It's responsible for
+ * showing/hiding it when appropriate and (un)registering listeners
+ */
+class QuickQSBrightnessController @VisibleForTesting constructor(
+ private val brightnessControllerFactory: () -> BrightnessController
+) : MirroredBrightnessController {
+
+ @Inject constructor(
+ brightnessControllerFactory: BrightnessController.Factory,
+ brightnessSliderControllerFactory: BrightnessSliderController.Factory,
+ quickQSPanel: QuickQSPanel
+ ) : this(brightnessControllerFactory = {
+ val slider = brightnessSliderControllerFactory.create(quickQSPanel.context,
+ quickQSPanel)
+ slider.init()
+ quickQSPanel.setBrightnessView(slider.rootView)
+ brightnessControllerFactory.create(slider)
+ })
+
+ private var isListening = false
+ private var brightnessController: BrightnessController? = null
+ private var mirrorController: BrightnessMirrorController? = null
+
+ fun init(shouldUseSplitNotificationShade: Boolean) {
+ refreshVisibility(shouldUseSplitNotificationShade)
+ }
+
+ /**
+ * Starts/Stops listening for brightness changing events.
+ * It's fine to call this function even if slider is not visible (which would be the case for
+ * all small screen devices), it will just do nothing in that case
+ */
+ fun setListening(listening: Boolean) {
+ if (listening) {
+ // controller can be null when slider was never shown
+ if (!isListening && brightnessController != null) {
+ brightnessController?.registerCallbacks()
+ isListening = true
+ }
+ } else {
+ brightnessController?.unregisterCallbacks()
+ isListening = false
+ }
+ }
+
+ fun checkRestrictionAndSetEnabled() {
+ brightnessController?.checkRestrictionAndSetEnabled()
+ }
+
+ fun refreshVisibility(shouldUseSplitNotificationShade: Boolean) {
+ if (shouldUseSplitNotificationShade) {
+ showBrightnessSlider()
+ } else {
+ hideBrightnessSlider()
+ }
+ }
+
+ override fun setMirror(controller: BrightnessMirrorController) {
+ mirrorController = controller
+ mirrorController?.let { brightnessController?.setMirror(it) }
+ }
+
+ private fun hideBrightnessSlider() {
+ brightnessController?.hideSlider()
+ }
+
+ private fun showBrightnessSlider() {
+ if (brightnessController == null) {
+ brightnessController = brightnessControllerFactory()
+ mirrorController?.also { brightnessController?.setMirror(it) }
+ brightnessController?.registerCallbacks()
+ isListening = true
+ }
+ brightnessController?.showSlider()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index c5bfe97403e7..613e7f87371c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -33,23 +33,16 @@ import com.android.systemui.plugins.qs.QSTile.State;
*/
public class QuickQSPanel extends QSPanel {
- public static final String NUM_QUICK_TILES = "sysui_qqs_count";
private static final String TAG = "QuickQSPanel";
- // A default value so that we never return 0.
- public static final int DEFAULT_MAX_TILES = 6;
+ // A fallback value for max tiles number when setting via Tuner (parseNumTiles)
+ public static final int TUNER_MAX_TILES_FALLBACK = 6;
private boolean mDisabledByPolicy;
private int mMaxTiles;
public QuickQSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
- mMaxTiles = Math.min(DEFAULT_MAX_TILES,
- getResources().getInteger(R.integer.quick_qs_panel_max_columns));
- }
-
- @Override
- public void setBrightnessView(View view) {
- // Don't add brightness view
+ mMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_tiles);
}
@Override
@@ -106,7 +99,7 @@ public class QuickQSPanel extends QSPanel {
}
public void setMaxTiles(int maxTiles) {
- mMaxTiles = Math.min(maxTiles, DEFAULT_MAX_TILES);
+ mMaxTiles = maxTiles;
}
@Override
@@ -122,17 +115,18 @@ public class QuickQSPanel extends QSPanel {
}
/**
- * Parses the String setting into the number of tiles. Defaults to {@code mDefaultMaxTiles}
+ * Parses the String setting into the number of tiles. Defaults to
+ * {@link #TUNER_MAX_TILES_FALLBACK}
*
* @param numTilesValue value of the setting to parse
- * @return parsed value of numTilesValue OR {@code mDefaultMaxTiles} on error
+ * @return parsed value of numTilesValue OR {@link #TUNER_MAX_TILES_FALLBACK} on error
*/
public static int parseNumTiles(String numTilesValue) {
try {
return Integer.parseInt(numTilesValue);
} catch (NumberFormatException e) {
// Couldn't read an int from the new setting value. Use default.
- return DEFAULT_MAX_TILES;
+ return TUNER_MAX_TILES_FALLBACK;
}
}
@@ -193,7 +187,7 @@ public class QuickQSPanel extends QSPanel {
public boolean updateResources() {
mCellHeightResId = R.dimen.qs_quick_tile_size;
boolean b = super.updateResources();
- mMaxAllowedRows = 2;
+ mMaxAllowedRows = getResources().getInteger(R.integer.quick_qs_panel_max_rows);
return b;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index fee56b984ecc..92690c7d1202 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -17,6 +17,7 @@
package com.android.systemui.qs;
import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
+import static com.android.systemui.qs.dagger.QSFragmentModule.QQS_FOOTER;
import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
import com.android.internal.logging.MetricsLogger;
@@ -29,7 +30,8 @@ import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
+import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import java.util.ArrayList;
import java.util.List;
@@ -43,22 +45,32 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel>
private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
newConfig -> {
- int newMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
+ int newMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_tiles);
if (newMaxTiles != mView.getNumQuickTiles()) {
setMaxTiles(newMaxTiles);
}
};
+ // brightness is visible only in split shade
+ private final QuickQSBrightnessController mBrightnessController;
+ private final BrightnessMirrorHandler mBrightnessMirrorHandler;
+ private final FooterActionsController mFooterActionsController;
+
@Inject
QuickQSPanelController(QuickQSPanel view, QSTileHost qsTileHost,
QSCustomizerController qsCustomizerController,
@Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
@Named(QUICK_QS_PANEL) MediaHost mediaHost,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager, FeatureFlags featureFlags
+ DumpManager dumpManager,
+ QuickQSBrightnessController quickQSBrightnessController,
+ @Named(QQS_FOOTER) FooterActionsController footerActionsController
) {
super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
- uiEventLogger, qsLogger, dumpManager, featureFlags);
+ uiEventLogger, qsLogger, dumpManager);
+ mBrightnessController = quickQSBrightnessController;
+ mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
+ mFooterActionsController = footerActionsController;
}
@Override
@@ -67,18 +79,30 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel>
mMediaHost.setExpansion(0.0f);
mMediaHost.setShowsOnlyActiveMedia(true);
mMediaHost.init(MediaHierarchyManager.LOCATION_QQS);
+ mBrightnessController.init(mShouldUseSplitNotificationShade);
+ mFooterActionsController.init();
+ mFooterActionsController.refreshVisibility(mShouldUseSplitNotificationShade);
}
@Override
protected void onViewAttached() {
super.onViewAttached();
mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
+ mBrightnessMirrorHandler.onQsPanelAttached();
}
@Override
protected void onViewDetached() {
super.onViewDetached();
mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
+ mBrightnessMirrorHandler.onQsPanelDettached();
+ }
+
+ @Override
+ void setListening(boolean listening) {
+ super.setListening(listening);
+ mBrightnessController.setListening(listening);
+ mFooterActionsController.setListening(listening);
}
public boolean isListening() {
@@ -91,6 +115,18 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel>
}
@Override
+ public void refreshAllTiles() {
+ mBrightnessController.checkRestrictionAndSetEnabled();
+ super.refreshAllTiles();
+ }
+
+ @Override
+ protected void onConfigurationChanged() {
+ mBrightnessController.refreshVisibility(mShouldUseSplitNotificationShade);
+ mFooterActionsController.refreshVisibility(mShouldUseSplitNotificationShade);
+ }
+
+ @Override
public void setTiles() {
List<QSTile> tiles = new ArrayList<>();
for (QSTile tile : mHost.getTiles()) {
@@ -110,4 +146,8 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel>
public int getNumQuickTiles() {
return mView.getNumQuickTiles();
}
+
+ public void setBrightnessMirror(BrightnessMirrorController brightnessMirrorController) {
+ mBrightnessMirrorHandler.setController(brightnessMirrorController);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 84b961e7c48a..071e0535e7c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -34,9 +34,10 @@ import android.widget.Space;
import androidx.annotation.NonNull;
+import com.android.internal.policy.SystemBarUtils;
import com.android.settingslib.Utils;
-import com.android.systemui.BatteryMeterView;
import com.android.systemui.R;
+import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.qs.QSDetail.Callback;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
@@ -66,7 +67,7 @@ public class QuickStatusBarHeader extends FrameLayout {
// DateView next to clock. Visible on QQS
private VariableDateView mClockDateView;
private View mSecurityHeaderView;
- private View mClockIconsView;
+ private View mStatusIconsView;
private View mContainer;
private View mQSCarriers;
@@ -120,7 +121,7 @@ public class QuickStatusBarHeader extends FrameLayout {
mHeaderQsPanel = findViewById(R.id.quick_qs_panel);
mDatePrivacyView = findViewById(R.id.quick_status_bar_date_privacy);
- mClockIconsView = findViewById(R.id.quick_qs_status_icons);
+ mStatusIconsView = findViewById(R.id.quick_qs_status_icons);
mQSCarriers = findViewById(R.id.carrier_group);
mContainer = findViewById(R.id.qs_container);
mIconContainer = findViewById(R.id.statusIcons);
@@ -145,8 +146,6 @@ public class QuickStatusBarHeader extends FrameLayout {
setSecurityHeaderContainerVisibility(
config.orientation == Configuration.ORIENTATION_LANDSCAPE);
- // Don't need to worry about tuner settings for this icon
- mBatteryRemainingIcon.setIgnoreTunerUpdates(true);
// QS will always show the estimate, and BatteryMeterView handles the case where
// it's unavailable or charging
mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
@@ -231,26 +230,30 @@ public class QuickStatusBarHeader extends FrameLayout {
void updateResources() {
Resources resources = mContext.getResources();
+ // status bar is already displayed out of QS in split shade
+ boolean shouldUseSplitShade =
+ resources.getBoolean(R.bool.config_use_split_notification_shade);
+ mStatusIconsView.setVisibility(shouldUseSplitShade ? View.GONE : View.VISIBLE);
+ mDatePrivacyView.setVisibility(shouldUseSplitShade ? View.GONE : View.VISIBLE);
mConfigShowBatteryEstimate = resources.getBoolean(R.bool.config_showBatteryEstimateQSBH);
mRoundedCornerPadding = resources.getDimensionPixelSize(
R.dimen.rounded_corner_content_padding);
- int qsOffsetHeight = resources.getDimensionPixelSize(
- com.android.internal.R.dimen.quick_qs_offset_height);
+ int qsOffsetHeight = SystemBarUtils.getQuickQsOffsetHeight(mContext);
mDatePrivacyView.getLayoutParams().height =
Math.max(qsOffsetHeight, mDatePrivacyView.getMinimumHeight());
mDatePrivacyView.setLayoutParams(mDatePrivacyView.getLayoutParams());
- mClockIconsView.getLayoutParams().height =
- Math.max(qsOffsetHeight, mClockIconsView.getMinimumHeight());
- mClockIconsView.setLayoutParams(mClockIconsView.getLayoutParams());
+ mStatusIconsView.getLayoutParams().height =
+ Math.max(qsOffsetHeight, mStatusIconsView.getMinimumHeight());
+ mStatusIconsView.setLayoutParams(mStatusIconsView.getLayoutParams());
ViewGroup.LayoutParams lp = getLayoutParams();
if (mQsDisabled) {
- lp.height = mClockIconsView.getLayoutParams().height;
+ lp.height = mStatusIconsView.getLayoutParams().height;
} else {
lp.height = WRAP_CONTENT;
}
@@ -410,7 +413,7 @@ public class QuickStatusBarHeader extends FrameLayout {
if (disabled == mQsDisabled) return;
mQsDisabled = disabled;
mHeaderQsPanel.setDisabledByPolicy(disabled);
- mClockIconsView.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
+ mStatusIconsView.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
updateResources();
}
@@ -424,7 +427,7 @@ public class QuickStatusBarHeader extends FrameLayout {
StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
cutout, cornerCutoutPadding, -1);
mDatePrivacyView.setPadding(padding.first, 0, padding.second, 0);
- mClockIconsView.setPadding(padding.first, 0, padding.second, 0);
+ mStatusIconsView.setPadding(padding.first, 0, padding.second, 0);
LinearLayout.LayoutParams datePrivacySeparatorLayoutParams =
(LinearLayout.LayoutParams) mDatePrivacySeparator.getLayoutParams();
LinearLayout.LayoutParams mClockIconsSeparatorLayoutParams =
@@ -489,7 +492,7 @@ public class QuickStatusBarHeader extends FrameLayout {
private void updateHeadersPadding() {
setContentMargins(mDatePrivacyView, 0, 0);
- setContentMargins(mClockIconsView, 0, 0);
+ setContentMargins(mStatusIconsView, 0, 0);
int paddingLeft = 0;
int paddingRight = 0;
@@ -515,7 +518,7 @@ public class QuickStatusBarHeader extends FrameLayout {
mWaterfallTopInset,
paddingRight,
0);
- mClockIconsView.setPadding(paddingLeft,
+ mStatusIconsView.setPadding(paddingLeft,
mWaterfallTopInset,
paddingRight,
0);
@@ -542,7 +545,7 @@ public class QuickStatusBarHeader extends FrameLayout {
* @param scrollY the scroll of the QSPanel container
*/
public void setExpandedScrollAmount(int scrollY) {
- mClockIconsView.setScrollY(scrollY);
+ mStatusIconsView.setScrollY(scrollY);
mDatePrivacyView.setScrollY(scrollY);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index 18d6e646b007..38428c53fead 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -25,9 +25,11 @@ import androidx.annotation.NonNull;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
+import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.privacy.OngoingPrivacyChip;
import com.android.systemui.privacy.PrivacyChipEvent;
@@ -37,7 +39,6 @@ import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.privacy.logging.PrivacyLogger;
import com.android.systemui.qs.carrier.QSCarrierGroupController;
import com.android.systemui.qs.dagger.QSScope;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
@@ -59,7 +60,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
private final ActivityStarter mActivityStarter;
private final UiEventLogger mUiEventLogger;
private final QSCarrierGroupController mQSCarrierGroupController;
- private final QuickQSPanelController mHeaderQsPanelController;
+ private final QuickQSPanelController mQuickQSPanelController;
private final OngoingPrivacyChip mPrivacyChip;
private final Clock mClockView;
private final StatusBarIconController mStatusBarIconController;
@@ -70,6 +71,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
private final PrivacyLogger mPrivacyLogger;
private final PrivacyDialogController mPrivacyDialogController;
private final QSExpansionPathInterpolator mQSExpansionPathInterpolator;
+ private final BatteryMeterViewController mBatteryMeterViewController;
private final FeatureFlags mFeatureFlags;
private final VariableDateViewController mVariableDateViewControllerDateView;
@@ -138,6 +140,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
SysuiColorExtractor colorExtractor,
PrivacyDialogController privacyDialogController,
QSExpansionPathInterpolator qsExpansionPathInterpolator,
+ BatteryMeterViewController batteryMeterViewController,
FeatureFlags featureFlags,
VariableDateViewController.Factory variableDateViewControllerFactory) {
super(view);
@@ -146,10 +149,11 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mUiEventLogger = uiEventLogger;
mStatusBarIconController = statusBarIconController;
mDemoModeController = demoModeController;
- mHeaderQsPanelController = quickQSPanelController;
+ mQuickQSPanelController = quickQSPanelController;
mPrivacyLogger = privacyLogger;
mPrivacyDialogController = privacyDialogController;
mQSExpansionPathInterpolator = qsExpansionPathInterpolator;
+ mBatteryMeterViewController = batteryMeterViewController;
mFeatureFlags = featureFlags;
mQSCarrierGroupController = qsCarrierGroupControllerBuilder
@@ -178,6 +182,14 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mCameraSlot = getResources().getString(com.android.internal.R.string.status_bar_camera);
mMicSlot = getResources().getString(com.android.internal.R.string.status_bar_microphone);
mLocationSlot = getResources().getString(com.android.internal.R.string.status_bar_location);
+
+ // Don't need to worry about tuner settings for this icon
+ mBatteryMeterViewController.ignoreTunerUpdates();
+ }
+
+ @Override
+ protected void onInit() {
+ mBatteryMeterViewController.init();
}
@Override
@@ -239,12 +251,12 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
}
mListening = listening;
- mHeaderQsPanelController.setListening(listening);
- if (mHeaderQsPanelController.isListening()) {
- mHeaderQsPanelController.refreshAllTiles();
+ mQuickQSPanelController.setListening(listening);
+ if (mQuickQSPanelController.isListening()) {
+ mQuickQSPanelController.refreshAllTiles();
}
- if (mHeaderQsPanelController.switchTileLayout(false)) {
+ if (mQuickQSPanelController.switchTileLayout(false)) {
mView.updateResources();
}
@@ -300,7 +312,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
}
public void setContentMargins(int marginStart, int marginEnd) {
- mHeaderQsPanelController.setContentMargins(marginStart, marginEnd);
+ mQuickQSPanelController.setContentMargins(marginStart, marginEnd);
}
private static class ClockDemoModeReceiver implements DemoMode {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 1a890a7ad07b..58c05089b062 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -41,6 +41,8 @@ public class TileLayout extends ViewGroup implements QSTileLayout {
private int mMinRows = 1;
private int mMaxColumns = NO_MAX_COLUMNS;
protected int mResourceColumns;
+ private float mSquishinessFraction = 1f;
+ private int mLastTileBottom;
public TileLayout(Context context) {
this(context, null);
@@ -210,10 +212,11 @@ public class TileLayout extends ViewGroup implements QSTileLayout {
return mMaxCellHeight;
}
- protected void layoutTileRecords(int numRecords) {
+ private void layoutTileRecords(int numRecords, boolean forLayout) {
final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
int row = 0;
int column = 0;
+ mLastTileBottom = 0;
// Layout each QS tile.
final int tilesToLayout = Math.min(numRecords, mRows * mColumns);
@@ -228,17 +231,23 @@ public class TileLayout extends ViewGroup implements QSTileLayout {
final int top = getRowTop(row);
final int left = getColumnStart(isRtl ? mColumns - column - 1 : column);
final int right = left + mCellWidth;
- record.tileView.layout(left, top, right, top + record.tileView.getMeasuredHeight());
+ final int bottom = top + record.tileView.getMeasuredHeight();
+ if (forLayout) {
+ record.tileView.layout(left, top, right, bottom);
+ } else {
+ record.tileView.setLeftTopRightBottom(left, top, right, bottom);
+ }
+ mLastTileBottom = bottom;
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
- layoutTileRecords(mRecords.size());
+ layoutTileRecords(mRecords.size(), true /* forLayout */);
}
protected int getRowTop(int row) {
- return row * (mCellHeight + mCellMarginVertical);
+ return (int) (row * (mCellHeight * mSquishinessFraction + mCellMarginVertical));
}
protected int getColumnStart(int column) {
@@ -264,4 +273,17 @@ public class TileLayout extends ViewGroup implements QSTileLayout {
// up.
return Math.max(mColumns * mRows, 1);
}
+
+ public int getTilesHeight() {
+ return mLastTileBottom + getPaddingBottom();
+ }
+
+ @Override
+ public void setSquishinessFraction(float squishinessFraction) {
+ if (Float.compare(mSquishinessFraction, squishinessFraction) == 0) {
+ return;
+ }
+ mSquishinessFraction = squishinessFraction;
+ layoutTileRecords(mRecords.size(), false /* forLayout */);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index 67c4d33d53d3..fec61d911577 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -40,10 +40,10 @@ import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
+import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
import com.android.systemui.util.CarrierConfigTracker;
import java.util.function.Consumer;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 7518b200c7e2..0a452627427b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -33,9 +33,10 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.plugins.qs.QSContainerController;
import com.android.systemui.qs.QSDetailClipper;
import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
+import com.android.systemui.util.Utils;
/**
* Allows full-screen customization of QS, through show() and hide().
@@ -54,7 +55,7 @@ public class QSCustomizer extends LinearLayout {
private boolean isShown;
private final RecyclerView mRecyclerView;
private boolean mCustomizing;
- private NotificationsQuickSettingsContainer mNotifQsContainer;
+ private QSContainerController mQsContainerController;
private QS mQs;
private int mX;
private int mY;
@@ -84,8 +85,7 @@ public class QSCustomizer extends LinearLayout {
void updateResources() {
LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
- lp.height = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.quick_qs_offset_height);
+ lp.height = Utils.getQsHeaderSystemIconsAreaHeight(mContext);
mTransparentView.setLayoutParams(lp);
}
@@ -103,8 +103,8 @@ public class QSCustomizer extends LinearLayout {
lightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown);
}
- public void setContainer(NotificationsQuickSettingsContainer notificationsQsContainer) {
- mNotifQsContainer = notificationsQsContainer;
+ public void setContainerController(QSContainerController controller) {
+ mQsContainerController = controller;
}
public void setQs(QS qs) {
@@ -123,8 +123,8 @@ public class QSCustomizer extends LinearLayout {
mOpening = true;
setVisibility(View.VISIBLE);
mClipper.animateCircularClip(mX, mY, true, new ExpandAnimatorListener(tileAdapter));
- mNotifQsContainer.setCustomizerAnimating(true);
- mNotifQsContainer.setCustomizerShowing(true);
+ mQsContainerController.setCustomizerAnimating(true);
+ mQsContainerController.setCustomizerShowing(true);
}
}
@@ -136,8 +136,8 @@ public class QSCustomizer extends LinearLayout {
mClipper.showBackground();
isShown = true;
setCustomizing(true);
- mNotifQsContainer.setCustomizerAnimating(false);
- mNotifQsContainer.setCustomizerShowing(true);
+ mQsContainerController.setCustomizerAnimating(false);
+ mQsContainerController.setCustomizerShowing(true);
}
}
@@ -154,8 +154,8 @@ public class QSCustomizer extends LinearLayout {
} else {
setVisibility(View.GONE);
}
- mNotifQsContainer.setCustomizerAnimating(animate);
- mNotifQsContainer.setCustomizerShowing(false);
+ mQsContainerController.setCustomizerAnimating(animate);
+ mQsContainerController.setCustomizerShowing(false);
}
}
@@ -193,7 +193,7 @@ public class QSCustomizer extends LinearLayout {
setCustomizing(true);
}
mOpening = false;
- mNotifQsContainer.setCustomizerAnimating(false);
+ mQsContainerController.setCustomizerAnimating(false);
mRecyclerView.setAdapter(mTileAdapter);
}
@@ -201,7 +201,7 @@ public class QSCustomizer extends LinearLayout {
public void onAnimationCancel(Animator animation) {
mOpening = false;
mQs.notifyCustomizeChanged();
- mNotifQsContainer.setCustomizerAnimating(false);
+ mQsContainerController.setCustomizerAnimating(false);
}
}
@@ -211,7 +211,7 @@ public class QSCustomizer extends LinearLayout {
if (!isShown) {
setVisibility(View.GONE);
}
- mNotifQsContainer.setCustomizerAnimating(false);
+ mQsContainerController.setCustomizerAnimating(false);
}
@Override
@@ -219,7 +219,7 @@ public class QSCustomizer extends LinearLayout {
if (!isShown) {
setVisibility(View.GONE);
}
- mNotifQsContainer.setCustomizerAnimating(false);
+ mQsContainerController.setCustomizerAnimating(false);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index 49d18e62346a..618a429f6b51 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -35,13 +35,13 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.plugins.qs.QSContainerController;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSEditEvent;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -233,8 +233,8 @@ public class QSCustomizerController extends ViewController<QSCustomizer> {
}
/** */
- public void setContainer(NotificationsQuickSettingsContainer container) {
- mView.setContainer(container);
+ public void setContainerController(QSContainerController controller) {
+ mView.setContainerController(controller);
}
public boolean isShown() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index f2832b3d45ff..993bbd039b60 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -34,6 +34,7 @@ import android.widget.Button;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.qs.QSTileHost;
@@ -41,7 +42,6 @@ import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.util.leak.GarbageMonitor;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
index 6fa44eb513e9..103ac656e846 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
@@ -20,7 +20,7 @@ import android.content.Context;
import android.hardware.display.ColorDisplayManager;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.util.settings.GlobalSettings;
import javax.inject.Named;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
index 8cc05026e1f1..63cbc21ddf74 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
@@ -21,6 +21,7 @@ import com.android.systemui.qs.QSContainerImplController;
import com.android.systemui.qs.QSFooter;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.qs.QSSquishinessController;
import com.android.systemui.qs.QuickQSPanelController;
import com.android.systemui.qs.customize.QSCustomizerController;
@@ -57,4 +58,7 @@ public interface QSFragmentComponent {
/** Construct a {@link QSCustomizerController}. */
QSCustomizerController getQSCustomizerController();
+
+ /** Construct a {@link QSSquishinessController}. */
+ QSSquishinessController getQSSquishinessController();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index 4d633492ed76..b11420afa529 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -23,8 +23,13 @@ import android.view.LayoutInflater;
import android.view.View;
import com.android.systemui.R;
+import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.qs.FooterActionsController;
+import com.android.systemui.qs.FooterActionsController.ExpansionState;
+import com.android.systemui.qs.FooterActionsControllerBuilder;
+import com.android.systemui.qs.FooterActionsView;
import com.android.systemui.qs.QSContainerImpl;
import com.android.systemui.qs.QSFooter;
import com.android.systemui.qs.QSFooterView;
@@ -33,9 +38,7 @@ import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QuickQSPanel;
import com.android.systemui.qs.QuickStatusBarHeader;
-import com.android.systemui.qs.carrier.QSCarrierGroupController;
import com.android.systemui.qs.customize.QSCustomizer;
-import com.android.systemui.statusbar.phone.MultiUserSwitch;
import javax.inject.Named;
@@ -49,6 +52,8 @@ import dagger.Provides;
@Module
public interface QSFragmentModule {
String QS_SECURITY_FOOTER_VIEW = "qs_security_footer";
+ String QQS_FOOTER = "qqs_footer";
+ String QS_FOOTER = "qs_footer";
String QS_USING_MEDIA_PLAYER = "qs_using_media_player";
/**
@@ -76,12 +81,6 @@ public interface QSFragmentModule {
/** */
@Provides
- static MultiUserSwitch providesMultiUserSWitch(QSFooterView qsFooterView) {
- return qsFooterView.findViewById(R.id.multi_user_switch);
- }
-
- /** */
- @Provides
static QSPanel provideQSPanel(@RootView View view) {
return view.findViewById(R.id.quick_settings_panel);
}
@@ -110,12 +109,56 @@ public interface QSFragmentModule {
/** */
@Provides
+ static BatteryMeterView providesBatteryMeterView(QuickStatusBarHeader quickStatusBarHeader) {
+ return quickStatusBarHeader.findViewById(R.id.batteryRemainingIcon);
+ }
+
+ /** */
+ @Provides
static QSFooterView providesQSFooterView(@RootView View view) {
return view.findViewById(R.id.qs_footer);
}
/** */
@Provides
+ @Named(QS_FOOTER)
+ static FooterActionsView providesQSFooterActionsView(@RootView View view) {
+ return view.findViewById(R.id.qs_footer_actions);
+ }
+
+ /** */
+ @Provides
+ @Named(QQS_FOOTER)
+ static FooterActionsView providesQQSFooterActionsView(@RootView View view) {
+ return view.findViewById(R.id.qqs_footer_actions);
+ }
+
+ /** */
+ @Provides
+ @Named(QQS_FOOTER)
+ static FooterActionsController providesQQSFooterActionsController(
+ FooterActionsControllerBuilder footerActionsControllerBuilder,
+ @Named(QQS_FOOTER) FooterActionsView qqsFooterActionsView) {
+ return footerActionsControllerBuilder
+ .withView(qqsFooterActionsView)
+ .withButtonsVisibleWhen(ExpansionState.COLLAPSED)
+ .build();
+ }
+
+ /** */
+ @Provides
+ @Named(QS_FOOTER)
+ static FooterActionsController providesQSFooterActionsController(
+ FooterActionsControllerBuilder footerActionsControllerBuilder,
+ @Named(QS_FOOTER) FooterActionsView qsFooterActionsView) {
+ return footerActionsControllerBuilder
+ .withView(qsFooterActionsView)
+ .withButtonsVisibleWhen(ExpansionState.EXPANDED)
+ .build();
+ }
+
+ /** */
+ @Provides
@QSScope
static QSFooter providesQSFooter(QSFooterViewController qsFooterViewController) {
qsFooterViewController.init();
@@ -146,9 +189,4 @@ public interface QSFragmentModule {
static boolean providesQSUsingMediaPlayer(Context context) {
return useQsMediaPlayer(context);
}
-
- /** */
- @Binds
- QSCarrierGroupController.SlotIndexResolver provideSlotIndexResolver(
- QSCarrierGroupController.SubscriptionManagerSlotIndexResolver impl);
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt
index 866fa097d6fd..61d68eceb961 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt
@@ -21,9 +21,8 @@ interface HeightOverrideable {
const val NO_OVERRIDE = -1
}
- var heightOverride: Int
+ abstract var heightOverride: Int
+ abstract fun resetOverride()
- fun resetOverride() {
- heightOverride = NO_OVERRIDE
- }
+ abstract var squishinessFraction: Float
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 222539d49526..69be33261995 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -42,6 +42,7 @@ import androidx.annotation.VisibleForTesting
import com.android.settingslib.Utils
import com.android.systemui.FontSizeUtils
import com.android.systemui.R
+import com.android.systemui.animation.LaunchableView
import com.android.systemui.plugins.qs.QSIconView
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.qs.QSTile.BooleanState
@@ -54,7 +55,7 @@ open class QSTileViewImpl @JvmOverloads constructor(
context: Context,
private val _icon: QSIconView,
private val collapsed: Boolean = false
-) : QSTileView(context), HeightOverrideable {
+) : QSTileView(context), HeightOverrideable, LaunchableView {
companion object {
private const val INVALID = -1
@@ -68,6 +69,18 @@ open class QSTileViewImpl @JvmOverloads constructor(
}
override var heightOverride: Int = HeightOverrideable.NO_OVERRIDE
+ set(value) {
+ if (field == value) return
+ field = value
+ updateHeight()
+ }
+
+ override var squishinessFraction: Float = 1f
+ set(value) {
+ if (field == value) return
+ field = value
+ updateHeight()
+ }
private val colorActive = Utils.getColorAttrDefaultColor(context,
com.android.internal.R.attr.colorAccentPrimary)
@@ -118,6 +131,8 @@ open class QSTileViewImpl @JvmOverloads constructor(
private var lastStateDescription: CharSequence? = null
private var tileState = false
private var lastState = INVALID
+ private var blockVisibilityChanges = false
+ private var lastVisibility = View.VISIBLE
private val locInScreen = IntArray(2)
@@ -148,6 +163,11 @@ open class QSTileViewImpl @JvmOverloads constructor(
updateResources()
}
+ override fun resetOverride() {
+ heightOverride = HeightOverrideable.NO_OVERRIDE
+ updateHeight()
+ }
+
fun updateResources() {
FontSizeUtils.updateFontSize(label, R.dimen.qs_tile_text_size)
FontSizeUtils.updateFontSize(secondaryLabel, R.dimen.qs_tile_text_size)
@@ -218,9 +238,17 @@ open class QSTileViewImpl @JvmOverloads constructor(
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)
- if (heightOverride != HeightOverrideable.NO_OVERRIDE) {
- bottom = top + heightOverride
- }
+ updateHeight()
+ }
+
+ private fun updateHeight() {
+ val actualHeight = (if (heightOverride != HeightOverrideable.NO_OVERRIDE) {
+ heightOverride
+ } else {
+ measuredHeight
+ } * squishinessFraction).toInt()
+ bottom = top + actualHeight
+ scrollY = (actualHeight - height) / 2
}
override fun updateAccessibilityOrder(previousView: View?): View {
@@ -294,6 +322,36 @@ open class QSTileViewImpl @JvmOverloads constructor(
return sideView
}
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {
+ blockVisibilityChanges = block
+
+ if (block) {
+ lastVisibility = visibility
+ } else {
+ visibility = lastVisibility
+ }
+ }
+
+ override fun setVisibility(visibility: Int) {
+ if (blockVisibilityChanges) {
+ lastVisibility = visibility
+ return
+ }
+
+ super.setVisibility(visibility)
+ }
+
+ override fun setTransitionVisibility(visibility: Int) {
+ if (blockVisibilityChanges) {
+ // View.setTransitionVisibility just sets the visibility flag, so we don't have to save
+ // the transition visibility separately from the normal visibility.
+ lastVisibility = visibility
+ return
+ }
+
+ super.setTransitionVisibility(visibility)
+ }
+
// Accessibility
override fun onInitializeAccessibilityEvent(event: AccessibilityEvent) {
@@ -459,7 +517,7 @@ open class QSTileViewImpl @JvmOverloads constructor(
}
private fun setColor(color: Int) {
- colorBackgroundDrawable.setTint(color)
+ colorBackgroundDrawable.mutate().setTint(color)
paintColor = color
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 04f089d31664..9de6ceb2de4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -51,13 +51,13 @@ import com.android.systemui.qs.QSDetailItems.Item;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
import java.util.ArrayList;
import java.util.LinkedHashMap;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 8e886e82d352..35dadd45eb3e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -56,11 +56,11 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.SignalTileView;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
+import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
+import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import javax.inject.Inject;
@@ -275,7 +275,7 @@ public class CellularTile extends QSTileImpl<SignalState> {
return;
}
mInfo.dataSubscriptionName = mController.getMobileDataNetworkName();
- mInfo.dataContentDescription = indicators.description != null
+ mInfo.dataContentDescription = indicators.qsDescription != null
? indicators.typeContentDescriptionHtml : null;
mInfo.activityIn = indicators.activityIn;
mInfo.activityOut = indicators.activityOut;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index bc21b2d0fba7..80ec0adc21a9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -112,20 +112,9 @@ class DeviceControlsTile @Inject constructor(
}
mUiHandler.post {
- if (keyguardStateController.isUnlocked) {
- mActivityStarter.startActivity(
- intent, true /* dismissShade */, animationController)
- } else {
- if (state.state == Tile.STATE_ACTIVE) {
- mHost.collapsePanels()
- // With an active tile, don't use ActivityStarter so that the activity is
- // started without prompting keyguard unlock.
- mContext.startActivity(intent)
- } else {
- mActivityStarter.postStartActivityDismissingKeyguard(
- intent, 0 /* delay */, animationController)
- }
- }
+ val showOverLockscreenWhenLocked = state.state == Tile.STATE_ACTIVE
+ mActivityStarter.startActivity(
+ intent, true /* dismissShade */, animationController, showOverLockscreenWhenLocked)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index cc9e7485dcff..23b2a7642e36 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -52,13 +52,13 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
-import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
-import com.android.systemui.statusbar.policy.WifiIcons;
+import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.NetworkController.AccessPointController;
+import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
+import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
+import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
+import com.android.systemui.statusbar.connectivity.WifiIcons;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -124,7 +124,7 @@ public class InternetTile extends QSTileImpl<SignalState> {
protected void handleClick(@Nullable View view) {
mHandler.post(() -> mInternetDialogFactory.create(true,
mAccessPointController.canConfigMobileData(),
- mAccessPointController.canConfigWifi()));
+ mAccessPointController.canConfigWifi(), view));
}
@Override
@@ -279,9 +279,9 @@ public class InternetTile extends QSTileImpl<SignalState> {
// Not data sim, don't display.
return;
}
- mCellularInfo.mDataSubscriptionName = indicators.description == null
- ? mController.getMobileDataNetworkName() : indicators.description;
- mCellularInfo.mDataContentDescription = indicators.description != null
+ mCellularInfo.mDataSubscriptionName = indicators.qsDescription == null
+ ? mController.getMobileDataNetworkName() : indicators.qsDescription;
+ mCellularInfo.mDataContentDescription = indicators.qsDescription != null
? indicators.typeContentDescriptionHtml : null;
mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon;
mCellularInfo.mQsTypeIcon = indicators.qsType;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 82b6c0c1805d..d9919bdac889 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -131,22 +131,16 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
Intent intent = new Intent(mContext, WalletActivity.class)
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
- if (mKeyguardStateController.isUnlocked()) {
- mActivityStarter.startActivity(intent, true /* dismissShade */,
- animationController);
- } else {
- mHost.collapsePanels();
- // Do not use ActivityStarter here because the WalletActivity is required to be
- // started without prompting keyguard when the device is locked.
- mContext.startActivity(intent);
- }
+ mActivityStarter.startActivity(intent, true /* dismissShade */,
+ animationController, true /* showOverLockscreenWhenLocked */);
} else {
- if (mController.getWalletClient().createWalletIntent() == null) {
+ Intent intent = mController.getWalletClient().createWalletIntent();
+ if (intent == null) {
Log.w(TAG, "Could not get intent of the wallet app.");
return;
}
mActivityStarter.postStartActivityDismissingKeyguard(
- mController.getWalletClient().createWalletIntent(),
+ intent,
/* delay= */ 0,
animationController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 4e936b8137af..0bbb5bdd851a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -16,19 +16,12 @@
package com.android.systemui.qs.tiles;
-import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
-
-import android.Manifest;
-import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.hardware.SensorPrivacyManager;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
-import android.provider.Settings.Secure;
import android.service.quicksettings.Tile;
import android.view.View;
import android.widget.Switch;
@@ -45,25 +38,18 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.SecureSetting;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
-import com.android.systemui.util.settings.SecureSettings;
import javax.inject.Inject;
/** Quick settings tile: Rotation **/
-public class RotationLockTile extends QSTileImpl<BooleanState> implements
- BatteryController.BatteryStateChangeCallback {
+public class RotationLockTile extends QSTileImpl<BooleanState> {
private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_auto_rotate);
private final RotationLockController mController;
- private final SensorPrivacyManager mPrivacyManager;
- private final BatteryController mBatteryController;
- private final SecureSetting mSetting;
@Inject
public RotationLockTile(
@@ -75,38 +61,12 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
- RotationLockController rotationLockController,
- SensorPrivacyManager privacyManager,
- BatteryController batteryController,
- SecureSettings secureSettings
+ RotationLockController rotationLockController
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mController = rotationLockController;
mController.observe(this, mCallback);
- mPrivacyManager = privacyManager;
- mBatteryController = batteryController;
- mPrivacyManager
- .addSensorPrivacyListener(CAMERA, (sensor, enabled) -> refreshState());
- int currentUser = host.getUserContext().getUserId();
- mSetting = new SecureSetting(
- secureSettings,
- mHandler,
- Secure.CAMERA_AUTOROTATE,
- currentUser
- ) {
- @Override
- protected void handleValueChanged(int value, boolean observedChange) {
- // mHandler is the background handler so calling this is OK
- handleRefreshState(value);
- }
- };
- mBatteryController.observe(getLifecycle(), this);
- }
-
- @Override
- public void onPowerSaveChanged(boolean isPowerSave) {
- refreshState();
}
@Override
@@ -135,33 +95,14 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements
protected void handleUpdateState(BooleanState state, Object arg) {
final boolean rotationLocked = mController.isRotationLocked();
- final boolean powerSave = mBatteryController.isPowerSave();
- final boolean cameraLocked = mPrivacyManager.isSensorPrivacyEnabled(
- SensorPrivacyManager.Sensors.CAMERA);
- final boolean cameraRotation =
- !powerSave && !cameraLocked && hasSufficientPermission(mContext)
- && mController.isCameraRotationEnabled();
state.value = !rotationLocked;
state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
state.icon = mIcon;
state.contentDescription = getAccessibilityString(rotationLocked);
- if (!rotationLocked && cameraRotation) {
- state.secondaryLabel = mContext.getResources().getString(
- R.string.rotation_lock_camera_rotation_on);
- } else {
- state.secondaryLabel = "";
- }
-
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
- @Override
- protected void handleUserSwitch(int newUserId) {
- mSetting.setUserId(newUserId);
- handleRefreshState(mSetting.getValue());
- }
-
public static boolean isCurrentOrientationLockPortrait(RotationLockController controller,
Resources resources) {
int lockOrientation = controller.getRotationLockOrientation();
@@ -199,11 +140,4 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements
refreshState(rotationLocked);
}
};
-
- private boolean hasSufficientPermission(Context context) {
- final PackageManager packageManager = context.getPackageManager();
- final String rotationPackage = packageManager.getRotationResolverPackageName();
- return rotationPackage != null && packageManager.checkPermission(
- Manifest.permission.CAMERA, rotationPackage) == PackageManager.PERMISSION_GRANTED;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 04437ea14bb3..821bd5117d18 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -39,6 +39,8 @@ import com.android.systemui.qs.PseudoGridView;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import java.util.function.Consumer;
+
import javax.inject.Inject;
/**
@@ -75,6 +77,7 @@ public class UserDetailView extends PseudoGridView {
private View mCurrentUserView;
private final UiEventLogger mUiEventLogger;
private final FalsingManager mFalsingManager;
+ private Consumer<UserSwitcherController.UserRecord> mClickCallback;
@Inject
public Adapter(Context context, UserSwitcherController controller,
@@ -92,6 +95,10 @@ public class UserDetailView extends PseudoGridView {
return createUserDetailItemView(convertView, parent, item);
}
+ public void injectCallback(Consumer<UserSwitcherController.UserRecord> clickCallback) {
+ mClickCallback = clickCallback;
+ }
+
public UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
UserSwitcherController.UserRecord item) {
UserDetailItemView v = UserDetailItemView.convertOrInflate(
@@ -167,6 +174,13 @@ public class UserDetailView extends PseudoGridView {
}
onUserListItemClicked(tag);
}
+ if (mClickCallback != null) {
+ mClickCallback.accept(tag);
+ }
+ }
+
+ public void linkToViewGroup(ViewGroup viewGroup) {
+ PseudoGridView.ViewGroupAdapterBridge.link(viewGroup, this);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 41a3020191a6..e6e7e21263bb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -52,11 +52,11 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSIconViewImpl;
import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
-import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
-import com.android.systemui.statusbar.policy.WifiIcons;
+import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.NetworkController.AccessPointController;
+import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
+import com.android.systemui.statusbar.connectivity.WifiIcons;
import com.android.wifitrackerlib.WifiEntry;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index d0271f72153e..f6dbb0b95ecd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -15,14 +15,11 @@
*/
package com.android.systemui.qs.tiles.dialog;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
-
import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA;
import android.app.AlertDialog;
import android.content.Context;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
+import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -41,10 +38,7 @@ import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import android.view.Window;
-import android.view.WindowInsets;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -87,7 +81,6 @@ public class InternetDialog extends SystemUIDialog implements
private final Handler mHandler;
private final Executor mBackgroundExecutor;
- private final LinearLayoutManager mLayoutManager;
@VisibleForTesting
protected InternetAdapter mAdapter;
@@ -130,7 +123,7 @@ public class InternetDialog extends SystemUIDialog implements
private Switch mWiFiToggle;
private FrameLayout mDoneLayout;
private Drawable mBackgroundOn;
- private int mListMaxHeight;
+ private Drawable mBackgroundOff = null;
private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private boolean mCanConfigMobileData;
@@ -149,20 +142,11 @@ public class InternetDialog extends SystemUIDialog implements
mInternetDialogSubTitle.setText(getSubtitleText());
};
- private final ViewTreeObserver.OnGlobalLayoutListener mInternetListLayoutListener = () -> {
- // Set max height for list
- if (mInternetDialogLayout.getHeight() > mListMaxHeight) {
- ViewGroup.LayoutParams params = mInternetDialogLayout.getLayoutParams();
- params.height = mListMaxHeight;
- mInternetDialogLayout.setLayoutParams(params);
- }
- };
-
public InternetDialog(Context context, InternetDialogFactory internetDialogFactory,
InternetDialogController internetDialogController, boolean canConfigMobileData,
boolean canConfigWifi, boolean aboveStatusBar, UiEventLogger uiEventLogger,
@Main Handler handler, @Background Executor executor) {
- super(context, R.style.Theme_SystemUI_Dialog_Internet);
+ super(context);
if (DEBUG) {
Log.d(TAG, "Init InternetDialog");
}
@@ -178,14 +162,6 @@ public class InternetDialog extends SystemUIDialog implements
mCanConfigMobileData = canConfigMobileData;
mCanConfigWifi = canConfigWifi;
- mLayoutManager = new LinearLayoutManager(mContext) {
- @Override
- public boolean canScrollVertically() {
- return false;
- }
- };
- mListMaxHeight = context.getResources().getDimensionPixelSize(
- R.dimen.internet_dialog_list_max_height);
mUiEventLogger = uiEventLogger;
mAdapter = new InternetAdapter(mInternetDialogController);
if (!aboveStatusBar) {
@@ -203,21 +179,9 @@ public class InternetDialog extends SystemUIDialog implements
mDialogView = LayoutInflater.from(mContext).inflate(R.layout.internet_connectivity_dialog,
null);
final Window window = getWindow();
- final WindowManager.LayoutParams layoutParams = window.getAttributes();
- layoutParams.gravity = Gravity.BOTTOM;
- // Move down the dialog to overlay the navigation bar.
- layoutParams.setFitInsetsTypes(
- layoutParams.getFitInsetsTypes() & ~WindowInsets.Type.navigationBars());
- layoutParams.setFitInsetsSides(WindowInsets.Side.all());
- layoutParams.setFitInsetsIgnoringVisibility(true);
- window.setAttributes(layoutParams);
window.setContentView(mDialogView);
- //Only fix the width for large screen or tablet.
- window.setLayout(mContext.getResources().getDimensionPixelSize(
- R.dimen.internet_dialog_list_max_width), ViewGroup.LayoutParams.WRAP_CONTENT);
+
window.setWindowAnimations(R.style.Animation_InternetDialog);
- window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
- window.addFlags(FLAG_LAYOUT_NO_LIMITS);
mInternetDialogLayout = mDialogView.requireViewById(R.id.internet_connectivity_dialog);
mInternetDialogTitle = mDialogView.requireViewById(R.id.internet_dialog_title);
@@ -244,14 +208,20 @@ public class InternetDialog extends SystemUIDialog implements
mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle);
mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle);
mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on);
- mInternetDialogLayout.getViewTreeObserver().addOnGlobalLayoutListener(
- mInternetListLayoutListener);
mInternetDialogTitle.setText(getDialogTitleText());
mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
+ TypedArray typedArray = mContext.obtainStyledAttributes(
+ new int[]{android.R.attr.selectableItemBackground});
+ try {
+ mBackgroundOff = typedArray.getDrawable(0 /* index */);
+ } finally {
+ typedArray.recycle();
+ }
+
setOnClickListener();
mTurnWifiOnLayout.setBackground(null);
- mWifiRecyclerView.setLayoutManager(mLayoutManager);
+ mWifiRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
mWifiRecyclerView.setAdapter(mAdapter);
}
@@ -404,7 +374,8 @@ public class InternetDialog extends SystemUIDialog implements
mMobileSummaryText.setTextAppearance(isCarrierNetworkConnected
? R.style.TextAppearance_InternetDialog_Secondary_Active
: R.style.TextAppearance_InternetDialog_Secondary);
- mMobileNetworkLayout.setBackground(isCarrierNetworkConnected ? mBackgroundOn : null);
+ mMobileNetworkLayout.setBackground(
+ isCarrierNetworkConnected ? mBackgroundOn : mBackgroundOff);
mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
}
@@ -502,10 +473,6 @@ public class InternetDialog extends SystemUIDialog implements
}
private void setProgressBarVisible(boolean visible) {
- if (mWifiManager.isWifiEnabled() && mAdapter.mHolderView != null
- && mAdapter.mHolderView.isAttachedToWindow()) {
- mIsProgressBarVisible = true;
- }
mIsProgressBarVisible = visible;
mProgressBar.setVisibility(mIsProgressBarVisible ? View.VISIBLE : View.GONE);
mDivider.setVisibility(mIsProgressBarVisible ? View.GONE : View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 67e34113bebb..5673136e1828 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -71,14 +71,15 @@ import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.SignalStrengthUtil;
import com.android.settingslib.wifi.WifiUtils;
import com.android.systemui.R;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.NetworkController.AccessPointController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
import com.android.systemui.toast.SystemUIToast;
import com.android.systemui.toast.ToastFactory;
import com.android.systemui.util.CarrierConfigTracker;
@@ -152,6 +153,7 @@ public class InternetDialogController implements WifiEntry.DisconnectCallback,
private ToastFactory mToastFactory;
private SignalDrawable mSignalDrawable;
private LocationController mLocationController;
+ private DialogLaunchAnimator mDialogLaunchAnimator;
@VisibleForTesting
static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
@@ -202,7 +204,8 @@ public class InternetDialogController implements WifiEntry.DisconnectCallback,
WindowManager windowManager, ToastFactory toastFactory,
@Background Handler workerHandler,
CarrierConfigTracker carrierConfigTracker,
- LocationController locationController) {
+ LocationController locationController,
+ DialogLaunchAnimator dialogLaunchAnimator) {
if (DEBUG) {
Log.d(TAG, "Init InternetDialogController");
}
@@ -231,6 +234,7 @@ public class InternetDialogController implements WifiEntry.DisconnectCallback,
mToastFactory = toastFactory;
mSignalDrawable = new SignalDrawable(mContext);
mLocationController = locationController;
+ mDialogLaunchAnimator = dialogLaunchAnimator;
}
void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) {
@@ -596,20 +600,32 @@ public class InternetDialogController implements WifiEntry.DisconnectCallback,
}
void launchNetworkSetting() {
+ // Dismissing a dialog into its touch surface and starting an activity at the same time
+ // looks bad, so let's make sure the dialog just fades out quickly.
+ mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
mCallback.dismissDialog();
+
mActivityStarter.postStartActivityDismissingKeyguard(getSettingsIntent(), 0);
}
void launchWifiNetworkDetailsSetting(String key) {
Intent intent = getWifiDetailsSettingsIntent(key);
if (intent != null) {
+ // Dismissing a dialog into its touch surface and starting an activity at the same time
+ // looks bad, so let's make sure the dialog just fades out quickly.
+ mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
mCallback.dismissDialog();
+
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
}
}
void launchWifiScanningSetting() {
+ // Dismissing a dialog into its touch surface and starting an activity at the same time
+ // looks bad, so let's make sure the dialog just fades out quickly.
+ mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
mCallback.dismissDialog();
+
final Intent intent = new Intent(ACTION_WIFI_SCANNING_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
index ea5df17bca58..93828b3bcc99 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
@@ -18,9 +18,11 @@ package com.android.systemui.qs.tiles.dialog
import android.content.Context
import android.os.Handler
import android.util.Log
+import android.view.View
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -37,14 +39,20 @@ class InternetDialogFactory @Inject constructor(
@Background private val executor: Executor,
private val internetDialogController: InternetDialogController,
private val context: Context,
- private val uiEventLogger: UiEventLogger
+ private val uiEventLogger: UiEventLogger,
+ private val dialogLaunchAnimator: DialogLaunchAnimator
) {
companion object {
var internetDialog: InternetDialog? = null
}
- /** Creates a [InternetDialog]. */
- fun create(aboveStatusBar: Boolean, canConfigMobileData: Boolean, canConfigWifi: Boolean) {
+ /** Creates a [InternetDialog]. The dialog will be animated from [view] if it is not null. */
+ fun create(
+ aboveStatusBar: Boolean,
+ canConfigMobileData: Boolean,
+ canConfigWifi: Boolean,
+ view: View?
+ ) {
if (internetDialog != null) {
if (DEBUG) {
Log.d(TAG, "InternetDialog is showing, do not create it twice.")
@@ -54,7 +62,11 @@ class InternetDialogFactory @Inject constructor(
internetDialog = InternetDialog(context, this, internetDialogController,
canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler,
executor)
- internetDialog?.show()
+ if (view != null) {
+ dialogLaunchAnimator.showFromView(internetDialog!!, view)
+ } else {
+ internetDialog?.show()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
new file mode 100644
index 000000000000..26d1bbde2a54
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.systemui.qs.user
+
+import android.content.Context
+import android.os.Bundle
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManager
+import com.android.systemui.qs.PseudoGridView
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.R
+
+/**
+ * Dialog for switching users or creating new ones.
+ */
+class UserDialog(
+ context: Context
+) : SystemUIDialog(context) {
+
+ // create() is no-op after creation
+ private lateinit var _doneButton: View
+ /**
+ * Button with text "Done" in dialog.
+ */
+ val doneButton: View
+ get() {
+ create()
+ return _doneButton
+ }
+
+ private lateinit var _settingsButton: View
+ /**
+ * Button with text "User Settings" in dialog.
+ */
+ val settingsButton: View
+ get() {
+ create()
+ return _settingsButton
+ }
+
+ private lateinit var _grid: PseudoGridView
+ /**
+ * Grid to populate with user avatar from adapter
+ */
+ val grid: ViewGroup
+ get() {
+ create()
+ return _grid
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ window?.apply {
+ setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+ attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
+ attributes.receiveInsetsIgnoringZOrder = true
+ setGravity(Gravity.CENTER)
+ }
+ setContentView(R.layout.qs_user_dialog_content)
+
+ _doneButton = requireViewById(R.id.done)
+ _settingsButton = requireViewById(R.id.settings)
+ _grid = requireViewById(R.id.grid)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
new file mode 100644
index 000000000000..bae7996517c5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.systemui.qs.user
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.tiles.UserDetailView
+import javax.inject.Inject
+import javax.inject.Provider
+
+/**
+ * Controller for [UserDialog].
+ */
+@SysUISingleton
+class UserSwitchDialogController @VisibleForTesting constructor(
+ private val userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>,
+ private val activityStarter: ActivityStarter,
+ private val falsingManager: FalsingManager,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val dialogFactory: (Context) -> UserDialog
+) {
+
+ @Inject
+ constructor(
+ userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>,
+ activityStarter: ActivityStarter,
+ falsingManager: FalsingManager,
+ dialogLaunchAnimator: DialogLaunchAnimator
+ ) : this(
+ userDetailViewAdapterProvider,
+ activityStarter,
+ falsingManager,
+ dialogLaunchAnimator,
+ { UserDialog(it) }
+ )
+
+ companion object {
+ private val USER_SETTINGS_INTENT = Intent(Settings.ACTION_USER_SETTINGS)
+ }
+
+ /**
+ * Show a [UserDialog].
+ *
+ * Populate the dialog with information from and adapter obtained from
+ * [userDetailViewAdapterProvider] and show it as launched from [view].
+ */
+ fun showDialog(view: View) {
+ with(dialogFactory(view.context)) {
+ setShowForAllUsers(true)
+ setCanceledOnTouchOutside(true)
+ create() // Needs to be called before we can retrieve views
+
+ settingsButton.setOnClickListener {
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
+ activityStarter.postStartActivityDismissingKeyguard(
+ USER_SETTINGS_INTENT,
+ 0
+ )
+ }
+ dismiss()
+ }
+ doneButton.setOnClickListener { dismiss() }
+
+ val adapter = userDetailViewAdapterProvider.get()
+ adapter.injectCallback {
+ dismiss()
+ }
+ adapter.linkToViewGroup(grid)
+
+ dialogLaunchAnimator.showFromView(this, view)
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
index bbeff6ece902..77c61a4f1845 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
@@ -42,7 +42,7 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation {
private final static String TAG = "OverviewProxyRecentsImpl";
@Nullable
- private final Lazy<StatusBar> mStatusBarLazy;
+ private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
private Context mContext;
private Handler mHandler;
@@ -51,8 +51,8 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation {
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
- public OverviewProxyRecentsImpl(Optional<Lazy<StatusBar>> statusBarLazy) {
- mStatusBarLazy = statusBarLazy.orElse(null);
+ public OverviewProxyRecentsImpl(Lazy<Optional<StatusBar>> statusBarOptionalLazy) {
+ mStatusBarOptionalLazy = statusBarOptionalLazy;
}
@Override
@@ -109,8 +109,9 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation {
}
};
// Preload only if device for current user is unlocked
- if (mStatusBarLazy != null && mStatusBarLazy.get().isKeyguardShowing()) {
- mStatusBarLazy.get().executeRunnableDismissingKeyguard(() -> {
+ final Optional<StatusBar> statusBarOptional = mStatusBarOptionalLazy.get();
+ if (statusBarOptional.map(StatusBar::isKeyguardShowing).orElse(false)) {
+ statusBarOptional.get().executeRunnableDismissingKeyguard(() -> {
// Flush trustmanager before checking device locked per user
mTrustManager.reportKeyguardShowingChanged();
mHandler.post(toggleRecents);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index cb0c411b2753..721a6af37e21 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -34,6 +34,7 @@ import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUP
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
@@ -77,6 +78,8 @@ import com.android.internal.util.ScreenshotHelper;
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBar;
import com.android.systemui.navigationbar.NavigationBarController;
@@ -92,6 +95,7 @@ import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.CallbackController;
@@ -109,6 +113,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
+import java.util.function.Supplier;
import javax.inject.Inject;
@@ -134,7 +139,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
private final Context mContext;
private final Optional<Pip> mPipOptional;
- private final Optional<Lazy<StatusBar>> mStatusBarOptionalLazy;
+ private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
private final Optional<SplitScreen> mSplitScreenOptional;
private SysUiState mSysUiState;
@@ -171,55 +176,33 @@ public class OverviewProxyService extends CurrentUserTracker implements
public ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
@Override
public void startScreenPinning(int taskId) {
- if (!verifyCaller("startScreenPinning")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mHandler.post(() -> {
- mStatusBarOptionalLazy.ifPresent(
- statusBarLazy -> statusBarLazy.get().showScreenPinningRequest(taskId,
- false /* allowCancel */));
- });
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ verifyCallerAndClearCallingIdentityPostMain("startScreenPinning", () ->
+ mStatusBarOptionalLazy.get().ifPresent(
+ statusBar -> statusBar.showScreenPinningRequest(taskId,
+ false /* allowCancel */)));
}
@Override
public void stopScreenPinning() {
- if (!verifyCaller("stopScreenPinning")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mHandler.post(() -> {
- try {
- ActivityTaskManager.getService().stopSystemLockTaskMode();
- } catch (RemoteException e) {
- Log.e(TAG_OPS, "Failed to stop screen pinning");
- }
- });
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ verifyCallerAndClearCallingIdentityPostMain("stopScreenPinning", () -> {
+ try {
+ ActivityTaskManager.getService().stopSystemLockTaskMode();
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to stop screen pinning");
+ }
+ });
}
// TODO: change the method signature to use (boolean inputFocusTransferStarted)
@Override
public void onStatusBarMotionEvent(MotionEvent event) {
- if (!verifyCaller("onStatusBarMotionEvent")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
+ verifyCallerAndClearCallingIdentity("onStatusBarMotionEvent", () -> {
// TODO move this logic to message queue
- mStatusBarOptionalLazy.ifPresent(statusBarLazy -> {
- StatusBar statusBar = statusBarLazy.get();
+ mStatusBarOptionalLazy.get().ifPresent(statusBar -> {
if (event.getActionMasked() == ACTION_DOWN) {
statusBar.getPanelController().startExpandLatencyTracking();
}
- mHandler.post(()-> {
+ mHandler.post(() -> {
int action = event.getActionMasked();
if (action == ACTION_DOWN) {
mInputFocusTransferStarted = true;
@@ -231,50 +214,44 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
if (action == ACTION_UP || action == ACTION_CANCEL) {
mInputFocusTransferStarted = false;
+ float velocity = (event.getY() - mInputFocusTransferStartY)
+ / (event.getEventTime() - mInputFocusTransferStartMillis);
statusBar.onInputFocusTransfer(mInputFocusTransferStarted,
action == ACTION_CANCEL,
- (event.getY() - mInputFocusTransferStartY)
- / (event.getEventTime() - mInputFocusTransferStartMillis));
+ velocity);
}
event.recycle();
});
});
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ });
}
@Override
public void onBackPressed() throws RemoteException {
- if (!verifyCaller("onBackPressed")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mHandler.post(() -> {
- sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
- sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
+ verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
+ sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
+ sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
- notifyBackAction(true, -1, -1, true, false);
- });
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ notifyBackAction(true, -1, -1, true, false);
+ });
}
@Override
public void setHomeRotationEnabled(boolean enabled) {
- if (!verifyCaller("setHomeRotationEnabled")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mHandler.post(() -> {
- mHandler.post(() -> notifyHomeRotationEnabled(enabled));
- });
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ verifyCallerAndClearCallingIdentityPostMain("setHomeRotationEnabled", () ->
+ mHandler.post(() -> notifyHomeRotationEnabled(enabled)));
+ }
+
+ @Override
+ public void notifyTaskbarStatus(boolean visible, boolean stashed) {
+ verifyCallerAndClearCallingIdentityPostMain("notifyTaskbarStatus", () ->
+ onTaskbarStatusUpdated(visible, stashed));
+ }
+
+ @Override
+ public void notifyTaskbarAutohideSuspend(boolean suspend) {
+ verifyCallerAndClearCallingIdentityPostMain("notifyTaskbarAutohideSuspend", () ->
+ onTaskbarAutohideSuspend(suspend));
}
private boolean sendEvent(int action, int code) {
@@ -291,124 +268,74 @@ public class OverviewProxyService extends CurrentUserTracker implements
@Override
public void onOverviewShown(boolean fromHome) {
- if (!verifyCaller("onOverviewShown")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mHandler.post(() -> {
- for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onOverviewShown(fromHome);
- }
- });
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ verifyCallerAndClearCallingIdentityPostMain("onOverviewShown", () -> {
+ for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+ mConnectionCallbacks.get(i).onOverviewShown(fromHome);
+ }
+ });
}
@Override
public Rect getNonMinimizedSplitScreenSecondaryBounds() {
- if (!verifyCaller("getNonMinimizedSplitScreenSecondaryBounds")) {
- return null;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- return mLegacySplitScreenOptional.map(splitScreen ->
- splitScreen.getDividerView().getNonMinimizedSplitScreenSecondaryBounds())
- .orElse(null);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ return verifyCallerAndClearCallingIdentity(
+ "getNonMinimizedSplitScreenSecondaryBounds",
+ () -> mLegacySplitScreenOptional.map(splitScreen ->
+ splitScreen
+ .getDividerView()
+ .getNonMinimizedSplitScreenSecondaryBounds())
+ .orElse(null)
+ );
}
@Override
public void setNavBarButtonAlpha(float alpha, boolean animate) {
- if (!verifyCaller("setNavBarButtonAlpha")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mNavBarButtonAlpha = alpha;
- mHandler.post(() -> notifyNavBarButtonAlphaChanged(alpha, animate));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ verifyCallerAndClearCallingIdentityPostMain("setNavBarButtonAlpha", () ->
+ notifyNavBarButtonAlphaChanged(alpha, animate));
}
@Override
public void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {
- if (!verifyCaller("onAssistantProgress")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mHandler.post(() -> notifyAssistantProgress(progress));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ verifyCallerAndClearCallingIdentityPostMain("onAssistantProgress", () ->
+ notifyAssistantProgress(progress));
}
@Override
public void onAssistantGestureCompletion(float velocity) {
- if (!verifyCaller("onAssistantGestureCompletion")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mHandler.post(() -> notifyAssistantGestureCompletion(velocity));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ verifyCallerAndClearCallingIdentityPostMain("onAssistantGestureCompletion", () ->
+ notifyAssistantGestureCompletion(velocity));
}
@Override
public void startAssistant(Bundle bundle) {
- if (!verifyCaller("startAssistant")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mHandler.post(() -> notifyStartAssistant(bundle));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ verifyCallerAndClearCallingIdentityPostMain("startAssistant", () ->
+ notifyStartAssistant(bundle));
}
@Override
public void notifyAccessibilityButtonClicked(int displayId) {
- if (!verifyCaller("notifyAccessibilityButtonClicked")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- AccessibilityManager.getInstance(mContext)
- .notifyAccessibilityButtonClicked(displayId);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ verifyCallerAndClearCallingIdentity("notifyAccessibilityButtonClicked", () ->
+ AccessibilityManager.getInstance(mContext)
+ .notifyAccessibilityButtonClicked(displayId));
}
@Override
public void notifyAccessibilityButtonLongClicked() {
- if (!verifyCaller("notifyAccessibilityButtonLongClicked")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- final Intent intent =
- new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
- final String chooserClassName = AccessibilityButtonChooserActivity.class.getName();
- intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ verifyCallerAndClearCallingIdentity("notifyAccessibilityButtonLongClicked",
+ () -> {
+ final Intent intent =
+ new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
+ final String chooserClassName = AccessibilityButtonChooserActivity
+ .class.getName();
+ intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
+ intent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ });
}
@Override
public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen,
- Insets visibleInsets, int taskId) {
+ Insets visibleInsets, int taskId) {
// Deprecated
}
@@ -420,43 +347,22 @@ public class OverviewProxyService extends CurrentUserTracker implements
@Override
public void notifySwipeToHomeFinished() {
- if (!verifyCaller("notifySwipeToHomeFinished")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mPipOptional.ifPresent(
- pip -> pip.setPinnedStackAnimationType(
- PipAnimationController.ANIM_TYPE_ALPHA));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ verifyCallerAndClearCallingIdentity("notifySwipeToHomeFinished", () ->
+ mPipOptional.ifPresent(
+ pip -> pip.setPinnedStackAnimationType(
+ PipAnimationController.ANIM_TYPE_ALPHA)));
}
@Override
public void notifySwipeUpGestureStarted() {
- if (!verifyCaller("notifySwipeUpGestureStarted")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mHandler.post(() -> notifySwipeUpGestureStartedInternal());
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ verifyCallerAndClearCallingIdentityPostMain("notifySwipeUpGestureStarted", () ->
+ notifySwipeUpGestureStartedInternal());
}
@Override
public void notifyPrioritizedRotation(@Surface.Rotation int rotation) {
- if (!verifyCaller("notifyPrioritizedRotation")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mHandler.post(() -> notifyPrioritizedRotationInternal(rotation));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ verifyCallerAndClearCallingIdentityPostMain("notifyPrioritizedRotation", () ->
+ notifyPrioritizedRotationInternal(rotation));
}
@Override
@@ -476,15 +382,8 @@ public class OverviewProxyService extends CurrentUserTracker implements
@Override
public void expandNotificationPanel() {
- if (!verifyCaller("expandNotificationPanel")) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ verifyCallerAndClearCallingIdentity("expandNotificationPanel",
+ () -> mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN));
}
private boolean verifyCaller(String reason) {
@@ -496,6 +395,29 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
return true;
}
+
+ private <T> T verifyCallerAndClearCallingIdentity(String reason, Supplier<T> supplier) {
+ if (!verifyCaller(reason)) {
+ return null;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return supplier.get();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void verifyCallerAndClearCallingIdentity(String reason, Runnable runnable) {
+ verifyCallerAndClearCallingIdentity(reason, () -> {
+ runnable.run();
+ return null;
+ });
+ }
+
+ private void verifyCallerAndClearCallingIdentityPostMain(String reason, Runnable runnable) {
+ verifyCallerAndClearCallingIdentity(reason, () -> mHandler.post(runnable));
+ }
};
private final Runnable mDeferredConnectionCallback = () -> {
@@ -613,12 +535,14 @@ public class OverviewProxyService extends CurrentUserTracker implements
Optional<Pip> pipOptional,
Optional<LegacySplitScreen> legacySplitScreenOptional,
Optional<SplitScreen> splitScreenOptional,
- Optional<Lazy<StatusBar>> statusBarOptionalLazy,
+ Lazy<Optional<StatusBar>> statusBarOptionalLazy,
Optional<OneHanded> oneHandedOptional,
BroadcastDispatcher broadcastDispatcher,
ShellTransitions shellTransitions,
+ ScreenLifecycle screenLifecycle,
Optional<StartingSurface> startingSurface,
- SmartspaceTransitionController smartspaceTransitionController) {
+ SmartspaceTransitionController smartspaceTransitionController,
+ DumpManager dumpManager) {
super(broadcastDispatcher);
mContext = context;
mPipOptional = pipOptional;
@@ -631,7 +555,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
com.android.internal.R.string.config_recentsComponentName));
mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
.setPackage(mRecentsComponentName.getPackageName());
- mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources());
+ mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils
.supportsRoundedCornersOnWindows(mContext.getResources());
mSysUiState = sysUiState;
@@ -642,6 +566,8 @@ public class OverviewProxyService extends CurrentUserTracker implements
// Assumes device always starts with back button until launcher tells it that it does not
mNavBarButtonAlpha = 1.0f;
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
+
// Listen for nav bar mode changes
mNavBarMode = navModeController.addListener(this);
@@ -675,6 +601,13 @@ public class OverviewProxyService extends CurrentUserTracker implements
// Listen for user setup
startTracking();
+ screenLifecycle.addObserver(new ScreenLifecycle.Observer() {
+ @Override
+ public void onScreenTurnedOn() {
+ notifyScreenTurnedOn();
+ }
+ });
+
// Connect to the service
updateEnabledState();
startConnectionToCurrentUser();
@@ -704,18 +637,22 @@ public class OverviewProxyService extends CurrentUserTracker implements
mNavBarControllerLazy.get().getDefaultNavigationBar();
final NavigationBarView navBarView =
mNavBarControllerLazy.get().getNavigationBarView(mContext.getDisplayId());
+ final NotificationPanelViewController panelController =
+ mStatusBarOptionalLazy.get().get().getPanelController();
if (SysUiState.DEBUG) {
Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment
- + " navBarView=" + navBarView);
+ + " navBarView=" + navBarView + " panelController=" + panelController);
}
if (navBarFragment != null) {
navBarFragment.updateSystemUiStateFlags(-1);
}
if (navBarView != null) {
- navBarView.updatePanelSystemUiStateFlags();
navBarView.updateDisabledSystemUiStateFlags();
}
+ if (panelController != null) {
+ panelController.updateSystemUiStateFlags();
+ }
if (mStatusBarWinController != null) {
mStatusBarWinController.notifyStateChangedCallbacks();
}
@@ -736,12 +673,13 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
- boolean bouncerShowing) {
+ boolean bouncerShowing, boolean isDozing) {
mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
keyguardShowing && !keyguardOccluded)
.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED,
keyguardShowing && keyguardOccluded)
.setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing)
+ .setFlag(SYSUI_STATE_DEVICE_DOZING, isDozing)
.commitUpdate(mContext.getDisplayId());
}
@@ -766,10 +704,9 @@ public class OverviewProxyService extends CurrentUserTracker implements
public void cleanupAfterDeath() {
if (mInputFocusTransferStarted) {
mHandler.post(() -> {
- mStatusBarOptionalLazy.ifPresent(statusBarLazy -> {
+ mStatusBarOptionalLazy.get().ifPresent(statusBar -> {
mInputFocusTransferStarted = false;
- statusBarLazy.get().onInputFocusTransfer(false, true /* cancel */,
- 0 /* velocity */);
+ statusBar.onInputFocusTransfer(false, true /* cancel */, 0 /* velocity */);
});
});
}
@@ -881,6 +818,18 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
}
+ private void onTaskbarStatusUpdated(boolean visible, boolean stashed) {
+ for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+ mConnectionCallbacks.get(i).onTaskbarStatusUpdated(visible, stashed);
+ }
+ }
+
+ private void onTaskbarAutohideSuspend(boolean suspend) {
+ for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+ mConnectionCallbacks.get(i).onTaskbarAutohideSuspend(suspend);
+ }
+ }
+
private void notifyConnectionChanged() {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null);
@@ -961,25 +910,61 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
}
+ /**
+ * Notifies the Launcher that screen turned on and ready to use
+ */
+ public void notifyScreenTurnedOn() {
+ try {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.onScreenTurnedOn();
+ } else {
+ Log.e(TAG_OPS, "Failed to get overview proxy for screen turned on event.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call notifyScreenTurnedOn()", e);
+ }
+ }
+
void notifyToggleRecentApps() {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onToggleRecentApps();
}
}
- public void notifyImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
- boolean showImeSwitcher) {
+ public void disable(int displayId, int state1, int state2, boolean animate) {
try {
if (mOverviewProxy != null) {
- mOverviewProxy.onImeWindowStatusChanged(displayId, token, vis, backDisposition,
- showImeSwitcher);
+ mOverviewProxy.disable(displayId, state1, state2, animate);
} else {
- Log.e(TAG_OPS, "Failed to get overview proxy for setting IME status.");
+ Log.e(TAG_OPS, "Failed to get overview proxy for disable flags.");
}
} catch (RemoteException e) {
- Log.e(TAG_OPS, "Failed to call notifyImeWindowStatus()", e);
+ Log.e(TAG_OPS, "Failed to call disable()", e);
}
+ }
+ public void onRotationProposal(int rotation, boolean isValid) {
+ try {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.onRotationProposal(rotation, isValid);
+ } else {
+ Log.e(TAG_OPS, "Failed to get overview proxy for proposing rotation.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onRotationProposal()", e);
+ }
+ }
+
+ public void onSystemBarAttributesChanged(int displayId, int behavior) {
+ try {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.onSystemBarAttributesChanged(displayId, behavior);
+ } else {
+ Log.e(TAG_OPS, "Failed to get overview proxy for system bar attr change.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onSystemBarAttributesChanged()", e);
+ }
}
private void updateEnabledState() {
@@ -1026,11 +1011,11 @@ public class OverviewProxyService extends CurrentUserTracker implements
/** Notify changes in the nav bar button alpha */
default void onNavBarButtonAlphaChanged(float alpha, boolean animate) {}
default void onHomeRotationEnabled(boolean enabled) {}
+ default void onTaskbarStatusUpdated(boolean visible, boolean stashed) {}
+ default void onTaskbarAutohideSuspend(boolean suspend) {}
default void onSystemUiStateChanged(int sysuiStateFlags) {}
default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
default void onAssistantGestureCompletion(float velocity) {}
default void startAssistant(Bundle bundle) {}
- default void onImeWindowStatusChanged(int displayId, IBinder token, int vis,
- int backDisposition, boolean showImeSwitcher) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index aa8d710e7570..85bf98c09f59 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -51,10 +51,10 @@ import android.widget.TextView;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.util.leak.RotationUtils;
@@ -69,7 +69,7 @@ public class ScreenPinningRequest implements View.OnClickListener,
NavigationModeController.ModeChangedListener {
private final Context mContext;
- private final Optional<Lazy<StatusBar>> mStatusBarOptionalLazy;
+ private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
private final AccessibilityManager mAccessibilityService;
private final WindowManager mWindowManager;
@@ -82,7 +82,7 @@ public class ScreenPinningRequest implements View.OnClickListener,
private int taskId;
@Inject
- public ScreenPinningRequest(Context context, Optional<Lazy<StatusBar>> statusBarOptionalLazy) {
+ public ScreenPinningRequest(Context context, Lazy<Optional<StatusBar>> statusBarOptionalLazy) {
mContext = context;
mStatusBarOptionalLazy = statusBarOptionalLazy;
mAccessibilityService = (AccessibilityManager)
@@ -266,8 +266,9 @@ public class ScreenPinningRequest implements View.OnClickListener,
.setVisibility(View.INVISIBLE);
}
- NavigationBarView navigationBarView = mStatusBarOptionalLazy.map(
- statusBarLazy -> statusBarLazy.get().getNavigationBarView()).orElse(null);
+ final Optional<StatusBar> statusBarOptional = mStatusBarOptionalLazy.get();
+ NavigationBarView navigationBarView =
+ statusBarOptional.map(StatusBar::getNavigationBarView).orElse(null);
final boolean recentsVisible = navigationBarView != null
&& navigationBarView.isRecentsButtonVisible();
boolean touchExplorationEnabled = mAccessibilityService.isTouchExplorationEnabled();
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java
index 0aa9d4d662a5..5a6f2a2a912d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java
@@ -27,6 +27,7 @@ import android.media.MediaMuxer;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
import android.util.Log;
+import android.util.MathUtils;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -128,30 +129,64 @@ public class ScreenInternalAudioRecorder {
mThread = new Thread(() -> {
short[] bufferInternal = null;
short[] bufferMic = null;
- byte[] buffer = null;
+ byte[] buffer = new byte[size];
if (mMic) {
bufferInternal = new short[size / 2];
bufferMic = new short[size / 2];
- } else {
- buffer = new byte[size];
}
+ int readBytes = 0;
+ int readShortsInternal = 0;
+ int offsetShortsInternal = 0;
+ int readShortsMic = 0;
+ int offsetShortsMic = 0;
while (true) {
- int readBytes = 0;
- int readShortsInternal = 0;
- int readShortsMic = 0;
if (mMic) {
- readShortsInternal = mAudioRecord.read(bufferInternal, 0,
- bufferInternal.length);
- readShortsMic = mAudioRecordMic.read(bufferMic, 0, bufferMic.length);
+ readShortsInternal = mAudioRecord.read(bufferInternal, offsetShortsInternal,
+ bufferInternal.length - offsetShortsInternal);
+ readShortsMic = mAudioRecordMic.read(
+ bufferMic, offsetShortsMic, bufferMic.length - offsetShortsMic);
+
+ // if both error, end the recording
+ if (readShortsInternal < 0 && readShortsMic < 0) {
+ break;
+ }
+
+ // if one has an errors, fill its buffer with zeros and assume it is mute
+ // with the same size as the other buffer
+ if (readShortsInternal < 0) {
+ readShortsInternal = readShortsMic;
+ offsetShortsInternal = offsetShortsMic;
+ java.util.Arrays.fill(bufferInternal, (short) 0);
+ }
+
+ if (readShortsMic < 0) {
+ readShortsMic = readShortsInternal;
+ offsetShortsMic = offsetShortsInternal;
+ java.util.Arrays.fill(bufferMic, (short) 0);
+ }
+
+ // Add offset (previous unmixed values) to the buffer
+ readShortsInternal += offsetShortsInternal;
+ readShortsMic += offsetShortsMic;
+
+ int minShorts = Math.min(readShortsInternal, readShortsMic);
+ readBytes = minShorts * 2;
// modify the volume
- bufferMic = scaleValues(bufferMic,
- readShortsMic, MIC_VOLUME_SCALE);
- readBytes = Math.min(readShortsInternal, readShortsMic) * 2;
- buffer = addAndConvertBuffers(bufferInternal, readShortsInternal, bufferMic,
- readShortsMic);
+ // scale only mixed shorts
+ scaleValues(bufferMic, minShorts, MIC_VOLUME_SCALE);
+ // Mix the two buffers
+ addAndConvertBuffers(bufferInternal, bufferMic, buffer, minShorts);
+
+ // shift unmixed shorts to the beginning of the buffer
+ shiftToStart(bufferInternal, minShorts, offsetShortsInternal);
+ shiftToStart(bufferMic, minShorts, offsetShortsMic);
+
+ // reset the offset for the next loop
+ offsetShortsInternal = readShortsInternal - minShorts;
+ offsetShortsMic = readShortsMic - minShorts;
} else {
readBytes = mAudioRecord.read(buffer, 0, buffer.length);
}
@@ -169,40 +204,31 @@ public class ScreenInternalAudioRecorder {
});
}
- private short[] scaleValues(short[] buff, int len, float scale) {
+ /**
+ * moves all bits from start to end to the beginning of the array
+ */
+ private void shiftToStart(short[] target, int start, int end) {
+ for (int i = 0; i < end - start; i++) {
+ target[i] = target[start + i];
+ }
+ }
+
+ private void scaleValues(short[] buff, int len, float scale) {
for (int i = 0; i < len; i++) {
- int oldValue = buff[i];
int newValue = (int) (buff[i] * scale);
- if (newValue > Short.MAX_VALUE) {
- newValue = Short.MAX_VALUE;
- } else if (newValue < Short.MIN_VALUE) {
- newValue = Short.MIN_VALUE;
- }
- buff[i] = (short) (newValue);
+ buff[i] = (short) MathUtils.constrain(newValue, Short.MIN_VALUE, Short.MAX_VALUE);
}
- return buff;
}
- private byte[] addAndConvertBuffers(short[] a1, int a1Limit, short[] a2, int a2Limit) {
- int size = Math.max(a1Limit, a2Limit);
- if (size < 0) return new byte[0];
- byte[] buff = new byte[size * 2];
- for (int i = 0; i < size; i++) {
- int sum;
- if (i > a1Limit) {
- sum = a2[i];
- } else if (i > a2Limit) {
- sum = a1[i];
- } else {
- sum = (int) a1[i] + (int) a2[i];
- }
- if (sum > Short.MAX_VALUE) sum = Short.MAX_VALUE;
- if (sum < Short.MIN_VALUE) sum = Short.MIN_VALUE;
+ private void addAndConvertBuffers(short[] src1, short[] src2, byte[] dst, int sizeShorts) {
+ for (int i = 0; i < sizeShorts; i++) {
+ int sum;
+ sum = (short) MathUtils.constrain(
+ (int) src1[i] + (int) src2[i], Short.MIN_VALUE, Short.MAX_VALUE);
int byteIndex = i * 2;
- buff[byteIndex] = (byte) (sum & 0xff);
- buff[byteIndex + 1] = (byte) ((sum >> 8) & 0xff);
+ dst[byteIndex] = (byte) (sum & 0xff);
+ dst[byteIndex + 1] = (byte) ((sum >> 8) & 0xff);
}
- return buff;
}
private void encode(byte[] buffer, int readBytes) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 31d51f1d1a60..a42b34cf23d0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -326,18 +326,11 @@ public class LongScreenshotActivity extends Activity {
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
mTransitionView.setImageBitmap(mOutputBitmap);
+ mTransitionView.setVisibility(View.VISIBLE);
mTransitionView.setTransitionName(
ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
// TODO: listen for transition completing instead of finishing onStop
mTransitionStarted = true;
- int[] locationOnScreen = new int[2];
- mTransitionView.getLocationOnScreen(locationOnScreen);
- int[] locationInWindow = new int[2];
- mTransitionView.getLocationInWindow(locationInWindow);
- int deltaX = locationOnScreen[0] - locationInWindow[0];
- int deltaY = locationOnScreen[1] - locationInWindow[1];
- mTransitionView.setX(mTransitionView.getX() - deltaX);
- mTransitionView.setY(mTransitionView.getY() - deltaY);
startActivity(intent,
ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView,
ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle());
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 8def475c192c..5b4db1449b34 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -940,12 +940,10 @@ public class ScreenshotController {
*/
private Supplier<ActionTransition> getActionTransitionSupplier() {
return () -> {
- View preview = mScreenshotView.getTransitionView();
- preview.setX(preview.getX() - mScreenshotView.getStaticLeftMargin());
Pair<ActivityOptions, ExitTransitionCoordinator> transition =
ActivityOptions.startSharedElementAnimation(
mWindow, new ScreenshotExitTransitionCallbacksSupplier(true).get(),
- null, Pair.create(mScreenshotView.getTransitionView(),
+ null, Pair.create(mScreenshotView.getScreenshotPreview(),
ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
transition.second.startExit();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index dfb39e300450..7222b0313fb4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -137,13 +137,11 @@ public class ScreenshotView extends FrameLayout implements
private int mNavMode;
private boolean mOrientationPortrait;
private boolean mDirectionLTR;
- private int mStaticLeftMargin;
private ScreenshotSelectorView mScreenshotSelectorView;
private ImageView mScrollingScrim;
private View mScreenshotStatic;
private ImageView mScreenshotPreview;
- private View mTransitionView;
private View mScreenshotPreviewBorder;
private ImageView mScrollablePreview;
private ImageView mScreenshotFlash;
@@ -341,7 +339,6 @@ public class ScreenshotView extends FrameLayout implements
mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim));
mScreenshotStatic = requireNonNull(findViewById(R.id.global_screenshot_static));
mScreenshotPreview = requireNonNull(findViewById(R.id.global_screenshot_preview));
- mTransitionView = requireNonNull(findViewById(R.id.screenshot_transition_view));
mScreenshotPreviewBorder = requireNonNull(
findViewById(R.id.global_screenshot_preview_border));
mScreenshotPreview.setClipToOutline(true);
@@ -387,12 +384,8 @@ public class ScreenshotView extends FrameLayout implements
requestFocus();
}
- View getTransitionView() {
- return mTransitionView;
- }
-
- int getStaticLeftMargin() {
- return mStaticLeftMargin;
+ View getScreenshotPreview() {
+ return mScreenshotPreview;
}
/**
@@ -442,7 +435,6 @@ public class ScreenshotView extends FrameLayout implements
Math.max(navBarInsets.bottom, waterfall.bottom));
}
}
- mStaticLeftMargin = p.leftMargin;
mScreenshotStatic.setLayoutParams(p);
mScreenshotStatic.requestLayout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 1ad253e7f867..d7d1de00c82d 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -30,7 +30,6 @@ import android.hardware.display.DisplayManager.DisplayListener;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
@@ -47,15 +46,14 @@ import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtilsInternal;
-import com.android.systemui.Dependency;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.settings.CurrentUserTracker;
-
-import java.util.ArrayList;
+import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import javax.inject.Inject;
-public class BrightnessController implements ToggleSlider.Listener {
+public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
private static final String TAG = "StatusBar.BrightnessController";
private static final int SLIDER_ANIMATION_DURATION = 3000;
@@ -92,13 +90,9 @@ public class BrightnessController implements ToggleSlider.Listener {
@Override
public void onDisplayChanged(int displayId) {
mBackgroundHandler.post(mUpdateSliderRunnable);
- notifyCallbacks();
}
};
- private ArrayList<BrightnessStateChangeCallback> mChangeCallbacks =
- new ArrayList<BrightnessStateChangeCallback>();
-
private volatile boolean mAutomatic; // Brightness adjusted automatically using ambient light.
private volatile boolean mIsVrModeEnabled;
private boolean mListening;
@@ -109,9 +103,9 @@ public class BrightnessController implements ToggleSlider.Listener {
private ValueAnimator mSliderAnimator;
- public interface BrightnessStateChangeCallback {
- /** Indicates that some of the brightness settings have changed */
- void onBrightnessLevelChanged();
+ @Override
+ public void setMirror(BrightnessMirrorController controller) {
+ mControl.setMirrorControllerAndMirror(controller);
}
/** ContentObserver to watch brightness */
@@ -134,7 +128,6 @@ public class BrightnessController implements ToggleSlider.Listener {
mBackgroundHandler.post(mUpdateModeRunnable);
mBackgroundHandler.post(mUpdateSliderRunnable);
}
- notifyCallbacks();
}
public void startObserving() {
@@ -282,12 +275,15 @@ public class BrightnessController implements ToggleSlider.Listener {
}
};
- public BrightnessController(Context context, ToggleSlider control,
- BroadcastDispatcher broadcastDispatcher) {
+ public BrightnessController(
+ Context context,
+ ToggleSlider control,
+ BroadcastDispatcher broadcastDispatcher,
+ @Background Handler bgHandler) {
mContext = context;
mControl = control;
mControl.setMax(GAMMA_SPACE_MAX);
- mBackgroundHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
+ mBackgroundHandler = bgHandler;
mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
@Override
public void onUserSwitched(int newUserId) {
@@ -309,14 +305,6 @@ public class BrightnessController implements ToggleSlider.Listener {
Context.VR_SERVICE));
}
- public void addStateChangedCallback(BrightnessStateChangeCallback cb) {
- mChangeCallbacks.add(cb);
- }
-
- public boolean removeStateChangedCallback(BrightnessStateChangeCallback cb) {
- return mChangeCallbacks.remove(cb);
- }
-
public void registerCallbacks() {
mBackgroundHandler.post(mStartListeningRunnable);
}
@@ -367,10 +355,6 @@ public class BrightnessController implements ToggleSlider.Listener {
}
});
}
-
- for (BrightnessStateChangeCallback cb : mChangeCallbacks) {
- cb.onBrightnessLevelChanged();
- }
}
public void checkRestrictionAndSetEnabled() {
@@ -385,6 +369,14 @@ public class BrightnessController implements ToggleSlider.Listener {
});
}
+ public void hideSlider() {
+ mControl.hideView();
+ }
+
+ public void showSlider() {
+ mControl.showView();
+ }
+
private void setBrightness(float brightness) {
mDisplayManager.setTemporaryBrightness(mDisplayId, brightness);
}
@@ -419,8 +411,12 @@ public class BrightnessController implements ToggleSlider.Listener {
}
private void animateSliderTo(int target) {
- if (!mControlValueInitialized) {
+ if (!mControlValueInitialized || !mControl.isVisible()) {
// Don't animate the first value since its default state isn't meaningful to users.
+ // We also don't want to animate slider if it's not visible - especially important when
+ // two sliders are active at the same time in split shade (one in QS and one in QQS),
+ // as this negatively affects transition between them and they share mirror slider -
+ // animating it from two different sources causes janky motion
mControl.setValue(target);
mControlValueInitialized = true;
}
@@ -439,27 +435,29 @@ public class BrightnessController implements ToggleSlider.Listener {
mSliderAnimator.start();
}
- private void notifyCallbacks() {
- final int size = mChangeCallbacks.size();
- for (int i = 0; i < size; i++) {
- mChangeCallbacks.get(i).onBrightnessLevelChanged();
- }
- }
-
/** Factory for creating a {@link BrightnessController}. */
public static class Factory {
private final Context mContext;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final Handler mBackgroundHandler;
@Inject
- public Factory(Context context, BroadcastDispatcher broadcastDispatcher) {
+ public Factory(
+ Context context,
+ BroadcastDispatcher broadcastDispatcher,
+ @Background Handler bgHandler) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
+ mBackgroundHandler = bgHandler;
}
/** Create a {@link BrightnessController} */
public BrightnessController create(ToggleSlider toggleSlider) {
- return new BrightnessController(mContext, toggleSlider, mBroadcastDispatcher);
+ return new BrightnessController(
+ mContext,
+ toggleSlider,
+ mBroadcastDispatcher,
+ mBackgroundHandler);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 0f97e43c466b..c9c1a9b55c3f 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -21,6 +21,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.Activity;
import android.os.Bundle;
+import android.os.Handler;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
@@ -32,6 +33,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
import javax.inject.Inject;
@@ -39,15 +41,18 @@ import javax.inject.Inject;
public class BrightnessDialog extends Activity {
private BrightnessController mBrightnessController;
- private final BrightnessSlider.Factory mToggleSliderFactory;
+ private final BrightnessSliderController.Factory mToggleSliderFactory;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final Handler mBackgroundHandler;
@Inject
public BrightnessDialog(
BroadcastDispatcher broadcastDispatcher,
- BrightnessSlider.Factory factory) {
+ BrightnessSliderController.Factory factory,
+ @Background Handler bgHandler) {
mBroadcastDispatcher = broadcastDispatcher;
mToggleSliderFactory = factory;
+ mBackgroundHandler = bgHandler;
}
@@ -72,11 +77,12 @@ public class BrightnessDialog extends Activity {
// The brightness mirror container is INVISIBLE by default.
frame.setVisibility(View.VISIBLE);
- BrightnessSlider controller = mToggleSliderFactory.create(this, frame);
+ BrightnessSliderController controller = mToggleSliderFactory.create(this, frame);
controller.init();
frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
- mBrightnessController = new BrightnessController(this, controller, mBroadcastDispatcher);
+ mBrightnessController = new BrightnessController(
+ this, controller, mBroadcastDispatcher, mBackgroundHandler);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
new file mode 100644
index 000000000000..51aa339149a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.systemui.settings.brightness
+
+import com.android.systemui.statusbar.policy.BrightnessMirrorController
+import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener
+
+class BrightnessMirrorHandler(private val brightnessController: MirroredBrightnessController) {
+
+ private var mirrorController: BrightnessMirrorController? = null
+
+ private val brightnessMirrorListener = BrightnessMirrorListener { updateBrightnessMirror() }
+
+ fun onQsPanelAttached() {
+ mirrorController?.addCallback(brightnessMirrorListener)
+ }
+
+ fun onQsPanelDettached() {
+ mirrorController?.removeCallback(brightnessMirrorListener)
+ }
+
+ fun setController(controller: BrightnessMirrorController) {
+ mirrorController?.removeCallback(brightnessMirrorListener)
+ mirrorController = controller
+ mirrorController?.addCallback(brightnessMirrorListener)
+ updateBrightnessMirror()
+ }
+
+ private fun updateBrightnessMirror() {
+ mirrorController?.let { brightnessController.setMirror(it) }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index 0ff6216ea87e..6c8190af27f7 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -44,7 +44,8 @@ import javax.inject.Inject;
*
* @see BrightnessMirrorController
*/
-public class BrightnessSlider extends ViewController<BrightnessSliderView> implements ToggleSlider {
+public class BrightnessSliderController extends ViewController<BrightnessSliderView> implements
+ ToggleSlider {
private Listener mListener;
private ToggleSlider mMirror;
@@ -69,7 +70,7 @@ public class BrightnessSlider extends ViewController<BrightnessSliderView> imple
}
};
- BrightnessSlider(
+ BrightnessSliderController(
BrightnessSliderView brightnessSliderView,
FalsingManager falsingManager) {
super(brightnessSliderView);
@@ -140,13 +141,7 @@ public class BrightnessSlider extends ViewController<BrightnessSliderView> imple
@Override
public void setMirrorControllerAndMirror(BrightnessMirrorController c) {
mMirrorController = c;
- if (c != null) {
- setMirror(c.getToggleSlider());
- } else {
- // If there's no mirror, we may be the ones dispatching, events but we should not mirror
- // them
- mView.setOnDispatchTouchEventListener(null);
- }
+ setMirror(c.getToggleSlider());
}
@Override
@@ -180,6 +175,25 @@ public class BrightnessSlider extends ViewController<BrightnessSliderView> imple
return mView.getValue();
}
+ @Override
+ public void hideView() {
+ mView.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void showView() {
+ mView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public boolean isVisible() {
+ // this should be called rarely - once or twice per slider's value change, but not for
+ // every value change when user slides finger - only the final one.
+ // If view is not visible this call is quick (around 50 µs) as it sees parent is not visible
+ // otherwise it's slightly longer (70 µs) because there are more checks to be done
+ return mView.isVisibleToUser();
+ }
+
private final SeekBar.OnSeekBarChangeListener mSeekListener =
new SeekBar.OnSeekBarChangeListener() {
@Override
@@ -218,7 +232,7 @@ public class BrightnessSlider extends ViewController<BrightnessSliderView> imple
};
/**
- * Creates a {@link BrightnessSlider} with its associated view.
+ * Creates a {@link BrightnessSliderController} with its associated view.
*/
public static class Factory {
@@ -236,11 +250,11 @@ public class BrightnessSlider extends ViewController<BrightnessSliderView> imple
* @param viewRoot the {@link ViewGroup} that will contain the hierarchy. The inflated
* hierarchy will not be attached
*/
- public BrightnessSlider create(Context context, @Nullable ViewGroup viewRoot) {
+ public BrightnessSliderController create(Context context, @Nullable ViewGroup viewRoot) {
int layout = getLayout();
BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context)
.inflate(layout, viewRoot, false);
- return new BrightnessSlider(root, mFalsingManager);
+ return new BrightnessSliderController(root, mFalsingManager);
}
/** Get the layout to inflate based on what slider to use */
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index 15aa2b730adf..0e037ad56257 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -60,6 +60,7 @@ public class BrightnessSliderView extends FrameLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ setLayerType(LAYER_TYPE_HARDWARE, null);
mSlider = requireViewById(R.id.slider);
mSlider.setAccessibilityLabel(getContentDescription().toString());
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
new file mode 100644
index 000000000000..8d857dec2108
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.systemui.settings.brightness
+
+import com.android.systemui.statusbar.policy.BrightnessMirrorController
+
+/**
+ * Indicates controller that has brightness slider and uses [BrightnessMirrorController]
+ */
+interface MirroredBrightnessController {
+ fun setMirror(controller: BrightnessMirrorController)
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
index a988c7aeb436..648e33b1d228 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
@@ -35,4 +35,8 @@ public interface ToggleSlider {
int getMax();
void setValue(int value);
int getValue();
+
+ void showView();
+ void hideView();
+ boolean isVisible();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 50911d162113..6676901997bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -51,6 +51,7 @@ import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.view.InsetsState.InternalInsetsType;
+import android.view.InsetsVisibilities;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -78,7 +79,8 @@ import java.util.ArrayList;
* coalescing these calls so they don't stack up. For the calls
* are coalesced, note that they are all idempotent.
*/
-public class CommandQueue extends IStatusBar.Stub implements CallbackController<Callbacks>,
+public class CommandQueue extends IStatusBar.Stub implements
+ CallbackController<Callbacks>,
DisplayManager.DisplayListener {
private static final String TAG = CommandQueue.class.getSimpleName();
@@ -337,7 +339,8 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
*/
default void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, boolean isFullscreen) { }
+ @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+ String packageName) { }
/**
* @see IStatusBar#showTransient(int, int[]).
@@ -997,7 +1000,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
@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) {
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
args.argi1 = displayId;
@@ -1005,7 +1008,8 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
args.argi3 = navbarColorManagedByIme ? 1 : 0;
args.arg1 = appearanceRegions;
args.argi4 = behavior;
- args.argi5 = isFullscreen ? 1 : 0;
+ args.arg2 = requestedVisibilities;
+ args.arg3 = packageName;
mHandler.obtainMessage(MSG_SYSTEM_BAR_CHANGED, args).sendToTarget();
}
}
@@ -1389,7 +1393,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onSystemBarAttributesChanged(args.argi1, args.argi2,
(AppearanceRegion[]) args.arg1, args.argi3 == 1, args.argi4,
- args.argi5 == 1);
+ (InsetsVisibilities) args.arg2, (String) args.arg3);
}
args.recycle();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt
new file mode 100644
index 000000000000..cf34db233b06
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.systemui.statusbar
+
+import android.app.StatusBarManager.DISABLE_BACK
+import android.app.StatusBarManager.DISABLE_CLOCK
+import android.app.StatusBarManager.DISABLE_EXPAND
+import android.app.StatusBarManager.DISABLE_HOME
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS
+import android.app.StatusBarManager.DISABLE_ONGOING_CALL_CHIP
+import android.app.StatusBarManager.DISABLE_RECENT
+import android.app.StatusBarManager.DISABLE_SEARCH
+import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
+import android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS
+import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
+import android.app.StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS
+import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS
+import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * A singleton that creates concise but readable strings representing the values of the disable
+ * flags for debugging.
+ *
+ * See [CommandQueue.disable] for information about disable flags.
+ *
+ * Note that, for both lists passed in, each flag must have a distinct [DisableFlag.flagIsSetSymbol]
+ * and distinct [DisableFlag.flagNotSetSymbol] within the list. If this isn't true, the logs could
+ * be ambiguous so an [IllegalArgumentException] is thrown.
+ */
+@SysUISingleton
+class DisableFlagsLogger constructor(
+ private val disable1FlagsList: List<DisableFlag>,
+ private val disable2FlagsList: List<DisableFlag>
+) {
+
+ @Inject
+ constructor() : this(defaultDisable1FlagsList, defaultDisable2FlagsList)
+
+ init {
+ if (flagsListHasDuplicateSymbols(disable1FlagsList)) {
+ throw IllegalArgumentException("disable1 flags must have unique symbols")
+ }
+ if (flagsListHasDuplicateSymbols(disable2FlagsList)) {
+ throw IllegalArgumentException("disable2 flags must have unique symbols")
+ }
+ }
+
+ private fun flagsListHasDuplicateSymbols(list: List<DisableFlag>): Boolean {
+ val numDistinctFlagOffStatus = list.map { it.getFlagStatus(0) }.distinct().count()
+ val numDistinctFlagOnStatus = list
+ .map { it.getFlagStatus(Int.MAX_VALUE) }
+ .distinct()
+ .count()
+ return numDistinctFlagOffStatus < list.count() || numDistinctFlagOnStatus < list.count()
+ }
+
+ /**
+ * Returns a string representing the, old, new, and new-after-modification disable flag states,
+ * as well as the differences between each of the states.
+ *
+ * Example:
+ * Old: EnaiHbcRso.qINgr | New: EnaihBcRso.qiNGR (hB.iGR) | New after local modification:
+ * EnaihBcRso.qInGR (.n)
+ *
+ * A capital character signifies the flag is set and a lowercase character signifies that the
+ * flag isn't set. The flag states will be logged in the same order as the passed-in lists.
+ *
+ * The difference between states is written between parentheses, and won't be included if there
+ * is no difference. the new-after-modification state also won't be included if there's no
+ * difference from the new state.
+ *
+ * @param old the disable state that had been previously sent.
+ * @param new the new disable state that has just been sent.
+ * @param newAfterLocalModification the new disable states after a class has locally modified
+ * them. Null if the class does not locally modify.
+ */
+ fun getDisableFlagsString(
+ old: DisableState,
+ new: DisableState,
+ newAfterLocalModification: DisableState? = null
+ ): String {
+ val builder = StringBuilder("Received new disable state. ")
+ builder.append("Old: ")
+ builder.append(getFlagsString(old))
+ builder.append(" | New: ")
+ if (old != new) {
+ builder.append(getFlagsStringWithDiff(old, new))
+ } else {
+ builder.append(getFlagsString(old))
+ }
+
+ if (newAfterLocalModification != null && new != newAfterLocalModification) {
+ builder.append(" | New after local modification: ")
+ builder.append(getFlagsStringWithDiff(new, newAfterLocalModification))
+ }
+
+ return builder.toString()
+ }
+
+ /**
+ * Returns a string representing [new] state, as well as the difference from [old] to [new]
+ * (if there is one).
+ */
+ private fun getFlagsStringWithDiff(old: DisableState, new: DisableState): String {
+ val builder = StringBuilder()
+ builder.append(getFlagsString(new))
+ builder.append(" ")
+ builder.append(getDiffString(old, new))
+ return builder.toString()
+ }
+
+ /**
+ * Returns a string representing the difference between [old] and [new], or an empty string if
+ * there is no difference.
+ *
+ * For example, if old was "abc.DE" and new was "aBC.De", the difference returned would be
+ * "(BC.e)".
+ */
+ private fun getDiffString(old: DisableState, new: DisableState): String {
+ if (old == new) {
+ return ""
+ }
+
+ val builder = StringBuilder("(")
+ disable1FlagsList.forEach {
+ val newSymbol = it.getFlagStatus(new.disable1)
+ if (it.getFlagStatus(old.disable1) != newSymbol) {
+ builder.append(newSymbol)
+ }
+ }
+ builder.append(".")
+ disable2FlagsList.forEach {
+ val newSymbol = it.getFlagStatus(new.disable2)
+ if (it.getFlagStatus(old.disable2) != newSymbol) {
+ builder.append(newSymbol)
+ }
+ }
+ builder.append(")")
+ return builder.toString()
+ }
+
+ /** Returns a string representing the disable flag states, e.g. "EnaihBcRso.qiNGR". */
+ private fun getFlagsString(state: DisableState): String {
+ val builder = StringBuilder("")
+ disable1FlagsList.forEach { builder.append(it.getFlagStatus(state.disable1)) }
+ builder.append(".")
+ disable2FlagsList.forEach { builder.append(it.getFlagStatus(state.disable2)) }
+ return builder.toString()
+ }
+
+ /** A POJO representing each disable flag. */
+ class DisableFlag(
+ private val bitMask: Int,
+ private val flagIsSetSymbol: Char,
+ private val flagNotSetSymbol: Char
+ ) {
+
+ /**
+ * Returns a character representing whether or not this flag is set in [state].
+ *
+ * A capital character signifies the flag is set and a lowercase character signifies that
+ * the flag isn't set.
+ */
+ internal fun getFlagStatus(state: Int): Char =
+ if (0 != state and this.bitMask) this.flagIsSetSymbol
+ else this.flagNotSetSymbol
+ }
+
+ /** POJO to hold [disable1] and [disable2]. */
+ data class DisableState(val disable1: Int, val disable2: Int)
+}
+
+// LINT.IfChange
+private val defaultDisable1FlagsList: List<DisableFlagsLogger.DisableFlag> = listOf(
+ DisableFlagsLogger.DisableFlag(DISABLE_EXPAND, 'E', 'e'),
+ DisableFlagsLogger.DisableFlag(DISABLE_NOTIFICATION_ICONS, 'N', 'n'),
+ DisableFlagsLogger.DisableFlag(DISABLE_NOTIFICATION_ALERTS, 'A', 'a'),
+ DisableFlagsLogger.DisableFlag(DISABLE_SYSTEM_INFO, 'I', 'i'),
+ DisableFlagsLogger.DisableFlag(DISABLE_HOME, 'H', 'h'),
+ DisableFlagsLogger.DisableFlag(DISABLE_BACK, 'B', 'b'),
+ DisableFlagsLogger.DisableFlag(DISABLE_CLOCK, 'C', 'c'),
+ DisableFlagsLogger.DisableFlag(DISABLE_RECENT, 'R', 'r'),
+ DisableFlagsLogger.DisableFlag(DISABLE_SEARCH, 'S', 's'),
+ DisableFlagsLogger.DisableFlag(DISABLE_ONGOING_CALL_CHIP, 'O', 'o')
+)
+
+private val defaultDisable2FlagsList: List<DisableFlagsLogger.DisableFlag> = listOf(
+ DisableFlagsLogger.DisableFlag(DISABLE2_QUICK_SETTINGS, 'Q', 'q'),
+ DisableFlagsLogger.DisableFlag(DISABLE2_SYSTEM_ICONS, 'I', 'i'),
+ DisableFlagsLogger.DisableFlag(DISABLE2_NOTIFICATION_SHADE, 'N', 'n'),
+ DisableFlagsLogger.DisableFlag(DISABLE2_GLOBAL_ACTIONS, 'G', 'g'),
+ DisableFlagsLogger.DisableFlag(DISABLE2_ROTATE_SUGGESTIONS, 'R', 'r')
+)
+// LINT.ThenChange(frameworks/base/core/java/android/app/StatusBarManager.java) \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
deleted file mode 100644
index 5a4245853a6f..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import android.content.Context;
-import android.util.FeatureFlagUtils;
-
-import com.android.systemui.R;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlagReader;
-
-import javax.inject.Inject;
-
-/**
- * Class to manage simple DeviceConfig-based feature flags.
- *
- * See {@link FeatureFlagReader} for instructions on defining and flipping flags.
- */
-@SysUISingleton
-public class FeatureFlags {
- private final FeatureFlagReader mFlagReader;
- private final Context mContext;
-
- @Inject
- public FeatureFlags(FeatureFlagReader flagReader, Context context) {
- mFlagReader = flagReader;
- mContext = context;
- }
-
- public boolean isNewNotifPipelineEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2);
- }
-
- public boolean isNewNotifPipelineRenderingEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2_rendering);
- }
-
- /** b/171917882 */
- public boolean isTwoColumnNotificationShadeEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_notification_twocolumn);
- }
-
- public boolean isKeyguardLayoutEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_keyguard_layout);
- }
-
- public boolean useNewLockscreenAnimations() {
- return mFlagReader.isEnabled(R.bool.flag_lockscreen_animations);
- }
-
- public boolean isPeopleTileEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_conversations);
- }
-
- public boolean isMonetEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_monet);
- }
-
- public boolean isPMLiteEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_pm_lite);
- }
-
- public boolean isChargingRippleEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_charging_ripple);
- }
-
- public boolean isOngoingCallStatusBarChipEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_ongoing_call_status_bar_chip);
- }
-
- public boolean isSmartspaceEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_smartspace);
- }
-
- public boolean isSmartspaceDedupingEnabled() {
- return isSmartspaceEnabled() && mFlagReader.isEnabled(R.bool.flag_smartspace_deduping);
- }
-
- public boolean isNewKeyguardSwipeAnimationEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_new_unlock_swipe_animation);
- }
-
- public boolean isSmartSpaceSharedElementTransitionEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_smartspace_shared_element_transition);
- }
-
- /** Whether or not to use the provider model behavior for the status bar icons */
- public boolean isCombinedStatusBarSignalIconsEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_combined_status_bar_signal_icons);
- }
-
- /** System setting for provider model behavior */
- public boolean isProviderModelSettingEnabled() {
- return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
- }
-
- /** static method for the system setting */
- public static boolean isProviderModelSettingEnabled(Context context) {
- return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 8a397199dc84..74ebfe5ad5e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -147,7 +147,6 @@ public class KeyguardIndicationController {
private boolean mBatteryPresent = true;
private long mChargingTimeRemaining;
private String mMessageToShowOnScreenOn;
- protected int mLockScreenMode;
private boolean mInited;
private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
@@ -600,10 +599,6 @@ public class KeyguardIndicationController {
mHideTransientMessageOnScreenOff = hideOnScreenOff && transientIndication != null;
mHandler.removeMessages(MSG_HIDE_TRANSIENT);
mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
- if (mDozing && !TextUtils.isEmpty(mTransientIndication)) {
- // Make sure this doesn't get stuck and burns in. Acquire wakelock until its cleared.
- mWakeLock.setAcquired(true);
- }
hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
updateIndication(false);
@@ -623,10 +618,6 @@ public class KeyguardIndicationController {
}
protected final void updateIndication(boolean animate) {
- if (TextUtils.isEmpty(mTransientIndication)) {
- mWakeLock.setAcquired(false);
- }
-
if (!mVisible) {
return;
}
@@ -644,24 +635,31 @@ public class KeyguardIndicationController {
// colors can be hard to read in low brightness.
mTopIndicationView.setTextColor(Color.WHITE);
if (!TextUtils.isEmpty(mTransientIndication)) {
- mTopIndicationView.switchIndication(mTransientIndication, null);
+ mWakeLock.setAcquired(true);
+ mTopIndicationView.switchIndication(mTransientIndication, null,
+ true, () -> mWakeLock.setAcquired(false));
} else if (!mBatteryPresent) {
// If there is no battery detected, hide the indication and bail
mIndicationArea.setVisibility(GONE);
} else if (!TextUtils.isEmpty(mAlignmentIndication)) {
- mTopIndicationView.switchIndication(mAlignmentIndication, null);
+ mTopIndicationView.switchIndication(mAlignmentIndication, null,
+ false /* animate */, null /* onAnimationEndCallback */);
mTopIndicationView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
} else if (mPowerPluggedIn || mEnableBatteryDefender) {
String indication = computePowerIndication();
if (animate) {
- animateText(mTopIndicationView, indication);
+ mWakeLock.setAcquired(true);
+ mTopIndicationView.switchIndication(indication, null, true /* animate */,
+ () -> mWakeLock.setAcquired(false));
} else {
- mTopIndicationView.switchIndication(indication, null);
+ mTopIndicationView.switchIndication(indication, null, false /* animate */,
+ null /* onAnimationEndCallback */);
}
} else {
String percentage = NumberFormat.getPercentInstance()
.format(mBatteryLevel / 100f);
- mTopIndicationView.switchIndication(percentage, null);
+ mTopIndicationView.switchIndication(percentage, null /* indication */,
+ false /* animate */, null /* onAnimationEnd*/);
}
return;
}
@@ -819,12 +817,15 @@ public class KeyguardIndicationController {
}
}
- private void showTryFingerprintMsg(String a11yString) {
+ private void showTryFingerprintMsg(int msgId, String a11yString) {
if (mKeyguardUpdateMonitor.isUdfpsAvailable()) {
// if udfps available, there will always be a tappable affordance to unlock
// For example, the lock icon
if (mKeyguardBypassController.getUserHasDeviceEntryIntent()) {
showTransientIndication(R.string.keyguard_unlock_press);
+ } else if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) {
+ // since face is locked out, simply show "try fingerprint"
+ showTransientIndication(R.string.keyguard_try_fingerprint);
} else {
showTransientIndication(R.string.keyguard_face_failed_use_fp);
}
@@ -860,11 +861,6 @@ public class KeyguardIndicationController {
public static final int HIDE_DELAY_MS = 5000;
@Override
- public void onLockScreenModeChanged(int mode) {
- mLockScreenMode = mode;
- }
-
- @Override
public void onRefreshBatteryInfo(BatteryStatus status) {
boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING
|| status.status == BatteryManager.BATTERY_STATUS_FULL;
@@ -916,7 +912,7 @@ public class KeyguardIndicationController {
} else if (mKeyguardUpdateMonitor.isScreenOn()) {
if (biometricSourceType == BiometricSourceType.FACE
&& shouldSuppressFaceMsgAndShowTryFingerprintMsg()) {
- showTryFingerprintMsg(helpString);
+ showTryFingerprintMsg(msgId, helpString);
return;
}
showTransientIndication(helpString, false /* isError */, showActionToUnlock);
@@ -936,7 +932,7 @@ public class KeyguardIndicationController {
&& shouldSuppressFaceMsgAndShowTryFingerprintMsg()
&& !mStatusBarKeyguardViewManager.isBouncerShowing()
&& mKeyguardUpdateMonitor.isScreenOn()) {
- showTryFingerprintMsg(errString);
+ showTryFingerprintMsg(msgId, errString);
return;
}
if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
@@ -945,7 +941,7 @@ public class KeyguardIndicationController {
if (!mStatusBarKeyguardViewManager.isBouncerShowing()
&& mKeyguardUpdateMonitor.isUdfpsEnrolled()
&& mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
- showTryFingerprintMsg(errString);
+ showTryFingerprintMsg(msgId, errString);
} else if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
mStatusBarKeyguardViewManager.showBouncerMessage(
mContext.getResources().getString(R.string.keyguard_unlock_press),
@@ -989,8 +985,8 @@ public class KeyguardIndicationController {
private boolean shouldSuppressFaceMsgAndShowTryFingerprintMsg() {
// For dual biometric, don't show face auth messages
return mKeyguardUpdateMonitor.isFingerprintDetectionRunning()
- && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
- true /* isStrongBiometric */);
+ && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ true /* isStrongBiometric */);
}
private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index bbee1942cfc4..77e329f94a36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -12,8 +12,10 @@ import android.graphics.PorterDuffXfermode
import android.graphics.RadialGradient
import android.graphics.Shader
import android.util.AttributeSet
+import android.util.MathUtils.lerp
import android.view.View
import com.android.systemui.animation.Interpolators
+import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
import java.util.function.Consumer
/**
@@ -63,12 +65,12 @@ object LiftReveal : LightRevealEffect {
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
val interpolatedAmount = INTERPOLATOR.getInterpolation(amount)
val ovalWidthIncreaseAmount =
- LightRevealEffect.getPercentPastThreshold(interpolatedAmount, WIDEN_OVAL_THRESHOLD)
+ getPercentPastThreshold(interpolatedAmount, WIDEN_OVAL_THRESHOLD)
val initialWidthMultiplier = (1f - OVAL_INITIAL_WIDTH_PERCENT) / 2f
with(scrim) {
- revealGradientEndColorAlpha = 1f - LightRevealEffect.getPercentPastThreshold(
+ revealGradientEndColorAlpha = 1f - getPercentPastThreshold(
amount, FADE_END_COLOR_OUT_THRESHOLD)
setRevealGradientBounds(
scrim.width * initialWidthMultiplier +
@@ -83,6 +85,58 @@ object LiftReveal : LightRevealEffect {
}
}
+class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevealEffect {
+
+ private val INTERPOLATOR = Interpolators.FAST_OUT_SLOW_IN_REVERSE
+
+ override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
+ val interpolatedAmount = INTERPOLATOR.getInterpolation(amount)
+
+ scrim.startColorAlpha =
+ getPercentPastThreshold(1 - interpolatedAmount,
+ threshold = 1 - START_COLOR_REVEAL_PERCENTAGE)
+
+ scrim.revealGradientEndColorAlpha =
+ 1f - getPercentPastThreshold(interpolatedAmount,
+ threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE)
+
+ // Start changing gradient bounds later to avoid harsh gradient in the beginning
+ val gradientBoundsAmount = lerp(GRADIENT_START_BOUNDS_PERCENTAGE, 1.0f, interpolatedAmount)
+
+ if (isVertical) {
+ scrim.setRevealGradientBounds(
+ left = scrim.width / 2 - (scrim.width / 2) * gradientBoundsAmount,
+ top = 0f,
+ right = scrim.width / 2 + (scrim.width / 2) * gradientBoundsAmount,
+ bottom = scrim.height.toFloat()
+ )
+ } else {
+ scrim.setRevealGradientBounds(
+ left = 0f,
+ top = scrim.height / 2 - (scrim.height / 2) * gradientBoundsAmount,
+ right = scrim.width.toFloat(),
+ bottom = scrim.height / 2 + (scrim.height / 2) * gradientBoundsAmount
+ )
+ }
+ }
+
+ private companion object {
+ // From which percentage we should start the gradient reveal width
+ // E.g. if 0 - starts with 0px width, 0.3f - starts with 30% width
+ private const val GRADIENT_START_BOUNDS_PERCENTAGE = 0.3f
+
+ // When to start changing alpha color of the gradient scrim
+ // E.g. if 0.6f - starts fading the gradient away at 60% and becomes completely
+ // transparent at 100%
+ private const val REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE = 0.6f
+
+ // When to finish displaying start color fill that reveals the content
+ // E.g. if 0.3f - the content won't be visible at 0% and it will gradually
+ // reduce the alpha until 30% (at this point the color fill is invisible)
+ private const val START_COLOR_REVEAL_PERCENTAGE = 0.3f
+ }
+}
+
class CircleReveal(
/** X-value of the circle center of the reveal. */
val centerX: Float,
@@ -96,7 +150,7 @@ class CircleReveal(
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
// reveal amount updates already have an interpolator, so we intentionally use the
// non-interpolated amount
- val fadeAmount = LightRevealEffect.getPercentPastThreshold(amount, 0.5f)
+ val fadeAmount = getPercentPastThreshold(amount, 0.5f)
val radius = startRadius + ((endRadius - startRadius) * amount)
scrim.revealGradientEndColorAlpha = 1f - fadeAmount
scrim.setRevealGradientBounds(
@@ -124,8 +178,7 @@ class PowerButtonReveal(
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
val interpolatedAmount = Interpolators.FAST_OUT_SLOW_IN_REVERSE.getInterpolation(amount)
- val fadeAmount =
- LightRevealEffect.getPercentPastThreshold(interpolatedAmount, 0.5f)
+ val fadeAmount = getPercentPastThreshold(interpolatedAmount, 0.5f)
with(scrim) {
revealGradientEndColorAlpha = 1f - fadeAmount
@@ -202,6 +255,23 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context,
var revealGradientWidth: Float = 0f
var revealGradientHeight: Float = 0f
+ /**
+ * Alpha of the fill that can be used in the beginning of the animation to hide the content.
+ * Normally the gradient bounds are animated from small size so the content is not visible,
+ * but if the start gradient bounds allow to see some content this could be used to make the
+ * reveal smoother. It can help to add fade in effect in the beginning of the animation.
+ * The color of the fill is determined by [revealGradientEndColor].
+ *
+ * 0 - no fill and content is visible, 1 - the content is covered with the start color
+ */
+ var startColorAlpha = 0f
+ set(value) {
+ if (field != value) {
+ field = value
+ invalidate()
+ }
+ }
+
var revealGradientEndColor: Int = Color.BLACK
set(value) {
if (field != value) {
@@ -295,6 +365,10 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context,
return
}
+ if (startColorAlpha > 0f) {
+ canvas.drawColor(updateColorAlpha(revealGradientEndColor, startColorAlpha))
+ }
+
with(shaderGradientMatrix) {
setScale(revealGradientWidth, revealGradientHeight, 0f, 0f)
postTranslate(revealGradientCenter.x, revealGradientCenter.y)
@@ -308,11 +382,15 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context,
private fun setPaintColorFilter() {
gradientPaint.colorFilter = PorterDuffColorFilter(
- Color.argb(
- (revealGradientEndColorAlpha * 255).toInt(),
- Color.red(revealGradientEndColor),
- Color.green(revealGradientEndColor),
- Color.blue(revealGradientEndColor)),
- PorterDuff.Mode.MULTIPLY)
+ updateColorAlpha(revealGradientEndColor, revealGradientEndColorAlpha),
+ PorterDuff.Mode.MULTIPLY)
}
+
+ private fun updateColorAlpha(color: Int, alpha: Float): Int =
+ Color.argb(
+ (alpha * 255).toInt(),
+ Color.red(color),
+ Color.green(color),
+ Color.blue(color)
+ )
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 6fa06a76dc5e..dca7f70d3470 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -60,7 +60,6 @@ class LockscreenShadeTransitionController @Inject constructor(
private val mediaHierarchyManager: MediaHierarchyManager,
private val scrimController: ScrimController,
private val depthController: NotificationShadeDepthController,
- private val featureFlags: FeatureFlags,
private val context: Context,
configurationController: ConfigurationController,
falsingManager: FalsingManager
@@ -147,7 +146,7 @@ class LockscreenShadeTransitionController @Inject constructor(
R.dimen.lockscreen_shade_scrim_transition_distance)
fullTransitionDistance = context.resources.getDimensionPixelSize(
R.dimen.lockscreen_shade_qs_transition_distance)
- useSplitShade = Utils.shouldUseSplitNotificationShade(featureFlags, context.resources)
+ useSplitShade = Utils.shouldUseSplitNotificationShade(context.resources)
}
fun setStackScroller(nsslController: NotificationStackScrollLayoutController) {
@@ -299,9 +298,8 @@ class LockscreenShadeTransitionController @Inject constructor(
nsslController.setTransitionToFullShadeAmount(field)
notificationPanelController.setTransitionToFullShadeAmount(field,
false /* animate */, 0 /* delay */)
- // TODO: appear qs also in split shade
- val qsAmount = if (useSplitShade) 0f else field
- qS.setTransitionToFullShadeAmount(qsAmount, false /* animate */)
+ val progress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
+ qS.setTransitionToFullShadeAmount(field, progress)
// TODO: appear media also in split shade
val mediaAmount = if (useSplitShade) 0f else field
mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index e7e9404c9d3e..856052e1a4d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -19,8 +19,8 @@ import static android.app.Notification.VISIBILITY_SECRET;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
import android.app.ActivityManager;
import android.app.KeyguardManager;
@@ -50,6 +50,7 @@ import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.recents.OverviewProxyService;
@@ -72,7 +73,9 @@ import javax.inject.Inject;
*/
@SysUISingleton
public class NotificationLockscreenUserManagerImpl implements
- Dumpable, NotificationLockscreenUserManager, StateListener {
+ Dumpable,
+ NotificationLockscreenUserManager,
+ StateListener {
private static final String TAG = "LockscreenUserManager";
private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
@@ -202,7 +205,8 @@ public class NotificationLockscreenUserManagerImpl implements
StatusBarStateController statusBarStateController,
@Main Handler mainHandler,
DeviceProvisionedController deviceProvisionedController,
- KeyguardStateController keyguardStateController) {
+ KeyguardStateController keyguardStateController,
+ DumpManager dumpManager) {
mContext = context;
mMainHandler = mainHandler;
mDevicePolicyManager = devicePolicyManager;
@@ -215,6 +219,8 @@ public class NotificationLockscreenUserManagerImpl implements
mBroadcastDispatcher = broadcastDispatcher;
mDeviceProvisionedController = deviceProvisionedController;
mKeyguardStateController = keyguardStateController;
+
+ dumpManager.registerDumpable(this);
}
public void setUpWithPresenter(NotificationPresenter presenter) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 25cbdc5c2187..dcb1e4f34188 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -52,6 +52,8 @@ import com.android.systemui.Dumpable;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.MediaData;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.SmartspaceMediaData;
@@ -72,7 +74,6 @@ import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.ScrimState;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.Utils;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -84,6 +85,7 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import dagger.Lazy;
@@ -132,7 +134,7 @@ public class NotificationMediaManager implements Dumpable {
private final Context mContext;
private final MediaSessionManager mMediaSessionManager;
private final ArrayList<MediaListener> mMediaListeners;
- private final Lazy<StatusBar> mStatusBarLazy;
+ private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
private final MediaArtworkProcessor mMediaArtworkProcessor;
private final Set<AsyncTask<?, ?, ?>> mProcessArtworkTasks = new ArraySet<>();
@@ -177,7 +179,7 @@ public class NotificationMediaManager implements Dumpable {
*/
public NotificationMediaManager(
Context context,
- Lazy<StatusBar> statusBarLazy,
+ Lazy<Optional<StatusBar>> statusBarOptionalLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
@@ -186,8 +188,8 @@ public class NotificationMediaManager implements Dumpable {
NotifCollection notifCollection,
FeatureFlags featureFlags,
@Main DelayableExecutor mainExecutor,
- DeviceConfigProxy deviceConfig,
- MediaDataManager mediaDataManager) {
+ MediaDataManager mediaDataManager,
+ DumpManager dumpManager) {
mContext = context;
mMediaArtworkProcessor = mediaArtworkProcessor;
mKeyguardBypassController = keyguardBypassController;
@@ -197,7 +199,7 @@ public class NotificationMediaManager implements Dumpable {
mMediaSessionManager = (MediaSessionManager) mContext.getSystemService(
Context.MEDIA_SESSION_SERVICE);
// TODO: use KeyguardStateController#isOccluded to remove this dependency
- mStatusBarLazy = statusBarLazy;
+ mStatusBarOptionalLazy = statusBarOptionalLazy;
mNotificationShadeWindowController = notificationShadeWindowController;
mEntryManager = notificationEntryManager;
mMainExecutor = mainExecutor;
@@ -212,6 +214,8 @@ public class NotificationMediaManager implements Dumpable {
setupNotifPipeline();
mUsingNotifPipeline = true;
}
+
+ dumpManager.registerDumpable(this);
}
private void setupNotifPipeline() {
@@ -694,7 +698,8 @@ public class NotificationMediaManager implements Dumpable {
NotificationShadeWindowController windowController =
mNotificationShadeWindowController.get();
- boolean hideBecauseOccluded = mStatusBarLazy.get().isOccluded();
+ boolean hideBecauseOccluded =
+ mStatusBarOptionalLazy.get().map(StatusBar::isOccluded).orElse(false);
final boolean hasArtwork = artworkDrawable != null;
mColorExtractor.setHasMediaArtwork(hasMediaArtwork);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 4552138761c0..1ce7f0350019 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -15,21 +15,16 @@
*/
package com.android.systemui.statusbar;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteInput;
-import android.app.RemoteInputHistoryItem;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
-import android.net.Uri;
import android.os.Handler;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -45,14 +40,20 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.RemoteViews;
+import android.widget.RemoteViews.InteractionHandler;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.StatusBarDependenciesModule;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -68,10 +69,10 @@ import com.android.systemui.statusbar.policy.RemoteInputView;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
-import java.util.stream.Stream;
import dagger.Lazy;
@@ -89,27 +90,7 @@ public class NotificationRemoteInputManager implements Dumpable {
private static final boolean DEBUG = false;
private static final String TAG = "NotifRemoteInputManager";
- /**
- * How long to wait before auto-dismissing a notification that was kept for remote input, and
- * has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel
- * these given that they technically don't exist anymore. We wait a bit in case the app issues
- * an update.
- */
- private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200;
-
- /**
- * Notifications that are already removed but are kept around because we want to show the
- * remote input history. See {@link RemoteInputHistoryExtender} and
- * {@link SmartReplyHistoryExtender}.
- */
- protected final ArraySet<String> mKeysKeptForRemoteInputHistory = new ArraySet<>();
-
- /**
- * Notifications that are already removed but are kept around because the remote input is
- * actively being used (i.e. user is typing in it). See {@link RemoteInputActiveExtender}.
- */
- protected final ArraySet<NotificationEntry> mEntriesKeptForRemoteInputActive =
- new ArraySet<>();
+ private RemoteInputListener mRemoteInputListener;
// Dependencies:
private final NotificationLockscreenUserManager mLockscreenUserManager;
@@ -118,30 +99,31 @@ public class NotificationRemoteInputManager implements Dumpable {
private final Handler mMainHandler;
private final ActionClickLogger mLogger;
- private final Lazy<StatusBar> mStatusBarLazy;
+ private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
protected final Context mContext;
+ protected final FeatureFlags mFeatureFlags;
private final UserManager mUserManager;
private final KeyguardManager mKeyguardManager;
+ private final RemoteInputNotificationRebuilder mRebuilder;
private final StatusBarStateController mStatusBarStateController;
private final RemoteInputUriController mRemoteInputUriController;
private final NotificationClickNotifier mClickNotifier;
protected RemoteInputController mRemoteInputController;
- protected NotificationLifetimeExtender.NotificationSafeToRemoveCallback
- mNotificationLifetimeFinishedCallback;
protected IStatusBarService mBarService;
protected Callback mCallback;
- protected final ArrayList<NotificationLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
- private final RemoteViews.InteractionHandler
- mInteractionHandler = new RemoteViews.InteractionHandler() {
+ private final List<RemoteInputController.Callback> mControllerCallbacks = new ArrayList<>();
+
+ private final InteractionHandler mInteractionHandler = new InteractionHandler() {
@Override
public boolean onInteraction(
View view, PendingIntent pendingIntent, RemoteViews.RemoteResponse response) {
- mStatusBarLazy.get().wakeUpIfDozing(SystemClock.uptimeMillis(), view,
- "NOTIFICATION_CLICK");
+ mStatusBarOptionalLazy.get().ifPresent(
+ statusBar -> statusBar.wakeUpIfDozing(
+ SystemClock.uptimeMillis(), view, "NOTIFICATION_CLICK"));
final NotificationEntry entry = getNotificationForParent(view.getParent());
mLogger.logInitialClick(entry, pendingIntent);
@@ -220,6 +202,7 @@ public class NotificationRemoteInputManager implements Dumpable {
ViewGroup actionGroup = (ViewGroup) parent;
buttonIndex = actionGroup.indexOfChild(view);
}
+ // TODO(b/204183781): get this from the current pipeline
final int count = mEntryManager.getActiveNotificationsCount();
final int rank = entry.getRanking().getRank();
@@ -277,31 +260,41 @@ public class NotificationRemoteInputManager implements Dumpable {
*/
public NotificationRemoteInputManager(
Context context,
+ FeatureFlags featureFlags,
NotificationLockscreenUserManager lockscreenUserManager,
SmartReplyController smartReplyController,
NotificationEntryManager notificationEntryManager,
- Lazy<StatusBar> statusBarLazy,
+ RemoteInputNotificationRebuilder rebuilder,
+ Lazy<Optional<StatusBar>> statusBarOptionalLazy,
StatusBarStateController statusBarStateController,
@Main Handler mainHandler,
RemoteInputUriController remoteInputUriController,
NotificationClickNotifier clickNotifier,
- ActionClickLogger logger) {
+ ActionClickLogger logger,
+ DumpManager dumpManager) {
mContext = context;
+ mFeatureFlags = featureFlags;
mLockscreenUserManager = lockscreenUserManager;
mSmartReplyController = smartReplyController;
mEntryManager = notificationEntryManager;
- mStatusBarLazy = statusBarLazy;
+ mStatusBarOptionalLazy = statusBarOptionalLazy;
mMainHandler = mainHandler;
mLogger = logger;
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- addLifetimeExtenders();
+ mRebuilder = rebuilder;
+ if (!featureFlags.isNewNotifPipelineRenderingEnabled()) {
+ mRemoteInputListener = createLegacyRemoteInputLifetimeExtender(mainHandler,
+ notificationEntryManager, smartReplyController);
+ }
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mStatusBarStateController = statusBarStateController;
mRemoteInputUriController = remoteInputUriController;
mClickNotifier = clickNotifier;
+ dumpManager.registerDumpable(this);
+
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onPreEntryUpdated(NotificationEntry entry) {
@@ -326,26 +319,45 @@ public class NotificationRemoteInputManager implements Dumpable {
});
}
+ /** Add a listener for various remote input events. Works with NEW pipeline only. */
+ public void setRemoteInputListener(@NonNull RemoteInputListener remoteInputListener) {
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ if (mRemoteInputListener != null) {
+ throw new IllegalStateException("mRemoteInputListener is already set");
+ }
+ mRemoteInputListener = remoteInputListener;
+ if (mRemoteInputController != null) {
+ mRemoteInputListener.setRemoteInputController(mRemoteInputController);
+ }
+ }
+ }
+
+ @NonNull
+ @VisibleForTesting
+ protected LegacyRemoteInputLifetimeExtender createLegacyRemoteInputLifetimeExtender(
+ Handler mainHandler,
+ NotificationEntryManager notificationEntryManager,
+ SmartReplyController smartReplyController) {
+ return new LegacyRemoteInputLifetimeExtender();
+ }
+
/** Initializes this component with the provided dependencies. */
public void setUpWithCallback(Callback callback, RemoteInputController.Delegate delegate) {
mCallback = callback;
mRemoteInputController = new RemoteInputController(delegate, mRemoteInputUriController);
+ if (mRemoteInputListener != null) {
+ mRemoteInputListener.setRemoteInputController(mRemoteInputController);
+ }
+ // Register all stored callbacks from before the Controller was initialized.
+ for (RemoteInputController.Callback cb : mControllerCallbacks) {
+ mRemoteInputController.addCallback(cb);
+ }
+ mControllerCallbacks.clear();
mRemoteInputController.addCallback(new RemoteInputController.Callback() {
@Override
public void onRemoteInputSent(NotificationEntry entry) {
- if (FORCE_REMOTE_INPUT_HISTORY
- && isNotificationKeptForRemoteInputHistory(entry.getKey())) {
- mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey());
- } else if (mEntriesKeptForRemoteInputActive.contains(entry)) {
- // We're currently holding onto this notification, but from the apps point of
- // view it is already canceled, so we'll need to cancel it on the apps behalf
- // after sending - unless the app posts an update in the mean time, so wait a
- // bit.
- mMainHandler.postDelayed(() -> {
- if (mEntriesKeptForRemoteInputActive.remove(entry)) {
- mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey());
- }
- }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
+ if (mRemoteInputListener != null) {
+ mRemoteInputListener.onRemoteInputSent(entry);
}
try {
mBarService.onNotificationDirectReplied(entry.getSbn().getKey());
@@ -367,12 +379,28 @@ public class NotificationRemoteInputManager implements Dumpable {
}
}
});
- mSmartReplyController.setCallback((entry, reply) -> {
- StatusBarNotification newSbn =
- rebuildNotificationWithRemoteInputInserted(entry, reply, true /* showSpinner */,
- null /* mimeType */, null /* uri */);
- mEntryManager.updateNotification(newSbn, null /* ranking */);
- });
+ if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ mSmartReplyController.setCallback((entry, reply) -> {
+ StatusBarNotification newSbn = mRebuilder.rebuildForSendingSmartReply(entry, reply);
+ mEntryManager.updateNotification(newSbn, null /* ranking */);
+ });
+ }
+ }
+
+ public void addControllerCallback(RemoteInputController.Callback callback) {
+ if (mRemoteInputController != null) {
+ mRemoteInputController.addCallback(callback);
+ } else {
+ mControllerCallbacks.add(callback);
+ }
+ }
+
+ public void removeControllerCallback(RemoteInputController.Callback callback) {
+ if (mRemoteInputController != null) {
+ mRemoteInputController.removeCallback(callback);
+ } else {
+ mControllerCallbacks.remove(callback);
+ }
}
/**
@@ -544,60 +572,52 @@ public class NotificationRemoteInputManager implements Dumpable {
if (v == null) {
return null;
}
- return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
- }
-
- /**
- * Adds all the notification lifetime extenders. Each extender represents a reason for the
- * NotificationRemoteInputManager to keep a notification lifetime extended.
- */
- protected void addLifetimeExtenders() {
- mLifetimeExtenders.add(new RemoteInputHistoryExtender());
- mLifetimeExtenders.add(new SmartReplyHistoryExtender());
- mLifetimeExtenders.add(new RemoteInputActiveExtender());
+ return v.findViewWithTag(RemoteInputView.VIEW_TAG);
}
public ArrayList<NotificationLifetimeExtender> getLifetimeExtenders() {
- return mLifetimeExtenders;
- }
-
- @Nullable
- public RemoteInputController getController() {
- return mRemoteInputController;
+ // OLD pipeline code ONLY; can assume implementation
+ return ((LegacyRemoteInputLifetimeExtender) mRemoteInputListener).mLifetimeExtenders;
}
@VisibleForTesting
void onPerformRemoveNotification(NotificationEntry entry, final String key) {
- if (mKeysKeptForRemoteInputHistory.contains(key)) {
- mKeysKeptForRemoteInputHistory.remove(key);
- }
- if (mRemoteInputController.isRemoteInputActive(entry)) {
+ // OLD pipeline code ONLY; can assume implementation
+ ((LegacyRemoteInputLifetimeExtender) mRemoteInputListener)
+ .mKeysKeptForRemoteInputHistory.remove(key);
+ cleanUpRemoteInputForUserRemoval(entry);
+ }
+
+ /**
+ * Disable remote input on the entry and remove the remote input view.
+ * This should be called when a user dismisses a notification that won't be lifetime extended.
+ */
+ public void cleanUpRemoteInputForUserRemoval(NotificationEntry entry) {
+ if (isRemoteInputActive(entry)) {
entry.mRemoteEditImeVisible = false;
mRemoteInputController.removeRemoteInput(entry, null);
}
}
+ /** Informs the remote input system that the panel has collapsed */
public void onPanelCollapsed() {
- for (int i = 0; i < mEntriesKeptForRemoteInputActive.size(); i++) {
- NotificationEntry entry = mEntriesKeptForRemoteInputActive.valueAt(i);
- mRemoteInputController.removeRemoteInput(entry, null);
- if (mNotificationLifetimeFinishedCallback != null) {
- mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey());
- }
+ if (mRemoteInputListener != null) {
+ mRemoteInputListener.onPanelCollapsed();
}
- mEntriesKeptForRemoteInputActive.clear();
}
+ /** Returns whether the given notification is lifetime extended because of remote input */
public boolean isNotificationKeptForRemoteInputHistory(String key) {
- return mKeysKeptForRemoteInputHistory.contains(key);
+ return mRemoteInputListener != null
+ && mRemoteInputListener.isNotificationKeptForRemoteInputHistory(key);
}
+ /** Returns whether the notification should be lifetime extended for remote input history */
public boolean shouldKeepForRemoteInputHistory(NotificationEntry entry) {
if (!FORCE_REMOTE_INPUT_HISTORY) {
return false;
}
- return (mRemoteInputController.isSpinning(entry.getKey())
- || entry.hasJustSentRemoteInput());
+ return isSpinning(entry.getKey()) || entry.hasJustSentRemoteInput();
}
/**
@@ -610,16 +630,12 @@ public class NotificationRemoteInputManager implements Dumpable {
if (entry == null) {
return;
}
- final String key = entry.getKey();
- if (isNotificationKeptForRemoteInputHistory(key)) {
- mMainHandler.postDelayed(() -> {
- if (isNotificationKeptForRemoteInputHistory(key)) {
- mNotificationLifetimeFinishedCallback.onSafeToRemove(key);
- }
- }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
+ if (mRemoteInputListener != null) {
+ mRemoteInputListener.releaseNotificationIfKeptForRemoteInputHistory(entry);
}
}
+ /** Returns whether the notification should be lifetime extended for smart reply history */
public boolean shouldKeepForSmartReplyHistory(NotificationEntry entry) {
if (!FORCE_REMOTE_INPUT_HISTORY) {
return false;
@@ -630,69 +646,16 @@ public class NotificationRemoteInputManager implements Dumpable {
public void checkRemoteInputOutside(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar
&& event.getX() == 0 && event.getY() == 0 // a touch outside both bars
- && mRemoteInputController.isRemoteInputActive()) {
- mRemoteInputController.closeRemoteInputs();
+ && isRemoteInputActive()) {
+ closeRemoteInputs();
}
}
- @VisibleForTesting
- StatusBarNotification rebuildNotificationForCanceledSmartReplies(
- NotificationEntry entry) {
- return rebuildNotificationWithRemoteInputInserted(entry, null /* remoteInputTest */,
- false /* showSpinner */, null /* mimeType */, null /* uri */);
- }
-
- @VisibleForTesting
- StatusBarNotification rebuildNotificationWithRemoteInputInserted(NotificationEntry entry,
- CharSequence remoteInputText, boolean showSpinner, String mimeType, Uri uri) {
- StatusBarNotification sbn = entry.getSbn();
-
- Notification.Builder b = Notification.Builder
- .recoverBuilder(mContext, sbn.getNotification().clone());
- if (remoteInputText != null || uri != null) {
- RemoteInputHistoryItem newItem = uri != null
- ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText)
- : new RemoteInputHistoryItem(remoteInputText);
- Parcelable[] oldHistoryItems = sbn.getNotification().extras
- .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
- RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null
- ? Stream.concat(
- Stream.of(newItem),
- Arrays.stream(oldHistoryItems).map(p -> (RemoteInputHistoryItem) p))
- .toArray(RemoteInputHistoryItem[]::new)
- : new RemoteInputHistoryItem[] { newItem };
- b.setRemoteInputHistory(newHistoryItems);
- }
- b.setShowRemoteInputSpinner(showSpinner);
- b.setHideSmartReplies(true);
-
- Notification newNotification = b.build();
-
- // Undo any compatibility view inflation
- newNotification.contentView = sbn.getNotification().contentView;
- newNotification.bigContentView = sbn.getNotification().bigContentView;
- newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
-
- return new StatusBarNotification(
- sbn.getPackageName(),
- sbn.getOpPkg(),
- sbn.getId(),
- sbn.getTag(),
- sbn.getUid(),
- sbn.getInitialPid(),
- newNotification,
- sbn.getUser(),
- sbn.getOverrideGroupKey(),
- sbn.getPostTime());
- }
-
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("NotificationRemoteInputManager state:");
- pw.print(" mKeysKeptForRemoteInputHistory: ");
- pw.println(mKeysKeptForRemoteInputHistory);
- pw.print(" mEntriesKeptForRemoteInputActive: ");
- pw.println(mEntriesKeptForRemoteInputActive);
+ if (mRemoteInputListener instanceof Dumpable) {
+ ((Dumpable) mRemoteInputListener).dump(fd, pw, args);
+ }
}
public void bindRow(ExpandableNotificationRow row) {
@@ -708,133 +671,21 @@ public class NotificationRemoteInputManager implements Dumpable {
return mInteractionHandler;
}
- @VisibleForTesting
- public Set<NotificationEntry> getEntriesKeptForRemoteInputActive() {
- return mEntriesKeptForRemoteInputActive;
+ public boolean isRemoteInputActive() {
+ return mRemoteInputController != null && mRemoteInputController.isRemoteInputActive();
}
- /**
- * NotificationRemoteInputManager has multiple reasons to keep notification lifetime extended
- * so we implement multiple NotificationLifetimeExtenders
- */
- protected abstract class RemoteInputExtender implements NotificationLifetimeExtender {
- @Override
- public void setCallback(NotificationSafeToRemoveCallback callback) {
- if (mNotificationLifetimeFinishedCallback == null) {
- mNotificationLifetimeFinishedCallback = callback;
- }
- }
+ public boolean isRemoteInputActive(NotificationEntry entry) {
+ return mRemoteInputController != null && mRemoteInputController.isRemoteInputActive(entry);
}
- /**
- * Notification is kept alive as it was cancelled in response to a remote input interaction.
- * This allows us to show what you replied and allows you to continue typing into it.
- */
- protected class RemoteInputHistoryExtender extends RemoteInputExtender {
- @Override
- public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
- return shouldKeepForRemoteInputHistory(entry);
- }
-
- @Override
- public void setShouldManageLifetime(NotificationEntry entry,
- boolean shouldExtend) {
- if (shouldExtend) {
- CharSequence remoteInputText = entry.remoteInputText;
- if (TextUtils.isEmpty(remoteInputText)) {
- remoteInputText = entry.remoteInputTextWhenReset;
- }
- String remoteInputMimeType = entry.remoteInputMimeType;
- Uri remoteInputUri = entry.remoteInputUri;
- StatusBarNotification newSbn = rebuildNotificationWithRemoteInputInserted(entry,
- remoteInputText, false /* showSpinner */, remoteInputMimeType,
- remoteInputUri);
- entry.onRemoteInputInserted();
-
- if (newSbn == null) {
- return;
- }
-
- mEntryManager.updateNotification(newSbn, null);
-
- // Ensure the entry hasn't already been removed. This can happen if there is an
- // inflation exception while updating the remote history
- if (entry.isRemoved()) {
- return;
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Keeping notification around after sending remote input "
- + entry.getKey());
- }
-
- mKeysKeptForRemoteInputHistory.add(entry.getKey());
- } else {
- mKeysKeptForRemoteInputHistory.remove(entry.getKey());
- }
- }
+ public boolean isSpinning(String entryKey) {
+ return mRemoteInputController != null && mRemoteInputController.isSpinning(entryKey);
}
- /**
- * Notification is kept alive for smart reply history. Similar to REMOTE_INPUT_HISTORY but with
- * {@link SmartReplyController} specific logic
- */
- protected class SmartReplyHistoryExtender extends RemoteInputExtender {
- @Override
- public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
- return shouldKeepForSmartReplyHistory(entry);
- }
-
- @Override
- public void setShouldManageLifetime(NotificationEntry entry,
- boolean shouldExtend) {
- if (shouldExtend) {
- StatusBarNotification newSbn = rebuildNotificationForCanceledSmartReplies(entry);
-
- if (newSbn == null) {
- return;
- }
-
- mEntryManager.updateNotification(newSbn, null);
-
- if (entry.isRemoved()) {
- return;
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Keeping notification around after sending smart reply "
- + entry.getKey());
- }
-
- mKeysKeptForRemoteInputHistory.add(entry.getKey());
- } else {
- mKeysKeptForRemoteInputHistory.remove(entry.getKey());
- mSmartReplyController.stopSending(entry);
- }
- }
- }
-
- /**
- * Notification is kept alive because the user is still using the remote input
- */
- protected class RemoteInputActiveExtender extends RemoteInputExtender {
- @Override
- public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
- return mRemoteInputController.isRemoteInputActive(entry);
- }
-
- @Override
- public void setShouldManageLifetime(NotificationEntry entry,
- boolean shouldExtend) {
- if (shouldExtend) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Keeping notification around while remote input active "
- + entry.getKey());
- }
- mEntriesKeptForRemoteInputActive.add(entry);
- } else {
- mEntriesKeptForRemoteInputActive.remove(entry);
- }
+ public void closeRemoteInputs() {
+ if (mRemoteInputController != null) {
+ mRemoteInputController.closeRemoteInputs();
}
}
@@ -931,4 +782,256 @@ public class NotificationRemoteInputManager implements Dumpable {
*/
boolean showBouncerIfNecessary();
}
+
+ /** An interface for listening to remote input events that relate to notification lifetime */
+ public interface RemoteInputListener {
+ /** Called when remote input pending intent has been sent */
+ void onRemoteInputSent(@NonNull NotificationEntry entry);
+
+ /** Called when the notification shade becomes fully closed */
+ void onPanelCollapsed();
+
+ /** @return whether lifetime of a notification is being extended by the listener */
+ boolean isNotificationKeptForRemoteInputHistory(@NonNull String key);
+
+ /** Called on user interaction to end lifetime extension for history */
+ void releaseNotificationIfKeptForRemoteInputHistory(@NonNull NotificationEntry entry);
+
+ /** Called when the RemoteInputController is attached to the manager */
+ void setRemoteInputController(@NonNull RemoteInputController remoteInputController);
+ }
+
+ @VisibleForTesting
+ protected class LegacyRemoteInputLifetimeExtender implements RemoteInputListener, Dumpable {
+
+ /**
+ * How long to wait before auto-dismissing a notification that was kept for remote input,
+ * and has now sent a remote input. We auto-dismiss, because the app may not see a reason to
+ * cancel these given that they technically don't exist anymore. We wait a bit in case the
+ * app issues an update.
+ */
+ private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200;
+
+ /**
+ * Notifications that are already removed but are kept around because we want to show the
+ * remote input history. See {@link RemoteInputHistoryExtender} and
+ * {@link SmartReplyHistoryExtender}.
+ */
+ protected final ArraySet<String> mKeysKeptForRemoteInputHistory = new ArraySet<>();
+
+ /**
+ * Notifications that are already removed but are kept around because the remote input is
+ * actively being used (i.e. user is typing in it). See {@link RemoteInputActiveExtender}.
+ */
+ protected final ArraySet<NotificationEntry> mEntriesKeptForRemoteInputActive =
+ new ArraySet<>();
+
+ protected NotificationLifetimeExtender.NotificationSafeToRemoveCallback
+ mNotificationLifetimeFinishedCallback;
+
+ protected final ArrayList<NotificationLifetimeExtender> mLifetimeExtenders =
+ new ArrayList<>();
+ private RemoteInputController mRemoteInputController;
+
+ LegacyRemoteInputLifetimeExtender() {
+ addLifetimeExtenders();
+ }
+
+ /**
+ * Adds all the notification lifetime extenders. Each extender represents a reason for the
+ * NotificationRemoteInputManager to keep a notification lifetime extended.
+ */
+ protected void addLifetimeExtenders() {
+ mLifetimeExtenders.add(new RemoteInputHistoryExtender());
+ mLifetimeExtenders.add(new SmartReplyHistoryExtender());
+ mLifetimeExtenders.add(new RemoteInputActiveExtender());
+ }
+
+ @Override
+ public void setRemoteInputController(@NonNull RemoteInputController remoteInputController) {
+ mRemoteInputController= remoteInputController;
+ }
+
+ @Override
+ public void onRemoteInputSent(@NonNull NotificationEntry entry) {
+ if (FORCE_REMOTE_INPUT_HISTORY
+ && isNotificationKeptForRemoteInputHistory(entry.getKey())) {
+ mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey());
+ } else if (mEntriesKeptForRemoteInputActive.contains(entry)) {
+ // We're currently holding onto this notification, but from the apps point of
+ // view it is already canceled, so we'll need to cancel it on the apps behalf
+ // after sending - unless the app posts an update in the mean time, so wait a
+ // bit.
+ mMainHandler.postDelayed(() -> {
+ if (mEntriesKeptForRemoteInputActive.remove(entry)) {
+ mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey());
+ }
+ }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
+ }
+ }
+
+ @Override
+ public void onPanelCollapsed() {
+ for (int i = 0; i < mEntriesKeptForRemoteInputActive.size(); i++) {
+ NotificationEntry entry = mEntriesKeptForRemoteInputActive.valueAt(i);
+ if (mRemoteInputController != null) {
+ mRemoteInputController.removeRemoteInput(entry, null);
+ }
+ if (mNotificationLifetimeFinishedCallback != null) {
+ mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.getKey());
+ }
+ }
+ mEntriesKeptForRemoteInputActive.clear();
+ }
+
+ @Override
+ public boolean isNotificationKeptForRemoteInputHistory(@NonNull String key) {
+ return mKeysKeptForRemoteInputHistory.contains(key);
+ }
+
+ @Override
+ public void releaseNotificationIfKeptForRemoteInputHistory(
+ @NonNull NotificationEntry entry) {
+ final String key = entry.getKey();
+ if (isNotificationKeptForRemoteInputHistory(key)) {
+ mMainHandler.postDelayed(() -> {
+ if (isNotificationKeptForRemoteInputHistory(key)) {
+ mNotificationLifetimeFinishedCallback.onSafeToRemove(key);
+ }
+ }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
+ }
+ }
+
+ @VisibleForTesting
+ public Set<NotificationEntry> getEntriesKeptForRemoteInputActive() {
+ return mEntriesKeptForRemoteInputActive;
+ }
+
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
+ @NonNull String[] args) {
+ pw.println("LegacyRemoteInputLifetimeExtender:");
+ pw.print(" mKeysKeptForRemoteInputHistory: ");
+ pw.println(mKeysKeptForRemoteInputHistory);
+ pw.print(" mEntriesKeptForRemoteInputActive: ");
+ pw.println(mEntriesKeptForRemoteInputActive);
+ }
+
+ /**
+ * NotificationRemoteInputManager has multiple reasons to keep notification lifetime
+ * extended so we implement multiple NotificationLifetimeExtenders
+ */
+ protected abstract class RemoteInputExtender implements NotificationLifetimeExtender {
+ @Override
+ public void setCallback(NotificationSafeToRemoveCallback callback) {
+ if (mNotificationLifetimeFinishedCallback == null) {
+ mNotificationLifetimeFinishedCallback = callback;
+ }
+ }
+ }
+
+ /**
+ * Notification is kept alive as it was cancelled in response to a remote input interaction.
+ * This allows us to show what you replied and allows you to continue typing into it.
+ */
+ protected class RemoteInputHistoryExtender extends RemoteInputExtender {
+ @Override
+ public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
+ return shouldKeepForRemoteInputHistory(entry);
+ }
+
+ @Override
+ public void setShouldManageLifetime(NotificationEntry entry,
+ boolean shouldExtend) {
+ if (shouldExtend) {
+ StatusBarNotification newSbn = mRebuilder.rebuildForRemoteInputReply(entry);
+ entry.onRemoteInputInserted();
+
+ if (newSbn == null) {
+ return;
+ }
+
+ mEntryManager.updateNotification(newSbn, null);
+
+ // Ensure the entry hasn't already been removed. This can happen if there is an
+ // inflation exception while updating the remote history
+ if (entry.isRemoved()) {
+ return;
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Keeping notification around after sending remote input "
+ + entry.getKey());
+ }
+
+ mKeysKeptForRemoteInputHistory.add(entry.getKey());
+ } else {
+ mKeysKeptForRemoteInputHistory.remove(entry.getKey());
+ }
+ }
+ }
+
+ /**
+ * Notification is kept alive for smart reply history. Similar to REMOTE_INPUT_HISTORY but
+ * with {@link SmartReplyController} specific logic
+ */
+ protected class SmartReplyHistoryExtender extends RemoteInputExtender {
+ @Override
+ public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
+ return shouldKeepForSmartReplyHistory(entry);
+ }
+
+ @Override
+ public void setShouldManageLifetime(NotificationEntry entry,
+ boolean shouldExtend) {
+ if (shouldExtend) {
+ StatusBarNotification newSbn = mRebuilder.rebuildForCanceledSmartReplies(entry);
+
+ if (newSbn == null) {
+ return;
+ }
+
+ mEntryManager.updateNotification(newSbn, null);
+
+ if (entry.isRemoved()) {
+ return;
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Keeping notification around after sending smart reply "
+ + entry.getKey());
+ }
+
+ mKeysKeptForRemoteInputHistory.add(entry.getKey());
+ } else {
+ mKeysKeptForRemoteInputHistory.remove(entry.getKey());
+ mSmartReplyController.stopSending(entry);
+ }
+ }
+ }
+
+ /**
+ * Notification is kept alive because the user is still using the remote input
+ */
+ protected class RemoteInputActiveExtender extends RemoteInputExtender {
+ @Override
+ public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
+ return isRemoteInputActive(entry);
+ }
+
+ @Override
+ public void setShouldManageLifetime(NotificationEntry entry,
+ boolean shouldExtend) {
+ if (shouldExtend) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Keeping notification around while remote input active "
+ + entry.getKey());
+ }
+ mEntriesKeptForRemoteInputActive.add(entry);
+ } else {
+ mEntriesKeptForRemoteInputActive.remove(entry);
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 98e6879d208f..5648741e3caf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
-import android.app.WallpaperManager
import android.os.SystemClock
import android.os.Trace
import android.util.IndentingPrintWriter
@@ -33,6 +32,7 @@ import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.systemui.Dumpable
import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.PanelExpansionListener
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.WallpaperController
import java.io.FileDescriptor
import java.io.PrintWriter
import javax.inject.Inject
@@ -57,7 +58,7 @@ class NotificationShadeDepthController @Inject constructor(
private val biometricUnlockController: BiometricUnlockController,
private val keyguardStateController: KeyguardStateController,
private val choreographer: Choreographer,
- private val wallpaperManager: WallpaperManager,
+ private val wallpaperController: WallpaperController,
private val notificationShadeWindowController: NotificationShadeWindowController,
private val dozeParameters: DozeParameters,
dumpManager: DumpManager
@@ -73,7 +74,7 @@ class NotificationShadeDepthController @Inject constructor(
}
/**
- * Is did we already unblur while dozing?
+ * Did we already unblur while dozing?
*/
private var alreadyUnblurredWhileDozing = false
lateinit var root: View
@@ -184,12 +185,12 @@ class NotificationShadeDepthController @Inject constructor(
val animationRadius = MathUtils.constrain(shadeAnimation.radius,
blurUtils.minBlurRadius.toFloat(), blurUtils.maxBlurRadius.toFloat())
val expansionRadius = blurUtils.blurRadiusOfRatio(
- Interpolators.getNotificationScrimAlpha(
- if (shouldApplyShadeBlur()) shadeExpansion else 0f, false))
+ ShadeInterpolation.getNotificationScrimAlpha(
+ if (shouldApplyShadeBlur()) shadeExpansion else 0f))
var combinedBlur = (expansionRadius * INTERACTION_BLUR_FRACTION +
animationRadius * ANIMATION_BLUR_FRACTION)
- val qsExpandedRatio = Interpolators.getNotificationScrimAlpha(qsPanelExpansion,
- false /* notification */) * shadeExpansion
+ val qsExpandedRatio = ShadeInterpolation.getNotificationScrimAlpha(qsPanelExpansion) *
+ shadeExpansion
combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(qsExpandedRatio))
combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress))
var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius)
@@ -218,15 +219,7 @@ class NotificationShadeDepthController @Inject constructor(
Trace.traceCounter(Trace.TRACE_TAG_APP, "shade_blur_radius", blur)
blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur, opaque)
lastAppliedBlur = blur
- try {
- if (root.isAttachedToWindow && root.windowToken != null) {
- wallpaperManager.setWallpaperZoomOut(root.windowToken, zoomOut)
- } else {
- Log.i(TAG, "Won't set zoom. Window not attached $root")
- }
- } catch (e: IllegalArgumentException) {
- Log.w(TAG, "Can't set zoom. Window is gone: ${root.windowToken}", e)
- }
+ wallpaperController.setNotificationShadeZoom(zoomOut)
listeners.forEach {
it.onWallpaperZoomOutChanged(zoomOut)
it.onBlurRadiusChanged(blur)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index cd5cce4f3ee7..51a66aad39fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -30,8 +30,9 @@ import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
+import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -114,7 +115,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
private void initDimens() {
Resources res = getResources();
- mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
+ mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
ViewGroup.LayoutParams layoutParams = getLayoutParams();
@@ -168,8 +169,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
viewState.clipTopAmount = 0;
if (ambientState.isExpansionChanging() && !ambientState.isOnKeyguard()) {
- viewState.alpha = Interpolators.getNotificationScrimAlpha(
- ambientState.getExpansionFraction(), true /* notification */);
+ float expansion = ambientState.getExpansionFraction();
+ viewState.alpha = ShadeInterpolation.getContentAlpha(expansion);
} else {
viewState.alpha = 1f - ambientState.getHideAmount();
}
@@ -183,7 +184,6 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
viewState.hidden = !mAmbientState.isShadeExpanded()
- || mAmbientState.isQsCustomizerShowing()
|| algorithmState.firstViewInShelf == null;
final int indexOfFirstViewInShelf = algorithmState.visibleChildren.indexOf(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 396d86bab825..464b2b69c58e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -27,6 +27,7 @@ import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
@@ -72,6 +73,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
// Dependencies:
private final DynamicChildBindController mDynamicChildBindController;
+ private final FeatureFlags mFeatureFlags;
protected final NotificationLockscreenUserManager mLockscreenUserManager;
protected final NotificationGroupManagerLegacy mGroupManager;
protected final VisualStabilityManager mVisualStabilityManager;
@@ -107,6 +109,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
public NotificationViewHierarchyManager(
Context context,
@Main Handler mainHandler,
+ FeatureFlags featureFlags,
NotificationLockscreenUserManager notificationLockscreenUserManager,
NotificationGroupManagerLegacy groupManager,
VisualStabilityManager visualStabilityManager,
@@ -121,6 +124,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
AssistantFeedbackController assistantFeedbackController) {
mContext = context;
mHandler = mainHandler;
+ mFeatureFlags = featureFlags;
mLockscreenUserManager = notificationLockscreenUserManager;
mBypassController = bypassController;
mGroupManager = groupManager;
@@ -142,7 +146,9 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
NotificationListContainer listContainer) {
mPresenter = presenter;
mListContainer = listContainer;
- mDynamicPrivacyController.addListener(this);
+ if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ mDynamicPrivacyController.addListener(this);
+ }
}
/**
@@ -151,6 +157,10 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
//TODO: Rewrite this to focus on Entries, or some other data object instead of views
public void updateNotificationViews() {
Assert.isMainThread();
+ if (!mFeatureFlags.checkLegacyPipelineEnabled()) {
+ return;
+ }
+
beginUpdate();
List<NotificationEntry> activeNotifications = mEntryManager.getVisibleNotifications();
@@ -425,6 +435,10 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
*/
public void updateRowStates() {
Assert.isMainThread();
+ if (!mFeatureFlags.checkLegacyPipelineEnabled()) {
+ return;
+ }
+
beginUpdate();
updateRowStatesInternal();
endUpdate();
@@ -510,6 +524,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
@Override
public void onDynamicPrivacyChanged() {
+ mFeatureFlags.assertLegacyPipelineEnabled();
if (mPerformingUpdate) {
Log.w(TAG, "onDynamicPrivacyChanged made a re-entrant call");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
index cc7a4f836c63..4a6d7e184ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -15,46 +15,18 @@
package com.android.systemui.statusbar;
import android.content.Context;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.telephony.ServiceState;
-import android.telephony.SubscriptionInfo;
-import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.TextView;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.WirelessUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.demomode.DemoModeCommandReceiver;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerService.Tunable;
import java.util.List;
/** Shows the operator name */
-public class OperatorNameView extends TextView implements DemoModeCommandReceiver, DarkReceiver,
- SignalCallback, Tunable {
-
- private static final String KEY_SHOW_OPERATOR_NAME = "show_operator_name";
-
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+public class OperatorNameView extends TextView {
private boolean mDemoMode;
- private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
- @Override
- public void onRefreshCarrierInfo() {
- updateText();
- }
- };
-
public OperatorNameView(Context context) {
this(context, null);
}
@@ -67,62 +39,14 @@ public class OperatorNameView extends TextView implements DemoModeCommandReceive
super(context, attrs, defStyle);
}
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
- mKeyguardUpdateMonitor.registerCallback(mCallback);
- Dependency.get(DarkIconDispatcher.class).addDarkReceiver(this);
- Dependency.get(NetworkController.class).addCallback(this);
- Dependency.get(TunerService.class).addTunable(this, KEY_SHOW_OPERATOR_NAME);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mKeyguardUpdateMonitor.removeCallback(mCallback);
- Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(this);
- Dependency.get(NetworkController.class).removeCallback(this);
- Dependency.get(TunerService.class).removeTunable(this);
- }
-
- @Override
- public void onDarkChanged(Rect area, float darkIntensity, int tint) {
- setTextColor(DarkIconDispatcher.getTint(area, this, tint));
- }
-
- @Override
- public void setIsAirplaneMode(IconState icon) {
- update();
- }
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- update();
- }
-
- @Override
- public void dispatchDemoCommand(String command, Bundle args) {
- setText(args.getString("name"));
- }
-
- @Override
- public void onDemoModeStarted() {
- mDemoMode = true;
- }
-
- @Override
- public void onDemoModeFinished() {
- mDemoMode = false;
- update();
+ void setDemoMode(boolean demoMode) {
+ mDemoMode = demoMode;
}
- private void update() {
- boolean showOperatorName = Dependency.get(TunerService.class)
- .getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0;
+ void update(boolean showOperatorName, boolean hasMobile,
+ List<OperatorNameViewController.SubInfo> subs) {
setVisibility(showOperatorName ? VISIBLE : GONE);
- boolean hasMobile = mContext.getSystemService(TelephonyManager.class).isDataCapable();
boolean airplaneMode = WirelessUtils.isAirplaneModeOn(mContext);
if (!hasMobile || airplaneMode) {
setText(null);
@@ -131,22 +55,19 @@ public class OperatorNameView extends TextView implements DemoModeCommandReceive
}
if (!mDemoMode) {
- updateText();
+ updateText(subs);
}
}
- private void updateText() {
+ void updateText(List<OperatorNameViewController.SubInfo> subs) {
CharSequence displayText = null;
- List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
final int N = subs.size();
for (int i = 0; i < N; i++) {
- int subId = subs.get(i).getSubscriptionId();
- int simState = mKeyguardUpdateMonitor.getSimState(subId);
+ OperatorNameViewController.SubInfo subInfo = subs.get(i);
CharSequence carrierName = subs.get(i).getCarrierName();
- if (!TextUtils.isEmpty(carrierName) && simState == TelephonyManager.SIM_STATE_READY) {
- ServiceState ss = mKeyguardUpdateMonitor.getServiceState(subId);
- if (ss != null && ss.getState() == ServiceState.STATE_IN_SERVICE) {
- displayText = carrierName;
+ if (!TextUtils.isEmpty(carrierName) && subInfo.simReady()) {
+ if (subInfo.stateInService()) {
+ displayText = subInfo.getCarrierName();
break;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
new file mode 100644
index 000000000000..bcba5cc09c5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
@@ -0,0 +1,201 @@
+/*
+ * 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.systemui.statusbar;
+
+import android.os.Bundle;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+import android.view.View;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.demomode.DemoModeCommandReceiver;
+import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.ViewController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+/** Controller for {@link OperatorNameView}. */
+public class OperatorNameViewController extends ViewController<OperatorNameView> {
+ private static final String KEY_SHOW_OPERATOR_NAME = "show_operator_name";
+
+ private final DarkIconDispatcher mDarkIconDispatcher;
+ private final NetworkController mNetworkController;
+ private final TunerService mTunerService;
+ private final TelephonyManager mTelephonyManager;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+ private OperatorNameViewController(OperatorNameView view,
+ DarkIconDispatcher darkIconDispatcher,
+ NetworkController networkController,
+ TunerService tunerService,
+ TelephonyManager telephonyManager,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ super(view);
+ mDarkIconDispatcher = darkIconDispatcher;
+ mNetworkController = networkController;
+ mTunerService = tunerService;
+ mTelephonyManager = telephonyManager;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mDarkIconDispatcher.addDarkReceiver(mDarkReceiver);
+ mNetworkController.addCallback(mSignalCallback);
+ mTunerService.addTunable(mTunable, KEY_SHOW_OPERATOR_NAME);
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mDarkIconDispatcher.removeDarkReceiver(mDarkReceiver);
+ mNetworkController.removeCallback(mSignalCallback);
+ mTunerService.removeTunable(mTunable);
+ mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
+ }
+
+ private void update() {
+ mView.update(mTunerService.getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0,
+ mTelephonyManager.isDataCapable(), getSubInfos());
+ }
+
+ private List<SubInfo> getSubInfos() {
+ List<SubInfo> result = new ArrayList<>();
+ List<SubscriptionInfo> subscritionInfos =
+ mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
+
+ for (SubscriptionInfo subscriptionInfo : subscritionInfos) {
+ int subId = subscriptionInfo.getSubscriptionId();
+ result.add(new SubInfo(
+ subscriptionInfo.getCarrierName(),
+ mKeyguardUpdateMonitor.getSimState(subId),
+ mKeyguardUpdateMonitor.getServiceState(subId)));
+ }
+
+ return result;
+ }
+
+ /** Factory for constructing an {@link OperatorNameViewController}. */
+ public static class Factory {
+ private final DarkIconDispatcher mDarkIconDispatcher;
+ private final NetworkController mNetworkController;
+ private final TunerService mTunerService;
+ private final TelephonyManager mTelephonyManager;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+ @Inject
+ public Factory(DarkIconDispatcher darkIconDispatcher, NetworkController networkController,
+ TunerService tunerService, TelephonyManager telephonyManager,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ mDarkIconDispatcher = darkIconDispatcher;
+ mNetworkController = networkController;
+ mTunerService = tunerService;
+ mTelephonyManager = telephonyManager;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ }
+
+ /** Create an {@link OperatorNameViewController}. */
+ public OperatorNameViewController create(OperatorNameView view) {
+ return new OperatorNameViewController(view, mDarkIconDispatcher, mNetworkController,
+ mTunerService, mTelephonyManager, mKeyguardUpdateMonitor);
+ }
+ }
+
+ /**
+ * Needed because of how {@link CollapsedStatusBarFragment} works.
+ *
+ * Ideally this can be done internally.
+ **/
+ public View getView() {
+ return mView;
+ }
+
+ private final DarkIconDispatcher.DarkReceiver mDarkReceiver =
+ (area, darkIntensity, tint) ->
+ mView.setTextColor(DarkIconDispatcher.getTint(area, mView, tint));
+
+ private final NetworkController.SignalCallback mSignalCallback =
+ new NetworkController.SignalCallback() {
+ @Override
+ public void setIsAirplaneMode(NetworkController.IconState icon) {
+ update();
+ }
+ };
+
+ private final TunerService.Tunable mTunable = (key, newValue) -> update();
+
+
+ private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onRefreshCarrierInfo() {
+ mView.updateText(getSubInfos());
+ }
+ };
+
+ // TODO: do we even register this anywhere?
+ private final DemoModeCommandReceiver mDemoModeCommandReceiver = new DemoModeCommandReceiver() {
+ @Override
+ public void onDemoModeStarted() {
+ mView.setDemoMode(true);
+ }
+
+ @Override
+ public void onDemoModeFinished() {
+ mView.setDemoMode(false);
+ update();
+ }
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+ mView.setText(args.getString("name"));
+ }
+ };
+
+ static class SubInfo {
+ private final CharSequence mCarrierName;
+ private final int mSimState;
+ private final ServiceState mServiceState;
+
+ private SubInfo(CharSequence carrierName,
+ int simState, ServiceState serviceState) {
+ mCarrierName = carrierName;
+ mSimState = simState;
+ mServiceState = serviceState;
+ }
+
+ boolean simReady() {
+ return mSimState == TelephonyManager.SIM_STATE_READY;
+ }
+
+ CharSequence getCarrierName() {
+ return mCarrierName;
+ }
+
+ boolean stateInService() {
+ return mServiceState != null
+ && mServiceState.getState() == ServiceState.STATE_IN_SERVICE;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index 180f81c22a9d..cde3b0e2e76b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -25,7 +25,6 @@ import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.Pair;
-import com.android.internal.util.Preconditions;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -245,6 +244,10 @@ public class RemoteInputController {
mCallbacks.add(callback);
}
+ public void removeCallback(Callback callback) {
+ mCallbacks.remove(callback);
+ }
+
public void remoteInputSent(NotificationEntry entry) {
int N = mCallbacks.size();
for (int i = 0; i < N; i++) {
@@ -296,6 +299,9 @@ public class RemoteInputController {
default void onRemoteInputSent(NotificationEntry entry) {}
}
+ /**
+ * This is a delegate which implements some view controller pieces of the remote input process
+ */
public interface Delegate {
/**
* Activate remote input if necessary.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
new file mode 100644
index 000000000000..90abec17771c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
@@ -0,0 +1,141 @@
+/*
+ * 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.systemui.statusbar;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.RemoteInputHistoryItem;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Parcelable;
+import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+
+/**
+ * A helper class which will augment the notifications using arguments and other information
+ * accessible to the entry in order to provide intermediate remote input states.
+ */
+@SysUISingleton
+public class RemoteInputNotificationRebuilder {
+
+ private final Context mContext;
+
+ @Inject
+ RemoteInputNotificationRebuilder(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * When a smart reply is sent off to the app, we insert the text into the remote input history,
+ * and show a spinner to indicate that the app has yet to respond.
+ */
+ @NonNull
+ public StatusBarNotification rebuildForSendingSmartReply(NotificationEntry entry,
+ CharSequence reply) {
+ return rebuildWithRemoteInputInserted(entry, reply,
+ true /* showSpinner */,
+ null /* mimeType */, null /* uri */);
+ }
+
+ /**
+ * When the app cancels a notification in response to a smart reply, we remove the spinner
+ * and leave the previously-added reply. This is the lifetime-extended appearance of the
+ * notification.
+ */
+ @NonNull
+ public StatusBarNotification rebuildForCanceledSmartReplies(
+ NotificationEntry entry) {
+ return rebuildWithRemoteInputInserted(entry, null /* remoteInputTest */,
+ false /* showSpinner */, null /* mimeType */, null /* uri */);
+ }
+
+ /**
+ * When the app cancels a notification in response to a remote input reply, we update the
+ * notification with the reply text and/or attachment. This is the lifetime-extended
+ * appearance of the notification.
+ */
+ @NonNull
+ public StatusBarNotification rebuildForRemoteInputReply(NotificationEntry entry) {
+ CharSequence remoteInputText = entry.remoteInputText;
+ if (TextUtils.isEmpty(remoteInputText)) {
+ remoteInputText = entry.remoteInputTextWhenReset;
+ }
+ String remoteInputMimeType = entry.remoteInputMimeType;
+ Uri remoteInputUri = entry.remoteInputUri;
+ StatusBarNotification newSbn = rebuildWithRemoteInputInserted(entry,
+ remoteInputText, false /* showSpinner */, remoteInputMimeType,
+ remoteInputUri);
+ return newSbn;
+ }
+
+ /** Inner method for generating the SBN */
+ @VisibleForTesting
+ @NonNull
+ StatusBarNotification rebuildWithRemoteInputInserted(NotificationEntry entry,
+ CharSequence remoteInputText, boolean showSpinner, String mimeType, Uri uri) {
+ StatusBarNotification sbn = entry.getSbn();
+
+ Notification.Builder b = Notification.Builder
+ .recoverBuilder(mContext, sbn.getNotification().clone());
+ if (remoteInputText != null || uri != null) {
+ RemoteInputHistoryItem newItem = uri != null
+ ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText)
+ : new RemoteInputHistoryItem(remoteInputText);
+ Parcelable[] oldHistoryItems = sbn.getNotification().extras
+ .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null
+ ? Stream.concat(
+ Stream.of(newItem),
+ Arrays.stream(oldHistoryItems).map(p -> (RemoteInputHistoryItem) p))
+ .toArray(RemoteInputHistoryItem[]::new)
+ : new RemoteInputHistoryItem[] { newItem };
+ b.setRemoteInputHistory(newHistoryItems);
+ }
+ b.setShowRemoteInputSpinner(showSpinner);
+ b.setHideSmartReplies(true);
+
+ Notification newNotification = b.build();
+
+ // Undo any compatibility view inflation
+ newNotification.contentView = sbn.getNotification().contentView;
+ newNotification.bigContentView = sbn.getNotification().bigContentView;
+ newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
+
+ return new StatusBarNotification(
+ sbn.getPackageName(),
+ sbn.getOpPkg(),
+ sbn.getId(),
+ sbn.getTag(),
+ sbn.getUid(),
+ sbn.getInitialPid(),
+ newNotification,
+ sbn.getUser(),
+ sbn.getOverrideGroupKey(),
+ sbn.getPostTime());
+ }
+
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
index 7fc18b753d40..e288b1530d4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
@@ -19,35 +19,44 @@ import android.app.Notification;
import android.os.RemoteException;
import android.util.ArraySet;
+import androidx.annotation.NonNull;
+
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.Dumpable;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.Set;
/**
* Handles when smart replies are added to a notification
* and clicked upon.
*/
-public class SmartReplyController {
+public class SmartReplyController implements Dumpable {
private final IStatusBarService mBarService;
private final NotificationEntryManager mEntryManager;
private final NotificationClickNotifier mClickNotifier;
- private Set<String> mSendingKeys = new ArraySet<>();
+ private final Set<String> mSendingKeys = new ArraySet<>();
private Callback mCallback;
/**
* Injected constructor. See {@link StatusBarModule}.
*/
- public SmartReplyController(NotificationEntryManager entryManager,
+ public SmartReplyController(
+ DumpManager dumpManager,
+ NotificationEntryManager entryManager,
IStatusBarService statusBarService,
NotificationClickNotifier clickNotifier) {
mBarService = statusBarService;
mEntryManager = entryManager;
mClickNotifier = clickNotifier;
+ dumpManager.registerDumpable(this);
}
public void setCallback(Callback callback) {
@@ -75,6 +84,7 @@ public class SmartReplyController {
public void smartActionClicked(
NotificationEntry entry, int actionIndex, Notification.Action action,
boolean generatedByAssistant) {
+ // TODO(b/204183781): get this from the current pipeline
final int count = mEntryManager.getActiveNotificationsCount();
final int rank = entry.getRanking().getRank();
NotificationVisibility.NotificationLocation location =
@@ -112,6 +122,14 @@ public class SmartReplyController {
}
}
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("mSendingKeys: " + mSendingKeys.size());
+ for (String key : mSendingKeys) {
+ pw.println(" * " + key);
+ }
+ }
+
/**
* Callback for any class that needs to do something in response to a smart reply being sent.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 0725bf961e13..cbb3aba5cc64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -16,6 +16,10 @@
package com.android.systemui.statusbar;
+import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
+
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_TO_AOD;
@@ -23,10 +27,16 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
+import android.os.SystemProperties;
import android.text.format.DateFormat;
import android.util.FloatProperty;
import android.util.Log;
+import android.view.InsetsFlags;
+import android.view.InsetsVisibilities;
import android.view.View;
+import android.view.ViewDebug;
+import android.view.WindowInsetsController.Appearance;
+import android.view.WindowInsetsController.Behavior;
import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
@@ -38,6 +48,7 @@ import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.policy.CallbackController;
@@ -53,9 +64,14 @@ import javax.inject.Inject;
* Tracks and reports on {@link StatusBarState}.
*/
@SysUISingleton
-public class StatusBarStateControllerImpl implements SysuiStatusBarStateController,
- CallbackController<StateListener>, Dumpable {
+public class StatusBarStateControllerImpl implements
+ SysuiStatusBarStateController,
+ CallbackController<StateListener>,
+ Dumpable {
private static final String TAG = "SbStateController";
+ private static final boolean DEBUG_IMMERSIVE_APPS =
+ SystemProperties.getBoolean("persist.debug.immersive_apps", false);
+
// Must be a power of 2
private static final int HISTORY_SIZE = 32;
@@ -133,11 +149,13 @@ public class StatusBarStateControllerImpl implements SysuiStatusBarStateControll
private Interpolator mDozeInterpolator = Interpolators.FAST_OUT_SLOW_IN;
@Inject
- public StatusBarStateControllerImpl(UiEventLogger uiEventLogger) {
+ public StatusBarStateControllerImpl(UiEventLogger uiEventLogger, DumpManager dumpManager) {
mUiEventLogger = uiEventLogger;
for (int i = 0; i < HISTORY_SIZE; i++) {
mHistoricalRecords[i] = new HistoricalState();
}
+
+ dumpManager.registerDumpable(this);
}
@Override
@@ -420,15 +438,31 @@ public class StatusBarStateControllerImpl implements SysuiStatusBarStateControll
}
@Override
- public void setFullscreenState(boolean isFullscreen) {
+ public void setSystemBarAttributes(@Appearance int appearance, @Behavior int behavior,
+ InsetsVisibilities requestedVisibilities, String packageName) {
+ boolean isFullscreen = !requestedVisibilities.getVisibility(ITYPE_STATUS_BAR)
+ || !requestedVisibilities.getVisibility(ITYPE_NAVIGATION_BAR);
if (mIsFullscreen != isFullscreen) {
mIsFullscreen = isFullscreen;
synchronized (mListeners) {
for (RankedListener rl : new ArrayList<>(mListeners)) {
- rl.mListener.onFullscreenStateChanged(isFullscreen, true /* isImmersive */);
+ rl.mListener.onFullscreenStateChanged(isFullscreen);
}
}
}
+
+ // TODO (b/190543382): Finish the logging logic.
+ // This section can be removed if we don't need to print it on logcat.
+ if (DEBUG_IMMERSIVE_APPS) {
+ boolean dim = (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0;
+ String behaviorName = ViewDebug.flagsToString(InsetsFlags.class, "behavior", behavior);
+ String requestedVisibilityString = requestedVisibilities.toString();
+ if (requestedVisibilityString.isEmpty()) {
+ requestedVisibilityString = "none";
+ }
+ Log.d(TAG, packageName + " dim=" + dim + " behavior=" + behaviorName
+ + " requested visibilities: " + requestedVisibilityString);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
deleted file mode 100644
index e4ae560ba69b..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-
-import com.android.systemui.R;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
-import com.android.systemui.statusbar.phone.StatusBarWindowView;
-import com.android.systemui.util.InjectionInflationController;
-
-import javax.inject.Inject;
-
-/**
- * Creates a single instance of super_status_bar and super_notification_shade that can be shared
- * across various system ui objects.
- */
-@SysUISingleton
-public class SuperStatusBarViewFactory {
-
- private final Context mContext;
- private final InjectionInflationController mInjectionInflationController;
- private final NotificationShelfComponent.Builder mNotificationShelfComponentBuilder;
-
- private NotificationShadeWindowView mNotificationShadeWindowView;
- private StatusBarWindowView mStatusBarWindowView;
- private NotificationShelfController mNotificationShelfController;
-
- @Inject
- public SuperStatusBarViewFactory(Context context,
- InjectionInflationController injectionInflationController,
- NotificationShelfComponent.Builder notificationShelfComponentBuilder) {
- mContext = context;
- mInjectionInflationController = injectionInflationController;
- mNotificationShelfComponentBuilder = notificationShelfComponentBuilder;
- }
-
- /**
- * Gets the inflated {@link NotificationShadeWindowView} from
- * {@link R.layout#super_notification_shade}.
- * Returns a cached instance, if it has already been inflated.
- */
- public NotificationShadeWindowView getNotificationShadeWindowView() {
- if (mNotificationShadeWindowView != null) {
- return mNotificationShadeWindowView;
- }
-
- mNotificationShadeWindowView = (NotificationShadeWindowView)
- mInjectionInflationController.injectable(
- LayoutInflater.from(mContext)).inflate(R.layout.super_notification_shade,
- /* root= */ null);
- if (mNotificationShadeWindowView == null) {
- throw new IllegalStateException(
- "R.layout.super_notification_shade could not be properly inflated");
- }
-
- return mNotificationShadeWindowView;
- }
-
- /**
- * Gets the inflated {@link StatusBarWindowView} from {@link R.layout#super_status_bar}.
- * Returns a cached instance, if it has already been inflated.
- */
- public StatusBarWindowView getStatusBarWindowView() {
- if (mStatusBarWindowView != null) {
- return mStatusBarWindowView;
- }
-
- mStatusBarWindowView =
- (StatusBarWindowView) mInjectionInflationController.injectable(
- LayoutInflater.from(mContext)).inflate(R.layout.super_status_bar,
- /* root= */ null);
- if (mStatusBarWindowView == null) {
- throw new IllegalStateException(
- "R.layout.super_status_bar could not be properly inflated");
- }
- return mStatusBarWindowView;
- }
-
- /**
- * Gets the inflated {@link NotificationShelf} from
- * {@link R.layout#status_bar_notification_shelf}.
- * Returns a cached instance, if it has already been inflated.
- *
- * @param container the expected container to hold the {@link NotificationShelf}. The view
- * isn't immediately attached, but the layout params of this view is used
- * during inflation.
- */
- public NotificationShelfController getNotificationShelfController(ViewGroup container) {
- if (mNotificationShelfController != null) {
- return mNotificationShelfController;
- }
-
- NotificationShelf view = (NotificationShelf) LayoutInflater.from(mContext)
- .inflate(R.layout.status_bar_notification_shelf, container, /* attachToRoot= */
- false);
-
- if (view == null) {
- throw new IllegalStateException(
- "R.layout.status_bar_notification_shelf could not be properly inflated");
- }
-
- NotificationShelfComponent component = mNotificationShelfComponentBuilder
- .notificationShelf(view)
- .build();
- mNotificationShelfController = component.getNotificationShelfController();
- mNotificationShelfController.init();
-
- return mNotificationShelfController;
- }
-
- public NotificationPanelView getNotificationPanelView() {
- NotificationShadeWindowView notificationShadeWindowView = getNotificationShadeWindowView();
- if (notificationShadeWindowView == null) {
- return null;
- }
-
- return mNotificationShadeWindowView.findViewById(R.id.notification_panel);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index 25200501a916..f0b2c2d54dbe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -19,7 +19,10 @@ package com.android.systemui.statusbar;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
+import android.view.InsetsVisibilities;
import android.view.View;
+import android.view.WindowInsetsController.Appearance;
+import android.view.WindowInsetsController.Behavior;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -155,9 +158,10 @@ public interface SysuiStatusBarStateController extends StatusBarStateController
boolean isKeyguardRequested();
/**
- * Set the fullscreen state
+ * Set the system bar attributes
*/
- void setFullscreenState(boolean isFullscreen);
+ void setSystemBarAttributes(@Appearance int appearance, @Behavior int behavior,
+ InsetsVisibilities requestedVisibilities, String packageName);
/**
* Set pulsing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
index 22bbb81b44e6..04c60fc197d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
@@ -29,7 +29,7 @@ import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.Utils
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
@@ -114,9 +114,6 @@ class WiredChargingRippleController @Inject constructor(
override fun onThemeChanged() {
updateRippleColor()
}
- override fun onOverlayChanged() {
- updateRippleColor()
- }
override fun onConfigChanged(newConfig: Configuration?) {
normalizedPortPosX = context.resources.getFloat(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
index 6d6320e6962d..a23d73d7ed84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.connectivity;
import android.content.Context;
import android.content.Intent;
@@ -55,6 +55,7 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
+/** */
public class AccessPointControllerImpl
implements NetworkController.AccessPointController,
WifiPickerTracker.WifiPickerTrackerCallback, LifecycleOwner {
@@ -116,17 +117,19 @@ public class AccessPointControllerImpl
super.finalize();
}
+ /** */
public boolean canConfigWifi() {
return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI,
new UserHandle(mCurrentUser));
}
+ /** */
public boolean canConfigMobileData() {
return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
UserHandle.of(mCurrentUser)) && mUserTracker.getUserInfo().isAdmin();
}
- public void onUserSwitched(int newUserId) {
+ void onUserSwitched(int newUserId) {
mCurrentUser = newUserId;
}
@@ -225,7 +228,7 @@ public class AccessPointControllerImpl
}
}
- public void dump(PrintWriter pw) {
+ void dump(PrintWriter pw) {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
ipw.println("AccessPointControllerImpl:");
ipw.increaseIndent();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java
index 7ac6d63430d6..052a789200e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.connectivity;
import android.os.Handler;
import android.os.Looper;
@@ -22,11 +22,11 @@ import android.telephony.SubscriptionInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
-import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
+import com.android.systemui.statusbar.connectivity.NetworkController.EmergencyListener;
+import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
+import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
+import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
@@ -235,7 +235,7 @@ public class CallbackHandler extends Handler implements EmergencyListener, Signa
.append("icon=").append(icon)
.toString();
recordLastCallback(log);
- obtainMessage(MSG_ETHERNET_CHANGED, icon).sendToTarget();;
+ obtainMessage(MSG_ETHERNET_CHANGED, icon).sendToTarget();
}
@Override
@@ -252,14 +252,14 @@ public class CallbackHandler extends Handler implements EmergencyListener, Signa
.toString();
recordLastCallback(log);
}
- obtainMessage(MSG_AIRPLANE_MODE_CHANGED, icon).sendToTarget();;
+ obtainMessage(MSG_AIRPLANE_MODE_CHANGED, icon).sendToTarget();
}
- public void setListening(EmergencyListener listener, boolean listening) {
+ void setListening(EmergencyListener listener, boolean listening) {
obtainMessage(MSG_ADD_REMOVE_EMERGENCY, listening ? 1 : 0, 0, listener).sendToTarget();
}
- public void setListening(SignalCallback listener, boolean listening) {
+ void setListening(SignalCallback listener, boolean listening) {
obtainMessage(MSG_ADD_REMOVE_SIGNAL, listening ? 1 : 0, 0, listener).sendToTarget();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/EthernetIcons.java
index b391bd9cf651..196aad99dcb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/EthernetIcons.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.connectivity;
import com.android.systemui.R;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/EthernetSignalController.java
index 80b75a740965..c9d40adde644 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/EthernetSignalController.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.connectivity;
import android.content.Context;
import android.net.NetworkCapabilities;
@@ -21,12 +21,12 @@ import android.net.NetworkCapabilities;
import com.android.settingslib.AccessibilityContentDescriptions;
import com.android.settingslib.SignalIcon.IconGroup;
import com.android.settingslib.SignalIcon.State;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
+import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
import java.util.BitSet;
-
+/** */
public class EthernetSignalController extends
SignalController<State, IconGroup> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
index 43781f3941ba..20ef4eec0b97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.connectivity;
import static com.android.settingslib.mobile.MobileMappings.getDefaultIcons;
import static com.android.settingslib.mobile.MobileMappings.getIconKey;
@@ -57,10 +57,10 @@ import com.android.settingslib.mobile.MobileStatusTracker.SubscriptionDefaults;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.SignalStrengthUtil;
import com.android.systemui.R;
-import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
+import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
+import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
import com.android.systemui.util.CarrierConfigTracker;
import java.io.PrintWriter;
@@ -91,8 +91,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
private int mImsType = IMS_TYPE_WWAN;
// Save entire info for logging, we only use the id.
final SubscriptionInfo mSubscriptionInfo;
- // @VisibleForDemoMode
- Map<String, MobileIconGroup> mNetworkToIconLookup;
+ private Map<String, MobileIconGroup> mNetworkToIconLookup;
// Since some pieces of the phone state are interdependent we store it locally,
// this could potentially become part of MobileState for simplification/complication
@@ -108,8 +107,6 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
private Config mConfig;
@VisibleForTesting
boolean mInflateSignalStrengths = false;
- private MobileStatusTracker.Callback mCallback;
- private RegistrationCallback mRegistrationCallback;
private int mLastWwanLevel;
private int mLastWlanLevel;
private int mLastWlanCrossSimLevel;
@@ -121,6 +118,82 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
// Where to copy the next state into.
private int mMobileStatusHistoryIndex;
+ private final MobileStatusTracker.Callback mMobileCallback =
+ new MobileStatusTracker.Callback() {
+ private String mLastStatus;
+
+ @Override
+ public void onMobileStatusChanged(boolean updateTelephony,
+ MobileStatus mobileStatus) {
+ if (Log.isLoggable(mTag, Log.DEBUG)) {
+ Log.d(mTag, "onMobileStatusChanged="
+ + " updateTelephony=" + updateTelephony
+ + " mobileStatus=" + mobileStatus.toString());
+ }
+ String currentStatus = mobileStatus.toString();
+ if (!currentStatus.equals(mLastStatus)) {
+ mLastStatus = currentStatus;
+ String status = new StringBuilder()
+ .append(SSDF.format(System.currentTimeMillis())).append(",")
+ .append(currentStatus)
+ .toString();
+ recordLastMobileStatus(status);
+ }
+ updateMobileStatus(mobileStatus);
+ if (updateTelephony) {
+ updateTelephony();
+ } else {
+ notifyListenersIfNecessary();
+ }
+ }
+ };
+
+ private final RegistrationCallback mRegistrationCallback = new RegistrationCallback() {
+ @Override
+ public void onRegistered(ImsRegistrationAttributes attributes) {
+ Log.d(mTag, "onRegistered: " + "attributes=" + attributes);
+ int imsTransportType = attributes.getTransportType();
+ int registrationAttributes = attributes.getAttributeFlags();
+ if (imsTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+ mImsType = IMS_TYPE_WWAN;
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
+ getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ } else if (imsTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
+ if (registrationAttributes == 0) {
+ mImsType = IMS_TYPE_WLAN;
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWlanLevel, /* isWifi= */true),
+ getCallStrengthDescription(mLastWlanLevel, /* isWifi= */true));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ } else if (registrationAttributes
+ == ImsRegistrationAttributes.ATTR_EPDG_OVER_CELL_INTERNET) {
+ mImsType = IMS_TYPE_WLAN_CROSS_SIM;
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWlanCrossSimLevel, /* isWifi= */false),
+ getCallStrengthDescription(
+ mLastWlanCrossSimLevel, /* isWifi= */false));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ }
+ }
+ }
+
+ @Override
+ public void onUnregistered(ImsReasonInfo info) {
+ Log.d(mTag, "onDeregistered: " + "info=" + info);
+ mImsType = IMS_TYPE_WWAN;
+ IconState statusIcon = new IconState(
+ true,
+ getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
+ getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
+ notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
+ }
+ };
+
// TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
// need listener lists anymore.
public MobileSignalController(
@@ -144,8 +217,8 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
mPhone = phone;
mDefaults = defaults;
mSubscriptionInfo = info;
- mNetworkNameSeparator = getTextIfExists(R.string.status_bar_network_name_separator)
- .toString();
+ mNetworkNameSeparator = getTextIfExists(
+ R.string.status_bar_network_name_separator).toString();
mNetworkNameDefault = getTextIfExists(
com.android.internal.R.string.lockscreen_carrier_default).toString();
mReceiverHandler = new Handler(receiverLooper);
@@ -165,88 +238,14 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
updateTelephony();
}
};
- mCallback = new MobileStatusTracker.Callback() {
- private String mLastStatus;
-
- @Override
- public void onMobileStatusChanged(boolean updateTelephony,
- MobileStatus mobileStatus) {
- if (Log.isLoggable(mTag, Log.DEBUG)) {
- Log.d(mTag, "onMobileStatusChanged="
- + " updateTelephony=" + updateTelephony
- + " mobileStatus=" + mobileStatus.toString());
- }
- String currentStatus = mobileStatus.toString();
- if (!currentStatus.equals(mLastStatus)) {
- mLastStatus = currentStatus;
- String status = new StringBuilder()
- .append(SSDF.format(System.currentTimeMillis())).append(",")
- .append(currentStatus)
- .toString();
- recordLastMobileStatus(status);
- }
- updateMobileStatus(mobileStatus);
- if (updateTelephony) {
- updateTelephony();
- } else {
- notifyListenersIfNecessary();
- }
- }
- };
-
- mRegistrationCallback = new RegistrationCallback() {
- @Override
- public void onRegistered(ImsRegistrationAttributes attributes) {
- Log.d(mTag, "onRegistered: " + "attributes=" + attributes);
- int imsTransportType = attributes.getTransportType();
- int registrationAttributes = attributes.getAttributeFlags();
- if (imsTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
- mImsType = IMS_TYPE_WWAN;
- IconState statusIcon = new IconState(
- true,
- getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
- getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
- notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
- } else if (imsTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
- if (registrationAttributes == 0) {
- mImsType = IMS_TYPE_WLAN;
- IconState statusIcon = new IconState(
- true,
- getCallStrengthIcon(mLastWlanLevel, /* isWifi= */true),
- getCallStrengthDescription(mLastWlanLevel, /* isWifi= */true));
- notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
- } else if (registrationAttributes
- == ImsRegistrationAttributes.ATTR_EPDG_OVER_CELL_INTERNET) {
- mImsType = IMS_TYPE_WLAN_CROSS_SIM;
- IconState statusIcon = new IconState(
- true,
- getCallStrengthIcon(mLastWlanCrossSimLevel, /* isWifi= */false),
- getCallStrengthDescription(
- mLastWlanCrossSimLevel, /* isWifi= */false));
- notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
- }
- }
- }
-
- @Override
- public void onUnregistered(ImsReasonInfo info) {
- Log.d(mTag, "onDeregistered: " + "info=" + info);
- mImsType = IMS_TYPE_WWAN;
- IconState statusIcon = new IconState(
- true,
- getCallStrengthIcon(mLastWwanLevel, /* isWifi= */false),
- getCallStrengthDescription(mLastWwanLevel, /* isWifi= */false));
- notifyCallStateChange(statusIcon, mSubscriptionInfo.getSubscriptionId());
- }
- };
mImsMmTelManager = ImsMmTelManager.createForSubscriptionId(info.getSubscriptionId());
mMobileStatusTracker = new MobileStatusTracker(mPhone, receiverLooper,
- info, mDefaults, mCallback);
+ info, mDefaults, mMobileCallback);
mProviderModelBehavior = featureFlags.isCombinedStatusBarSignalIconsEnabled();
mProviderModelSetting = featureFlags.isProviderModelSettingEnabled();
}
- public void setConfiguration(Config config) {
+ void setConfiguration(Config config) {
mConfig = config;
updateInflateSignalStrength();
mNetworkToIconLookup = mapIconSets(mConfig);
@@ -254,12 +253,12 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
updateTelephony();
}
- public void setAirplaneMode(boolean airplaneMode) {
+ void setAirplaneMode(boolean airplaneMode) {
mCurrentState.airplaneMode = airplaneMode;
notifyListenersIfNecessary();
}
- public void setUserSetupComplete(boolean userSetup) {
+ void setUserSetupComplete(boolean userSetup) {
mCurrentState.userSetup = userSetup;
notifyListenersIfNecessary();
}
@@ -273,7 +272,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
notifyListenersIfNecessary();
}
- public void setCarrierNetworkChangeMode(boolean carrierNetworkChangeMode) {
+ void setCarrierNetworkChangeMode(boolean carrierNetworkChangeMode) {
mCurrentState.carrierNetworkChangeMode = carrierNetworkChangeMode;
updateTelephony();
}
@@ -385,89 +384,83 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
if (mCurrentState.inetCondition == 0) {
dataContentDescription = mContext.getString(R.string.data_connection_no_internet);
}
- final boolean dataDisabled = (mCurrentState.iconGroup == TelephonyIcons.DATA_DISABLED
- || (mCurrentState.iconGroup == TelephonyIcons.NOT_DEFAULT_DATA))
- && mCurrentState.userSetup;
- if (mProviderModelBehavior) {
- // Show icon in QS when we are connected or data is disabled.
- boolean showDataIcon = mCurrentState.dataConnected || dataDisabled;
-
- int qsTypeIcon = 0;
- IconState qsIcon = null;
- CharSequence description = null;
- // Only send data sim callbacks to QS.
- if (mCurrentState.dataSim && mCurrentState.isDefault) {
- qsTypeIcon =
- (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.qsDataType : 0;
- qsIcon = new IconState(mCurrentState.enabled
- && !mCurrentState.isEmergency, getQsCurrentIconId(), contentDescription);
- description = mCurrentState.isEmergency ? null : mCurrentState.networkName;
+ final QsInfo qsInfo = getQsInfo(contentDescription, icons.dataType);
+ final SbInfo sbInfo = getSbInfo(contentDescription, icons.dataType);
+
+ MobileDataIndicators mobileDataIndicators = new MobileDataIndicators(
+ sbInfo.icon,
+ qsInfo.icon,
+ sbInfo.ratTypeIcon,
+ qsInfo.ratTypeIcon,
+ mCurrentState.hasActivityIn(),
+ mCurrentState.hasActivityOut(),
+ dataContentDescription,
+ dataContentDescriptionHtml,
+ qsInfo.description,
+ mSubscriptionInfo.getSubscriptionId(),
+ mCurrentState.roaming,
+ sbInfo.showTriangle);
+ callback.setMobileDataIndicators(mobileDataIndicators);
+ }
+
+ private QsInfo getQsInfo(String contentDescription, int dataTypeIcon) {
+ int qsTypeIcon = 0;
+ IconState qsIcon = null;
+ CharSequence qsDescription = null;
+
+ boolean pm = mProviderModelSetting || mProviderModelBehavior;
+ if (mCurrentState.dataSim) {
+ // If using provider model behavior, only show QS icons if the state is also default
+ if (pm && !mCurrentState.isDefault) {
+ return new QsInfo(qsTypeIcon, qsIcon, qsDescription);
+ }
+
+ if (mCurrentState.showQuickSettingsRatIcon() || mConfig.alwaysShowDataRatIcon) {
+ qsTypeIcon = dataTypeIcon;
+ }
+
+ boolean qsIconVisible = mCurrentState.enabled && !mCurrentState.isEmergency;
+ qsIcon = new IconState(qsIconVisible, getQsCurrentIconId(), contentDescription);
+
+ if (!mCurrentState.isEmergency) {
+ qsDescription = mCurrentState.networkName;
}
- boolean activityIn = mCurrentState.dataConnected
- && !mCurrentState.carrierNetworkChangeMode
- && mCurrentState.activityIn;
- boolean activityOut = mCurrentState.dataConnected
- && !mCurrentState.carrierNetworkChangeMode
- && mCurrentState.activityOut;
- showDataIcon &= mCurrentState.dataSim && mCurrentState.isDefault;
- boolean showTriangle = showDataIcon && !mCurrentState.airplaneMode;
- int typeIcon = (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.dataType : 0;
- showDataIcon |= mCurrentState.roaming;
- IconState statusIcon = new IconState(showDataIcon && !mCurrentState.airplaneMode,
+ }
+
+ return new QsInfo(qsTypeIcon, qsIcon, qsDescription);
+ }
+
+ private SbInfo getSbInfo(String contentDescription, int dataTypeIcon) {
+ final boolean dataDisabled = mCurrentState.isDataDisabledOrNotDefault();
+ boolean showTriangle = false;
+ int typeIcon = 0;
+ IconState statusIcon = null;
+
+ if (mProviderModelBehavior) {
+ boolean showDataIconStatusBar = (mCurrentState.dataConnected || dataDisabled)
+ && (mCurrentState.dataSim && mCurrentState.isDefault);
+ typeIcon =
+ (showDataIconStatusBar || mConfig.alwaysShowDataRatIcon) ? dataTypeIcon : 0;
+ showDataIconStatusBar |= mCurrentState.roaming;
+ statusIcon = new IconState(
+ showDataIconStatusBar && !mCurrentState.airplaneMode,
getCurrentIconId(), contentDescription);
- MobileDataIndicators mobileDataIndicators = new MobileDataIndicators(
- statusIcon, qsIcon, typeIcon, qsTypeIcon,
- activityIn, activityOut, dataContentDescription, dataContentDescriptionHtml,
- description, icons.isWide, mSubscriptionInfo.getSubscriptionId(),
- mCurrentState.roaming, showTriangle);
- callback.setMobileDataIndicators(mobileDataIndicators);
+
+ showTriangle = showDataIconStatusBar && !mCurrentState.airplaneMode;
} else {
- boolean showDataIcon = mCurrentState.dataConnected || dataDisabled;
- IconState statusIcon = new IconState(
+ statusIcon = new IconState(
mCurrentState.enabled && !mCurrentState.airplaneMode,
getCurrentIconId(), contentDescription);
- int qsTypeIcon = 0;
- IconState qsIcon = null;
- CharSequence description = null;
- // Only send data sim callbacks to QS.
- if (mProviderModelSetting) {
- if (mCurrentState.dataSim && mCurrentState.isDefault) {
- qsTypeIcon =
- (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.qsDataType : 0;
- qsIcon = new IconState(
- mCurrentState.enabled && !mCurrentState.isEmergency,
- getQsCurrentIconId(), contentDescription);
- description = mCurrentState.isEmergency ? null : mCurrentState.networkName;
- }
- } else {
- if (mCurrentState.dataSim) {
- qsTypeIcon =
- (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.qsDataType : 0;
- qsIcon = new IconState(
- mCurrentState.enabled && !mCurrentState.isEmergency,
- getQsCurrentIconId(), contentDescription);
- description = mCurrentState.isEmergency ? null : mCurrentState.networkName;
- }
- }
-
- boolean activityIn = mCurrentState.dataConnected
- && !mCurrentState.carrierNetworkChangeMode
- && mCurrentState.activityIn;
- boolean activityOut = mCurrentState.dataConnected
- && !mCurrentState.carrierNetworkChangeMode
- && mCurrentState.activityOut;
- showDataIcon &= mCurrentState.isDefault || dataDisabled;
- int typeIcon = (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.dataType : 0;
- boolean showTriangle = mCurrentState.enabled && !mCurrentState.airplaneMode;
- MobileDataIndicators mobileDataIndicators = new MobileDataIndicators(
- statusIcon, qsIcon, typeIcon, qsTypeIcon,
- activityIn, activityOut, dataContentDescription, dataContentDescriptionHtml,
- description, icons.isWide, mSubscriptionInfo.getSubscriptionId(),
- mCurrentState.roaming, showTriangle);
- callback.setMobileDataIndicators(mobileDataIndicators);
+ boolean showDataIconInStatusBar =
+ (mCurrentState.dataConnected && mCurrentState.isDefault) || dataDisabled;
+ typeIcon =
+ (showDataIconInStatusBar || mConfig.alwaysShowDataRatIcon) ? dataTypeIcon : 0;
+ showTriangle = mCurrentState.enabled && !mCurrentState.airplaneMode;
}
+
+ return new SbInfo(showTriangle, typeIcon, statusIcon);
}
@Override
@@ -508,7 +501,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
return mCurrentState.carrierNetworkChangeMode;
}
- public void handleBroadcast(Intent intent) {
+ void handleBroadcast(Intent intent) {
String action = intent.getAction();
if (action.equals(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)) {
updateNetworkName(intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false),
@@ -747,10 +740,10 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
* mTelephonyDisplayInfo, and mSimState. It should be called any time one of these is updated.
* This will call listeners if necessary.
*/
- private final void updateTelephony() {
+ private void updateTelephony() {
if (Log.isLoggable(mTag, Log.DEBUG)) {
- Log.d(mTag, "updateTelephonySignalStrength: hasService=" +
- Utils.isInService(mServiceState) + " ss=" + mSignalStrength
+ Log.d(mTag, "updateTelephonySignalStrength: hasService="
+ + Utils.isInService(mServiceState) + " ss=" + mSignalStrength
+ " displayInfo=" + mTelephonyDisplayInfo);
}
checkDefaultData();
@@ -841,12 +834,15 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
public void dump(PrintWriter pw) {
super.dump(pw);
pw.println(" mSubscription=" + mSubscriptionInfo + ",");
+ pw.println(" mProviderModelSetting=" + mProviderModelSetting + ",");
+ pw.println(" mProviderModelBehavior=" + mProviderModelBehavior + ",");
pw.println(" mServiceState=" + mServiceState + ",");
pw.println(" mSignalStrength=" + mSignalStrength + ",");
pw.println(" mTelephonyDisplayInfo=" + mTelephonyDisplayInfo + ",");
pw.println(" mDataState=" + mDataState + ",");
pw.println(" mInflateSignalStrengths=" + mInflateSignalStrengths + ",");
pw.println(" isDataDisabled=" + isDataDisabled() + ",");
+ pw.println(" mNetworkToIconLookup=" + mNetworkToIconLookup + ",");
pw.println(" MobileStatusHistory");
int size = 0;
for (int i = 0; i < STATUS_HISTORY_SIZE; i++) {
@@ -862,4 +858,31 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
+ mMobileStatusHistory[i & (STATUS_HISTORY_SIZE - 1)]);
}
}
+
+ /** Box for QS icon info */
+ private static final class QsInfo {
+ final int ratTypeIcon;
+ final IconState icon;
+ final CharSequence description;
+
+ QsInfo(int typeIcon, IconState iconState, CharSequence desc) {
+ ratTypeIcon = typeIcon;
+ icon = iconState;
+ description = desc;
+ }
+ }
+
+ /** Box for StatusBar icon info */
+ private static final class SbInfo {
+ final boolean showTriangle;
+ final int ratTypeIcon;
+ final IconState icon;
+
+ SbInfo(boolean show, int typeIcon, IconState iconState) {
+ showTriangle = show;
+ ratTypeIcon = typeIcon;
+ icon = iconState;
+ }
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java
index eeea699a0b74..143309658779 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.connectivity;
import android.content.Context;
import android.content.Intent;
@@ -22,29 +22,44 @@ import android.telephony.SubscriptionInfo;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.demomode.DemoMode;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.CallbackController;
+import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.wifitrackerlib.MergedCarrierEntry;
import com.android.wifitrackerlib.WifiEntry;
import java.util.List;
+/** */
public interface NetworkController extends CallbackController<SignalCallback>, DemoMode {
-
+ /** */
boolean hasMobileDataFeature();
+ /** */
void setWifiEnabled(boolean enabled);
+ /** */
AccessPointController getAccessPointController();
+ /** */
DataUsageController getMobileDataController();
+ /** */
DataSaverController getDataSaverController();
+ /** */
String getMobileDataNetworkName();
+ /** */
boolean isMobileDataNetworkInService();
+ /** */
int getNumberSubscriptions();
+ /** */
boolean hasVoiceCallingFeature();
+ /** */
void addEmergencyListener(EmergencyListener listener);
+ /** */
void removeEmergencyListener(EmergencyListener listener);
+ /** */
boolean hasEmergencyCryptKeeperText();
+ /** */
boolean isRadioOn();
/**
@@ -81,7 +96,7 @@ public interface NetworkController extends CallbackController<SignalCallback>, D
.append(",qsIcon=").append(qsIcon == null ? "" : qsIcon.toString())
.append(",activityIn=").append(activityIn)
.append(",activityOut=").append(activityOut)
- .append(",description=").append(description)
+ .append(",qsDescription=").append(description)
.append(",isTransient=").append(isTransient)
.append(",statusLabel=").append(statusLabel)
.append(']').toString();
@@ -100,8 +115,7 @@ public interface NetworkController extends CallbackController<SignalCallback>, D
public boolean activityOut;
public CharSequence typeContentDescription;
public CharSequence typeContentDescriptionHtml;
- public CharSequence description;
- public boolean isWide;
+ public CharSequence qsDescription;
public int subId;
public boolean roaming;
public boolean showTriangle;
@@ -109,7 +123,7 @@ public interface NetworkController extends CallbackController<SignalCallback>, D
public MobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
int qsType, boolean activityIn, boolean activityOut,
CharSequence typeContentDescription, CharSequence typeContentDescriptionHtml,
- CharSequence description, boolean isWide, int subId, boolean roaming,
+ CharSequence qsDescription, int subId, boolean roaming,
boolean showTriangle) {
this.statusIcon = statusIcon;
this.qsIcon = qsIcon;
@@ -119,8 +133,7 @@ public interface NetworkController extends CallbackController<SignalCallback>, D
this.activityOut = activityOut;
this.typeContentDescription = typeContentDescription;
this.typeContentDescriptionHtml = typeContentDescriptionHtml;
- this.description = description;
- this.isWide = isWide;
+ this.qsDescription = qsDescription;
this.subId = subId;
this.roaming = roaming;
this.showTriangle = showTriangle;
@@ -137,8 +150,7 @@ public interface NetworkController extends CallbackController<SignalCallback>, D
.append(",activityOut=").append(activityOut)
.append(",typeContentDescription=").append(typeContentDescription)
.append(",typeContentDescriptionHtml=").append(typeContentDescriptionHtml)
- .append(",description=").append(description)
- .append(",isWide=").append(isWide)
+ .append(",description=").append(qsDescription)
.append(",subId=").append(subId)
.append(",roaming=").append(roaming)
.append(",showTriangle=").append(showTriangle)
@@ -146,7 +158,8 @@ public interface NetworkController extends CallbackController<SignalCallback>, D
}
}
- public interface SignalCallback {
+ /** */
+ interface SignalCallback {
/**
* Callback for listeners to be able to update the state of any UI tracking connectivity of
* WiFi networks.
@@ -159,14 +172,19 @@ public interface NetworkController extends CallbackController<SignalCallback>, D
*/
default void setMobileDataIndicators(MobileDataIndicators mobileDataIndicators) {}
+ /** */
default void setSubs(List<SubscriptionInfo> subs) {}
+ /** */
default void setNoSims(boolean show, boolean simDetected) {}
+ /** */
default void setEthernetIndicators(IconState icon) {}
+ /** */
default void setIsAirplaneMode(IconState icon) {}
+ /** */
default void setMobileDataEnabled(boolean enabled) {}
/**
@@ -186,11 +204,13 @@ public interface NetworkController extends CallbackController<SignalCallback>, D
default void setCallIndicator(IconState statusIcon, int subId) {}
}
- public interface EmergencyListener {
+ /** */
+ interface EmergencyListener {
void setEmergencyCallsOnly(boolean emergencyOnly);
}
- public static class IconState {
+ /** */
+ class IconState {
public final boolean visible;
public final int icon;
public final String contentDescription;
@@ -220,7 +240,7 @@ public interface NetworkController extends CallbackController<SignalCallback>, D
* Tracks changes in access points. Allows listening for changes, scanning for new APs,
* and connecting to new ones.
*/
- public interface AccessPointController {
+ interface AccessPointController {
void addAccessPointCallback(AccessPointCallback callback);
void removeAccessPointCallback(AccessPointCallback callback);
void scanForAccessPoints();
@@ -230,7 +250,7 @@ public interface NetworkController extends CallbackController<SignalCallback>, D
boolean canConfigWifi();
boolean canConfigMobileData();
- public interface AccessPointCallback {
+ interface AccessPointCallback {
void onAccessPointsChanged(List<WifiEntry> accessPoints);
void onSettingsActivityTriggered(Intent settingsIntent);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index af0d41331c28..daae43f69d3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.connectivity;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -72,11 +72,16 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.qs.tiles.dialog.InternetDialogUtil;
import com.android.systemui.settings.CurrentUserTracker;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.DataSaverControllerImpl;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+import com.android.systemui.statusbar.policy.EncryptionHelper;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.CarrierConfigTracker;
@@ -226,9 +231,9 @@ public class NetworkControllerImpl extends BroadcastReceiver
DemoModeController demoModeController,
CarrierConfigTracker carrierConfigTracker,
FeatureFlags featureFlags,
- DumpManager dumpManager,
@Main Handler handler,
- InternetDialogFactory internetDialogFactory) {
+ InternetDialogFactory internetDialogFactory,
+ DumpManager dumpManager) {
this(context, connectivityManager,
telephonyManager,
telephonyListenerManager,
@@ -327,8 +332,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
deviceProvisionedController.addCallback(new DeviceProvisionedListener() {
@Override
public void onUserSetupChanged() {
- setUserSetupComplete(deviceProvisionedController.isUserSetup(
- deviceProvisionedController.getCurrentUser()));
+ setUserSetupComplete(deviceProvisionedController.isCurrentUserSetup());
}
});
@@ -376,11 +380,10 @@ public class NetworkControllerImpl extends BroadcastReceiver
@Override
public void onCapabilitiesChanged(
- Network network, NetworkCapabilities networkCapabilities) {
- boolean lastValidated = (mLastNetworkCapabilities != null) &&
- mLastNetworkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED);
- boolean validated =
- networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED);
+ Network network, NetworkCapabilities networkCapabilities) {
+ boolean lastValidated = (mLastNetworkCapabilities != null)
+ && mLastNetworkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED);
+ boolean validated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED);
// This callback is invoked a lot (i.e. when RSSI changes), so avoid updating
// icons when connectivity state has remained the same.
@@ -544,19 +547,23 @@ public class NetworkControllerImpl extends BroadcastReceiver
return mDataUsageController;
}
+ /** */
public void addEmergencyListener(EmergencyListener listener) {
mCallbackHandler.setListening(listener, true);
mCallbackHandler.setEmergencyCallsOnly(isEmergencyOnly());
}
+ /** */
public void removeEmergencyListener(EmergencyListener listener) {
mCallbackHandler.setListening(listener, false);
}
+ /** */
public boolean hasMobileDataFeature() {
return mHasMobileDataFeature;
}
+ /** */
public boolean hasVoiceCallingFeature() {
return mPhone.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE;
}
@@ -661,7 +668,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
}
}
- public boolean isEmergencyOnly() {
+ boolean isEmergencyOnly() {
if (mMobileSignalControllers.size() == 0) {
// When there are no active subscriptions, determine emengency state from last
// broadcast.
@@ -690,8 +697,10 @@ public class NetworkControllerImpl extends BroadcastReceiver
if (mMobileSignalControllers.size() == 1) {
mEmergencySource = EMERGENCY_ASSUMED_VOICE_CONTROLLER
+ mMobileSignalControllers.keyAt(0);
- if (DEBUG) Log.d(TAG, "Getting assumed emergency from "
- + mMobileSignalControllers.keyAt(0));
+ if (DEBUG) {
+ Log.d(TAG, "Getting assumed emergency from "
+ + mMobileSignalControllers.keyAt(0));
+ }
return mMobileSignalControllers.valueAt(0).getState().isEmergency;
}
if (DEBUG) Log.e(TAG, "Cannot find controller for voice sub: " + voiceSubId);
@@ -802,7 +811,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
break;
case Settings.Panel.ACTION_INTERNET_CONNECTIVITY:
mMainHandler.post(() -> mInternetDialogFactory.create(true,
- mAccessPoints.canConfigMobileData(), mAccessPoints.canConfigWifi()));
+ mAccessPoints.canConfigMobileData(), mAccessPoints.canConfigWifi(),
+ null /* view */));
break;
default:
int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
@@ -915,7 +925,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
@GuardedBy("mLock")
@VisibleForTesting
- public void setCurrentSubscriptionsLocked(List<SubscriptionInfo> subscriptions) {
+ void setCurrentSubscriptionsLocked(List<SubscriptionInfo> subscriptions) {
Collections.sort(subscriptions, new Comparator<SubscriptionInfo>() {
@Override
public int compare(SubscriptionInfo lhs, SubscriptionInfo rhs) {
@@ -1125,6 +1135,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
mEthernetSignalController.updateConnectivity(mConnectedTransports, mValidatedTransports);
}
+ /** */
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("NetworkController state:");
@@ -1178,7 +1189,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
mCallbackHandler.dump(pw);
}
- private static final String emergencyToString(int emergencySource) {
+ private static String emergencyToString(int emergencySource) {
if (emergencySource > EMERGENCY_NO_SUB) {
return "ASSUMED_VOICE_CONTROLLER(" + (emergencySource - EMERGENCY_VOICE_CONTROLLER)
+ ")";
@@ -1423,10 +1434,12 @@ public class NetworkControllerImpl extends BroadcastReceiver
return info;
}
+ /** */
public boolean hasEmergencyCryptKeeperText() {
return EncryptionHelper.IS_DATA_ENCRYPTED;
}
+ /** */
public boolean isRadioOn() {
return !mAirplaneMode;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java
index 4b6722c17b85..d23dba579be6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.connectivity;
-import static com.android.systemui.statusbar.policy.NetworkControllerImpl.TAG;
+import static com.android.systemui.statusbar.connectivity.NetworkControllerImpl.TAG;
import android.annotation.NonNull;
import android.content.Context;
@@ -23,8 +23,8 @@ import android.util.Log;
import com.android.settingslib.SignalIcon.IconGroup;
import com.android.settingslib.SignalIcon.State;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
+import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
import java.io.PrintWriter;
import java.util.BitSet;
@@ -83,7 +83,7 @@ public abstract class SignalController<T extends State, I extends IconGroup> {
return mCurrentState;
}
- public void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) {
+ void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) {
mCurrentState.inetCondition = validatedTransports.get(mTransportType) ? 1 : 0;
notifyListenersIfNecessary();
}
@@ -114,7 +114,7 @@ public abstract class SignalController<T extends State, I extends IconGroup> {
return false;
}
- public void saveLastState() {
+ void saveLastState() {
if (RECORD_HISTORY) {
recordLastState();
}
@@ -161,7 +161,7 @@ public abstract class SignalController<T extends State, I extends IconGroup> {
}
}
- public void notifyListenersIfNecessary() {
+ void notifyListenersIfNecessary() {
if (isDirty()) {
saveLastState();
notifyListeners();
@@ -192,7 +192,7 @@ public abstract class SignalController<T extends State, I extends IconGroup> {
mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
}
- public void dump(PrintWriter pw) {
+ void dump(PrintWriter pw) {
pw.println(" - " + mTag + " -----");
pw.println(" Current State: " + mCurrentState);
if (RECORD_HISTORY) {
@@ -210,7 +210,7 @@ public abstract class SignalController<T extends State, I extends IconGroup> {
}
}
- public final void notifyListeners() {
+ final void notifyListeners() {
notifyListeners(mCallbackHandler);
}
@@ -219,7 +219,7 @@ public abstract class SignalController<T extends State, I extends IconGroup> {
* based on current state, and only need to be called in the scenario where
* mCurrentState != mLastState.
*/
- public abstract void notifyListeners(SignalCallback callback);
+ abstract void notifyListeners(SignalCallback callback);
/**
* Generate a blank T.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java
index 577cc4fd16b6..3c449ad270ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.connectivity;
import com.android.settingslib.AccessibilityContentDescriptions;
import com.android.settingslib.R;
import com.android.settingslib.SignalIcon.IconGroup;
+/** */
public class WifiIcons {
static final int[] WIFI_FULL_ICONS = {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
index f8e36476c4a6..3622a66767a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.connectivity;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
@@ -36,15 +36,16 @@ import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.wifi.WifiStatusTracker;
import com.android.systemui.R;
-import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
-import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
+import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
+import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
import java.io.PrintWriter;
import java.util.Objects;
+/** */
public class WifiSignalController extends
SignalController<WifiSignalController.WifiState, IconGroup> {
private final boolean mHasMobileDataFeature;
@@ -163,7 +164,7 @@ public class WifiSignalController extends
int qsTypeIcon = 0;
IconState qsIcon = null;
if (sbVisible) {
- qsTypeIcon = icons.qsDataType;
+ qsTypeIcon = icons.dataType;
qsIcon = new IconState(mCurrentState.connected, getQsCurrentIconIdForCarrierWifi(),
contentDescription);
}
@@ -172,7 +173,7 @@ public class WifiSignalController extends
MobileDataIndicators mobileDataIndicators = new MobileDataIndicators(
statusIcon, qsIcon, typeIcon, qsTypeIcon,
mCurrentState.activityIn, mCurrentState.activityOut, dataContentDescription,
- dataContentDescriptionHtml, description, icons.isWide,
+ dataContentDescriptionHtml, description,
mCurrentState.subId, /* roaming= */ false, /* showTriangle= */ true
);
callback.setMobileDataIndicators(mobileDataIndicators);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index 491959320ab7..bb697c3b0851 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -22,14 +22,19 @@ import android.content.Context;
import android.os.Handler;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.carrier.QSCarrierGroupController;
import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.MediaArtworkProcessor;
import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationListener;
@@ -38,10 +43,12 @@ import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
+import com.android.systemui.statusbar.RemoteInputNotificationRebuilder;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.commandline.CommandRegistry;
+import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.DynamicChildBindController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
@@ -60,11 +67,12 @@ import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
+import com.android.systemui.statusbar.phone.StatusBarWindowController;
+import com.android.systemui.statusbar.phone.SystemUIHostDialogProvider;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.tracing.ProtoTracer;
-import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.bubbles.Bubbles;
@@ -89,26 +97,32 @@ public interface StatusBarDependenciesModule {
@Provides
static NotificationRemoteInputManager provideNotificationRemoteInputManager(
Context context,
+ FeatureFlags featureFlags,
NotificationLockscreenUserManager lockscreenUserManager,
SmartReplyController smartReplyController,
NotificationEntryManager notificationEntryManager,
- Lazy<StatusBar> statusBarLazy,
+ RemoteInputNotificationRebuilder rebuilder,
+ Lazy<Optional<StatusBar>> statusBarOptionalLazy,
StatusBarStateController statusBarStateController,
Handler mainHandler,
RemoteInputUriController remoteInputUriController,
NotificationClickNotifier clickNotifier,
- ActionClickLogger actionClickLogger) {
+ ActionClickLogger actionClickLogger,
+ DumpManager dumpManager) {
return new NotificationRemoteInputManager(
context,
+ featureFlags,
lockscreenUserManager,
smartReplyController,
notificationEntryManager,
- statusBarLazy,
+ rebuilder,
+ statusBarOptionalLazy,
statusBarStateController,
mainHandler,
remoteInputUriController,
clickNotifier,
- actionClickLogger);
+ actionClickLogger,
+ dumpManager);
}
/** */
@@ -116,7 +130,7 @@ public interface StatusBarDependenciesModule {
@Provides
static NotificationMediaManager provideNotificationMediaManager(
Context context,
- Lazy<StatusBar> statusBarLazy,
+ Lazy<Optional<StatusBar>> statusBarOptionalLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
@@ -125,11 +139,11 @@ public interface StatusBarDependenciesModule {
NotifCollection notifCollection,
FeatureFlags featureFlags,
@Main DelayableExecutor mainExecutor,
- DeviceConfigProxy deviceConfigProxy,
- MediaDataManager mediaDataManager) {
+ MediaDataManager mediaDataManager,
+ DumpManager dumpManager) {
return new NotificationMediaManager(
context,
- statusBarLazy,
+ statusBarOptionalLazy,
notificationShadeWindowController,
notificationEntryManager,
mediaArtworkProcessor,
@@ -138,8 +152,8 @@ public interface StatusBarDependenciesModule {
notifCollection,
featureFlags,
mainExecutor,
- deviceConfigProxy,
- mediaDataManager);
+ mediaDataManager,
+ dumpManager);
}
/** */
@@ -157,10 +171,11 @@ public interface StatusBarDependenciesModule {
@SysUISingleton
@Provides
static SmartReplyController provideSmartReplyController(
+ DumpManager dumpManager,
NotificationEntryManager entryManager,
IStatusBarService statusBarService,
NotificationClickNotifier clickNotifier) {
- return new SmartReplyController(entryManager, statusBarService, clickNotifier);
+ return new SmartReplyController(dumpManager, entryManager, statusBarService, clickNotifier);
}
@@ -175,6 +190,7 @@ public interface StatusBarDependenciesModule {
static NotificationViewHierarchyManager provideNotificationViewHierarchyManager(
Context context,
@Main Handler mainHandler,
+ FeatureFlags featureFlags,
NotificationLockscreenUserManager notificationLockscreenUserManager,
NotificationGroupManagerLegacy groupManager,
VisualStabilityManager visualStabilityManager,
@@ -190,6 +206,7 @@ public interface StatusBarDependenciesModule {
return new NotificationViewHierarchyManager(
context,
mainHandler,
+ featureFlags,
notificationLockscreenUserManager,
groupManager,
visualStabilityManager,
@@ -245,12 +262,63 @@ public interface StatusBarDependenciesModule {
ActivityStarter activityStarter,
@Main Executor mainExecutor,
IActivityManager iActivityManager,
- OngoingCallLogger logger) {
+ OngoingCallLogger logger,
+ DumpManager dumpManager,
+ StatusBarWindowController statusBarWindowController,
+ SwipeStatusBarAwayGestureHandler swipeStatusBarAwayGestureHandler,
+ StatusBarStateController statusBarStateController) {
+ Optional<StatusBarWindowController> windowController =
+ featureFlags.isOngoingCallInImmersiveEnabled()
+ ? Optional.of(statusBarWindowController)
+ : Optional.empty();
+ Optional<SwipeStatusBarAwayGestureHandler> gestureHandler =
+ featureFlags.isOngoingCallInImmersiveEnabled()
+ ? Optional.of(swipeStatusBarAwayGestureHandler)
+ : Optional.empty();
OngoingCallController ongoingCallController =
new OngoingCallController(
- notifCollection, featureFlags, systemClock, activityStarter, mainExecutor,
- iActivityManager, logger);
+ notifCollection,
+ featureFlags,
+ systemClock,
+ activityStarter,
+ mainExecutor,
+ iActivityManager,
+ logger,
+ dumpManager,
+ windowController,
+ gestureHandler,
+ statusBarStateController);
ongoingCallController.init();
return ongoingCallController;
}
+
+ /** */
+ @Binds
+ QSCarrierGroupController.SlotIndexResolver provideSlotIndexResolver(
+ QSCarrierGroupController.SubscriptionManagerSlotIndexResolver impl);
+
+ /**
+ */
+ @Provides
+ @SysUISingleton
+ static LaunchAnimator provideLaunchAnimator(Context context) {
+ return new LaunchAnimator(context);
+ }
+
+ /**
+ */
+ @Provides
+ @SysUISingleton
+ static ActivityLaunchAnimator provideActivityLaunchAnimator(LaunchAnimator launchAnimator) {
+ return new ActivityLaunchAnimator(launchAnimator);
+ }
+
+ /**
+ */
+ @Provides
+ @SysUISingleton
+ static DialogLaunchAnimator provideDialogLaunchAnimator(Context context,
+ LaunchAnimator launchAnimator) {
+ return new DialogLaunchAnimator(context, launchAnimator, new SystemUIHostDialogProvider());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 1037e576f263..b97bac261ff0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -24,7 +24,6 @@ import android.util.Log
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
-
import com.android.internal.annotations.GuardedBy
import com.android.systemui.animation.Interpolators
import com.android.systemui.R
@@ -44,7 +43,6 @@ import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
import com.android.systemui.util.leak.RotationUtils.Rotation
-import java.lang.IllegalStateException
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -71,9 +69,6 @@ class PrivacyDotViewController @Inject constructor(
private val contentInsetsProvider: StatusBarContentInsetsProvider,
private val animationScheduler: SystemStatusAnimationScheduler
) {
- private var sbHeightPortrait = 0
- private var sbHeightLandscape = 0
-
private lateinit var tl: View
private lateinit var tr: View
private lateinit var bl: View
@@ -156,16 +151,12 @@ class PrivacyDotViewController @Inject constructor(
val newCorner = selectDesignatedCorner(rot, isRtl)
val index = newCorner.cornerIndex()
+ val paddingTop = contentInsetsProvider.getStatusBarPaddingTop(rot)
- val h = when (rot) {
- 0, 2 -> sbHeightPortrait
- 1, 3 -> sbHeightLandscape
- else -> 0
- }
synchronized(lock) {
nextViewState = nextViewState.copy(
rotation = rot,
- height = h,
+ paddingTop = paddingTop,
designatedCorner = newCorner,
cornerIndex = index)
}
@@ -203,26 +194,17 @@ class PrivacyDotViewController @Inject constructor(
}
}
- @UiThread
- private fun updateHeights(rot: Int) {
- val height = when (rot) {
- 0, 2 -> sbHeightPortrait
- 1, 3 -> sbHeightLandscape
- else -> 0
- }
-
- views.forEach { it.layoutParams.height = height }
- }
-
// Update the gravity and margins of the privacy views
@UiThread
- private fun updateRotations(rotation: Int) {
+ private fun updateRotations(rotation: Int, paddingTop: Int) {
// To keep a view in the corner, its gravity is always the description of its current corner
// Therefore, just figure out which view is in which corner. This turns out to be something
// like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and
// rotating the device counter-clockwise increments rotation by 1
views.forEach { corner ->
+ corner.setPadding(0, paddingTop, 0, 0)
+
val rotatedCorner = rotatedCorner(cornerForView(corner), rotation)
(corner.layoutParams as FrameLayout.LayoutParams).apply {
gravity = rotatedCorner.toGravity()
@@ -265,6 +247,7 @@ class PrivacyDotViewController @Inject constructor(
var rot = activeRotationForCorner(tl, rtl)
var contentInsets = state.contentRectForRotation(rot)
+ tl.setPadding(0, state.paddingTop, 0, 0)
(tl.layoutParams as FrameLayout.LayoutParams).apply {
height = contentInsets.height()
if (rtl) {
@@ -276,6 +259,7 @@ class PrivacyDotViewController @Inject constructor(
rot = activeRotationForCorner(tr, rtl)
contentInsets = state.contentRectForRotation(rot)
+ tr.setPadding(0, state.paddingTop, 0, 0)
(tr.layoutParams as FrameLayout.LayoutParams).apply {
height = contentInsets.height()
if (rtl) {
@@ -287,6 +271,7 @@ class PrivacyDotViewController @Inject constructor(
rot = activeRotationForCorner(br, rtl)
contentInsets = state.contentRectForRotation(rot)
+ br.setPadding(0, state.paddingTop, 0, 0)
(br.layoutParams as FrameLayout.LayoutParams).apply {
height = contentInsets.height()
if (rtl) {
@@ -298,6 +283,7 @@ class PrivacyDotViewController @Inject constructor(
rot = activeRotationForCorner(bl, rtl)
contentInsets = state.contentRectForRotation(rot)
+ bl.setPadding(0, state.paddingTop, 0, 0)
(bl.layoutParams as FrameLayout.LayoutParams).apply {
height = contentInsets.height()
if (rtl) {
@@ -412,6 +398,7 @@ class PrivacyDotViewController @Inject constructor(
val right = contentInsetsProvider.getStatusBarContentInsetsForRotation(ROTATION_LANDSCAPE)
val bottom = contentInsetsProvider
.getStatusBarContentInsetsForRotation(ROTATION_UPSIDE_DOWN)
+ val paddingTop = contentInsetsProvider.getStatusBarPaddingTop()
synchronized(lock) {
nextViewState = nextViewState.copy(
@@ -422,20 +409,12 @@ class PrivacyDotViewController @Inject constructor(
portraitRect = top,
landscapeRect = right,
upsideDownRect = bottom,
+ paddingTop = paddingTop,
layoutRtl = rtl
)
}
}
- /**
- * Set the status bar height in portrait and landscape, in pixels. If they are the same you can
- * pass the same value twice
- */
- fun setStatusBarHeights(portrait: Int, landscape: Int) {
- sbHeightPortrait = portrait
- sbHeightLandscape = landscape
- }
-
private fun updateStatusBarState() {
synchronized(lock) {
nextViewState = nextViewState.copy(shadeExpanded = isShadeInQs())
@@ -488,7 +467,7 @@ class PrivacyDotViewController @Inject constructor(
if (state.rotation != currentViewState.rotation) {
// A rotation has started, hide the views to avoid flicker
- updateRotations(state.rotation)
+ updateRotations(state.rotation, state.paddingTop)
}
if (state.needsLayout(currentViewState)) {
@@ -627,7 +606,7 @@ private data class ViewState(
val layoutRtl: Boolean = false,
val rotation: Int = 0,
- val height: Int = 0,
+ val paddingTop: Int = 0,
val cornerIndex: Int = -1,
val designatedCorner: View? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index 7291b5a8be3b..589446f3b075 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -24,13 +24,10 @@ import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout
-
import com.android.systemui.R
-import com.android.systemui.statusbar.SuperStatusBarViewFactory
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher
import com.android.systemui.statusbar.phone.StatusBarWindowController
import com.android.systemui.statusbar.phone.StatusBarWindowView
-
import javax.inject.Inject
/**
@@ -38,7 +35,7 @@ import javax.inject.Inject
*/
class SystemEventChipAnimationController @Inject constructor(
private val context: Context,
- private val statusBarViewFactory: SuperStatusBarViewFactory,
+ private val statusBarWindowView: StatusBarWindowView,
private val statusBarWindowController: StatusBarWindowController,
private val locationPublisher: StatusBarLocationPublisher
) : SystemStatusChipAnimationCallback {
@@ -51,7 +48,6 @@ class SystemEventChipAnimationController @Inject constructor(
private lateinit var animationWindowView: FrameLayout
private lateinit var animationDotView: View
- private lateinit var statusBarWindowView: StatusBarWindowView
private var currentAnimatedView: View? = null
// TODO: move to dagger
@@ -125,7 +121,6 @@ class SystemEventChipAnimationController @Inject constructor(
private fun init() {
initialized = true
- statusBarWindowView = statusBarViewFactory.statusBarWindowView
animationWindowView = LayoutInflater.from(context)
.inflate(R.layout.system_event_animation_window, null) as FrameLayout
animationDotView = animationWindowView.findViewById(R.id.dot_view)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
new file mode 100644
index 000000000000..80577ee24317
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
@@ -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.systemui.statusbar.gesture
+
+import android.content.Context
+import android.os.Looper
+import android.view.Choreographer
+import android.view.Display
+import android.view.InputEvent
+import android.view.MotionEvent
+import android.view.MotionEvent.*
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.system.InputChannelCompat
+import com.android.systemui.shared.system.InputMonitorCompat
+import com.android.systemui.statusbar.phone.StatusBarWindowController
+import javax.inject.Inject
+
+/**
+ * A class to detect when a user swipes away the status bar. To be notified when the swipe away
+ * gesture is detected, add a callback via [addOnGestureDetectedCallback].
+ */
+@SysUISingleton
+open class SwipeStatusBarAwayGestureHandler @Inject constructor(
+ context: Context,
+ private val statusBarWindowController: StatusBarWindowController,
+ private val logger: SwipeStatusBarAwayGestureLogger
+) {
+
+ /**
+ * Active callbacks, each associated with a tag. Gestures will only be monitored if
+ * [callbacks.size] > 0.
+ */
+ private val callbacks: MutableMap<String, () -> Unit> = mutableMapOf()
+
+ private var startY: Float = 0f
+ private var startTime: Long = 0L
+ private var monitoringCurrentTouch: Boolean = false
+
+ private var inputMonitor: InputMonitorCompat? = null
+ private var inputReceiver: InputChannelCompat.InputEventReceiver? = null
+
+ private var swipeDistanceThreshold: Int = context.resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.system_gestures_start_threshold
+ )
+
+ /** Adds a callback that will be triggered when the swipe away gesture is detected. */
+ fun addOnGestureDetectedCallback(tag: String, callback: () -> Unit) {
+ val callbacksWasEmpty = callbacks.isEmpty()
+ callbacks[tag] = callback
+ if (callbacksWasEmpty) {
+ startGestureListening()
+ }
+ }
+
+ /** Removes the callback. */
+ fun removeOnGestureDetectedCallback(tag: String) {
+ callbacks.remove(tag)
+ if (callbacks.isEmpty()) {
+ stopGestureListening()
+ }
+ }
+
+ private fun onInputEvent(ev: InputEvent) {
+ if (ev !is MotionEvent) {
+ return
+ }
+
+ when (ev.actionMasked) {
+ ACTION_DOWN -> {
+ if (
+ // Gesture starts just below the status bar
+ ev.y >= statusBarWindowController.statusBarHeight
+ && ev.y <= 3 * statusBarWindowController.statusBarHeight
+ ) {
+ logger.logGestureDetectionStarted(ev.y.toInt())
+ startY = ev.y
+ startTime = ev.eventTime
+ monitoringCurrentTouch = true
+ } else {
+ monitoringCurrentTouch = false
+ }
+ }
+ ACTION_MOVE -> {
+ if (!monitoringCurrentTouch) {
+ return
+ }
+ if (
+ // Gesture is up
+ ev.y < startY
+ // Gesture went far enough
+ && (startY - ev.y) >= swipeDistanceThreshold
+ // Gesture completed quickly enough
+ && (ev.eventTime - startTime) < SWIPE_TIMEOUT_MS
+ ) {
+ monitoringCurrentTouch = false
+ logger.logGestureDetected(ev.y.toInt())
+ callbacks.values.forEach { it.invoke() }
+ }
+ }
+ ACTION_CANCEL, ACTION_UP -> {
+ if (monitoringCurrentTouch) {
+ logger.logGestureDetectionEndedWithoutTriggering(ev.y.toInt())
+ }
+ monitoringCurrentTouch = false
+ }
+ }
+ }
+
+ /** Start listening for the swipe gesture. */
+ private fun startGestureListening() {
+ stopGestureListening()
+
+ logger.logInputListeningStarted()
+ inputMonitor = InputMonitorCompat(TAG, Display.DEFAULT_DISPLAY).also {
+ inputReceiver = it.getInputReceiver(
+ Looper.getMainLooper(),
+ Choreographer.getInstance(),
+ this::onInputEvent
+ )
+ }
+ }
+
+ /** Stop listening for the swipe gesture. */
+ private fun stopGestureListening() {
+ inputMonitor?.let {
+ logger.logInputListeningStopped()
+ inputMonitor = null
+ it.dispose()
+ }
+ inputReceiver?.let {
+ inputReceiver = null
+ it.dispose()
+ }
+ }
+}
+
+private const val SWIPE_TIMEOUT_MS: Long = 500
+private val TAG = SwipeStatusBarAwayGestureHandler::class.simpleName
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
new file mode 100644
index 000000000000..17feaa842165
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.systemui.statusbar.gesture
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.SwipeStatusBarAwayLog
+import javax.inject.Inject
+
+/** Log messages for [SwipeStatusBarAwayGestureHandler]. */
+class SwipeStatusBarAwayGestureLogger @Inject constructor(
+ @SwipeStatusBarAwayLog private val buffer: LogBuffer
+) {
+ fun logGestureDetectionStarted(y: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = y },
+ { "Beginning gesture detection. y=$int1" }
+ )
+ }
+
+ fun logGestureDetectionEndedWithoutTriggering(y: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = y },
+ { "Gesture finished; no swipe up gesture detected. Final y=$int1" }
+ )
+ }
+
+ fun logGestureDetected(y: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { int1 = y },
+ { "Gesture detected; notifying callbacks. y=$int1" }
+ )
+ }
+
+ fun logInputListeningStarted() {
+ buffer.log(TAG, LogLevel.VERBOSE, {}, { "Input listening started "})
+ }
+
+ fun logInputListeningStopped() {
+ buffer.log(TAG, LogLevel.VERBOSE, {}, { "Input listening stopped "})
+ }
+}
+
+private const val TAG = "SwipeStatusBarAwayGestureHandler" \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 0773460ecf67..4e5bc8e7d099 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -29,6 +29,7 @@ import android.net.Uri
import android.os.Handler
import android.os.UserHandle
import android.provider.Settings
+import android.util.Log
import android.view.View
import android.view.ViewGroup
import com.android.settingslib.Utils
@@ -42,7 +43,7 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.util.concurrency.Execution
@@ -73,27 +74,37 @@ class LockscreenSmartspaceController @Inject constructor(
@Main private val handler: Handler,
optionalPlugin: Optional<BcSmartspaceDataPlugin>
) {
+ companion object {
+ private const val TAG = "LockscreenSmartspaceController"
+ }
+
private var session: SmartspaceSession? = null
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
- private lateinit var smartspaceView: SmartspaceView
- lateinit var view: View
- private set
+ // Smartspace can be used on multiple displays, such as when the user casts their screen
+ private var smartspaceViews = mutableSetOf<SmartspaceView>()
private var showSensitiveContentForCurrentUser = false
private var showSensitiveContentForManagedUser = false
private var managedUserHandle: UserHandle? = null
- private val deviceProvisionedListener =
- object : DeviceProvisionedController.DeviceProvisionedListener {
- override fun onDeviceProvisionedChanged() {
- connectSession()
- }
+ var stateChangeListener = object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {
+ smartspaceViews.add(v as SmartspaceView)
+ connectSession()
- override fun onUserSetupChanged() {
- connectSession()
+ updateTextColorFromWallpaper()
+ statusBarStateListener.onDozeAmountChanged(0f, statusBarStateController.dozeAmount)
+ }
+
+ override fun onViewDetachedFromWindow(v: View) {
+ smartspaceViews.remove(v as SmartspaceView)
+
+ if (smartspaceViews.isEmpty()) {
+ disconnect()
}
}
+ }
private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
execution.assertIsMainThread()
@@ -125,10 +136,21 @@ class LockscreenSmartspaceController @Inject constructor(
private val statusBarStateListener = object : StatusBarStateController.StateListener {
override fun onDozeAmountChanged(linear: Float, eased: Float) {
execution.assertIsMainThread()
- smartspaceView.setDozeAmount(eased)
+ smartspaceViews.forEach { it.setDozeAmount(eased) }
}
}
+ private val deviceProvisionedListener =
+ object : DeviceProvisionedController.DeviceProvisionedListener {
+ override fun onDeviceProvisionedChanged() {
+ connectSession()
+ }
+
+ override fun onUserSetupChanged() {
+ connectSession()
+ }
+ }
+
init {
deviceProvisionedController.addCallback(deviceProvisionedListener)
}
@@ -140,17 +162,16 @@ class LockscreenSmartspaceController @Inject constructor(
}
/**
- * Constructs the smartspace view and connects it to the smartspace service. Subsequent calls
- * are idempotent until [disconnect] is called.
+ * Constructs the smartspace view and connects it to the smartspace service.
*/
- fun buildAndConnectView(parent: ViewGroup): View {
+ fun buildAndConnectView(parent: ViewGroup): View? {
execution.assertIsMainThread()
if (!isEnabled()) {
throw RuntimeException("Cannot build view when not enabled")
}
- buildView(parent)
+ val view = buildView(parent)
connectSession()
return view
@@ -160,38 +181,38 @@ class LockscreenSmartspaceController @Inject constructor(
session?.requestSmartspaceUpdate()
}
- private fun buildView(parent: ViewGroup) {
+ private fun buildView(parent: ViewGroup): View? {
if (plugin == null) {
- return
- }
- if (this::view.isInitialized) {
- // Due to some oddities with a singleton smartspace view, allow reparenting
- (view.getParent() as ViewGroup?)?.removeView(view)
- return
+ return null
}
val ssView = plugin.getView(parent)
ssView.registerDataProvider(plugin)
+
ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
- override fun startIntent(v: View?, i: Intent?) {
- activityStarter.startActivity(i, true /* dismissShade */)
+ override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) {
+ activityStarter.startActivity(
+ intent,
+ true, /* dismissShade */
+ null, /* launch animator - looks bad with the transparent smartspace bg */
+ showOnLockscreen
+ )
}
- override fun startPendingIntent(pi: PendingIntent?) {
- activityStarter.startPendingIntentDismissingKeyguard(pi)
+ override fun startPendingIntent(pi: PendingIntent, showOnLockscreen: Boolean) {
+ if (showOnLockscreen) {
+ pi.send()
+ } else {
+ activityStarter.startPendingIntentDismissingKeyguard(pi)
+ }
}
})
ssView.setFalsingManager(falsingManager)
-
- this.smartspaceView = ssView
- this.view = ssView as View
-
- updateTextColorFromWallpaper()
- statusBarStateListener.onDozeAmountChanged(0f, statusBarStateController.dozeAmount)
+ return (ssView as View).apply { addOnAttachStateChangeListener(stateChangeListener) }
}
private fun connectSession() {
- if (plugin == null || session != null || !this::smartspaceView.isInitialized) {
+ if (plugin == null || session != null || smartspaceViews.isEmpty()) {
return
}
@@ -204,6 +225,7 @@ class LockscreenSmartspaceController @Inject constructor(
val newSession = smartspaceManager.createSmartspaceSession(
SmartspaceConfig.Builder(context, "lockscreen").build())
+ Log.d(TAG, "Starting smartspace session for lockscreen")
newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
this.session = newSession
@@ -223,10 +245,10 @@ class LockscreenSmartspaceController @Inject constructor(
/**
* Disconnects the smartspace view from the smartspace service and cleans up any resources.
- * Calling [buildAndConnectView] again will cause the same view to be reconnected to the
- * service.
*/
fun disconnect() {
+ if (!smartspaceViews.isEmpty()) return
+
execution.assertIsMainThread()
if (session == null) {
@@ -244,6 +266,7 @@ class LockscreenSmartspaceController @Inject constructor(
session = null
plugin?.onTargetsAvailable(emptyList())
+ Log.d(TAG, "Ending smartspace session for lockscreen")
}
fun addListener(listener: SmartspaceTargetListener) {
@@ -277,7 +300,7 @@ class LockscreenSmartspaceController @Inject constructor(
private fun updateTextColorFromWallpaper() {
val wallpaperTextColor = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor)
- smartspaceView.setPrimaryTextColor(wallpaperTextColor)
+ smartspaceViews.forEach { it.setPrimaryTextColor(wallpaperTextColor) }
}
private fun reloadSmartspace() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt
index f19cf5d8d9c7..64a73054c434 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt
@@ -2,8 +2,8 @@ package com.android.systemui.statusbar.notification
import android.util.MathUtils
import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.LaunchAnimator
import kotlin.math.min
/** Parameters for the notifications expand animations. */
@@ -15,7 +15,7 @@ class ExpandAnimationParameters(
topCornerRadius: Float = 0f,
bottomCornerRadius: Float = 0f
-) : ActivityLaunchAnimator.State(top, bottom, left, right, topCornerRadius, bottomCornerRadius) {
+) : LaunchAnimator.State(top, bottom, left, right, topCornerRadius, bottomCornerRadius) {
@VisibleForTesting
constructor() : this(
top = 0, bottom = 0, left = 0, right = 0, topCornerRadius = 0f, bottomCornerRadius = 0f
@@ -55,6 +55,6 @@ class ExpandAnimationParameters(
}
fun getProgress(delay: Long, duration: Long): Float {
- return ActivityLaunchAnimator.getProgress(linearProgress, delay, duration)
+ return LaunchAnimator.getProgress(linearProgress, delay, duration)
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java
index 2537b19513d2..129fa5a7cc17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java
@@ -20,6 +20,7 @@ import android.content.Intent;
import android.service.notification.StatusBarNotification;
import android.view.View;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
@@ -37,6 +38,9 @@ public interface NotificationActivityStarter {
/** Called when the user clicks "Manage" or "History" in the Shade. */
void startHistoryIntent(View view, boolean showHistory);
+ /** Called when the user succeed to drop notification to proper target view. */
+ void onDragSuccess(NotificationEntry entry);
+
default boolean isCollapsingToShowActivityOverLockscreen() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index 0fb1c54bb150..da706215863e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -43,6 +43,14 @@ public final class NotificationClicker implements View.OnClickListener {
private final Optional<Bubbles> mBubblesOptional;
private final NotificationActivityStarter mNotificationActivityStarter;
+ private ExpandableNotificationRow.OnDragSuccessListener mOnDragSuccessListener =
+ new ExpandableNotificationRow.OnDragSuccessListener() {
+ @Override
+ public void onDragSuccess(NotificationEntry entry) {
+ mNotificationActivityStarter.onDragSuccess(entry);
+ }
+ };
+
private NotificationClicker(
NotificationClickerLogger logger,
Optional<StatusBar> statusBarOptional,
@@ -111,8 +119,10 @@ public final class NotificationClicker implements View.OnClickListener {
if (notification.contentIntent != null || notification.fullScreenIntent != null
|| row.getEntry().isBubble()) {
row.setOnClickListener(this);
+ row.setOnDragSuccessListener(mOnDragSuccessListener);
} else {
row.setOnClickListener(null);
+ row.setOnDragSuccessListener(null);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 1ab2a9e320b0..8bc41c20caaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -37,7 +37,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
@@ -98,15 +99,16 @@ public class NotificationEntryManager implements
CommonNotifCollection,
Dumpable,
VisualStabilityManager.Callback {
- private static final String TAG = "NotificationEntryMgr";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- /**
- * Used when a notification is removed and it doesn't have a reason that maps to one of the
- * reasons defined in NotificationListenerService
- * (e.g. {@link NotificationListenerService#REASON_CANCEL})
- */
- public static final int UNDEFINED_DISMISS_REASON = 0;
+ private final NotificationEntryManagerLogger mLogger;
+ private final NotificationGroupManagerLegacy mGroupManager;
+ private final FeatureFlags mFeatureFlags;
+ private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy;
+ private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
+ private final LeakDetector mLeakDetector;
+ private final ForegroundServiceDismissalFeatureController mFgsFeatureController;
+ private final IStatusBarService mStatusBarService;
+ private final DumpManager mDumpManager;
private final Set<NotificationEntry> mAllNotifications = new ArraySet<>();
private final Set<NotificationEntry> mReadOnlyAllNotifications =
@@ -129,20 +131,8 @@ public class NotificationEntryManager implements
private final Map<NotificationEntry, NotificationLifetimeExtender> mRetainedNotifications =
new ArrayMap<>();
- private final NotificationEntryManagerLogger mLogger;
-
- private final IStatusBarService mStatusBarService;
-
- // Lazily retrieved dependencies
- private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy;
- private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
- private final LeakDetector mLeakDetector;
private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
- private final NotificationGroupManagerLegacy mGroupManager;
- private final FeatureFlags mFeatureFlags;
- private final ForegroundServiceDismissalFeatureController mFgsFeatureController;
-
private LegacyNotificationRanker mRanker = new LegacyNotificationRankerStub();
private NotificationPresenter mPresenter;
private RankingMap mLatestRankingMap;
@@ -153,6 +143,40 @@ public class NotificationEntryManager implements
private final List<NotificationEntryListener> mNotificationEntryListeners = new ArrayList<>();
private final List<NotificationRemoveInterceptor> mRemoveInterceptors = new ArrayList<>();
+ /**
+ * Injected constructor. See {@link NotificationsModule}.
+ */
+ public NotificationEntryManager(
+ NotificationEntryManagerLogger logger,
+ NotificationGroupManagerLegacy groupManager,
+ FeatureFlags featureFlags,
+ Lazy<NotificationRowBinder> notificationRowBinderLazy,
+ Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
+ LeakDetector leakDetector,
+ ForegroundServiceDismissalFeatureController fgsFeatureController,
+ IStatusBarService statusBarService,
+ DumpManager dumpManager
+ ) {
+ mLogger = logger;
+ mGroupManager = groupManager;
+ mFeatureFlags = featureFlags;
+ mNotificationRowBinderLazy = notificationRowBinderLazy;
+ mRemoteInputManagerLazy = notificationRemoteInputManagerLazy;
+ mLeakDetector = leakDetector;
+ mFgsFeatureController = fgsFeatureController;
+ mStatusBarService = statusBarService;
+ mDumpManager = dumpManager;
+ }
+
+ /** Once called, the NEM will start processing notification events from system server. */
+ public void initialize(
+ NotificationListener notificationListener,
+ LegacyNotificationRanker ranker) {
+ mRanker = ranker;
+ notificationListener.addNotificationHandler(mNotifListener);
+ mDumpManager.registerDumpable(this);
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("NotificationEntryManager state:");
@@ -194,38 +218,6 @@ public class NotificationEntryManager implements
}
}
- /**
- * Injected constructor. See {@link NotificationsModule}.
- */
- public NotificationEntryManager(
- NotificationEntryManagerLogger logger,
- NotificationGroupManagerLegacy groupManager,
- FeatureFlags featureFlags,
- Lazy<NotificationRowBinder> notificationRowBinderLazy,
- Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
- LeakDetector leakDetector,
- ForegroundServiceDismissalFeatureController fgsFeatureController,
- IStatusBarService statusBarService
- ) {
- mLogger = logger;
- mGroupManager = groupManager;
- mFeatureFlags = featureFlags;
- mNotificationRowBinderLazy = notificationRowBinderLazy;
- mRemoteInputManagerLazy = notificationRemoteInputManagerLazy;
- mLeakDetector = leakDetector;
- mFgsFeatureController = fgsFeatureController;
- mStatusBarService = statusBarService;
- }
-
- /** Once called, the NEM will start processing notification events from system server. */
- public void attach(NotificationListener notificationListener) {
- notificationListener.addNotificationHandler(mNotifListener);
- }
-
- public void setRanker(LegacyNotificationRanker ranker) {
- mRanker = ranker;
- }
-
/** Adds a {@link NotificationEntryListener}. */
public void addNotificationEntryListener(NotificationEntryListener listener) {
mNotificationEntryListeners.add(listener);
@@ -313,9 +305,6 @@ public class NotificationEntryManager implements
NotificationEntry entry = mPendingNotifications.get(key);
entry.abortTask();
mPendingNotifications.remove(key);
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryCleanUp(entry);
- }
mLogger.logInflationAborted(key, "pending", reason);
}
NotificationEntry addedEntry = getActiveNotificationUnfiltered(key);
@@ -485,6 +474,18 @@ public class NotificationEntryManager implements
if (!lifetimeExtended) {
// At this point, we are guaranteed the notification will be removed
abortExistingInflation(key, "removeNotification");
+ // Fix for b/201097913: NotifCollectionListener#onEntryRemoved specifies that
+ // #onEntryRemoved should be called when a notification is cancelled,
+ // regardless of whether the notification was pending or active.
+ // Note that mNotificationEntryListeners are NOT notified of #onEntryRemoved
+ // because for that interface, #onEntryRemoved should only be called for
+ // active entries, NOT pending ones.
+ for (NotifCollectionListener listener : mNotifCollectionListeners) {
+ listener.onEntryRemoved(pendingEntry, REASON_UNKNOWN);
+ }
+ for (NotifCollectionListener listener : mNotifCollectionListeners) {
+ listener.onEntryCleanUp(pendingEntry);
+ }
mAllNotifications.remove(pendingEntry);
mLeakDetector.trackGarbage(pendingEntry);
}
@@ -688,8 +689,9 @@ public class NotificationEntryManager implements
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onPreEntryUpdated(entry);
}
+ final boolean fromSystem = ranking != null;
for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryUpdated(entry);
+ listener.onEntryUpdated(entry, fromSystem);
}
if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
@@ -976,4 +978,14 @@ public class NotificationEntryManager implements
/** true if the notification is for the current profiles */
boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
}
+
+ private static final String TAG = "NotificationEntryMgr";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ /**
+ * Used when a notification is removed and it doesn't have a reason that maps to one of the
+ * reasons defined in NotificationListenerService
+ * (e.g. {@link NotificationListenerService#REASON_CANCEL})
+ */
+ public static final int UNDEFINED_DISMISS_REASON = 0;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index 1bbef2562d21..22c3eda03b1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -3,6 +3,7 @@ package com.android.systemui.statusbar.notification
import android.view.ViewGroup
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.LaunchAnimator
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
@@ -54,7 +55,7 @@ class NotificationLaunchAnimatorController(
// Do nothing. Notifications are always animated inside their rootView.
}
- override fun createAnimatorState(): ActivityLaunchAnimator.State {
+ override fun createAnimatorState(): LaunchAnimator.State {
// If the notification panel is collapsed, the clip may be larger than the height.
val height = max(0, notification.actualHeight - notification.clipBottomAmount)
val location = notification.locationOnScreen
@@ -72,12 +73,12 @@ class NotificationLaunchAnimatorController(
notification.currentBackgroundRadiusTop
}
val params = ExpandAnimationParameters(
- top = windowTop,
- bottom = location[1] + height,
- left = location[0],
- right = location[0] + notification.width,
- topCornerRadius = topCornerRadius,
- bottomCornerRadius = notification.currentBackgroundRadiusBottom
+ top = windowTop,
+ bottom = location[1] + height,
+ left = location[0],
+ right = location[0] + notification.width,
+ topCornerRadius = topCornerRadius,
+ bottomCornerRadius = notification.currentBackgroundRadiusBottom
)
params.startTranslationZ = notification.translationZ
@@ -86,8 +87,8 @@ class NotificationLaunchAnimatorController(
params.startClipTopAmount = notification.clipTopAmount
if (notification.isChildInGroup) {
params.startNotificationTop += notification.notificationParent.translationY
- val parentRoundedClip = Math.max(clipStartLocation
- - notification.notificationParent.locationOnScreen[1], 0)
+ val parentRoundedClip = Math.max(
+ clipStartLocation - notification.notificationParent.locationOnScreen[1], 0)
params.parentStartRoundedTopClipping = parentRoundedClip
val parentClip = notification.notificationParent.clipTopAmount
@@ -157,7 +158,7 @@ class NotificationLaunchAnimatorController(
}
override fun onLaunchAnimationProgress(
- state: ActivityLaunchAnimator.State,
+ state: LaunchAnimator.State,
progress: Float,
linearProgress: Float
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index fd0476b76a9a..37eacada19fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -36,7 +36,7 @@ public abstract class ListEntry {
private final ListAttachState mPreviousAttachState = ListAttachState.create();
private final ListAttachState mAttachState = ListAttachState.create();
- ListEntry(String key, long creationTime) {
+ protected ListEntry(String key, long creationTime) {
mKey = key;
mCreationTime = creationTime;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 8ae31cba4cfb..f36f430fc29b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -47,7 +47,9 @@ import android.annotation.MainThread;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.Notification;
+import android.os.Handler;
import android.os.RemoteException;
+import android.os.Trace;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
@@ -61,9 +63,10 @@ import androidx.annotation.NonNull;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.LogBufferEulogizer;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent;
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler;
@@ -75,6 +78,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.En
import com.android.systemui.statusbar.notification.collection.notifcollection.EntryRemovedEvent;
import com.android.systemui.statusbar.notification.collection.notifcollection.EntryUpdatedEvent;
import com.android.systemui.statusbar.notification.collection.notifcollection.InitEntryEvent;
+import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
@@ -130,6 +134,7 @@ public class NotifCollection implements Dumpable {
private final SystemClock mClock;
private final FeatureFlags mFeatureFlags;
private final NotifCollectionLogger mLogger;
+ private final Handler mMainHandler;
private final LogBufferEulogizer mEulogizer;
private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>();
@@ -153,6 +158,7 @@ public class NotifCollection implements Dumpable {
SystemClock clock,
FeatureFlags featureFlags,
NotifCollectionLogger logger,
+ @Main Handler mainHandler,
LogBufferEulogizer logBufferEulogizer,
DumpManager dumpManager) {
Assert.isMainThread();
@@ -160,6 +166,7 @@ public class NotifCollection implements Dumpable {
mClock = clock;
mFeatureFlags = featureFlags;
mLogger = logger;
+ mMainHandler = mainHandler;
mEulogizer = logBufferEulogizer;
dumpManager.registerDumpable(TAG, this);
@@ -441,7 +448,7 @@ public class NotifCollection implements Dumpable {
mEventQueue.add(new BindEntryEvent(entry, sbn));
mLogger.logNotifUpdated(sbn.getKey());
- mEventQueue.add(new EntryUpdatedEvent(entry));
+ mEventQueue.add(new EntryUpdatedEvent(entry, true /* fromSystem */));
}
}
@@ -512,6 +519,7 @@ public class NotifCollection implements Dumpable {
}
private void dispatchEventsAndRebuildList() {
+ Trace.beginSection("NotifCollection.dispatchEventsAndRebuildList");
mAmDispatchingToOtherCode = true;
while (!mEventQueue.isEmpty()) {
mEventQueue.remove().dispatchTo(mNotifCollectionListeners);
@@ -521,9 +529,12 @@ public class NotifCollection implements Dumpable {
if (mBuildListener != null) {
mBuildListener.onBuildList(mReadOnlyNotificationSet);
}
+ Trace.endSection();
}
- private void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry) {
+ private void onEndLifetimeExtension(
+ @NonNull NotifLifetimeExtender extender,
+ @NonNull NotificationEntry entry) {
Assert.isMainThread();
if (!mAttached) {
return;
@@ -786,6 +797,51 @@ public class NotifCollection implements Dumpable {
private static final String TAG = "NotifCollection";
+ /**
+ * Get an object which can be used to update a notification (internally to the pipeline)
+ * in response to a user action.
+ *
+ * @param name the name of the component that will update notifiations
+ * @return an updater
+ */
+ public InternalNotifUpdater getInternalNotifUpdater(String name) {
+ return (sbn, reason) -> mMainHandler.post(
+ () -> updateNotificationInternally(sbn, name, reason));
+ }
+
+ /**
+ * Provide an updated StatusBarNotification for an existing entry. If no entry exists for the
+ * given notification key, this method does nothing.
+ *
+ * @param sbn the updated notification
+ * @param name the component which is updating the notification
+ * @param reason the reason the notification is being updated
+ */
+ private void updateNotificationInternally(StatusBarNotification sbn, String name,
+ String reason) {
+ Assert.isMainThread();
+ checkForReentrantCall();
+
+ // Make sure we have the notification to update
+ NotificationEntry entry = mNotificationSet.get(sbn.getKey());
+ if (entry == null) {
+ mLogger.logNotifInternalUpdateFailed(sbn.getKey(), name, reason);
+ return;
+ }
+ mLogger.logNotifInternalUpdate(sbn.getKey(), name, reason);
+
+ // First do the pieces of postNotification which are not about assuming the notification
+ // was sent by the app
+ entry.setSbn(sbn);
+ mEventQueue.add(new BindEntryEvent(entry, sbn));
+
+ mLogger.logNotifUpdated(sbn.getKey());
+ mEventQueue.add(new EntryUpdatedEvent(entry, false /* fromSystem */));
+
+ // Skip the applyRanking step and go straight to dispatching the events
+ dispatchEventsAndRebuildList();
+ }
+
@IntDef(prefix = { "REASON_" }, value = {
REASON_NOT_CANCELED,
REASON_UNKNOWN,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
index 47939f0579f5..27ba4c23db88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection;
+import android.os.Handler;
+
import androidx.annotation.Nullable;
import com.android.systemui.dagger.SysUISingleton;
@@ -23,12 +25,14 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
@@ -216,6 +220,22 @@ public class NotifPipeline implements CommonNotifCollection {
mShadeListBuilder.addOnBeforeRenderListListener(listener);
}
+ /** Registers an invalidator that can be used to invalidate the entire notif list. */
+ public void addPreRenderInvalidator(Invalidator invalidator) {
+ mShadeListBuilder.addPreRenderInvalidator(invalidator);
+ }
+
+ /**
+ * Get an object which can be used to update a notification (internally to the pipeline)
+ * in response to a user action.
+ *
+ * @param name the name of the component that will update notifiations
+ * @return an updater
+ */
+ public InternalNotifUpdater getInternalNotifUpdater(String name) {
+ return mNotifCollection.getInternalNotifUpdater(name);
+ }
+
/**
* Returns a read-only view in to the current shade list, i.e. the list of notifications that
* are currently present in the shade. If this method is called during pipeline execution it
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 94ee868ceebc..66d019e778bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -31,7 +31,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
import static java.util.Objects.requireNonNull;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index a0ef1b674af3..6d38389713a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -28,10 +28,9 @@ import static com.android.systemui.statusbar.notification.collection.listbuilder
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_SORTING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_TRANSFORMING;
-import static java.util.Objects.requireNonNull;
-
import android.annotation.MainThread;
import android.annotation.Nullable;
+import android.os.Trace;
import android.util.ArrayMap;
import androidx.annotation.NonNull;
@@ -47,6 +46,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState;
import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
@@ -54,6 +54,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
+import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
import com.android.systemui.util.Assert;
import com.android.systemui.util.time.SystemClock;
@@ -80,6 +81,8 @@ public class ShadeListBuilder implements Dumpable {
private final SystemClock mSystemClock;
private final ShadeListBuilderLogger mLogger;
private final NotificationInteractionTracker mInteractionTracker;
+ // used exclusivly by ShadeListBuilder#notifySectionEntriesUpdated
+ private final ArrayList<ListEntry> mTempSectionMembers = new ArrayList<>();
private List<ListEntry> mNotifList = new ArrayList<>();
private List<ListEntry> mNewNotifList = new ArrayList<>();
@@ -173,6 +176,13 @@ public class ShadeListBuilder implements Dumpable {
mOnBeforeRenderListListeners.add(listener);
}
+ void addPreRenderInvalidator(Invalidator invalidator) {
+ Assert.isMainThread();
+
+ mPipelineState.requireState(STATE_IDLE);
+ invalidator.setInvalidationListener(this::onPreRenderInvalidated);
+ }
+
void addPreGroupFilter(NotifFilter filter) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
@@ -255,6 +265,14 @@ public class ShadeListBuilder implements Dumpable {
}
};
+ private void onPreRenderInvalidated(Invalidator invalidator) {
+ Assert.isMainThread();
+
+ mLogger.logPreRenderInvalidated(invalidator.getName(), mPipelineState.getState());
+
+ rebuildListIfBefore(STATE_FINALIZING);
+ }
+
private void onPreGroupFilterInvalidated(NotifFilter filter) {
Assert.isMainThread();
@@ -315,6 +333,7 @@ public class ShadeListBuilder implements Dumpable {
* if we detect that behavior, we should crash instantly.
*/
private void buildList() {
+ Trace.beginSection("ShadeListBuilder.buildList");
mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
mPipelineState.setState(STATE_BUILD_STARTED);
@@ -344,14 +363,8 @@ public class ShadeListBuilder implements Dumpable {
mPipelineState.incrementTo(STATE_GROUP_STABILIZING);
stabilizeGroupingNotifs(mNotifList);
- // Step 5: Sort
- // Assign each top-level entry a section, then sort the list by section and then within
- // section by our list of custom comparators
- dispatchOnBeforeSort(mReadOnlyNotifList);
- mPipelineState.incrementTo(STATE_SORTING);
- sortList();
- // Step 6: Filter out entries after pre-group filtering, grouping, promoting and sorting
+ // Step 5: Filter out entries after pre-group filtering, grouping and promoting
// Now filters can see grouping information to determine whether to filter or not.
dispatchOnBeforeFinalizeFilter(mReadOnlyNotifList);
mPipelineState.incrementTo(STATE_FINALIZE_FILTERING);
@@ -359,6 +372,13 @@ public class ShadeListBuilder implements Dumpable {
applyNewNotifList();
pruneIncompleteGroups(mNotifList);
+ // Step 6: Sort
+ // Assign each top-level entry a section, then sort the list by section and then within
+ // section by our list of custom comparators
+ dispatchOnBeforeSort(mReadOnlyNotifList);
+ mPipelineState.incrementTo(STATE_SORTING);
+ sortListAndNotifySections();
+
// Step 7: Lock in our group structure and log anything that's changed since the last run
mPipelineState.incrementTo(STATE_FINALIZING);
logChanges();
@@ -367,9 +387,11 @@ public class ShadeListBuilder implements Dumpable {
// Step 8: Dispatch the new list, first to any listeners and then to the view layer
dispatchOnBeforeRenderList(mReadOnlyNotifList);
+ Trace.beginSection("ShadeListBuilder.onRenderList");
if (mOnRenderListListener != null) {
mOnRenderListListener.onRenderList(mReadOnlyNotifList);
}
+ Trace.endSection();
// Step 9: We're done!
mLogger.logEndBuildList(
@@ -381,6 +403,25 @@ public class ShadeListBuilder implements Dumpable {
}
mPipelineState.setState(STATE_IDLE);
mIterationCount++;
+ Trace.endSection();
+ }
+
+ private void notifySectionEntriesUpdated() {
+ Trace.beginSection("ShadeListBuilder.notifySectionEntriesUpdated");
+ NotifSection currentSection = null;
+ mTempSectionMembers.clear();
+ for (int i = 0; i < mNotifList.size(); i++) {
+ ListEntry currentEntry = mNotifList.get(i);
+ if (currentSection != currentEntry.getSection()) {
+ if (currentSection != null) {
+ currentSection.getSectioner().onEntriesUpdated(mTempSectionMembers);
+ mTempSectionMembers.clear();
+ }
+ currentSection = currentEntry.getSection();
+ }
+ mTempSectionMembers.add(currentEntry);
+ }
+ Trace.endSection();
}
/**
@@ -422,6 +463,7 @@ public class ShadeListBuilder implements Dumpable {
Collection<? extends ListEntry> entries,
List<ListEntry> out,
List<NotifFilter> filters) {
+ Trace.beginSection("ShadeListBuilder.filterNotifs");
final long now = mSystemClock.uptimeMillis();
for (ListEntry entry : entries) {
if (entry instanceof GroupEntry) {
@@ -453,9 +495,11 @@ public class ShadeListBuilder implements Dumpable {
}
}
}
+ Trace.endSection();
}
private void groupNotifs(List<ListEntry> entries, List<ListEntry> out) {
+ Trace.beginSection("ShadeListBuilder.groupNotifs");
for (ListEntry listEntry : entries) {
// since grouping hasn't happened yet, all notifs are NotificationEntries
NotificationEntry entry = (NotificationEntry) listEntry;
@@ -511,12 +555,14 @@ public class ShadeListBuilder implements Dumpable {
}
}
}
+ Trace.endSection();
}
private void stabilizeGroupingNotifs(List<ListEntry> topLevelList) {
if (mNotifStabilityManager == null) {
return;
}
+ Trace.beginSection("ShadeListBuilder.stabilizeGroupingNotifs");
for (int i = 0; i < topLevelList.size(); i++) {
final ListEntry tle = topLevelList.get(i);
@@ -542,6 +588,7 @@ public class ShadeListBuilder implements Dumpable {
}
}
}
+ Trace.endSection();
}
/**
@@ -574,6 +621,7 @@ public class ShadeListBuilder implements Dumpable {
}
private void promoteNotifs(List<ListEntry> list) {
+ Trace.beginSection("ShadeListBuilder.promoteNotifs");
for (int i = 0; i < list.size(); i++) {
final ListEntry tle = list.get(i);
@@ -592,9 +640,11 @@ public class ShadeListBuilder implements Dumpable {
});
}
}
+ Trace.endSection();
}
private void pruneIncompleteGroups(List<ListEntry> shadeList) {
+ Trace.beginSection("ShadeListBuilder.pruneIncompleteGroups");
for (int i = 0; i < shadeList.size(); i++) {
final ListEntry tle = shadeList.get(i);
@@ -649,6 +699,7 @@ public class ShadeListBuilder implements Dumpable {
}
}
}
+ Trace.endSection();
}
/**
@@ -714,14 +765,15 @@ public class ShadeListBuilder implements Dumpable {
}
}
- private void sortList() {
+ private void sortListAndNotifySections() {
+ Trace.beginSection("ShadeListBuilder.sortListAndNotifySections");
// Assign sections to top-level elements and sort their children
for (ListEntry entry : mNotifList) {
NotifSection section = applySections(entry);
if (entry instanceof GroupEntry) {
GroupEntry parent = (GroupEntry) entry;
for (NotificationEntry child : parent.getChildren()) {
- child.getAttachState().setSection(section);
+ setEntrySection(child, section);
}
parent.sortChildren(sChildComparator);
}
@@ -729,6 +781,10 @@ public class ShadeListBuilder implements Dumpable {
// Finally, sort all top-level elements
mNotifList.sort(mTopLevelComparator);
+
+ // notify sections since the list is sorted now
+ notifySectionEntriesUpdated();
+ Trace.endSection();
}
private void freeEmptyGroups() {
@@ -837,8 +893,8 @@ public class ShadeListBuilder implements Dumpable {
private final Comparator<ListEntry> mTopLevelComparator = (o1, o2) -> {
int cmp = Integer.compare(
- requireNonNull(o1.getSection()).getIndex(),
- requireNonNull(o2.getSection()).getIndex());
+ o1.getSectionIndex(),
+ o2.getSectionIndex());
if (cmp == 0) {
for (int i = 0; i < mNotifComparators.size(); i++) {
@@ -937,11 +993,18 @@ public class ShadeListBuilder implements Dumpable {
}
}
- entry.getAttachState().setSection(finalSection);
-
+ setEntrySection(entry, finalSection);
return finalSection;
}
+ private void setEntrySection(ListEntry entry, NotifSection finalSection) {
+ entry.getAttachState().setSection(finalSection);
+ NotificationEntry representativeEntry = entry.getRepresentativeEntry();
+ if (representativeEntry != null && finalSection != null) {
+ representativeEntry.setBucket(finalSection.getBucket());
+ }
+ }
+
@NonNull
private NotifSection findSection(ListEntry entry) {
for (int i = 0; i < mNotifSections.size(); i++) {
@@ -972,27 +1035,35 @@ public class ShadeListBuilder implements Dumpable {
}
private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) {
+ Trace.beginSection("ShadeListBuilder.dispatchOnBeforeTransformGroups");
for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) {
mOnBeforeTransformGroupsListeners.get(i).onBeforeTransformGroups(entries);
}
+ Trace.endSection();
}
private void dispatchOnBeforeSort(List<ListEntry> entries) {
+ Trace.beginSection("ShadeListBuilder.dispatchOnBeforeSort");
for (int i = 0; i < mOnBeforeSortListeners.size(); i++) {
mOnBeforeSortListeners.get(i).onBeforeSort(entries);
}
+ Trace.endSection();
}
private void dispatchOnBeforeFinalizeFilter(List<ListEntry> entries) {
+ Trace.beginSection("ShadeListBuilder.dispatchOnBeforeFinalizeFilter");
for (int i = 0; i < mOnBeforeFinalizeFilterListeners.size(); i++) {
mOnBeforeFinalizeFilterListeners.get(i).onBeforeFinalizeFilter(entries);
}
+ Trace.endSection();
}
private void dispatchOnBeforeRenderList(List<ListEntry> entries) {
+ Trace.beginSection("ShadeListBuilder.dispatchOnBeforeRenderList");
for (int i = 0; i < mOnBeforeRenderListListeners.size(); i++) {
mOnBeforeRenderListListeners.get(i).onBeforeRenderList(entries);
}
+ Trace.endSection();
}
@Override
@@ -1020,13 +1091,13 @@ public class ShadeListBuilder implements Dumpable {
void onRenderList(@NonNull List<ListEntry> entries);
}
- private static final NotifSectioner DEFAULT_SECTIONER =
- new NotifSectioner("UnknownSection") {
- @Override
- public boolean isInSection(ListEntry entry) {
- return true;
- }
- };
+ private static final NotifSectioner DEFAULT_SECTIONER = new NotifSectioner("UnknownSection",
+ NotificationPriorityBucketKt.BUCKET_UNKNOWN) {
+ @Override
+ public boolean isInSection(ListEntry entry) {
+ return true;
+ }
+ };
private static final int MIN_CHILDREN_FOR_GROUP = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
index 3a87f6853bcf..3a39c39cfb20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
@@ -23,13 +23,14 @@ import android.service.notification.StatusBarNotification;
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
+import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
import com.android.systemui.util.concurrency.DelayableExecutor;
import javax.inject.Inject;
@@ -47,7 +48,7 @@ import javax.inject.Inject;
* frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener
* frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender
*/
-@SysUISingleton
+@CoordinatorScope
public class AppOpsCoordinator implements Coordinator {
private static final String TAG = "AppOpsCoordinator";
@@ -102,7 +103,8 @@ public class AppOpsCoordinator implements Coordinator {
/**
* Puts foreground service notifications into its own section.
*/
- private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService") {
+ private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService",
+ NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
@Override
public boolean isInSection(ListEntry entry) {
NotificationEntry notificationEntry = entry.getRepresentativeEntry();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 29a030f910a4..15f0d885c2fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -16,10 +16,10 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
-import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
@@ -53,7 +53,7 @@ import javax.inject.Inject;
* respond to app-cancellations (ie: remove the bubble if the app cancels the notification).
*
*/
-@SysUISingleton
+@CoordinatorScope
public class BubbleCoordinator implements Coordinator {
private static final String TAG = "BubbleCoordinator";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
index f0eb084ea8ef..e59f4a62f9b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -16,16 +16,17 @@
package com.android.systemui.statusbar.notification.collection.coordinator
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.dagger.PeopleHeader
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
+import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
import javax.inject.Inject
/**
@@ -33,7 +34,7 @@ import javax.inject.Inject
* - Elevates important conversation notifications
* - Puts conversations into its own people section. @see [NotifCoordinators] for section ordering.
*/
-@SysUISingleton
+@CoordinatorScope
class ConversationCoordinator @Inject constructor(
private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
@PeopleHeader peopleHeaderController: NodeController
@@ -45,10 +46,12 @@ class ConversationCoordinator @Inject constructor(
}
}
- val sectioner = object : NotifSectioner("People") {
+ val sectioner = object : NotifSectioner("People", BUCKET_PEOPLE) {
override fun isInSection(entry: ListEntry): Boolean =
isConversation(entry.representativeEntry!!)
- override fun getHeaderNodeController() = peopleHeaderController
+ override fun getHeaderNodeController() =
+ // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController
+ if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null
}
override fun attach(pipeline: NotifPipeline) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
index 47928b42ed5e..e8652493da6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
@@ -23,9 +23,9 @@ import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.service.notification.StatusBarNotification;
-import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -36,7 +36,7 @@ import javax.inject.Inject;
* Special notifications with extra permissions and tags won't be filtered out even when the
* device is unprovisioned.
*/
-@SysUISingleton
+@CoordinatorScope
public class DeviceProvisionedCoordinator implements Coordinator {
private static final String TAG = "DeviceProvisionedCoordinator";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt
new file mode 100644
index 000000000000..dbecf1cc28d7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt
@@ -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.systemui.statusbar.notification.collection.coordinator
+
+import android.util.ArraySet
+import com.android.systemui.Dumpable
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener
+import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager
+import com.android.systemui.statusbar.notification.row.NotificationGuts
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import javax.inject.Inject
+
+private const val TAG = "GutsCoordinator"
+
+/**
+ * Coordinates the guts displayed by the [NotificationGutsManager] with the pipeline.
+ * Specifically, this just adds the lifetime extension necessary to keep guts from disappearing.
+ */
+@CoordinatorScope
+class GutsCoordinator @Inject constructor(
+ private val notifGutsViewManager: NotifGutsViewManager,
+ private val logger: GutsCoordinatorLogger,
+ dumpManager: DumpManager
+) : Coordinator, Dumpable {
+
+ /** Keys of any Notifications for which we've been told the guts are open */
+ private val notifsWithOpenGuts = ArraySet<String>()
+
+ /** Keys of any Notifications we've extended the lifetime for, based on guts */
+ private val notifsExtendingLifetime = ArraySet<String>()
+
+ /** Callback for ending lifetime extension */
+ private var onEndLifetimeExtensionCallback: OnEndLifetimeExtensionCallback? = null
+
+ init {
+ dumpManager.registerDumpable(TAG, this)
+ }
+
+ override fun attach(pipeline: NotifPipeline) {
+ notifGutsViewManager.setGutsListener(mGutsListener)
+ pipeline.addNotificationLifetimeExtender(mLifetimeExtender)
+ }
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
+ pw.println(" notifsWithOpenGuts: ${notifsWithOpenGuts.size}")
+ for (key in notifsWithOpenGuts) {
+ pw.println(" * $key")
+ }
+ pw.println(" notifsExtendingLifetime: ${notifsExtendingLifetime.size}")
+ for (key in notifsExtendingLifetime) {
+ pw.println(" * $key")
+ }
+ pw.println(" onEndLifetimeExtensionCallback: $onEndLifetimeExtensionCallback")
+ }
+
+ private val mLifetimeExtender: NotifLifetimeExtender = object : NotifLifetimeExtender {
+ override fun getName(): String {
+ return TAG
+ }
+
+ override fun setCallback(callback: OnEndLifetimeExtensionCallback) {
+ onEndLifetimeExtensionCallback = callback
+ }
+
+ override fun shouldExtendLifetime(entry: NotificationEntry, reason: Int): Boolean {
+ val isShowingGuts = isCurrentlyShowingGuts(entry)
+ if (isShowingGuts) {
+ notifsExtendingLifetime.add(entry.key)
+ }
+ return isShowingGuts
+ }
+
+ override fun cancelLifetimeExtension(entry: NotificationEntry) {
+ notifsExtendingLifetime.remove(entry.key)
+ }
+ }
+
+ private val mGutsListener: NotifGutsViewListener = object : NotifGutsViewListener {
+ override fun onGutsOpen(entry: NotificationEntry, guts: NotificationGuts) {
+ logger.logGutsOpened(entry.key, guts)
+ if (guts.isLeavebehind) {
+ // leave-behind guts should not extend the lifetime of the notification
+ closeGutsAndEndLifetimeExtension(entry)
+ } else {
+ notifsWithOpenGuts.add(entry.key)
+ }
+ }
+
+ override fun onGutsClose(entry: NotificationEntry) {
+ logger.logGutsClosed(entry.key)
+ closeGutsAndEndLifetimeExtension(entry)
+ }
+ }
+
+ private fun isCurrentlyShowingGuts(entry: ListEntry) =
+ notifsWithOpenGuts.contains(entry.key)
+
+ private fun closeGutsAndEndLifetimeExtension(entry: NotificationEntry) {
+ notifsWithOpenGuts.remove(entry.key)
+ if (notifsExtendingLifetime.remove(entry.key)) {
+ onEndLifetimeExtensionCallback?.onEndLifetimeExtension(mLifetimeExtender, entry)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
new file mode 100644
index 000000000000..e8f352f60da0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
@@ -0,0 +1,32 @@
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.row.NotificationGuts
+import javax.inject.Inject
+
+private const val TAG = "GutsCoordinator"
+
+class GutsCoordinatorLogger @Inject constructor(
+ @NotificationLog private val buffer: LogBuffer
+) {
+
+ fun logGutsOpened(key: String, guts: NotificationGuts) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = key
+ str2 = guts.gutsContent::class.simpleName
+ bool1 = guts.isLeavebehind
+ }, {
+ "Guts of type $str2 (leave behind: $bool1) opened for class $str1"
+ })
+ }
+
+ fun logGutsClosed(key: String) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = key
+ }, {
+ "Guts closed for class $str1"
+ })
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
index be1383fb6b8a..f8b4274188f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
@@ -19,13 +19,14 @@ package com.android.systemui.statusbar.notification.collection.coordinator;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
import static com.android.systemui.statusbar.notification.interruption.HeadsUpController.alertAgain;
-import android.annotation.Nullable;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
-import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
@@ -34,6 +35,7 @@ import com.android.systemui.statusbar.notification.collection.render.NodeControl
import com.android.systemui.statusbar.notification.dagger.IncomingHeader;
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
+import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -55,7 +57,7 @@ import javax.inject.Inject;
*
* Note: The inflation callback in {@link PreparationCoordinator} handles showing HUNs.
*/
-@SysUISingleton
+@CoordinatorScope
public class HeadsUpCoordinator implements Coordinator {
private static final String TAG = "HeadsUpCoordinator";
@@ -149,7 +151,7 @@ public class HeadsUpCoordinator implements Coordinator {
final String entryKey = entry.getKey();
if (mHeadsUpManager.isAlerting(entryKey)) {
boolean removeImmediatelyForRemoteInput =
- mRemoteInputManager.getController().isSpinning(entryKey)
+ mRemoteInputManager.isSpinning(entryKey)
&& !FORCE_REMOTE_INPUT_HISTORY;
mHeadsUpManager.removeNotification(entry.getKey(), removeImmediatelyForRemoteInput);
}
@@ -163,17 +165,17 @@ public class HeadsUpCoordinator implements Coordinator {
private final NotifLifetimeExtender mLifetimeExtender = new NotifLifetimeExtender() {
@Override
- public String getName() {
+ public @NonNull String getName() {
return TAG;
}
@Override
- public void setCallback(OnEndLifetimeExtensionCallback callback) {
+ public void setCallback(@NonNull OnEndLifetimeExtensionCallback callback) {
mEndLifetimeExtension = callback;
}
@Override
- public boolean shouldExtendLifetime(NotificationEntry entry, int reason) {
+ public boolean shouldExtendLifetime(@NonNull NotificationEntry entry, int reason) {
boolean isShowingHun = isCurrentlyShowingHun(entry);
if (isShowingHun) {
mNotifExtendingLifetime = entry;
@@ -182,7 +184,7 @@ public class HeadsUpCoordinator implements Coordinator {
}
@Override
- public void cancelLifetimeExtension(NotificationEntry entry) {
+ public void cancelLifetimeExtension(@NonNull NotificationEntry entry) {
if (Objects.equals(mNotifExtendingLifetime, entry)) {
mNotifExtendingLifetime = null;
}
@@ -196,7 +198,8 @@ public class HeadsUpCoordinator implements Coordinator {
}
};
- private final NotifSectioner mNotifSectioner = new NotifSectioner("HeadsUp") {
+ private final NotifSectioner mNotifSectioner = new NotifSectioner("HeadsUp",
+ NotificationPriorityBucketKt.BUCKET_HEADS_UP) {
@Override
public boolean isInSection(ListEntry entry) {
return isCurrentlyShowingHun(entry);
@@ -205,7 +208,11 @@ public class HeadsUpCoordinator implements Coordinator {
@Nullable
@Override
public NodeController getHeaderNodeController() {
- return mIncomingHeaderController;
+ // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController
+ if (RankingCoordinator.SHOW_ALL_SECTIONS) {
+ return mIncomingHeaderController;
+ }
+ return null;
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java
index 0059e7baa3c2..6684237c4ebf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java
@@ -20,13 +20,21 @@ import static com.android.systemui.statusbar.notification.collection.Notificatio
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import javax.inject.Inject;
+
/**
* Filters out notifications that have been dismissed locally (by the user) but that system server
* hasn't yet confirmed the removal of.
*/
+@CoordinatorScope
public class HideLocallyDismissedNotifsCoordinator implements Coordinator {
+
+ @Inject
+ HideLocallyDismissedNotifsCoordinator() { }
+
@Override
public void attach(NotifPipeline pipeline) {
pipeline.addPreGroupFilter(mFilter);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java
index e595dd4a2f71..7b5cf8511900 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java
@@ -23,6 +23,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import javax.inject.Inject;
@@ -37,6 +38,7 @@ import javax.inject.Inject;
* TODO: The NotificationLockscreenUserManager currently maintains the list of active user profiles.
* We should spin that off into a standalone section at some point.
*/
+@CoordinatorScope
public class HideNotifsForOtherUsersCoordinator implements Coordinator {
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final SharedCoordinatorLogger mLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 23d5369833c5..fe1cd7b98cf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -34,13 +34,13 @@ import androidx.annotation.MainThread;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -50,7 +50,7 @@ import javax.inject.Inject;
/**
* Filters low priority and privacy-sensitive notifications from the lockscreen.
*/
-@SysUISingleton
+@CoordinatorScope
public class KeyguardCoordinator implements Coordinator {
private static final String TAG = "KeyguardCoordinator";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
index 026a3ffb73cd..8769969834c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
@@ -21,6 +21,7 @@ import static com.android.systemui.media.MediaDataManagerKt.isMediaNotification;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import javax.inject.Inject;
@@ -28,6 +29,7 @@ import javax.inject.Inject;
/**
* Coordinates hiding (filtering) of media notifications.
*/
+@CoordinatorScope
public class MediaCoordinator implements Coordinator {
private static final String TAG = "MediaCoordinator";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
deleted file mode 100644
index d80cc082aada..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.coordinator;
-
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * Handles the attachment of {@link Coordinator}s to the {@link NotifPipeline} so that the
- * Coordinators can register their respective callbacks.
- */
-@SysUISingleton
-public class NotifCoordinators implements Dumpable {
- private static final String TAG = "NotifCoordinators";
- private final List<Coordinator> mCoordinators = new ArrayList<>();
- private final List<NotifSectioner> mOrderedSections = new ArrayList<>();
-
- /**
- * Creates all the coordinators.
- */
- @Inject
- public NotifCoordinators(
- DumpManager dumpManager,
- FeatureFlags featureFlags,
- HideNotifsForOtherUsersCoordinator hideNotifsForOtherUsersCoordinator,
- KeyguardCoordinator keyguardCoordinator,
- RankingCoordinator rankingCoordinator,
- AppOpsCoordinator appOpsCoordinator,
- DeviceProvisionedCoordinator deviceProvisionedCoordinator,
- BubbleCoordinator bubbleCoordinator,
- HeadsUpCoordinator headsUpCoordinator,
- ConversationCoordinator conversationCoordinator,
- PreparationCoordinator preparationCoordinator,
- MediaCoordinator mediaCoordinator,
- SmartspaceDedupingCoordinator smartspaceDedupingCoordinator,
- VisualStabilityCoordinator visualStabilityCoordinator) {
- dumpManager.registerDumpable(TAG, this);
-
- mCoordinators.add(new HideLocallyDismissedNotifsCoordinator());
- mCoordinators.add(hideNotifsForOtherUsersCoordinator);
- mCoordinators.add(keyguardCoordinator);
- mCoordinators.add(rankingCoordinator);
- mCoordinators.add(appOpsCoordinator);
- mCoordinators.add(deviceProvisionedCoordinator);
- mCoordinators.add(bubbleCoordinator);
- mCoordinators.add(conversationCoordinator);
- mCoordinators.add(mediaCoordinator);
- mCoordinators.add(visualStabilityCoordinator);
-
- if (featureFlags.isSmartspaceDedupingEnabled()) {
- mCoordinators.add(smartspaceDedupingCoordinator);
- }
-
- if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
- mCoordinators.add(headsUpCoordinator);
- mCoordinators.add(preparationCoordinator);
- }
-
- // Manually add Ordered Sections
- // HeadsUp > FGS > People > Alerting > Silent > Unknown/Default
- if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
- mOrderedSections.add(headsUpCoordinator.getSectioner()); // HeadsUp
- }
- mOrderedSections.add(appOpsCoordinator.getSectioner()); // ForegroundService
- mOrderedSections.add(conversationCoordinator.getSectioner()); // People
- mOrderedSections.add(rankingCoordinator.getAlertingSectioner()); // Alerting
- mOrderedSections.add(rankingCoordinator.getSilentSectioner()); // Silent
- }
-
- /**
- * Sends the pipeline to each coordinator when the pipeline is ready to accept
- * {@link Pluggable}s, {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s.
- */
- public void attach(NotifPipeline pipeline) {
- for (Coordinator c : mCoordinators) {
- c.attach(pipeline);
- }
-
- pipeline.setSections(mOrderedSections);
- }
-
- @Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println();
- pw.println(TAG + ":");
- for (Coordinator c : mCoordinators) {
- pw.println("\t" + c.getClass());
- }
-
- for (NotifSectioner s : mOrderedSections) {
- pw.println("\t" + s.getName());
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
new file mode 100644
index 000000000000..39b1ec4ff80e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.Dumpable
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.ArrayList
+import javax.inject.Inject
+
+/**
+ * Handles the attachment of [Coordinator]s to the [NotifPipeline] so that the
+ * Coordinators can register their respective callbacks.
+ */
+interface NotifCoordinators : Coordinator, Dumpable
+
+@CoordinatorScope
+class NotifCoordinatorsImpl @Inject constructor(
+ dumpManager: DumpManager,
+ featureFlags: FeatureFlags,
+ hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
+ hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
+ keyguardCoordinator: KeyguardCoordinator,
+ rankingCoordinator: RankingCoordinator,
+ appOpsCoordinator: AppOpsCoordinator,
+ deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
+ bubbleCoordinator: BubbleCoordinator,
+ headsUpCoordinator: HeadsUpCoordinator,
+ gutsCoordinator: GutsCoordinator,
+ conversationCoordinator: ConversationCoordinator,
+ preparationCoordinator: PreparationCoordinator,
+ mediaCoordinator: MediaCoordinator,
+ remoteInputCoordinator: RemoteInputCoordinator,
+ shadeEventCoordinator: ShadeEventCoordinator,
+ smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
+ viewConfigCoordinator: ViewConfigCoordinator,
+ visualStabilityCoordinator: VisualStabilityCoordinator,
+ sensitiveContentCoordinator: SensitiveContentCoordinator
+) : NotifCoordinators {
+
+ private val mCoordinators: MutableList<Coordinator> = ArrayList()
+ private val mOrderedSections: MutableList<NotifSectioner> = ArrayList()
+
+ /**
+ * Creates all the coordinators.
+ */
+ init {
+ dumpManager.registerDumpable(TAG, this)
+ mCoordinators.add(hideLocallyDismissedNotifsCoordinator)
+ mCoordinators.add(hideNotifsForOtherUsersCoordinator)
+ mCoordinators.add(keyguardCoordinator)
+ mCoordinators.add(rankingCoordinator)
+ mCoordinators.add(appOpsCoordinator)
+ mCoordinators.add(deviceProvisionedCoordinator)
+ mCoordinators.add(bubbleCoordinator)
+ mCoordinators.add(conversationCoordinator)
+ mCoordinators.add(mediaCoordinator)
+ mCoordinators.add(remoteInputCoordinator)
+ mCoordinators.add(shadeEventCoordinator)
+ mCoordinators.add(viewConfigCoordinator)
+ mCoordinators.add(visualStabilityCoordinator)
+ mCoordinators.add(sensitiveContentCoordinator)
+ if (featureFlags.isSmartspaceDedupingEnabled) {
+ mCoordinators.add(smartspaceDedupingCoordinator)
+ }
+ if (featureFlags.isNewNotifPipelineRenderingEnabled) {
+ mCoordinators.add(headsUpCoordinator)
+ mCoordinators.add(gutsCoordinator)
+ mCoordinators.add(preparationCoordinator)
+ }
+
+ // Manually add Ordered Sections
+ // HeadsUp > FGS > People > Alerting > Silent > Unknown/Default
+ if (featureFlags.isNewNotifPipelineRenderingEnabled) {
+ mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
+ }
+ mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
+ mOrderedSections.add(conversationCoordinator.sectioner) // People
+ mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
+ mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent
+ }
+
+ /**
+ * Sends the pipeline to each coordinator when the pipeline is ready to accept
+ * [Pluggable]s, [NotifCollectionListener]s and [NotifLifetimeExtender]s.
+ */
+ override fun attach(pipeline: NotifPipeline) {
+ for (c in mCoordinators) {
+ c.attach(pipeline)
+ }
+ pipeline.setSections(mOrderedSections)
+ }
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
+ pw.println()
+ pw.println("$TAG:")
+ for (c in mCoordinators) {
+ pw.println("\t${c.javaClass}")
+ }
+ for (s in mOrderedSections) {
+ pw.println("\t${s.name}")
+ }
+ }
+
+ companion object {
+ private const val TAG = "NotifCoordinators"
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 31826c7219de..afdfb3bdeef6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -57,6 +57,7 @@ import javax.inject.Inject;
* If a notification was uninflated, this coordinator will filter the notification out from the
* {@link ShadeListBuilder} until it is inflated.
*/
+// TODO(b/204468557): Move to @CoordinatorScope
@SysUISingleton
public class PreparationCoordinator implements Coordinator {
private static final String TAG = "PreparationCoordinator";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 6da4d8b70944..2ab2dd0b1273 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -16,19 +16,24 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
-import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
+import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.dagger.AlertingHeader;
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
+import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
+
+import java.util.List;
import javax.inject.Inject;
@@ -39,11 +44,13 @@ import javax.inject.Inject;
* - whether the notification's app is suspended or hiding its notifications
* - whether DND settings are hiding notifications from ambient display or the notification list
*/
-@SysUISingleton
+@CoordinatorScope
public class RankingCoordinator implements Coordinator {
+ public static final boolean SHOW_ALL_SECTIONS = false;
private final StatusBarStateController mStatusBarStateController;
private final HighPriorityProvider mHighPriorityProvider;
- private final NodeController mSilentHeaderController;
+ private final NodeController mSilentNodeController;
+ private final SectionHeaderController mSilentHeaderController;
private final NodeController mAlertingHeaderController;
@Inject
@@ -51,10 +58,12 @@ public class RankingCoordinator implements Coordinator {
StatusBarStateController statusBarStateController,
HighPriorityProvider highPriorityProvider,
@AlertingHeader NodeController alertingHeaderController,
- @SilentHeader NodeController silentHeaderController) {
+ @SilentHeader SectionHeaderController silentHeaderController,
+ @SilentHeader NodeController silentNodeController) {
mStatusBarStateController = statusBarStateController;
mHighPriorityProvider = highPriorityProvider;
mAlertingHeaderController = alertingHeaderController;
+ mSilentNodeController = silentNodeController;
mSilentHeaderController = silentHeaderController;
}
@@ -74,7 +83,8 @@ public class RankingCoordinator implements Coordinator {
return mSilentNotifSectioner;
}
- private final NotifSectioner mAlertingNotifSectioner = new NotifSectioner("Alerting") {
+ private final NotifSectioner mAlertingNotifSectioner = new NotifSectioner("Alerting",
+ NotificationPriorityBucketKt.BUCKET_ALERTING) {
@Override
public boolean isInSection(ListEntry entry) {
return mHighPriorityProvider.isHighPriority(entry);
@@ -83,11 +93,16 @@ public class RankingCoordinator implements Coordinator {
@Nullable
@Override
public NodeController getHeaderNodeController() {
- return mAlertingHeaderController;
+ // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mAlertingHeaderController
+ if (SHOW_ALL_SECTIONS) {
+ return mAlertingHeaderController;
+ }
+ return null;
}
};
- private final NotifSectioner mSilentNotifSectioner = new NotifSectioner("Silent") {
+ private final NotifSectioner mSilentNotifSectioner = new NotifSectioner("Silent",
+ NotificationPriorityBucketKt.BUCKET_SILENT) {
@Override
public boolean isInSection(ListEntry entry) {
return !mHighPriorityProvider.isHighPriority(entry);
@@ -96,7 +111,19 @@ public class RankingCoordinator implements Coordinator {
@Nullable
@Override
public NodeController getHeaderNodeController() {
- return mSilentHeaderController;
+ return mSilentNodeController;
+ }
+
+ @Nullable
+ @Override
+ public void onEntriesUpdated(@NonNull List<ListEntry> entries) {
+ for (int i = 0; i < entries.size(); i++) {
+ if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) {
+ mSilentHeaderController.setClearSectionEnabled(true);
+ return;
+ }
+ }
+ mSilentHeaderController.setClearSectionEnabled(false);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
new file mode 100644
index 000000000000..3397815f008f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
@@ -0,0 +1,225 @@
+/*
+ * 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.systemui.statusbar.notification.collection.coordinator
+
+import android.os.Handler
+import android.service.notification.NotificationListenerService.REASON_CANCEL
+import android.service.notification.NotificationListenerService.REASON_CLICK
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.NotificationRemoteInputManager.RemoteInputListener
+import com.android.systemui.statusbar.RemoteInputController
+import com.android.systemui.statusbar.RemoteInputNotificationRebuilder
+import com.android.systemui.statusbar.SmartReplyController
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.SelfTrackingLifetimeExtender
+import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import javax.inject.Inject
+
+private const val TAG = "RemoteInputCoordinator"
+
+/**
+ * How long to wait before auto-dismissing a notification that was kept for active remote input, and
+ * has now sent a remote input. We auto-dismiss, because the app may not cannot cancel
+ * these given that they technically don't exist anymore. We wait a bit in case the app issues
+ * an update, and to also give the other lifetime extenders a beat to decide they want it.
+ */
+private const val REMOTE_INPUT_ACTIVE_EXTENDER_AUTO_CANCEL_DELAY: Long = 500
+
+/**
+ * How long to wait before releasing a lifetime extension when requested to do so due to a user
+ * interaction (such as tapping another action).
+ * We wait a bit in case the app issues an update in response to the action, but not too long or we
+ * risk appearing unresponsive to the user.
+ */
+private const val REMOTE_INPUT_EXTENDER_RELEASE_DELAY: Long = 200
+
+/** Whether this class should print spammy debug logs */
+private val DEBUG: Boolean by lazy { Log.isLoggable(TAG, Log.DEBUG) }
+
+@SysUISingleton
+class RemoteInputCoordinator @Inject constructor(
+ dumpManager: DumpManager,
+ private val mRebuilder: RemoteInputNotificationRebuilder,
+ private val mNotificationRemoteInputManager: NotificationRemoteInputManager,
+ @Main private val mMainHandler: Handler,
+ private val mSmartReplyController: SmartReplyController
+) : Coordinator, RemoteInputListener, Dumpable {
+
+ @VisibleForTesting val mRemoteInputHistoryExtender = RemoteInputHistoryExtender()
+ @VisibleForTesting val mSmartReplyHistoryExtender = SmartReplyHistoryExtender()
+ @VisibleForTesting val mRemoteInputActiveExtender = RemoteInputActiveExtender()
+ private val mRemoteInputLifetimeExtenders = listOf(
+ mRemoteInputHistoryExtender,
+ mSmartReplyHistoryExtender,
+ mRemoteInputActiveExtender
+ )
+
+ private lateinit var mNotifUpdater: InternalNotifUpdater
+
+ init {
+ dumpManager.registerDumpable(this)
+ }
+
+ fun getLifetimeExtenders(): List<NotifLifetimeExtender> = mRemoteInputLifetimeExtenders
+
+ override fun attach(pipeline: NotifPipeline) {
+ mNotificationRemoteInputManager.setRemoteInputListener(this)
+ mRemoteInputLifetimeExtenders.forEach { pipeline.addNotificationLifetimeExtender(it) }
+ mNotifUpdater = pipeline.getInternalNotifUpdater(TAG)
+ pipeline.addCollectionListener(mCollectionListener)
+ }
+
+ val mCollectionListener = object : NotifCollectionListener {
+ override fun onEntryUpdated(entry: NotificationEntry, fromSystem: Boolean) {
+ if (DEBUG) {
+ Log.d(TAG, "mCollectionListener.onEntryUpdated(entry=${entry.key}," +
+ " fromSystem=$fromSystem)")
+ }
+ if (fromSystem) {
+ // Mark smart replies as sent whenever a notification is updated by the app,
+ // otherwise the smart replies are never marked as sent.
+ mSmartReplyController.stopSending(entry)
+ }
+ }
+
+ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ if (DEBUG) Log.d(TAG, "mCollectionListener.onEntryRemoved(entry=${entry.key})")
+ // We're removing the notification, the smart reply controller can forget about it.
+ // TODO(b/145659174): track 'sending' state on the entry to avoid having to clear it.
+ mSmartReplyController.stopSending(entry)
+
+ // When we know the entry will not be lifetime extended, clean up the remote input view
+ // TODO: Share code with NotifCollection.cannotBeLifetimeExtended
+ if (reason == REASON_CANCEL || reason == REASON_CLICK) {
+ mNotificationRemoteInputManager.cleanUpRemoteInputForUserRemoval(entry)
+ }
+ }
+ }
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+ mRemoteInputLifetimeExtenders.forEach { it.dump(fd, pw, args) }
+ }
+
+ override fun onRemoteInputSent(entry: NotificationEntry) {
+ if (DEBUG) Log.d(TAG, "onRemoteInputSent(entry=${entry.key})")
+ // These calls effectively ensure the freshness of the lifetime extensions.
+ // NOTE: This is some trickery! By removing the lifetime extensions when we know they should
+ // be immediately re-upped, we ensure that the side-effects of the lifetime extenders get to
+ // fire again, thus ensuring that we add subsequent replies to the notification.
+ mRemoteInputHistoryExtender.endLifetimeExtension(entry.key)
+ mSmartReplyHistoryExtender.endLifetimeExtension(entry.key)
+
+ // If we're extending for remote input being active, then from the apps point of
+ // view it is already canceled, so we'll need to cancel it on the apps behalf
+ // now that a reply has been sent. However, delay so that the app has time to posts an
+ // update in the mean time, and to give another lifetime extender time to pick it up.
+ mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(entry.key,
+ REMOTE_INPUT_ACTIVE_EXTENDER_AUTO_CANCEL_DELAY)
+ }
+
+ private fun onSmartReplySent(entry: NotificationEntry, reply: CharSequence) {
+ if (DEBUG) Log.d(TAG, "onSmartReplySent(entry=${entry.key})")
+ val newSbn = mRebuilder.rebuildForSendingSmartReply(entry, reply)
+ mNotifUpdater.onInternalNotificationUpdate(newSbn,
+ "Adding smart reply spinner for sent")
+
+ // If we're extending for remote input being active, then from the apps point of
+ // view it is already canceled, so we'll need to cancel it on the apps behalf
+ // now that a reply has been sent. However, delay so that the app has time to posts an
+ // update in the mean time, and to give another lifetime extender time to pick it up.
+ mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(entry.key,
+ REMOTE_INPUT_ACTIVE_EXTENDER_AUTO_CANCEL_DELAY)
+ }
+
+ override fun onPanelCollapsed() {
+ mRemoteInputActiveExtender.endAllLifetimeExtensions()
+ }
+
+ override fun isNotificationKeptForRemoteInputHistory(key: String) =
+ mRemoteInputHistoryExtender.isExtending(key) ||
+ mSmartReplyHistoryExtender.isExtending(key)
+
+ override fun releaseNotificationIfKeptForRemoteInputHistory(entry: NotificationEntry) {
+ if (DEBUG) Log.d(TAG, "releaseNotificationIfKeptForRemoteInputHistory(entry=${entry.key})")
+ mRemoteInputHistoryExtender.endLifetimeExtensionAfterDelay(entry.key,
+ REMOTE_INPUT_EXTENDER_RELEASE_DELAY)
+ mSmartReplyHistoryExtender.endLifetimeExtensionAfterDelay(entry.key,
+ REMOTE_INPUT_EXTENDER_RELEASE_DELAY)
+ mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(entry.key,
+ REMOTE_INPUT_EXTENDER_RELEASE_DELAY)
+ }
+
+ override fun setRemoteInputController(remoteInputController: RemoteInputController) {
+ mSmartReplyController.setCallback(this::onSmartReplySent)
+ }
+
+ @VisibleForTesting
+ inner class RemoteInputHistoryExtender :
+ SelfTrackingLifetimeExtender(TAG, "RemoteInputHistory", DEBUG, mMainHandler) {
+
+ override fun queryShouldExtendLifetime(entry: NotificationEntry): Boolean =
+ mNotificationRemoteInputManager.shouldKeepForRemoteInputHistory(entry)
+
+ override fun onStartedLifetimeExtension(entry: NotificationEntry) {
+ val newSbn = mRebuilder.rebuildForRemoteInputReply(entry)
+ entry.onRemoteInputInserted()
+ mNotifUpdater.onInternalNotificationUpdate(newSbn,
+ "Extending lifetime of notification with remote input")
+ // TODO: Check if the entry was removed due perhaps to an inflation exception?
+ }
+ }
+
+ @VisibleForTesting
+ inner class SmartReplyHistoryExtender :
+ SelfTrackingLifetimeExtender(TAG, "SmartReplyHistory", DEBUG, mMainHandler) {
+
+ override fun queryShouldExtendLifetime(entry: NotificationEntry): Boolean =
+ mNotificationRemoteInputManager.shouldKeepForSmartReplyHistory(entry)
+
+ override fun onStartedLifetimeExtension(entry: NotificationEntry) {
+ val newSbn = mRebuilder.rebuildForCanceledSmartReplies(entry)
+ mSmartReplyController.stopSending(entry)
+ mNotifUpdater.onInternalNotificationUpdate(newSbn,
+ "Extending lifetime of notification with smart reply")
+ // TODO: Check if the entry was removed due perhaps to an inflation exception?
+ }
+
+ override fun onCanceledLifetimeExtension(entry: NotificationEntry) {
+ // TODO(b/145659174): track 'sending' state on the entry to avoid having to clear it.
+ mSmartReplyController.stopSending(entry)
+ }
+ }
+
+ @VisibleForTesting
+ inner class RemoteInputActiveExtender :
+ SelfTrackingLifetimeExtender(TAG, "RemoteInputActive", DEBUG, mMainHandler) {
+
+ override fun queryShouldExtendLifetime(entry: NotificationEntry): Boolean =
+ mNotificationRemoteInputManager.isRemoteInputActive(entry)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
new file mode 100644
index 000000000000..a115e0400de3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.systemui.statusbar.notification.collection.coordinator
+
+import android.os.UserHandle
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.notification.DynamicPrivacyController
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
+import dagger.Module
+import dagger.Provides
+
+@Module
+object SensitiveContentCoordinatorModule {
+ @Provides
+ @JvmStatic
+ @CoordinatorScope
+ fun provideCoordinator(
+ dynamicPrivacyController: DynamicPrivacyController,
+ lockscreenUserManager: NotificationLockscreenUserManager
+ ): SensitiveContentCoordinator =
+ SensitiveContentCoordinatorImpl(dynamicPrivacyController, lockscreenUserManager)
+}
+
+/** Coordinates re-inflation and post-processing of sensitive notification content. */
+interface SensitiveContentCoordinator : Coordinator
+
+private class SensitiveContentCoordinatorImpl(
+ private val dynamicPrivacyController: DynamicPrivacyController,
+ private val lockscreenUserManager: NotificationLockscreenUserManager
+) : Invalidator("SensitiveContentInvalidator"),
+ SensitiveContentCoordinator,
+ DynamicPrivacyController.Listener,
+ OnBeforeRenderListListener {
+
+ override fun attach(pipeline: NotifPipeline) {
+ dynamicPrivacyController.addListener(this)
+ pipeline.addOnBeforeRenderListListener(this)
+ pipeline.addPreRenderInvalidator(this)
+ }
+
+ override fun onDynamicPrivacyChanged(): Unit = invalidateList()
+
+ override fun onBeforeRenderList(entries: List<ListEntry>) {
+ val currentUserId = lockscreenUserManager.currentUserId
+ val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
+ val deviceSensitive = devicePublic &&
+ !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)
+ val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked
+ for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) {
+ val notifUserId = entry.sbn.user.identifier
+ val userLockscreen = devicePublic ||
+ lockscreenUserManager.isLockscreenPublicMode(notifUserId)
+ val userPublic = when {
+ // if we're not on the lockscreen, we're definitely private
+ !userLockscreen -> false
+ // we are on the lockscreen, so unless we're dynamically unlocked, we're
+ // definitely public
+ !dynamicallyUnlocked -> true
+ // we're dynamically unlocked, but check if the notification needs
+ // a separate challenge if it's from a work profile
+ else -> when (notifUserId) {
+ currentUserId -> false
+ UserHandle.USER_ALL -> false
+ else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId)
+ }
+ }
+ val needsRedaction = lockscreenUserManager.needsRedaction(entry)
+ val isSensitive = userPublic && needsRedaction
+ entry.setSensitive(isSensitive, deviceSensitive)
+ }
+ }
+}
+
+private fun extractAllRepresentativeEntries(
+ entries: List<ListEntry>
+): Sequence<NotificationEntry> =
+ entries.asSequence().flatMap(::extractAllRepresentativeEntries)
+
+private fun extractAllRepresentativeEntries(listEntry: ListEntry): Sequence<NotificationEntry> =
+ sequence {
+ listEntry.representativeEntry?.let { yield(it) }
+ if (listEntry is GroupEntry) {
+ yieldAll(extractAllRepresentativeEntries(listEntry.children))
+ }
+ } \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt
new file mode 100644
index 000000000000..2d5c331e2071
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.systemui.statusbar.notification.collection.coordinator
+
+import android.service.notification.NotificationListenerService
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource
+import javax.inject.Inject
+
+/**
+ * A coordinator which provides callbacks to a view surfaces for various events relevant to the
+ * shade, such as when the user removes a notification, or when the shade is emptied.
+ */
+// TODO(b/204468557): Move to @CoordinatorScope
+@SysUISingleton
+class ShadeEventCoordinator @Inject internal constructor(
+ private val mLogger: ShadeEventCoordinatorLogger
+) : Coordinator, NotifShadeEventSource {
+ private var mNotifRemovedByUserCallback: Runnable? = null
+ private var mShadeEmptiedCallback: Runnable? = null
+ private var mEntryRemoved = false
+ private var mEntryRemovedByUser = false
+
+ override fun attach(pipeline: NotifPipeline) {
+ pipeline.addCollectionListener(mNotifCollectionListener)
+ pipeline.addOnBeforeRenderListListener(this::onBeforeRenderList)
+ }
+
+ private val mNotifCollectionListener = object : NotifCollectionListener {
+ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ mEntryRemoved = true
+ mEntryRemovedByUser =
+ reason == NotificationListenerService.REASON_CLICK ||
+ reason == NotificationListenerService.REASON_CANCEL_ALL ||
+ reason == NotificationListenerService.REASON_CANCEL
+ }
+ }
+
+ override fun setNotifRemovedByUserCallback(callback: Runnable) {
+ check(mNotifRemovedByUserCallback == null) { "mNotifRemovedByUserCallback already set" }
+ mNotifRemovedByUserCallback = callback
+ }
+
+ override fun setShadeEmptiedCallback(callback: Runnable) {
+ check(mShadeEmptiedCallback == null) { "mShadeEmptiedCallback already set" }
+ mShadeEmptiedCallback = callback
+ }
+
+ private fun onBeforeRenderList(entries: List<ListEntry>) {
+ if (mEntryRemoved && entries.isEmpty()) {
+ mLogger.logShadeEmptied()
+ mShadeEmptiedCallback?.run()
+ }
+ if (mEntryRemoved && mEntryRemovedByUser) {
+ mLogger.logNotifRemovedByUser()
+ mNotifRemovedByUserCallback?.run()
+ }
+ mEntryRemoved = false
+ mEntryRemovedByUser = false
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
new file mode 100644
index 000000000000..c687e1bacbc9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotificationLog
+import javax.inject.Inject
+
+private const val TAG = "ShadeEventCoordinator"
+
+/** Logger for the [ShadeEventCoordinator] */
+class ShadeEventCoordinatorLogger @Inject constructor(
+ @NotificationLog private val buffer: LogBuffer
+) {
+
+ fun logShadeEmptied() {
+ buffer.log(TAG, LogLevel.DEBUG, { }, { "Shade emptied" })
+ }
+
+ fun logNotifRemovedByUser() {
+ buffer.log(TAG, LogLevel.DEBUG, { }, { "Notification removed by user" })
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
index 442d9d2bb205..519d75ff07d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.smartspace.SmartspaceTarget
import android.os.Parcelable
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -28,6 +27,7 @@ import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import com.android.systemui.statusbar.notification.NotificationEntryManager
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -45,7 +45,7 @@ import javax.inject.Inject
*/
// This class is a singleton so that the same instance can be accessed by both the old and new
// pipelines
-@SysUISingleton
+@CoordinatorScope
class SmartspaceDedupingCoordinator @Inject constructor(
private val statusBarStateController: SysuiStatusBarStateController,
private val smartspaceController: LockscreenSmartspaceController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
new file mode 100644
index 000000000000..5b86de2a9d87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.systemui.statusbar.notification.collection.coordinator
+
+import com.android.internal.widget.MessagingGroup
+import com.android.internal.widget.MessagingMessage
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
+import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager
+import com.android.systemui.statusbar.policy.ConfigurationController
+import javax.inject.Inject
+
+/**
+ * A coordinator which ensures that notifications within the new pipeline are correctly inflated
+ * for the current uiMode and screen properties; additionally deferring those changes when a user
+ * change is in progress until that process has completed.
+ */
+@CoordinatorScope
+class ViewConfigCoordinator @Inject internal constructor(
+ configurationController: ConfigurationController,
+ lockscreenUserManager: NotificationLockscreenUserManagerImpl,
+ featureFlags: FeatureFlags,
+ private val mGutsManager: NotificationGutsManager,
+ private val mKeyguardUpdateMonitor: KeyguardUpdateMonitor
+) : Coordinator, UserChangedListener, ConfigurationController.ConfigurationListener {
+
+ private var mReinflateNotificationsOnUserSwitched = false
+ private var mDispatchUiModeChangeOnUserSwitched = false
+ private var mPipeline: NotifPipeline? = null
+
+ init {
+ if (featureFlags.isNewNotifPipelineRenderingEnabled) {
+ lockscreenUserManager.addUserChangedListener(this)
+ configurationController.addCallback(this)
+ }
+ }
+
+ override fun attach(pipeline: NotifPipeline) {
+ mPipeline = pipeline
+ }
+
+ override fun onDensityOrFontScaleChanged() {
+ MessagingMessage.dropCache()
+ MessagingGroup.dropCache()
+ if (!mKeyguardUpdateMonitor.isSwitchingUser) {
+ updateNotificationsOnDensityOrFontScaleChanged()
+ } else {
+ mReinflateNotificationsOnUserSwitched = true
+ }
+ }
+
+ override fun onUiModeChanged() {
+ if (!mKeyguardUpdateMonitor.isSwitchingUser) {
+ updateNotificationsOnUiModeChanged()
+ } else {
+ mDispatchUiModeChangeOnUserSwitched = true
+ }
+ }
+
+ override fun onThemeChanged() {
+ onDensityOrFontScaleChanged()
+ }
+
+ override fun onUserChanged(userId: Int) {
+ if (mReinflateNotificationsOnUserSwitched) {
+ updateNotificationsOnDensityOrFontScaleChanged()
+ mReinflateNotificationsOnUserSwitched = false
+ }
+ if (mDispatchUiModeChangeOnUserSwitched) {
+ updateNotificationsOnUiModeChanged()
+ mDispatchUiModeChangeOnUserSwitched = false
+ }
+ }
+
+ private fun updateNotificationsOnUiModeChanged() {
+ mPipeline?.allNotifs?.forEach { entry ->
+ val row = entry.row
+ row?.onUiModeChanged()
+ }
+ }
+
+ private fun updateNotificationsOnDensityOrFontScaleChanged() {
+ mPipeline?.allNotifs?.forEach { entry ->
+ entry.onDensityOrFontScaleChanged()
+ val exposedGuts = entry.areGutsExposed()
+ if (exposedGuts) {
+ mGutsManager.onDensityOrFontScaleChanged(entry)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 5d6c0437ce9b..5ba4c2ff36ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -50,6 +50,7 @@ import javax.inject.Inject;
* This is now integrated in the data-layer via
* {@link com.android.systemui.statusbar.notification.collection.ShadeListBuilder}.
*/
+// TODO(b/204468557): Move to @CoordinatorScope
@SysUISingleton
public class VisualStabilityCoordinator implements Coordinator {
private final DelayableExecutor mDelayableExecutor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
new file mode 100644
index 000000000000..a26d50d2a059
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.systemui.statusbar.notification.collection.coordinator.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators
+import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinatorsImpl
+import com.android.systemui.statusbar.notification.collection.coordinator.SensitiveContentCoordinatorModule
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.Subcomponent
+import javax.inject.Qualifier
+import javax.inject.Scope
+
+@Module(subcomponents = [CoordinatorsSubcomponent::class])
+object CoordinatorsModule {
+ @SysUISingleton
+ @JvmStatic
+ @Provides
+ fun notifCoordinators(factory: CoordinatorsSubcomponent.Factory): NotifCoordinators =
+ factory.create().notifCoordinators
+}
+
+@CoordinatorScope
+@Subcomponent(modules = [InternalCoordinatorsModule::class])
+interface CoordinatorsSubcomponent {
+ @get:Internal val notifCoordinators: NotifCoordinators
+
+ @Subcomponent.Factory
+ interface Factory {
+ fun create(): CoordinatorsSubcomponent
+ }
+}
+
+@Module(includes = [SensitiveContentCoordinatorModule::class])
+private abstract class InternalCoordinatorsModule {
+ @Binds
+ @Internal
+ abstract fun bindNotifCoordinators(impl: NotifCoordinatorsImpl): NotifCoordinators
+}
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+private annotation class Internal
+
+@Scope
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class CoordinatorScope \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java
index aec26474cf7d..518c3f1d1948 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.notification.collection.inflation;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index db49e4476a99..168e08603f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -21,7 +21,7 @@ import android.util.Log;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java
new file mode 100644
index 000000000000..4ee08ed4899f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.legacy;
+
+import static com.android.systemui.statusbar.phone.StatusBar.SPEW;
+
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
+
+import org.jetbrains.annotations.NotNull;
+
+import javax.inject.Inject;
+
+/**
+ * This is some logic extracted from the
+ * {@link com.android.systemui.statusbar.phone.StatusBarNotificationPresenter}
+ * into a class that implements a new-pipeline interface so that the new pipeline can implement it
+ * correctly.
+ *
+ * Specifically, this is the logic which updates notifications when uiMode and screen properties
+ * change, and which closes the shade when the last notification disappears.
+ */
+public class LegacyNotificationPresenterExtensions implements NotifShadeEventSource {
+ private static final String TAG = "LegacyNotifPresenter";
+ private final NotificationEntryManager mEntryManager;
+ private boolean mEntryListenerAdded;
+ private Runnable mShadeEmptiedCallback;
+ private Runnable mNotifRemovedByUserCallback;
+
+ @Inject
+ public LegacyNotificationPresenterExtensions(NotificationEntryManager entryManager) {
+ mEntryManager = entryManager;
+ }
+
+ private void ensureEntryListenerAdded() {
+ if (mEntryListenerAdded) return;
+ mEntryListenerAdded = true;
+ mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+ @Override
+ public void onEntryRemoved(
+ @NotNull NotificationEntry entry,
+ NotificationVisibility visibility,
+ boolean removedByUser,
+ int reason) {
+ StatusBarNotification old = entry.getSbn();
+ if (SPEW) {
+ Log.d(TAG, "removeNotification key=" + entry.getKey()
+ + " old=" + old + " reason=" + reason);
+ }
+
+ if (old != null && !mEntryManager.hasActiveNotifications()) {
+ if (mShadeEmptiedCallback != null) mShadeEmptiedCallback.run();
+ }
+ if (removedByUser) {
+ if (mNotifRemovedByUserCallback != null) mNotifRemovedByUserCallback.run();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setNotifRemovedByUserCallback(@NonNull Runnable callback) {
+ if (mNotifRemovedByUserCallback != null) {
+ throw new IllegalStateException("mNotifRemovedByUserCallback already set");
+ }
+ mNotifRemovedByUserCallback = callback;
+ ensureEntryListenerAdded();
+ }
+
+ @Override
+ public void setShadeEmptiedCallback(@NonNull Runnable callback) {
+ if (mShadeEmptiedCallback != null) {
+ throw new IllegalStateException("mShadeEmptiedCallback already set");
+ }
+ mShadeEmptiedCallback = callback;
+ ensureEntryListenerAdded();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
index f40f24a935c4..5993f1dee3a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
@@ -25,6 +25,7 @@ import android.util.Log;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.StatusBarState;
@@ -60,8 +61,12 @@ import dagger.Lazy;
* 2. Tracking group expansion states
*/
@SysUISingleton
-public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, StateListener,
- GroupMembershipManager, GroupExpansionManager, Dumpable {
+public class NotificationGroupManagerLegacy implements
+ OnHeadsUpChangedListener,
+ StateListener,
+ GroupMembershipManager,
+ GroupExpansionManager,
+ Dumpable {
private static final String TAG = "NotifGroupManager";
private static final boolean DEBUG = StatusBar.DEBUG;
@@ -87,10 +92,13 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
public NotificationGroupManagerLegacy(
StatusBarStateController statusBarStateController,
Lazy<PeopleNotificationIdentifier> peopleNotificationIdentifier,
- Optional<Bubbles> bubblesOptional) {
+ Optional<Bubbles> bubblesOptional,
+ DumpManager dumpManager) {
statusBarStateController.addCallback(this);
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
mBubblesOptional = bubblesOptional;
+
+ dumpManager.registerDumpable(this);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java
index 165df30b457d..6e47c7bdf927 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/VisualStabilityManager.java
@@ -24,6 +24,7 @@ import androidx.collection.ArraySet;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -71,9 +72,11 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener, Dumpabl
NotificationEntryManager notificationEntryManager,
@Main Handler handler,
StatusBarStateController statusBarStateController,
- WakefulnessLifecycle wakefulnessLifecycle) {
+ WakefulnessLifecycle wakefulnessLifecycle,
+ DumpManager dumpManager) {
mHandler = handler;
+ dumpManager.registerDumpable(this);
if (notificationEntryManager != null) {
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
index c9fc9929f0d3..6424e37ad328 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
@@ -18,14 +18,17 @@ package com.android.systemui.statusbar.notification.collection.listbuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.render.NodeController
+import com.android.systemui.statusbar.notification.stack.PriorityBucket
data class NotifSection(
val sectioner: NotifSectioner,
val index: Int
) {
val label: String
- get() = "Section($index, \"${sectioner.name}\")"
+ get() = "Section($index, $bucket, \"${sectioner.name}\")"
val headerController: NodeController?
get() = sectioner.headerNodeController
+
+ @PriorityBucket val bucket: Int = sectioner.bucket
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
index 798bfe7f39d0..027ac0f66b35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
@@ -82,8 +82,8 @@ public class PipelineState {
public static final int STATE_GROUPING = 4;
public static final int STATE_TRANSFORMING = 5;
public static final int STATE_GROUP_STABILIZING = 6;
- public static final int STATE_SORTING = 7;
- public static final int STATE_FINALIZE_FILTERING = 8;
+ public static final int STATE_FINALIZE_FILTERING = 7;
+ public static final int STATE_SORTING = 8;
public static final int STATE_FINALIZING = 9;
@IntDef(prefix = { "STATE_" }, value = {
@@ -94,8 +94,8 @@ public class PipelineState {
STATE_GROUPING,
STATE_TRANSFORMING,
STATE_GROUP_STABILIZING,
- STATE_SORTING,
STATE_FINALIZE_FILTERING,
+ STATE_SORTING,
STATE_FINALIZING,
})
@Retention(RetentionPolicy.SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 5a35127397b4..8fff90504798 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -47,6 +47,15 @@ class ShadeListBuilderLogger @Inject constructor(
})
}
+ fun logPreRenderInvalidated(filterName: String, pipelineState: Int) {
+ buffer.log(TAG, DEBUG, {
+ str1 = filterName
+ int1 = pipelineState
+ }, {
+ """Pre-render Invalidator "$str1" invalidated; pipeline state is $int1"""
+ })
+ }
+
fun logPreGroupFilterInvalidated(filterName: String, pipelineState: Int) {
buffer.log(TAG, DEBUG, {
str1 = filterName
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Invalidator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Invalidator.java
new file mode 100644
index 000000000000..d7092ecd536e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Invalidator.java
@@ -0,0 +1,24 @@
+/*
+ * 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.systemui.statusbar.notification.collection.listbuilder.pluggable;
+
+/** A {@link Pluggable} that can only invalidate. */
+public abstract class Invalidator extends Pluggable<Invalidator> {
+ protected Invalidator(String name) {
+ super(name);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java
index c8982d35c4a0..ef9ee11ef116 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSectioner.java
@@ -22,13 +22,28 @@ import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
import com.android.systemui.statusbar.notification.collection.render.NodeSpec;
+import com.android.systemui.statusbar.notification.stack.PriorityBucket;
+
+import java.util.List;
/**
- * Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSections}.
+ * Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSectioners}.
*/
public abstract class NotifSectioner extends Pluggable<NotifSectioner> {
- protected NotifSectioner(String name) {
+ @PriorityBucket
+ private final int mBucket;
+
+ protected NotifSectioner(String name, @PriorityBucket int bucket) {
super(name);
+ mBucket = bucket;
+ }
+
+ /**
+ * @return the "bucket" value to apply to entries in this section
+ */
+ @PriorityBucket
+ public final int getBucket() {
+ return mBucket;
}
/**
@@ -46,4 +61,10 @@ public abstract class NotifSectioner extends Pluggable<NotifSectioner> {
public @Nullable NodeController getHeaderNodeController() {
return null;
}
+
+ /**
+ * Notify of children of this section being updated
+ * @param entries of this section that are borrowed (must clone to store)
+ */
+ public void onEntriesUpdated(List<ListEntry> entries) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
index 8e4fb7523767..b981a9621526 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
import android.annotation.Nullable;
+import android.os.Trace;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -50,7 +51,9 @@ public abstract class Pluggable<This> {
*/
public final void invalidateList() {
if (mListener != null) {
+ Trace.beginSection("Pluggable<" + mName + ">.invalidateList");
mListener.onPluggableInvalidated((This) this);
+ Trace.endSection();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/InternalNotifUpdater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/InternalNotifUpdater.java
new file mode 100644
index 000000000000..5692fb2b523e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/InternalNotifUpdater.java
@@ -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 com.android.systemui.statusbar.notification.collection.notifcollection;
+
+import android.service.notification.StatusBarNotification;
+
+/**
+ * An object that allows Coordinators to update notifications internally to SystemUI.
+ * This is used when part of the UI involves updating the underlying appearance of a notification
+ * on behalf of an app, such as to add a spinner or remote input history.
+ */
+public interface InternalNotifUpdater {
+ /**
+ * Called when an already-existing notification needs to be updated to a new temporary
+ * appearance.
+ * This update is local to the SystemUI process.
+ * This has no effect if no notification with the given key exists in the pipeline.
+ *
+ * @param sbn a notification to update
+ * @param reason a debug reason for the update
+ */
+ void onInternalNotificationUpdate(StatusBarNotification sbn, String reason);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
index db0c1745f565..68a346f817e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
@@ -56,6 +56,17 @@ public interface NotifCollectionListener {
/**
* Called whenever a notification with the same key as an existing notification is posted. By
* the time this listener is called, the entry's SBN and Ranking will already have been updated.
+ * This delegates to {@link #onEntryUpdated(NotificationEntry)} by default.
+ * @param fromSystem If true, this update came from the NotificationManagerService.
+ * If false, the notification update is an internal change within systemui.
+ */
+ default void onEntryUpdated(@NonNull NotificationEntry entry, boolean fromSystem) {
+ onEntryUpdated(entry);
+ }
+
+ /**
+ * Called whenever a notification with the same key as an existing notification is posted. By
+ * the time this listener is called, the entry's SBN and Ranking will already have been updated.
*/
default void onEntryUpdated(@NonNull NotificationEntry entry) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index f8a778d6b1d2..1ebc66e4c665 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -121,6 +121,26 @@ class NotifCollectionLogger @Inject constructor(
})
}
+ fun logNotifInternalUpdate(key: String, name: String, reason: String) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ str2 = name
+ str3 = reason
+ }, {
+ "UPDATED INTERNALLY $str1 BY $str2 BECAUSE $str3"
+ })
+ }
+
+ fun logNotifInternalUpdateFailed(key: String, name: String, reason: String) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ str2 = name
+ str3 = reason
+ }, {
+ "FAILED INTERNAL UPDATE $str1 BY $str2 BECAUSE $str3"
+ })
+ }
+
fun logNoNotificationToRemoveWithKey(key: String) {
buffer.log(TAG, ERROR, {
str1 = key
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
index 2810b891373f..179e95328442 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
@@ -64,10 +64,11 @@ data class EntryAddedEvent(
}
data class EntryUpdatedEvent(
- val entry: NotificationEntry
+ val entry: NotificationEntry,
+ val fromSystem: Boolean
) : NotifEvent() {
override fun dispatchToListener(listener: NotifCollectionListener) {
- listener.onEntryUpdated(entry)
+ listener.onEntryUpdated(entry, fromSystem)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java
index f8fe0676e003..2fe3bd63c2e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection.notifcollection;
+import androidx.annotation.NonNull;
+
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -26,14 +28,14 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
*/
public interface NotifLifetimeExtender {
/** Name to associate with this extender (for the purposes of debugging) */
- String getName();
+ @NonNull String getName();
/**
* Called on the extender immediately after it has been registered. The extender should hang on
* to this callback and execute it whenever it no longer needs to extend the lifetime of a
* notification.
*/
- void setCallback(OnEndLifetimeExtensionCallback callback);
+ void setCallback(@NonNull OnEndLifetimeExtensionCallback callback);
/**
* Called by the NotifCollection whenever a notification has been retracted (by the app) or
@@ -43,7 +45,7 @@ public interface NotifLifetimeExtender {
* called on all lifetime extenders even if earlier ones return true (in other words, multiple
* lifetime extenders can be extending a notification at the same time).
*/
- boolean shouldExtendLifetime(NotificationEntry entry, @CancellationReason int reason);
+ boolean shouldExtendLifetime(@NonNull NotificationEntry entry, @CancellationReason int reason);
/**
* Called by the NotifCollection to inform a lifetime extender that its extension of a notif
@@ -51,7 +53,7 @@ public interface NotifLifetimeExtender {
* lifetime extension). The extender should clean up any references it has to the notif in
* question.
*/
- void cancelLifetimeExtension(NotificationEntry entry);
+ void cancelLifetimeExtension(@NonNull NotificationEntry entry);
/** Callback for notifying the NotifCollection that a lifetime extension has expired.*/
interface OnEndLifetimeExtensionCallback {
@@ -59,6 +61,8 @@ public interface NotifLifetimeExtender {
* Stop extending the lifetime of `entry` with `extender` and then immediately re-evaluates
* whether to continue lifetime extending this notification or to remove it.
*/
- void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry);
+ void onEndLifetimeExtension(
+ @NonNull NotifLifetimeExtender extender,
+ @NonNull NotificationEntry entry);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt
new file mode 100644
index 000000000000..145c1e54d732
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt
@@ -0,0 +1,113 @@
+package com.android.systemui.statusbar.notification.collection.notifcollection
+
+import android.os.Handler
+import android.util.ArrayMap
+import android.util.Log
+import com.android.systemui.Dumpable
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import java.io.FileDescriptor
+import java.io.PrintWriter
+
+/**
+ * A helpful class that implements the core contract of the lifetime extender internally,
+ * making it easier for coordinators to interact with them
+ */
+abstract class SelfTrackingLifetimeExtender(
+ private val tag: String,
+ private val name: String,
+ private val debug: Boolean,
+ private val mainHandler: Handler
+) : NotifLifetimeExtender, Dumpable {
+ private lateinit var mCallback: NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+ protected val mEntriesExtended = ArrayMap<String, NotificationEntry>()
+ private var mEnding = false
+
+ /**
+ * When debugging, warn if the call is happening during and "end lifetime extension" call.
+ *
+ * Note: this will warn a lot! The pipeline explicitly re-invokes all lifetime extenders
+ * whenever one ends, giving all of them a chance to re-up their lifetime extension.
+ */
+ private fun warnIfEnding() {
+ if (debug && mEnding) Log.w(tag, "reentrant code while ending a lifetime extension")
+ }
+
+ fun endAllLifetimeExtensions() {
+ // clear the map before iterating over a copy of the items, because the pipeline will
+ // always give us another chance to extend the lifetime again, and we don't want
+ // concurrent modification
+ val entries = mEntriesExtended.values.toList()
+ if (debug) Log.d(tag, "$name.endAllLifetimeExtensions() entries=$entries")
+ mEntriesExtended.clear()
+ warnIfEnding()
+ mEnding = true
+ entries.forEach { mCallback.onEndLifetimeExtension(this, it) }
+ mEnding = false
+ }
+
+ fun endLifetimeExtensionAfterDelay(key: String, delayMillis: Long) {
+ if (debug) {
+ Log.d(tag, "$name.endLifetimeExtensionAfterDelay" +
+ "(key=$key, delayMillis=$delayMillis)" +
+ " isExtending=${isExtending(key)}")
+ }
+ if (isExtending(key)) {
+ mainHandler.postDelayed({ endLifetimeExtension(key) }, delayMillis)
+ }
+ }
+
+ fun endLifetimeExtension(key: String) {
+ if (debug) {
+ Log.d(tag, "$name.endLifetimeExtension(key=$key)" +
+ " isExtending=${isExtending(key)}")
+ }
+ warnIfEnding()
+ mEnding = true
+ mEntriesExtended.remove(key)?.let { removedEntry ->
+ mCallback.onEndLifetimeExtension(this, removedEntry)
+ }
+ mEnding = false
+ }
+
+ fun isExtending(key: String) = mEntriesExtended.contains(key)
+
+ final override fun getName(): String = name
+
+ final override fun shouldExtendLifetime(entry: NotificationEntry, reason: Int): Boolean {
+ val shouldExtend = queryShouldExtendLifetime(entry)
+ if (debug) {
+ Log.d(tag, "$name.shouldExtendLifetime(key=${entry.key}, reason=$reason)" +
+ " isExtending=${isExtending(entry.key)}" +
+ " shouldExtend=$shouldExtend")
+ }
+ warnIfEnding()
+ if (shouldExtend && mEntriesExtended.put(entry.key, entry) == null) {
+ onStartedLifetimeExtension(entry)
+ }
+ return shouldExtend
+ }
+
+ final override fun cancelLifetimeExtension(entry: NotificationEntry) {
+ if (debug) {
+ Log.d(tag, "$name.cancelLifetimeExtension(key=${entry.key})" +
+ " isExtending=${isExtending(entry.key)}")
+ }
+ warnIfEnding()
+ mEntriesExtended.remove(entry.key)
+ onCanceledLifetimeExtension(entry)
+ }
+
+ abstract fun queryShouldExtendLifetime(entry: NotificationEntry): Boolean
+ open fun onStartedLifetimeExtension(entry: NotificationEntry) {}
+ open fun onCanceledLifetimeExtension(entry: NotificationEntry) {}
+
+ final override fun setCallback(callback: NotifLifetimeExtender.OnEndLifetimeExtensionCallback) {
+ mCallback = callback
+ }
+
+ final override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+ pw.println("LifetimeExtender: $name:")
+ pw.println(" mEntriesExtended: ${mEntriesExtended.size}")
+ mEntriesExtended.forEach { pw.println(" * ${it.key}") }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
index 727ce20cd72c..289dacbca69e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
@@ -78,7 +78,7 @@ fun treeSpecToStr(tree: NodeSpec): String {
}
private fun treeSpecToStrHelper(tree: NodeSpec, sb: StringBuilder, indent: String) {
- sb.append("${indent}ns{${tree.controller.nodeLabel}")
+ sb.append("${indent}{${tree.controller.nodeLabel}}\n")
if (tree.children.isNotEmpty()) {
val childIndent = "$indent "
for (child in tree.children) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
new file mode 100644
index 000000000000..010b6f80121c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.systemui.statusbar.notification.collection.render
+
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+import com.android.systemui.util.traceSection
+
+/**
+ * Converts a notif list (the output of the ShadeListBuilder) into a NodeSpec, an abstract
+ * representation of which views should be present in the shade. This spec will later be consumed
+ * by the ViewDiffer, which will add and remove views until the shade matches the spec. Up until
+ * this point, the pipeline has dealt with pure data representations of notifications (in the
+ * form of NotificationEntries). In this step, NotificationEntries finally become associated with
+ * the views that will represent them. In addition, we add in any non-notification views that also
+ * need to present in the shade, notably the section headers.
+ */
+class NodeSpecBuilder(
+ private val viewBarn: NotifViewBarn
+) {
+ fun buildNodeSpec(
+ rootController: NodeController,
+ notifList: List<ListEntry>
+ ): NodeSpec = traceSection("NodeSpecBuilder.buildNodeSpec") {
+ val root = NodeSpecImpl(null, rootController)
+ var currentSection: NotifSection? = null
+ val prevSections = mutableSetOf<NotifSection?>()
+
+ for (entry in notifList) {
+ val section = entry.section!!
+
+ if (prevSections.contains(section)) {
+ throw java.lang.RuntimeException("Section ${section.label} has been duplicated")
+ }
+
+ // If this notif begins a new section, first add the section's header view
+ if (section != currentSection) {
+ section.headerController?.let { headerController ->
+ root.children.add(NodeSpecImpl(root, headerController))
+ }
+ prevSections.add(currentSection)
+ currentSection = section
+ }
+
+ // Finally, add the actual notif node!
+ root.children.add(buildNotifNode(root, entry))
+ }
+
+ return root
+ }
+
+ private fun buildNotifNode(parent: NodeSpec, entry: ListEntry): NodeSpec = when (entry) {
+ is NotificationEntry -> NodeSpecImpl(parent, viewBarn.requireView(entry))
+ is GroupEntry -> NodeSpecImpl(parent, viewBarn.requireView(checkNotNull(entry.summary)))
+ .apply { entry.children.forEach { children.add(buildNotifNode(this, it)) } }
+ else -> throw RuntimeException("Unexpected entry: $entry")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt
new file mode 100644
index 000000000000..129f6b1750e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.systemui.statusbar.notification.collection.render
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.NotificationGuts
+
+/**
+ * Interface for listening to guts open and close events.
+ */
+interface NotifGutsViewListener {
+ /** A notification's guts are being opened */
+ fun onGutsOpen(entry: NotificationEntry, guts: NotificationGuts)
+
+ /** A notification's guts are being closed */
+ fun onGutsClose(entry: NotificationEntry)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt
new file mode 100644
index 000000000000..0260f89110f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.systemui.statusbar.notification.collection.render
+
+/** A type which provides open and close guts events to a single listener */
+interface NotifGutsViewManager {
+ /**
+ * @param listener the object that will listen to open and close guts events
+ */
+ fun setGutsListener(listener: NotifGutsViewListener?)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt
new file mode 100644
index 000000000000..e24f6a036095
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.systemui.statusbar.notification.collection.render
+
+/**
+ * This is an object which provides callbacks for certain important events related to the
+ * notification shade, such as notifications being removed by the user, or the shade becoming empty.
+ */
+interface NotifShadeEventSource {
+ /**
+ * Registers a callback to be invoked when the last notification has been removed from
+ * the shade for any reason
+ */
+ fun setShadeEmptiedCallback(callback: Runnable)
+
+ /**
+ * Registers a callback to be invoked when a notification has been removed from
+ * the shade by a user action
+ */
+ fun setNotifRemovedByUserCallback(callback: Runnable)
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
index 79bc3d757ebd..c79f59b5c625 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt
@@ -19,18 +19,16 @@ package com.android.systemui.statusbar.notification.collection.render
import android.view.textclassifier.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController
import javax.inject.Inject
/**
- * The ViewBarn is just a map from [ListEntry] to an instance of an
- * [ExpandableNotificationRowController].
+ * The ViewBarn is just a map from [ListEntry] to an instance of a [NodeController].
*/
@SysUISingleton
class NotifViewBarn @Inject constructor() {
- private val rowMap = mutableMapOf<String, ExpandableNotificationRowController>()
+ private val rowMap = mutableMapOf<String, NodeController>()
- fun requireView(forEntry: ListEntry): ExpandableNotificationRowController {
+ fun requireView(forEntry: ListEntry): NodeController {
if (DEBUG) {
Log.d(TAG, "requireView: $forEntry.key")
}
@@ -42,7 +40,7 @@ class NotifViewBarn @Inject constructor() {
return li
}
- fun registerViewForEntry(entry: ListEntry, controller: ExpandableNotificationRowController) {
+ fun registerViewForEntry(entry: ListEntry, controller: NodeController) {
if (DEBUG) {
Log.d(TAG, "registerViewForEntry: $entry.key")
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 1311e3e756dc..8c15647c5038 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -33,7 +33,8 @@ import javax.inject.Inject
interface SectionHeaderController {
fun reinflateView(parent: ViewGroup)
val headerView: SectionHeaderView?
- fun setOnClearAllClickListener(listener: View.OnClickListener)
+ fun setClearSectionEnabled(enabled: Boolean)
+ fun setOnClearSectionClickListener(listener: View.OnClickListener)
}
@SectionHeaderScope
@@ -46,6 +47,7 @@ internal class SectionHeaderNodeControllerImpl @Inject constructor(
) : NodeController, SectionHeaderController {
private var _view: SectionHeaderView? = null
+ private var clearAllButtonEnabled = false
private var clearAllClickListener: View.OnClickListener? = null
private val onHeaderClickListener = View.OnClickListener {
activityStarter.startActivity(
@@ -76,12 +78,18 @@ internal class SectionHeaderNodeControllerImpl @Inject constructor(
parent.addView(inflated, oldPos)
}
_view = inflated
+ _view?.setClearSectionButtonEnabled(clearAllButtonEnabled)
}
override val headerView: SectionHeaderView?
get() = _view
- override fun setOnClearAllClickListener(listener: View.OnClickListener) {
+ override fun setClearSectionEnabled(enabled: Boolean) {
+ clearAllButtonEnabled = enabled
+ _view?.setClearSectionButtonEnabled(enabled)
+ }
+
+ override fun setOnClearSectionClickListener(listener: View.OnClickListener) {
clearAllClickListener = listener
_view?.setOnClearAllClickListener(listener)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 7babbb40b6c1..6d4ae4b1a869 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.render
import android.annotation.MainThread
import android.view.View
import com.android.systemui.util.kotlin.transform
+import com.android.systemui.util.traceSection
/**
* Given a "spec" that describes a "tree" of views, adds and removes views from the
@@ -47,7 +48,7 @@ class ShadeViewDiffer(
* provided [spec]. The root node of the spec must match the root controller passed to the
* differ's constructor.
*/
- fun applySpec(spec: NodeSpec) {
+ fun applySpec(spec: NodeSpec) = traceSection("ShadeViewDiffer.applySpec") {
val specMap = treeToMap(spec)
if (spec.controller != rootNode.controller) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index c1a63e969d8e..b582a24f5a3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -20,10 +20,11 @@ import android.content.Context
import android.view.View
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.NotificationIconAreaController
+import com.android.systemui.util.traceSection
import javax.inject.Inject
/**
@@ -40,36 +41,28 @@ class ShadeViewManager constructor(
// We pass a shim view here because the listContainer may not actually have a view associated
// with it and the differ never actually cares about the root node's view.
private val rootController = RootNodeController(listContainer, View(context))
+ private val specBuilder = NodeSpecBuilder(viewBarn)
private val viewDiffer = ShadeViewDiffer(rootController, logger)
fun attach(listBuilder: ShadeListBuilder) =
listBuilder.setOnRenderListListener(::onNewNotifTree)
- private fun onNewNotifTree(tree: List<ListEntry>) = viewDiffer.applySpec(buildTree(tree))
-
- private fun buildTree(notifList: List<ListEntry>): NodeSpec {
- val root = NodeSpecImpl(null, rootController).apply {
- // Insert first section header, if present
- notifList.firstOrNull()?.section?.headerController?.let {
- children.add(NodeSpecImpl(this, it))
- }
- notifList.asSequence().zipWithNext().forEach { (prev, entry) ->
- // Insert new header if the section has changed between two entries
- entry.section.takeIf { it != prev.section }?.headerController?.let {
- children.add(NodeSpecImpl(this, it))
- }
- children.add(buildNotifNode(entry, this))
- }
+ private fun onNewNotifTree(notifList: List<ListEntry>) {
+ traceSection("ShadeViewManager.onNewNotifTree") {
+ viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
+ updateGroupCounts(notifList)
+ notificationIconAreaController.updateNotificationIcons(notifList)
}
- notificationIconAreaController.updateNotificationIcons(notifList)
- return root
}
- private fun buildNotifNode(entry: ListEntry, parent: NodeSpec): NodeSpec = when (entry) {
- is NotificationEntry -> NodeSpecImpl(parent, viewBarn.requireView(entry))
- is GroupEntry -> NodeSpecImpl(parent, viewBarn.requireView(checkNotNull(entry.summary)))
- .apply { entry.children.forEach { children.add(buildNotifNode(it, this)) } }
- else -> throw RuntimeException("Unexpected entry: $entry")
+ private fun updateGroupCounts(notifList: List<ListEntry>) {
+ traceSection("ShadeViewManager.updateGroupCounts") {
+ notifList.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry ->
+ val controller = viewBarn.requireView(checkNotNull(groupEntry.summary))
+ val row = controller.view as ExpandableNotificationRow
+ row.setUntruncatedChildCount(groupEntry.untruncatedChildCount)
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 6964838e7e41..1eb007e345ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -30,11 +30,12 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserContextProvider;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
@@ -44,10 +45,13 @@ import com.android.systemui.statusbar.notification.NotificationEntryManagerLogge
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.coordinator.ShadeEventCoordinator;
import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorsModule;
import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
+import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationPresenterExtensions;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.OnUserInteractionCallbackImplLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
@@ -57,6 +61,8 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl;
+import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
@@ -88,7 +94,10 @@ import dagger.Provides;
/**
* Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
*/
-@Module(includes = { NotificationSectionHeadersModule.class })
+@Module(includes = {
+ NotificationSectionHeadersModule.class,
+ CoordinatorsModule.class
+})
public interface NotificationsModule {
@Binds
StackScrollAlgorithm.SectionProvider bindSectionProvider(
@@ -109,7 +118,8 @@ public interface NotificationsModule {
Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
LeakDetector leakDetector,
ForegroundServiceDismissalFeatureController fgsFeatureController,
- IStatusBarService statusBarService) {
+ IStatusBarService statusBarService,
+ DumpManager dumpManager) {
return new NotificationEntryManager(
logger,
groupManager,
@@ -118,7 +128,8 @@ public interface NotificationsModule {
notificationRemoteInputManagerLazy,
leakDetector,
fgsFeatureController,
- statusBarService);
+ statusBarService,
+ dumpManager);
}
/** Provides an instance of {@link NotificationGutsManager} */
@@ -126,7 +137,7 @@ public interface NotificationsModule {
@Provides
static NotificationGutsManager provideNotificationGutsManager(
Context context,
- Lazy<StatusBar> statusBarLazy,
+ Lazy<Optional<StatusBar>> statusBarOptionalLazy,
@Main Handler mainHandler,
@Background Handler bgHandler,
AccessibilityManager accessibilityManager,
@@ -142,10 +153,11 @@ public interface NotificationsModule {
Optional<BubblesManager> bubblesManagerOptional,
UiEventLogger uiEventLogger,
OnUserInteractionCallback onUserInteractionCallback,
- ShadeController shadeController) {
+ ShadeController shadeController,
+ DumpManager dumpManager) {
return new NotificationGutsManager(
context,
- statusBarLazy,
+ statusBarOptionalLazy,
mainHandler,
bgHandler,
accessibilityManager,
@@ -161,23 +173,33 @@ public interface NotificationsModule {
bubblesManagerOptional,
uiEventLogger,
onUserInteractionCallback,
- shadeController);
+ shadeController,
+ dumpManager);
+ }
+
+ /** Provides an instance of {@link NotifGutsViewManager} */
+ @SysUISingleton
+ @Provides
+ static NotifGutsViewManager provideNotifGutsViewManager(
+ NotificationGutsManager notificationGutsManager) {
+ return notificationGutsManager;
}
/** Provides an instance of {@link VisualStabilityManager} */
@SysUISingleton
@Provides
static VisualStabilityManager provideVisualStabilityManager(
- FeatureFlags featureFlags,
NotificationEntryManager notificationEntryManager,
Handler handler,
StatusBarStateController statusBarStateController,
- WakefulnessLifecycle wakefulnessLifecycle) {
+ WakefulnessLifecycle wakefulnessLifecycle,
+ DumpManager dumpManager) {
return new VisualStabilityManager(
notificationEntryManager,
handler,
statusBarStateController,
- wakefulnessLifecycle);
+ wakefulnessLifecycle,
+ dumpManager);
}
/** Provides an instance of {@link NotificationLogger} */
@@ -256,6 +278,20 @@ public interface NotificationsModule {
}
/**
+ * Provide the active implementation for presenting notifications.
+ */
+ @Provides
+ @SysUISingleton
+ static NotifShadeEventSource provideNotifShadeEventSource(
+ FeatureFlags featureFlags,
+ Lazy<ShadeEventCoordinator> shadeEventCoordinatorLazy,
+ Lazy<LegacyNotificationPresenterExtensions> legacyNotificationPresenterExtensionsLazy) {
+ return featureFlags.isNewNotifPipelineRenderingEnabled()
+ ? shadeEventCoordinatorLazy.get()
+ : legacyNotificationPresenterExtensionsLazy.get();
+ }
+
+ /**
* Provide a dismissal callback that's triggered when a user manually dismissed a notification
* from the notification shade or it gets auto-cancelled by click.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 32d90d3333d0..11b0429d2cf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -20,7 +20,7 @@ import android.service.notification.StatusBarNotification
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.people.widget.PeopleSpaceWidgetManager
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
-import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.statusbar.NotificationListener
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.AnimatedImageNotificationManager
@@ -128,8 +128,7 @@ class NotificationsControllerImpl @Inject constructor(
groupManagerLegacy.get().setHeadsUpManager(headsUpManager)
groupAlertTransferHelper.setHeadsUpManager(headsUpManager)
- entryManager.setRanker(legacyRanker)
- entryManager.attach(notificationListener)
+ entryManager.initialize(notificationListener, legacyRanker)
}
peopleSpaceWidgetManager.attach(notificationListener)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
index 4d56e6013d71..b1c69e44da12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
@@ -152,7 +152,7 @@ public class HeadsUpController {
// Also we should not defer the removal if reordering isn't allowed since otherwise
// some notifications can't disappear before the panel is closed.
boolean ignoreEarliestRemovalTime =
- mRemoteInputManager.getController().isSpinning(key)
+ mRemoteInputManager.isSpinning(key)
&& !FORCE_REMOTE_INPUT_HISTORY
|| !mVisualStabilityManager.isReorderingAllowed();
mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
index c147023edf8d..9faef1b43bc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java
@@ -16,12 +16,12 @@
package com.android.systemui.statusbar.notification.logging;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_FOREGROUND_SERVICE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_HEADS_UP;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
import android.annotation.Nullable;
import android.service.notification.StatusBarNotification;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 4f3406c405ad..2eb20654716d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -21,7 +21,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
-import android.graphics.RectF;
+import android.graphics.Point;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.view.MotionEvent;
@@ -110,7 +110,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private Interpolator mCurrentAppearInterpolator;
NotificationBackgroundView mBackgroundNormal;
- private RectF mAppearAnimationRect = new RectF();
private float mAnimationTranslationY;
private boolean mDrawingAppearAnimation;
private ValueAnimator mAppearAnimator;
@@ -122,13 +121,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private long mLastActionUpTime;
private float mNormalBackgroundVisibilityAmount;
- private ValueAnimator.AnimatorUpdateListener mBackgroundVisibilityUpdater
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- setNormalBackgroundVisibilityAmount(mBackgroundNormal.getAlpha());
- }
- };
private FakeShadowView mFakeShadow;
private int mCurrentBackgroundTint;
private int mTargetTint;
@@ -137,9 +129,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private float mOverrideAmount;
private boolean mShadowHidden;
private boolean mIsHeadsUpAnimation;
- private int mHeadsUpAddStartLocation;
- private float mHeadsUpLocation;
- private boolean mIsAppearing;
+ /* In order to track headsup longpress coorindate. */
+ protected Point mTargetPoint;
private boolean mDismissed;
private boolean mRefocusOnDismiss;
private AccessibilityManager mAccessibilityManager;
@@ -151,7 +142,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
setClipChildren(false);
setClipToPadding(false);
updateColors();
- initDimens();
}
private void updateColors() {
@@ -163,17 +153,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
R.color.notification_ripple_untinted_color);
}
- private void initDimens() {
- mHeadsUpAddStartLocation = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_margin_start);
- }
-
- @Override
- public void onDensityOrFontScaleChanged() {
- super.onDensityOrFontScaleChanged();
- initDimens();
- }
-
/**
* Reload background colors from resources and invalidate views.
*/
@@ -435,7 +414,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) {
enableAppearDrawing(true);
mIsHeadsUpAnimation = isHeadsUpAnimation;
- mHeadsUpLocation = endLocation;
if (mDrawingAppearAnimation) {
startAppearAnimation(false /* isAppearing */, translationDirection,
delay, duration, onFinishedRunnable, animationListener);
@@ -449,7 +427,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
enableAppearDrawing(true);
mIsHeadsUpAnimation = isHeadsUpAppear;
- mHeadsUpLocation = mHeadsUpAddStartLocation;
if (mDrawingAppearAnimation) {
startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay,
duration, null, null);
@@ -471,7 +448,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mAppearAnimationTranslation = 0;
}
}
- mIsAppearing = isAppearing;
float targetValue;
if (isAppearing) {
@@ -521,8 +497,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
@Override
public void onAnimationStart(Animator animation) {
mWasCancelled = false;
- Configuration.Builder builder = new Configuration.Builder(getCujType(isAppearing))
- .setView(ActivatableNotificationView.this);
+ Configuration.Builder builder = Configuration.Builder
+ .withView(getCujType(isAppearing), ActivatableNotificationView.this);
InteractionJankMonitor.getInstance().begin(builder);
}
@@ -568,8 +544,19 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
final int actualHeight = getActualHeight();
float bottom = actualHeight * interpolatedFraction;
- setOutlineRect(0, mAppearAnimationTranslation, getWidth(),
- bottom + mAppearAnimationTranslation);
+ if (mTargetPoint != null) {
+ int width = getWidth();
+ float fraction = 1 - mAppearAnimationFraction;
+
+ setOutlineRect(mTargetPoint.x * fraction,
+ mAnimationTranslationY
+ + (mAnimationTranslationY - mTargetPoint.y) * fraction,
+ width - (width - mTargetPoint.x) * fraction,
+ actualHeight - (actualHeight - mTargetPoint.y) * fraction);
+ } else {
+ setOutlineRect(0, mAppearAnimationTranslation, getWidth(),
+ bottom + mAppearAnimationTranslation);
+ }
}
private float getInterpolatedAppearAnimationFraction() {
@@ -768,8 +755,4 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
void onActivated(ActivatableNotificationView view);
void onActivationReset(ActivatableNotificationView view);
}
-
- interface OnDimmedListener {
- void onSetDimmed(boolean dimmed);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 73bb6cd9ba1c..ccd4843e9ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -37,6 +37,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Path;
+import android.graphics.Point;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.ColorDrawable;
@@ -106,6 +107,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
+import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.wmshell.BubblesManager;
import java.io.FileDescriptor;
@@ -260,6 +262,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
// Use #setLongPressPosition to optionally assign positional data with the long press.
private LongPressListener mLongPressListener;
+ private ExpandableNotificationRowDragController mDragController;
+
private boolean mGroupExpansionChanging;
/**
@@ -331,6 +335,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
};
private OnClickListener mOnClickListener;
+ private OnDragSuccessListener mOnDragSuccessListener;
private boolean mHeadsupDisappearRunning;
private View mChildAfterViewWhenDismissed;
private View mGroupParentWhenDismissed;
@@ -1083,6 +1088,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mLongPressListener = longPressListener;
}
+ public void setDragController(ExpandableNotificationRowDragController dragController) {
+ mDragController = dragController;
+ }
+
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
super.setOnClickListener(l);
@@ -1241,6 +1250,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
mMenuRow.onConfigurationChanged();
}
@@ -1329,6 +1339,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public void dismiss(boolean refocusOnDismiss) {
super.dismiss(refocusOnDismiss);
setLongPressListener(null);
+ setDragController(null);
mGroupParentWhenDismissed = mNotificationParent;
mChildAfterViewWhenDismissed = null;
mEntry.getIcons().getStatusBarIcon().setDismissed();
@@ -1637,6 +1648,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
onHeightReset();
requestLayout();
+
+ setTargetPoint(null);
}
public void showFeedbackIcon(boolean show, Pair<Integer, Integer> resIds) {
@@ -1727,6 +1740,29 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mTranslateableViews.remove(mGutsStub);
}
+ /**
+ * Called once when starting drag motion after opening notification guts,
+ * in case of notification that has {@link android.app.Notification#contentIntent}
+ * and it is to start an activity.
+ */
+ public void doDragCallback(float x, float y) {
+ if (mDragController != null) {
+ setTargetPoint(new Point((int) x, (int) y));
+ mDragController.startDragAndDrop(this);
+ }
+ }
+
+ public void setOnDragSuccessListener(OnDragSuccessListener listener) {
+ mOnDragSuccessListener = listener;
+ }
+
+ /**
+ * Called when a notification is dropped on proper target window.
+ */
+ public void dragAndDropSuccess() {
+ mOnDragSuccessListener.onDragSuccess(getEntry());
+ }
+
private void doLongClickCallback() {
doLongClickCallback(getWidth() / 2, getHeight() / 2);
}
@@ -3167,13 +3203,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
- /** Sets whether dismiss gestures are right-to-left (instead of left-to-right). */
- public void setDismissRtl(boolean dismissRtl) {
- if (mMenuRow != null) {
- mMenuRow.setDismissRtl(dismissRtl);
- }
- }
-
private static class NotificationViewState extends ExpandableViewState {
@Override
@@ -3255,6 +3284,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
/**
+ * Called when notification drag and drop is finished successfully.
+ */
+ public interface OnDragSuccessListener {
+ /**
+ * @param entry NotificationEntry that succeed to drop on proper target window.
+ */
+ void onDragSuccess(NotificationEntry entry);
+ }
+
+ /**
* Equivalent to View.OnClickListener with coordinates
*/
public interface CoordinateOnClickListener {
@@ -3267,40 +3306,45 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- super.dump(fd, pw, args);
- pw.println(" Notification: " + mEntry.getKey());
- pw.print(" visibility: " + getVisibility());
- pw.print(", alpha: " + getAlpha());
- pw.print(", translation: " + getTranslation());
- pw.print(", removed: " + isRemoved());
- pw.print(", expandAnimationRunning: " + mExpandAnimationRunning);
- NotificationContentView showingLayout = getShowingLayout();
- pw.print(", privateShowing: " + (showingLayout == mPrivateLayout));
- pw.println();
- showingLayout.dump(fd, pw, args);
- pw.print(" ");
- if (getViewState() != null) {
- getViewState().dump(fd, pw, args);
- } else {
- pw.print("no viewState!!!");
- }
- pw.println();
- pw.println();
- if (mIsSummaryWithChildren) {
- pw.print(" ChildrenContainer");
- pw.print(" visibility: " + mChildrenContainer.getVisibility());
- pw.print(", alpha: " + mChildrenContainer.getAlpha());
- pw.print(", translationY: " + mChildrenContainer.getTranslationY());
- pw.println();
- List<ExpandableNotificationRow> notificationChildren = getAttachedChildren();
- pw.println(" Children: " + notificationChildren.size());
- pw.println(" {");
- for(ExpandableNotificationRow child : notificationChildren) {
- child.dump(fd, pw, args);
+ // Skip super call; dump viewState ourselves
+ pw.println("Notification: " + mEntry.getKey());
+ DumpUtilsKt.withIndenting(pw, ipw -> {
+ ipw.print("visibility: " + getVisibility());
+ ipw.print(", alpha: " + getAlpha());
+ ipw.print(", translation: " + getTranslation());
+ ipw.print(", removed: " + isRemoved());
+ ipw.print(", expandAnimationRunning: " + mExpandAnimationRunning);
+ NotificationContentView showingLayout = getShowingLayout();
+ ipw.print(", privateShowing: " + (showingLayout == mPrivateLayout));
+ ipw.println();
+ showingLayout.dump(fd, ipw, args);
+
+ if (getViewState() != null) {
+ getViewState().dump(fd, ipw, args);
+ ipw.println();
+ } else {
+ ipw.println("no viewState!!!");
}
- pw.println(" }");
- pw.println();
- }
+
+ if (mIsSummaryWithChildren) {
+ ipw.println();
+ ipw.print("ChildrenContainer");
+ ipw.print(" visibility: " + mChildrenContainer.getVisibility());
+ ipw.print(", alpha: " + mChildrenContainer.getAlpha());
+ ipw.print(", translationY: " + mChildrenContainer.getTranslationY());
+ ipw.println();
+ List<ExpandableNotificationRow> notificationChildren = getAttachedChildren();
+ ipw.println("Children: " + notificationChildren.size());
+ ipw.print("{");
+ ipw.increaseIndent();
+ for (ExpandableNotificationRow child : notificationChildren) {
+ ipw.println();
+ child.dump(fd, ipw, args);
+ }
+ ipw.decreaseIndent();
+ ipw.println("}");
+ }
+ });
}
/**
@@ -3321,4 +3365,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
}
+
+ private void setTargetPoint(Point p) {
+ mTargetPoint = p;
+ }
+ public Point getTargetPoint() {
+ return mTargetPoint;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index c9fcdac8e45f..0662a1eba8b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -25,6 +25,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
+import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -85,6 +86,8 @@ public class ExpandableNotificationRowController implements NodeController {
private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
private final Optional<BubblesManager> mBubblesManagerOptional;
+ private final ExpandableNotificationRowDragController mDragController;
+
@Inject
public ExpandableNotificationRowController(
ExpandableNotificationRow view,
@@ -109,7 +112,8 @@ public class ExpandableNotificationRowController implements NodeController {
FalsingManager falsingManager,
FalsingCollector falsingCollector,
PeopleNotificationIdentifier peopleNotificationIdentifier,
- Optional<BubblesManager> bubblesManagerOptional) {
+ Optional<BubblesManager> bubblesManagerOptional,
+ ExpandableNotificationRowDragController dragController) {
mView = view;
mListContainer = listContainer;
mActivatableNotificationViewController = activatableNotificationViewController;
@@ -134,6 +138,7 @@ public class ExpandableNotificationRowController implements NodeController {
mFalsingCollector = falsingCollector;
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
mBubblesManagerOptional = bubblesManagerOptional;
+ mDragController = dragController;
}
/**
@@ -164,6 +169,10 @@ public class ExpandableNotificationRowController implements NodeController {
);
mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
if (mAllowLongPress) {
+ if (mView.getResources().getBoolean(R.bool.config_notificationToContents)) {
+ mView.setDragController(mDragController);
+ }
+
mView.setLongPressListener((v, x, y, item) -> {
if (mView.isSummaryWithChildren()) {
mView.expandNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
new file mode 100644
index 000000000000..06b739b33e77
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -0,0 +1,171 @@
+/*
+ * 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.systemui.statusbar.notification.row;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+import android.view.DragEvent;
+import android.view.HapticFeedbackConstants;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+import javax.inject.Inject;
+
+/**
+ * Controller for Notification to window.
+ */
+public class ExpandableNotificationRowDragController {
+ private static final String TAG = ExpandableNotificationRowDragController.class.getSimpleName();
+ private int mIconSize;
+
+ private final Context mContext;
+ private final HeadsUpManager mHeadsUpManager;
+
+ @Inject
+ public ExpandableNotificationRowDragController(Context context,
+ HeadsUpManager headsUpManager) {
+ mContext = context;
+ mHeadsUpManager = headsUpManager;
+
+ init();
+ }
+
+ private void init() {
+ mIconSize = mContext.getResources().getDimensionPixelSize(R.dimen.drag_and_drop_icon_size);
+ }
+
+ /**
+ * Called when drag event beyond the touchslop,
+ * and start drag and drop.
+ *
+ * @param view notification that was long pressed and started to drag and drop.
+ */
+ @VisibleForTesting
+ public void startDragAndDrop(View view) {
+ ExpandableNotificationRow enr = null;
+ if (view instanceof ExpandableNotificationRow) {
+ enr = (ExpandableNotificationRow) view;
+ }
+
+ StatusBarNotification sn = enr.getEntry().getSbn();
+ Notification notification = sn.getNotification();
+ final PendingIntent contentIntent = notification.contentIntent != null
+ ? notification.contentIntent
+ : notification.fullScreenIntent;
+ Bitmap iconBitmap = getBitmapFromDrawable(
+ getPkgIcon(enr.getEntry().getSbn().getPackageName()));
+
+ final ImageView snapshot = new ImageView(mContext);
+ snapshot.setImageBitmap(iconBitmap);
+ snapshot.layout(0, 0, mIconSize, mIconSize);
+
+ ClipDescription clipDescription = new ClipDescription("Drag And Drop",
+ new String[]{ClipDescription.MIMETYPE_APPLICATION_ACTIVITY});
+ Intent dragIntent = new Intent();
+ dragIntent.putExtra("android.intent.extra.PENDING_INTENT", contentIntent);
+ dragIntent.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle());
+ ClipData.Item item = new ClipData.Item(dragIntent);
+ ClipData dragData = new ClipData(clipDescription, item);
+ View.DragShadowBuilder myShadow = new View.DragShadowBuilder(snapshot);
+ view.setOnDragListener(getDraggedViewDragListener());
+ view.startDragAndDrop(dragData, myShadow, null, View.DRAG_FLAG_GLOBAL);
+ }
+
+
+ private Drawable getPkgIcon(String pkgName) {
+ Drawable pkgicon = null;
+ PackageManager pm = mContext.getPackageManager();
+ ApplicationInfo info;
+ try {
+ info = pm.getApplicationInfo(
+ pkgName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+ if (info != null) {
+ pkgicon = pm.getApplicationIcon(info);
+ } else {
+ Log.d(TAG, " application info is null ");
+ pkgicon = pm.getDefaultActivityIcon();
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.d(TAG, "can not find package with : " + pkgName);
+ pkgicon = pm.getDefaultActivityIcon();
+ }
+
+ return pkgicon;
+ }
+
+ private Bitmap getBitmapFromDrawable(@NonNull Drawable drawable) {
+ final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bmp);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bmp;
+ }
+
+ private View.OnDragListener getDraggedViewDragListener() {
+ return (view, dragEvent) -> {
+ switch (dragEvent.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow enr = (ExpandableNotificationRow) view;
+ if (enr.isPinned()) {
+ mHeadsUpManager.releaseAllImmediately();
+ } else {
+ Dependency.get(ShadeController.class).animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
+ }
+ }
+ return true;
+ case DragEvent.ACTION_DRAG_ENDED:
+ if (dragEvent.getResult()) {
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow enr = (ExpandableNotificationRow) view;
+ enr.dragAndDropSuccess();
+ }
+ }
+ return true;
+ }
+ return false;
+ };
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 8b0764b1c313..fa2c1ee772cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -34,6 +34,7 @@ import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.util.DumpUtilsKt;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -743,6 +744,16 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable {
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(getClass().getSimpleName());
+ DumpUtilsKt.withIndenting(pw, ipw -> {
+ ExpandableViewState viewState = getViewState();
+ if (viewState == null) {
+ ipw.println("no viewState!!!");
+ } else {
+ viewState.dump(fd, ipw, args);
+ ipw.println();
+ }
+ });
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
index 9eb95c409009..b27a40a828f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
@@ -25,6 +25,10 @@ import android.view.View;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.ViewState;
+import com.android.systemui.util.DumpUtilsKt;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
public class FooterView extends StackScrollerDecorView {
private FooterViewButton mDismissButton;
@@ -45,6 +49,19 @@ public class FooterView extends StackScrollerDecorView {
}
@Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+ DumpUtilsKt.withIndenting(pw, ipw -> {
+ ipw.println("visibility: " + DumpUtilsKt.visibilityString(getVisibility()));
+ ipw.println("manageButton showHistory: " + mShowHistory);
+ ipw.println("manageButton visibility: "
+ + DumpUtilsKt.visibilityString(mDismissButton.getVisibility()));
+ ipw.println("dismissButton visibility: "
+ + DumpUtilsKt.visibilityString(mDismissButton.getVisibility()));
+ });
+ }
+
+ @Override
protected void onFinishInflate() {
super.onFinishInflate();
mDismissButton = (FooterViewButton) findSecondaryView();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 73c4b054fd4e..1530e5238c67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -874,7 +874,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mRow.getImageResolver().purgeCache();
}
- private class RtlEnabledContext extends ContextWrapper {
+ private static class RtlEnabledContext extends ContextWrapper {
private RtlEnabledContext(Context packageContext) {
super(packageContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 4f54e4feb21d..df484dd8ed77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1253,7 +1253,7 @@ public class NotificationContentView extends FrameLayout {
}
if (hasRemoteInput) {
existing.setWrapper(wrapper);
- existing.setOnVisibilityChangedListener(this::setRemoteInputVisible);
+ existing.addOnVisibilityChangedListener(this::setRemoteInputVisible);
if (existingPendingIntent != null || existing.isActive()) {
// The current action could be gone, or the pending intent no longer valid.
@@ -1938,7 +1938,6 @@ public class NotificationContentView extends FrameLayout {
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.print(" ");
pw.print("contentView visibility: " + getVisibility());
pw.print(", alpha: " + getAlpha());
pw.print(", clipBounds: " + getClipBounds());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 4319e29985d8..8e02d9f635d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -50,6 +50,7 @@ import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -64,6 +65,8 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener;
+import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -82,7 +85,8 @@ import dagger.Lazy;
* Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
* closing guts, and keeping track of the currently exposed notification guts.
*/
-public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender {
+public class NotificationGutsManager implements Dumpable, NotificationLifetimeExtender,
+ NotifGutsViewManager {
private static final String TAG = "NotificationGutsManager";
// Must match constant in Settings. Used to highlight preferences when linking to Settings.
@@ -116,13 +120,12 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
@VisibleForTesting
protected String mKeyToRemoveOnGutsClosed;
- private final Lazy<StatusBar> mStatusBarLazy;
+ private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
private final Handler mMainHandler;
private final Handler mBgHandler;
private final Optional<BubblesManager> mBubblesManagerOptional;
private Runnable mOpenRunnable;
private final INotificationManager mNotificationManager;
- private final NotificationEntryManager mNotificationEntryManager;
private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
private final LauncherApps mLauncherApps;
private final ShortcutManager mShortcutManager;
@@ -130,12 +133,13 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
private final UiEventLogger mUiEventLogger;
private final ShadeController mShadeController;
private final AppWidgetManager mAppWidgetManager;
+ private NotifGutsViewListener mGutsListener;
/**
* Injected constructor. See {@link NotificationsModule}.
*/
public NotificationGutsManager(Context context,
- Lazy<StatusBar> statusBarLazy,
+ Lazy<Optional<StatusBar>> statusBarOptionalLazy,
@Main Handler mainHandler,
@Background Handler bgHandler,
AccessibilityManager accessibilityManager,
@@ -151,15 +155,15 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
Optional<BubblesManager> bubblesManagerOptional,
UiEventLogger uiEventLogger,
OnUserInteractionCallback onUserInteractionCallback,
- ShadeController shadeController) {
+ ShadeController shadeController,
+ DumpManager dumpManager) {
mContext = context;
- mStatusBarLazy = statusBarLazy;
+ mStatusBarOptionalLazy = statusBarOptionalLazy;
mMainHandler = mainHandler;
mBgHandler = bgHandler;
mAccessibilityManager = accessibilityManager;
mHighPriorityProvider = highPriorityProvider;
mNotificationManager = notificationManager;
- mNotificationEntryManager = notificationEntryManager;
mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
mLauncherApps = launcherApps;
mShortcutManager = shortcutManager;
@@ -171,6 +175,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
mOnUserInteractionCallback = onUserInteractionCallback;
mShadeController = shadeController;
mAppWidgetManager = AppWidgetManager.getInstance(context);
+
+ dumpManager.registerDumpable(this);
}
public void setUpWithPresenter(NotificationPresenter presenter,
@@ -254,10 +260,10 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
@VisibleForTesting
protected boolean bindGuts(final ExpandableNotificationRow row,
NotificationMenuRowPlugin.MenuItem item) {
- StatusBarNotification sbn = row.getEntry().getSbn();
+ NotificationEntry entry = row.getEntry();
row.setGutsView(item);
- row.setTag(sbn.getPackageName());
+ row.setTag(entry.getSbn().getPackageName());
row.getGuts().setClosedListener((NotificationGuts g) -> {
row.onGutsClosed();
if (!g.willBeRemoved() && !row.isRemoved()) {
@@ -268,7 +274,10 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
mNotificationGutsExposed = null;
mGutsMenuItem = null;
}
- String key = sbn.getKey();
+ if (mGutsListener != null) {
+ mGutsListener.onGutsClose(entry);
+ }
+ String key = entry.getKey();
if (key.equals(mKeyToRemoveOnGutsClosed)) {
mKeyToRemoveOnGutsClosed = null;
if (mNotificationLifetimeFinishedCallback != null) {
@@ -561,17 +570,22 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
.setLeaveOpenOnKeyguardHide(true);
}
- Runnable r = () -> mMainHandler.post(
- () -> openGutsInternal(view, x, y, menuItem));
-
- mStatusBarLazy.get().executeRunnableDismissingKeyguard(
- r,
- null /* cancelAction */,
- false /* dismissShade */,
- true /* afterKeyguardGone */,
- true /* deferred */);
-
- return true;
+ Optional<StatusBar> statusBarOptional = mStatusBarOptionalLazy.get();
+ if (statusBarOptional.isPresent()) {
+ Runnable r = () -> mMainHandler.post(
+ () -> openGutsInternal(view, x, y, menuItem));
+ statusBarOptional.get().executeRunnableDismissingKeyguard(
+ r,
+ null /* cancelAction */,
+ false /* dismissShade */,
+ true /* afterKeyguardGone */,
+ true /* deferred */);
+ return true;
+ }
+ /**
+ * When {@link StatusBar} doesn't exist, falling through to call
+ * {@link #openGutsInternal(View,int,int,NotificationMenuRowPlugin.MenuItem)}.
+ */
}
}
return openGutsInternal(view, x, y, menuItem);
@@ -641,6 +655,10 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
needsFalsingProtection,
row::onGutsOpened);
+ if (mGutsListener != null) {
+ mGutsListener.onGutsOpen(row.getEntry(), guts);
+ }
+
row.closeRemoteInput();
mListContainer.onHeightChanged(row, true /* needsAnimation */);
mGutsMenuItem = menuItem;
@@ -686,10 +704,17 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("NotificationGutsManager state:");
- pw.print(" mKeyToRemoveOnGutsClosed: ");
+ pw.print(" mKeyToRemoveOnGutsClosed (legacy): ");
pw.println(mKeyToRemoveOnGutsClosed);
}
+ /**
+ * @param gutsListener the listener for open and close guts events
+ */
+ public void setGutsListener(NotifGutsViewListener gutsListener) {
+ mGutsListener = gutsListener;
+ }
+
public interface OnSettingsClickListener {
public void onSettingsClick(String key);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index c3dc7001f5e8..3a37fb44b33a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -41,8 +41,11 @@ import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.AlphaOptimizedImageView;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -82,7 +85,6 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
private ArrayList<MenuItem> mRightMenuItems;
private final Map<View, MenuItem> mMenuItemsByView = new ArrayMap<>();
private OnMenuEventListener mMenuListener;
- private boolean mDismissRtl;
private ValueAnimator mFadeAnimator;
private boolean mAnimating;
@@ -306,7 +308,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
final int showDismissSetting = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.SHOW_NEW_NOTIF_DISMISS, -1);
final boolean newFlowHideShelf = showDismissSetting == -1
- ? mContext.getResources().getBoolean(R.bool.flag_notif_updates)
+ ? Dependency.get(FeatureFlags.class).isEnabled(Flags.NOTIFICATION_UPDATES)
: showDismissSetting == 1;
if (newFlowHideShelf) {
return;
@@ -787,14 +789,6 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
return getParent().canViewBeDismissed();
}
- @Override
- public void setDismissRtl(boolean dismissRtl) {
- mDismissRtl = dismissRtl;
- if (mMenuContainer != null) {
- createMenuViews(true);
- }
- }
-
public static class NotificationMenuItem implements MenuItem {
View mMenuView;
GutsContent mGutsContent;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 41c88cc0a74a..a9cc3237d719 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -72,8 +72,6 @@ public class AmbientState {
private boolean mPanelFullWidth;
private boolean mPulsing;
private boolean mUnlockHintRunning;
- private boolean mQsCustomizerShowing;
- private int mIntrinsicPadding;
private float mHideAmount;
private boolean mAppearing;
private float mPulseHeight = MAX_PULSE_HEIGHT;
@@ -83,6 +81,7 @@ public class AmbientState {
private float mAppearFraction;
private boolean mIsShadeOpening;
private float mOverExpansion;
+ private int mStackTopMargin;
/** Distance of top of notifications panel from top of screen. */
private float mStackY = 0;
@@ -95,7 +94,6 @@ public class AmbientState {
/** Height of the notifications panel without top padding when expansion completes. */
private float mStackEndHeight;
- private float mTransitionToFullShadeAmount;
/**
* @return Height of the notifications panel without top padding when expansion completes.
@@ -494,22 +492,6 @@ public class AmbientState {
return mUnlockHintRunning;
}
- public boolean isQsCustomizerShowing() {
- return mQsCustomizerShowing;
- }
-
- public void setQsCustomizerShowing(boolean qsCustomizerShowing) {
- mQsCustomizerShowing = qsCustomizerShowing;
- }
-
- public void setIntrinsicPadding(int intrinsicPadding) {
- mIntrinsicPadding = intrinsicPadding;
- }
-
- public int getIntrinsicPadding() {
- return mIntrinsicPadding;
- }
-
/**
* @return whether a view is dozing and not pulsing right now
*/
@@ -586,30 +568,11 @@ public class AmbientState {
mOnPulseHeightChangedListener = onPulseHeightChangedListener;
}
- public Runnable getOnPulseHeightChangedListener() {
- return mOnPulseHeightChangedListener;
- }
-
public void setTrackedHeadsUpRow(ExpandableNotificationRow row) {
mTrackedHeadsUpRow = row;
}
/**
- * Set the amount of pixels we have currently dragged down if we're transitioning to the full
- * shade. 0.0f means we're not transitioning yet.
- */
- public void setTransitionToFullShadeAmount(float transitionToFullShadeAmount) {
- mTransitionToFullShadeAmount = transitionToFullShadeAmount;
- }
-
- /**
- * get
- */
- public float getTransitionToFullShadeAmount() {
- return mTransitionToFullShadeAmount;
- }
-
- /**
* Returns the currently tracked heads up row, if there is one and it is currently above the
* shelf (still appearing).
*/
@@ -631,4 +594,12 @@ public class AmbientState {
public void setHasAlertEntries(boolean hasAlertEntries) {
mHasAlertEntries = hasAlertEntries;
}
+
+ public void setStackTopMargin(int stackTopMargin) {
+ mStackTopMargin = stackTopMargin;
+ }
+
+ public int getStackTopMargin() {
+ return mStackTopMargin;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt
new file mode 100644
index 000000000000..31f4857e4b04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt
@@ -0,0 +1,25 @@
+package com.android.systemui.statusbar.notification.stack
+
+import android.annotation.IntDef
+
+/**
+ * For now, declare the available notification buckets (sections) here so that other
+ * presentation code can decide what to do based on an entry's buckets
+ */
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(
+ prefix = ["BUCKET_"],
+ value = [
+ BUCKET_UNKNOWN, BUCKET_MEDIA_CONTROLS, BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE,
+ BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT
+ ]
+)
+annotation class PriorityBucket
+
+const val BUCKET_UNKNOWN = 0
+const val BUCKET_MEDIA_CONTROLS = 1
+const val BUCKET_HEADS_UP = 2
+const val BUCKET_FOREGROUND_SERVICE = 3
+const val BUCKET_PEOPLE = 4
+const val BUCKET_ALERTING = 5
+const val BUCKET_SILENT = 6
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 594afceab63a..faf0fdfa0c8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -21,6 +21,8 @@ import android.util.MathUtils;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -41,6 +43,7 @@ public class NotificationRoundnessManager {
private final ExpandableView[] mTmpFirstInSectionViews;
private final ExpandableView[] mTmpLastInSectionViews;
private final KeyguardBypassController mBypassController;
+ private final FeatureFlags mFeatureFlags;
private boolean mExpanded;
private HashSet<ExpandableView> mAnimatedChildren;
private Runnable mRoundingChangedCallback;
@@ -55,7 +58,9 @@ public class NotificationRoundnessManager {
@Inject
NotificationRoundnessManager(
KeyguardBypassController keyguardBypassController,
- NotificationSectionsFeatureManager sectionsFeatureManager) {
+ NotificationSectionsFeatureManager sectionsFeatureManager,
+ FeatureFlags featureFlags) {
+ mFeatureFlags = featureFlags;
int numberOfSections = sectionsFeatureManager.getNumberOfBuckets();
mFirstInSectionViews = new ExpandableView[numberOfSections];
mLastInSectionViews = new ExpandableView[numberOfSections];
@@ -122,9 +127,8 @@ public class NotificationRoundnessManager {
void setViewsAffectedBySwipe(
ExpandableView viewBefore,
ExpandableView viewSwiped,
- ExpandableView viewAfter,
- boolean cornerAnimationsEnabled) {
- if (!cornerAnimationsEnabled) {
+ ExpandableView viewAfter) {
+ if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_UPDATES)) {
return;
}
final boolean animate = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index ab39de0f9bc7..bc172ce537f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.stack;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index 45ce20a1f08f..5f157a767c5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.stack
import android.annotation.ColorInt
-import android.annotation.IntDef
import android.annotation.LayoutRes
import android.util.Log
import android.view.LayoutInflater
@@ -350,7 +349,7 @@ class NotificationSectionsManager @Inject internal constructor(
silentHeaderView?.run {
val hasActiveClearableNotifications = this@NotificationSectionsManager.parent
.hasActiveClearableNotifications(NotificationStackScrollLayout.ROWS_GENTLE)
- setAreThereDismissableGentleNotifs(hasActiveClearableNotifications)
+ setClearSectionButtonEnabled(hasActiveClearableNotifications)
}
}
@@ -448,25 +447,3 @@ class NotificationSectionsManager @Inject internal constructor(
private const val DEBUG = false
}
}
-
-/**
- * For now, declare the available notification buckets (sections) here so that other
- * presentation code can decide what to do based on an entry's buckets
- */
-@Retention(AnnotationRetention.SOURCE)
-@IntDef(
- prefix = ["BUCKET_"],
- value = [
- BUCKET_UNKNOWN, BUCKET_MEDIA_CONTROLS, BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE,
- BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT
- ]
-)
-annotation class PriorityBucket
-
-const val BUCKET_UNKNOWN = 0
-const val BUCKET_MEDIA_CONTROLS = 1
-const val BUCKET_HEADS_UP = 2
-const val BUCKET_FOREGROUND_SERVICE = 3
-const val BUCKET_PEOPLE = 4
-const val BUCKET_ALERTING = 5
-const val BUCKET_SILENT = 6
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 733c0a92ec09..6a127102b726 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -17,9 +17,8 @@
package com.android.systemui.statusbar.notification.stack;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
import static com.android.systemui.util.Utils.shouldUseSplitNotificationShade;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -72,8 +71,10 @@ import android.widget.ScrollView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.policy.SystemBarUtils;
import com.android.keyguard.KeyguardSliceView;
import com.android.settingslib.Utils;
+import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.ExpandHelper;
import com.android.systemui.R;
@@ -81,15 +82,11 @@ import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.EmptyShadeView;
-import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationShelfController;
-import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
import com.android.systemui.statusbar.notification.FakeShadowView;
-import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.ShadeViewRefactor;
@@ -112,7 +109,7 @@ import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import com.android.systemui.util.Assert;
-import com.android.systemui.util.leak.RotationUtils;
+import com.android.systemui.util.DumpUtilsKt;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -125,9 +122,6 @@ import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
-import javax.inject.Inject;
-import javax.inject.Named;
-
/**
* A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
*/
@@ -160,7 +154,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
* gap is drawn between them). In this case we don't want to round their corners.
*/
private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1;
- private KeyguardBypassEnabledProvider mKeyguardBypassEnabledProvider;
+ private boolean mKeyguardBypassEnabled;
private ExpandHelper mExpandHelper;
private NotificationSwipeHelper mSwipeHelper;
@@ -168,7 +162,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private final Paint mBackgroundPaint = new Paint();
private final boolean mShouldDrawNotificationBackground;
private boolean mHighPriorityBeforeSpeedBump;
- private boolean mDismissRtl;
private float mExpandedHeight;
private int mOwnScrollY;
@@ -217,7 +210,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private GroupMembershipManager mGroupMembershipManager;
private GroupExpansionManager mGroupExpansionManager;
- private NotificationActivityStarter mNotificationActivityStarter;
private HashSet<ExpandableView> mChildrenToAddAnimated = new HashSet<>();
private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
private ArrayList<ExpandableView> mChildrenToRemoveAnimated = new ArrayList<>();
@@ -417,6 +409,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private boolean mBackwardScrollable;
private NotificationShelf mShelf;
private int mMaxDisplayedNotifications = -1;
+ private float mKeyguardBottomPadding = -1;
private int mStatusBarHeight;
private int mMinInteractionHeight;
private final Rect mClipRect = new Rect();
@@ -448,7 +441,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private final Rect mTmpRect = new Rect();
private DismissListener mDismissListener;
private DismissAllAnimationListener mDismissAllAnimationListener;
- private NotificationRemoteInputManager mRemoteInputManager;
private ShadeController mShadeController;
private Consumer<Boolean> mOnStackYChanged;
@@ -463,6 +455,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private float mLastSentExpandedHeight;
private boolean mWillExpand;
private int mGapHeight;
+ private boolean mIsRemoteInputActive;
/**
* The extra inset during the full shade transition
@@ -534,7 +527,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private NotificationEntry mTopHeadsUpEntry;
private long mNumHeadsUp;
private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
- private final FeatureFlags mFeatureFlags;
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private boolean mShouldUseSplitNotificationShade;
@@ -569,26 +561,20 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
};
- @Inject
- public NotificationStackScrollLayout(
- @Named(VIEW_CONTEXT) Context context,
- AttributeSet attrs,
- NotificationSectionsManager notificationSectionsManager,
- GroupMembershipManager groupMembershipManager,
- GroupExpansionManager groupExpansionManager,
- AmbientState ambientState,
- FeatureFlags featureFlags,
- UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
+ @Nullable
+ private OnClickListener mManageButtonClickListener;
+
+ public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
super(context, attrs, 0, 0);
Resources res = getResources();
- mSectionsManager = notificationSectionsManager;
- mFeatureFlags = featureFlags;
- mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+ mSectionsManager = Dependency.get(NotificationSectionsManager.class);
+ mUnlockedScreenOffAnimationController =
+ Dependency.get(UnlockedScreenOffAnimationController.class);
updateSplitNotificationShade();
mSectionsManager.initialize(this, LayoutInflater.from(context));
mSections = mSectionsManager.createSectionsForBuckets();
- mAmbientState = ambientState;
+ mAmbientState = Dependency.get(AmbientState.class);
mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackgroundFloating)
.getDefaultColor();
int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
@@ -614,8 +600,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mDebugPaint.setTextSize(25f);
}
mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
- mGroupMembershipManager = groupMembershipManager;
- mGroupExpansionManager = groupExpansionManager;
+ mGroupMembershipManager = Dependency.get(GroupMembershipManager.class);
+ mGroupExpansionManager = Dependency.get(GroupExpansionManager.class);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
@@ -627,16 +613,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
addView(mFgsSectionView, -1);
}
- void updateDismissRtlSetting(boolean dismissRtl) {
- mDismissRtl = dismissRtl;
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
- if (child instanceof ExpandableNotificationRow) {
- ((ExpandableNotificationRow) child).setDismissRtl(dismissRtl);
- }
- }
- }
-
/**
* Set the overexpansion of the panel to be applied to the view.
*/
@@ -656,12 +632,20 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
/**
+ * Sets whether keyguard bypass is enabled. If true, this layout will be rendered in bypass
+ * mode when it is on the keyguard.
+ */
+ public void setKeyguardBypassEnabled(boolean isEnabled) {
+ mKeyguardBypassEnabled = isEnabled;
+ }
+
+ /**
* @return the height at which we will wake up when pulsing
*/
public float getWakeUpHeight() {
ExpandableView firstChild = getFirstChildWithBackground();
if (firstChild != null) {
- if (mKeyguardBypassEnabledProvider.getBypassEnabled()) {
+ if (mKeyguardBypassEnabled) {
return firstChild.getHeadsUpHeightWithoutHeader();
} else {
return firstChild.getCollapsedHeight();
@@ -670,6 +654,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return 0f;
}
+ public float getNotificationSquishinessFraction() {
+ return mStackScrollAlgorithm.getNotificationSquishinessFraction(mAmbientState);
+ }
+
void reinflateViews() {
inflateFooterView();
inflateEmptyShadeView();
@@ -677,6 +665,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mSectionsManager.reinflateViews(LayoutInflater.from(mContext));
}
+ public void setIsRemoteInputActive(boolean isActive) {
+ mIsRemoteInputActive = isActive;
+ }
+
@VisibleForTesting
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void updateFooter() {
@@ -686,12 +678,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
// TODO: move this logic to controller, which will invoke updateFooterView directly
boolean showDismissView = mClearAllEnabled &&
mController.hasActiveClearableNotifications(ROWS_ALL);
- RemoteInputController remoteInputController = mRemoteInputManager.getController();
boolean showFooterView = (showDismissView || getVisibleNotificationCount() > 0)
&& mIsCurrentUserSetup // see: b/193149550
&& mStatusBarState != StatusBarState.KEYGUARD
&& !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
- && (remoteInputController == null || !remoteInputController.isRemoteInputActive());
+ && !mIsRemoteInputActive;
boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
@@ -743,6 +734,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mDebugPaint.setColor(Color.YELLOW);
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+ y = (int) mMaxLayoutHeight;
+ mDebugPaint.setColor(Color.MAGENTA);
+ canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+
+ if (mKeyguardBottomPadding >= 0) {
+ y = getHeight() - (int) mKeyguardBottomPadding;
+ mDebugPaint.setColor(Color.GRAY);
+ canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+ }
+
y = getHeight() - getEmptyBottomMargin();
mDebugPaint.setColor(Color.GREEN);
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
@@ -790,7 +791,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
}
boolean shouldDrawBackground;
- if (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard()) {
+ if (mKeyguardBypassEnabled && onKeyguard()) {
shouldDrawBackground = isPulseExpanding();
} else {
shouldDrawBackground = !mAmbientState.isDozing() || anySectionHasVisibleChild;
@@ -905,15 +906,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
private void reinitView() {
- initView(getContext(), mKeyguardBypassEnabledProvider, mSwipeHelper);
+ initView(getContext(), mSwipeHelper);
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- void initView(Context context,
- KeyguardBypassEnabledProvider keyguardBypassEnabledProvider,
- NotificationSwipeHelper swipeHelper) {
+ void initView(Context context, NotificationSwipeHelper swipeHelper) {
mScroller = new OverScroller(getContext());
- mKeyguardBypassEnabledProvider = keyguardBypassEnabledProvider;
mSwipeHelper = swipeHelper;
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
@@ -934,7 +932,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
res.getDimensionPixelSize(R.dimen.notification_divider_height));
mMinTopOverScrollToEscape = res.getDimensionPixelSize(
R.dimen.min_top_overscroll_to_qs);
- mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height);
+ mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
mMinimumPaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
mQsTilePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_margin_horizontal);
@@ -945,8 +943,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mCornerRadius = res.getDimensionPixelSize(R.dimen.notification_corner_radius);
mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize(
R.dimen.heads_up_status_bar_padding);
- mQsScrollBoundaryPosition = res.getDimensionPixelSize(
- com.android.internal.R.dimen.quick_qs_offset_height);
+ mQsScrollBoundaryPosition = SystemBarUtils.getQuickQsOffsetHeight(mContext);
}
void updateSidePadding(int viewWidth) {
@@ -955,7 +952,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return;
}
// Portrait is easy, just use the dimen for paddings
- if (RotationUtils.getRotation(mContext) == RotationUtils.ROTATION_NONE) {
+ if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
mSidePaddings = mMinimumPaddings;
return;
}
@@ -1353,7 +1350,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private void notifyAppearChangedListeners() {
float appear;
float expandAmount;
- if (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard()) {
+ if (mKeyguardBypassEnabled && onKeyguard()) {
appear = calculateAppearFractionBypass();
expandAmount = getPulseHeight();
} else {
@@ -1704,7 +1701,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
super.onConfigurationChanged(newConfig);
Resources res = getResources();
updateSplitNotificationShade();
- mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
+ mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
float densityScale = res.getDisplayMetrics().density;
mSwipeHelper.setDensityScale(densityScale);
float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
@@ -2396,8 +2393,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
minTopPosition = firstVisibleSection.getBounds().top;
}
boolean shiftPulsingWithFirst = mNumHeadsUp <= 1
- && (mAmbientState.isDozing()
- || (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard));
+ && (mAmbientState.isDozing() || (mKeyguardBypassEnabled && onKeyguard));
for (NotificationSection section : mSections) {
int minBottomPosition = minTopPosition;
if (section == lastSection) {
@@ -2540,7 +2536,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
} else {
mTopPaddingOverflow = 0;
}
- setTopPadding(topPadding, animate && !mKeyguardBypassEnabledProvider.getBypassEnabled());
+ setTopPadding(topPadding, animate && !mKeyguardBypassEnabled);
setExpandedHeight(mExpandedHeight);
}
@@ -2909,7 +2905,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
updateChronometerForChild(child);
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- row.setDismissRtl(mDismissRtl);
row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX);
}
@@ -3104,7 +3099,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
boolean performDisappearAnimation = !mIsExpanded
// Only animate if we still have pinned heads up, otherwise we just have the
// regular collapse animation of the lock screen
- || (mKeyguardBypassEnabledProvider.getBypassEnabled() && onKeyguard()
+ || (mKeyguardBypassEnabled && onKeyguard()
&& mInHeadsUpPinnedMode);
if (performDisappearAnimation && !isHeadsUp) {
type = row.wasJustClicked()
@@ -4275,7 +4270,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
void setIntrinsicPadding(int intrinsicPadding) {
mIntrinsicPadding = intrinsicPadding;
- mAmbientState.setIntrinsicPadding(intrinsicPadding);
}
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
@@ -4340,7 +4334,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
// Since we are clipping to the outline we need to make sure that the shadows aren't
// clipped when pulsing
float ownTranslationZ = 0;
- if (mKeyguardBypassEnabledProvider.getBypassEnabled() && mAmbientState.isHiddenAtAll()) {
+ if (mKeyguardBypassEnabled && mAmbientState.isHiddenAtAll()) {
ExpandableView firstChildNotGone = getFirstChildNotGone();
if (firstChildNotGone != null && firstChildNotGone.showingPulsing()) {
ownTranslationZ = firstChildNotGone.getTranslationZ();
@@ -4382,6 +4376,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return -1;
}
+ /**
+ * Returns whether or not a History button is shown in the footer. If there is no footer, then
+ * this will return false.
+ **/
+ public boolean isHistoryShown() {
+ return mFooterView != null && mFooterView.isHistoryShown();
+ }
+
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
void setFooterView(@NonNull FooterView footerView) {
int index = -1;
@@ -4391,6 +4393,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
mFooterView = footerView;
addView(mFooterView, index);
+ if (mManageButtonClickListener != null) {
+ mFooterView.setManageButtonClickListener(mManageButtonClickListener);
+ }
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -4797,6 +4802,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
}
+ /**
+ * This is used for debugging only; it will be used to draw the otherwise invisible line which
+ * NotificationPanelViewController treats as the bottom when calculating how many notifications
+ * appear on the keyguard.
+ * Setting a negative number will disable rendering this line.
+ */
+ public void setKeyguardBottomPadding(float keyguardBottomPadding) {
+ mKeyguardBottomPadding = keyguardBottomPadding;
+ }
+
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) {
mShouldShowShelfOnly = shouldShowShelfOnly;
@@ -4879,67 +4894,48 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- public void setQsCustomizerShowing(boolean isShowing) {
- mAmbientState.setQsCustomizerShowing(isShowing);
- requestChildrenUpdate();
- }
-
- @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void setHeadsUpGoingAwayAnimationsAllowed(boolean headsUpGoingAwayAnimationsAllowed) {
mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed;
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s"
- + " alpha=%f scrollY:%d maxTopPadding=%d showShelfOnly=%s"
- + " qsExpandFraction=%f"
- + " hideAmount=%f]",
- this.getClass().getSimpleName(),
- mPulsing ? "T" : "f",
- mAmbientState.isQsCustomizerShowing() ? "T" : "f",
- getVisibility() == View.VISIBLE ? "visible"
- : getVisibility() == View.GONE ? "gone"
- : "invisible",
- getAlpha(),
- mAmbientState.getScrollY(),
- mMaxTopPadding,
- mShouldShowShelfOnly ? "T" : "f",
- mQsExpansionFraction,
- mAmbientState.getHideAmount()));
- int childCount = getChildCount();
- pw.println(" Number of children: " + childCount);
- pw.println();
+ StringBuilder sb = new StringBuilder("[")
+ .append(this.getClass().getSimpleName()).append(":")
+ .append(" pulsing=").append(mPulsing ? "T" : "f")
+ .append(" visibility=").append(DumpUtilsKt.visibilityString(getVisibility()))
+ .append(" alpha=").append(getAlpha())
+ .append(" scrollY=").append(mAmbientState.getScrollY())
+ .append(" maxTopPadding=").append(mMaxTopPadding)
+ .append(" showShelfOnly=").append(mShouldShowShelfOnly ? "T" : "f")
+ .append(" qsExpandFraction=").append(mQsExpansionFraction)
+ .append(" isCurrentUserSetup=").append(mIsCurrentUserSetup)
+ .append(" hideAmount=").append(mAmbientState.getHideAmount())
+ .append("]");
+ pw.println(sb.toString());
+ DumpUtilsKt.withIndenting(pw, ipw -> {
+ int childCount = getChildCount();
+ ipw.println("Number of children: " + childCount);
+ ipw.println();
- for (int i = 0; i < childCount; i++) {
- ExpandableView child = (ExpandableView) getChildAt(i);
- child.dump(fd, pw, args);
- if (!(child instanceof ExpandableNotificationRow)) {
- pw.println(" " + child.getClass().getSimpleName());
- // Notifications dump it's viewstate as part of their dump to support children
- ExpandableViewState viewState = child.getViewState();
- if (viewState == null) {
- pw.println(" no viewState!!!");
- } else {
- pw.print(" ");
- viewState.dump(fd, pw, args);
- pw.println();
- pw.println();
- }
+ for (int i = 0; i < childCount; i++) {
+ ExpandableView child = (ExpandableView) getChildAt(i);
+ child.dump(fd, ipw, args);
+ ipw.println();
+ }
+ int transientViewCount = getTransientViewCount();
+ pw.println("Transient Views: " + transientViewCount);
+ for (int i = 0; i < transientViewCount; i++) {
+ ExpandableView child = (ExpandableView) getTransientView(i);
+ child.dump(fd, pw, args);
+ }
+ View swipedView = mSwipeHelper.getSwipedView();
+ pw.println("Swiped view: " + swipedView);
+ if (swipedView instanceof ExpandableView) {
+ ExpandableView expandableView = (ExpandableView) swipedView;
+ expandableView.dump(fd, pw, args);
}
- }
- int transientViewCount = getTransientViewCount();
- pw.println(" Transient Views: " + transientViewCount);
- for (int i = 0; i < transientViewCount; i++) {
- ExpandableView child = (ExpandableView) getTransientView(i);
- child.dump(fd, pw, args);
- }
- View swipedView = mSwipeHelper.getSwipedView();
- pw.println(" Swiped view: " + swipedView);
- if (swipedView instanceof ExpandableView) {
- ExpandableView expandableView = (ExpandableView) swipedView;
- expandableView.dump(fd, pw, args);
- }
+ });
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -5103,9 +5099,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return canChildBeDismissed(row) && matchesSelection(row, selection);
}
- public void setNotificationActivityStarter(
- NotificationActivityStarter notificationActivityStarter) {
- mNotificationActivityStarter = notificationActivityStarter;
+ /** Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked. */
+ public void setManageButtonClickListener(@Nullable OnClickListener listener) {
+ mManageButtonClickListener = listener;
+ if (mFooterView != null) {
+ mFooterView.setManageButtonClickListener(mManageButtonClickListener);
+ }
}
@VisibleForTesting
@@ -5120,9 +5119,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
clearNotifications(ROWS_ALL, true /* closeShade */);
footerView.setSecondaryVisible(false /* visible */, true /* animate */);
});
- footerView.setManageButtonClickListener(v -> {
- mNotificationActivityStarter.startHistoryIntent(v, mFooterView.isHistoryShown());
- });
setFooterView(footerView);
}
@@ -5174,7 +5170,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
public float setPulseHeight(float height) {
float overflow;
mAmbientState.setPulseHeight(height);
- if (mKeyguardBypassEnabledProvider.getBypassEnabled()) {
+ if (mKeyguardBypassEnabled) {
notifyAppearChangedListeners();
overflow = Math.max(0, height - getIntrinsicPadding());
} else {
@@ -5253,10 +5249,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mController.getNoticationRoundessManager().setAnimatedChildren(mChildrenToAddAnimated);
}
- public NotificationStackScrollLayoutController getController() {
- return mController;
- }
-
void addSwipedOutView(View v) {
mSwipedOutViews.add(v);
}
@@ -5286,10 +5278,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
}
mController.getNoticationRoundessManager()
- .setViewsAffectedBySwipe((ExpandableView) viewBefore,
+ .setViewsAffectedBySwipe(
+ (ExpandableView) viewBefore,
(ExpandableView) viewSwiped,
- (ExpandableView) viewAfter,
- getResources().getBoolean(R.bool.flag_notif_updates));
+ (ExpandableView) viewAfter);
updateFirstAndLastBackgroundViews();
requestDisallowInterceptTouchEvent(true);
@@ -5301,8 +5293,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
void onSwipeEnd() {
updateFirstAndLastBackgroundViews();
mController.getNoticationRoundessManager()
- .setViewsAffectedBySwipe(null, null, null,
- getResources().getBoolean(R.bool.flag_notif_updates));
+ .setViewsAffectedBySwipe(null, null, null);
// Round bottom corners for notification right before shelf.
mShelf.updateAppearance();
}
@@ -5376,10 +5367,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mFooterDismissListener = listener;
}
- public void setRemoteInputManager(NotificationRemoteInputManager remoteInputManager) {
- mRemoteInputManager = remoteInputManager;
- }
-
void setShadeController(ShadeController shadeController) {
mShadeController = shadeController;
}
@@ -5431,7 +5418,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
private void updateSplitNotificationShade() {
- boolean split = shouldUseSplitNotificationShade(mFeatureFlags, getResources());
+ boolean split = shouldUseSplitNotificationShade(getResources());
if (split != mShouldUseSplitNotificationShade) {
mShouldUseSplitNotificationShade = split;
updateDismissBehavior();
@@ -5688,6 +5675,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mSwipeHelper.resetExposedMenuView(animate, force);
}
+ boolean isUsingSplitNotificationShade() {
+ return mShouldUseSplitNotificationShade;
+ }
+
static boolean matchesSelection(
ExpandableNotificationRow row,
@SelectedRows int selection) {
@@ -6112,10 +6103,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
/** Only rows where entry.isHighPriority() is false. */
public static final int ROWS_GENTLE = 2;
- interface KeyguardBypassEnabledProvider {
- boolean getBypassEnabled();
- }
-
interface DismissListener {
void onDismiss(@SelectedRows int selectedRows);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 1e92ca9862f7..03fc76760133 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -48,6 +48,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.jank.InteractionJankMonitor;
@@ -66,13 +68,13 @@ import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -185,9 +187,14 @@ public class NotificationStackScrollLayoutController {
private int mBarState;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
+ private View mLongPressedView;
+
private final NotificationListContainerImpl mNotificationListContainer =
new NotificationListContainerImpl();
+ @Nullable
+ private NotificationActivityStarter mNotificationActivityStarter;
+
private ColorExtractor.OnColorsChangedListener mOnColorsChangedListener;
/**
@@ -265,15 +272,6 @@ public class NotificationStackScrollLayoutController {
}
@Override
- public void onOverlayChanged() {
- updateShowEmptyShadeView();
- mView.updateCornerRadius();
- mView.updateBgColor();
- mView.updateDecorViews();
- mView.reinflateViews();
- }
-
- @Override
public void onUiModeChanged() {
mView.updateBgColor();
mView.updateDecorViews();
@@ -281,6 +279,11 @@ public class NotificationStackScrollLayoutController {
@Override
public void onThemeChanged() {
+ updateShowEmptyShadeView();
+ mView.updateCornerRadius();
+ mView.updateBgColor();
+ mView.updateDecorViews();
+ mView.reinflateViews();
updateFooter();
}
@@ -513,6 +516,11 @@ public class NotificationStackScrollLayoutController {
}
@Override
+ public void onLongPressSent(View v) {
+ mLongPressedView = v;
+ }
+
+ @Override
public void onBeginDrag(View v) {
mFalsingCollector.onNotificationStartDismissing();
mView.onSwipeBegin(v);
@@ -707,7 +715,13 @@ public class NotificationStackScrollLayoutController {
NotificationPanelEvent.fromSelection(selection)));
mView.setFooterDismissListener(() ->
mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES));
- mView.setRemoteInputManager(mRemoteInputManager);
+ mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive());
+ mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() {
+ @Override
+ public void onRemoteInputActive(boolean active) {
+ mView.setIsRemoteInputActive(active);
+ }
+ });
mView.setShadeController(mShadeController);
if (mFgFeatureController.isForegroundServiceDismissalEnabled()) {
@@ -738,8 +752,15 @@ public class NotificationStackScrollLayoutController {
});
}
- mView.initView(mView.getContext(), mKeyguardBypassController::getBypassEnabled,
- mSwipeHelper);
+ mView.initView(mView.getContext(), mSwipeHelper);
+ mView.setKeyguardBypassEnabled(mKeyguardBypassController.getBypassEnabled());
+ mKeyguardBypassController
+ .registerOnBypassStateChangedListener(mView::setKeyguardBypassEnabled);
+ mView.setManageButtonClickListener(v -> {
+ if (mNotificationActivityStarter != null) {
+ mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
+ }
+ });
mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
@@ -762,9 +783,6 @@ public class NotificationStackScrollLayoutController {
mTunerService.addTunable(
(key, newValue) -> {
switch (key) {
- case Settings.Secure.NOTIFICATION_DISMISS_RTL:
- mView.updateDismissRtlSetting("1".equals(newValue));
- break;
case Settings.Secure.NOTIFICATION_HISTORY_ENABLED:
updateFooter();
break;
@@ -774,7 +792,6 @@ public class NotificationStackScrollLayoutController {
}
},
HIGH_PRIORITY,
- Settings.Secure.NOTIFICATION_DISMISS_RTL,
Settings.Secure.NOTIFICATION_HISTORY_ENABLED);
mKeyguardMediaController.setVisibilityChangedListener(visible -> {
@@ -789,14 +806,15 @@ public class NotificationStackScrollLayoutController {
return Unit.INSTANCE;
});
- // callback is invoked synchronously, updating mView immediately
+ // attach callback, and then call it to update mView immediately
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+ mDeviceProvisionedListener.onDeviceProvisionedChanged();
if (mView.isAttachedToWindow()) {
mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
}
mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
- mSilentHeaderController.setOnClearAllClickListener(v -> clearSilentNotifications());
+ mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications());
}
private boolean isInVisibleLocation(NotificationEntry entry) {
@@ -1056,6 +1074,10 @@ public class NotificationStackScrollLayoutController {
mView.setOnStackYChanged(onStackYChanged);
}
+ public float getNotificationSquishinessFraction() {
+ return mView.getNotificationSquishinessFraction();
+ }
+
public float calculateAppearFractionBypass() {
return mView.calculateAppearFractionBypass();
}
@@ -1145,11 +1167,16 @@ public class NotificationStackScrollLayoutController {
/**
* Update whether we should show the empty shade view (no notifications in the shade).
* If so, send the update to our view.
+ *
+ * When in split mode, notifications are always visible regardless of the state of the
+ * QuickSettings panel. That being the case, empty view is always shown if the other conditions
+ * are true.
*/
public void updateShowEmptyShadeView() {
mShowEmptyShadeView = mBarState != KEYGUARD
- && !mView.isQsExpanded()
+ && (!mView.isQsExpanded() || mView.isUsingSplitNotificationShade())
&& mView.getVisibleNotificationCount() == 0;
+
mView.updateEmptyShadeView(
mShowEmptyShadeView,
mZenModeController.areNotificationsHiddenInShade());
@@ -1243,6 +1270,16 @@ public class NotificationStackScrollLayoutController {
mNotificationListContainer.setMaxDisplayedNotifications(maxNotifications);
}
+ /**
+ * This is used for debugging only; it will be used to draw the otherwise invisible line which
+ * NotificationPanelViewController treats as the bottom when calculating how many notifications
+ * appear on the keyguard.
+ * Setting a negative number will disable rendering this line.
+ */
+ public void setKeyguardBottomPadding(float keyguardBottomPadding) {
+ mView.setKeyguardBottomPadding(keyguardBottomPadding);
+ }
+
public RemoteInputController.Delegate createDelegate() {
return new RemoteInputController.Delegate() {
public void setRemoteInputActive(NotificationEntry entry,
@@ -1264,6 +1301,9 @@ public class NotificationStackScrollLayoutController {
}
public void updateSectionBoundaries(String reason) {
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ return;
+ }
mView.updateSectionBoundaries(reason);
}
@@ -1450,6 +1490,10 @@ public class NotificationStackScrollLayoutController {
return mDynamicPrivacyController.isInLockedDownShade();
}
+ public boolean isLongPressInProgress() {
+ return mLongPressedView != null;
+ }
+
/**
* Set the dimmed state for all of the notification views.
*/
@@ -1487,6 +1531,11 @@ public class NotificationStackScrollLayoutController {
mView.setExtraTopInsetForFullShadeTransition(extraTopInset);
}
+ /** */
+ public void setWillExpand(boolean willExpand) {
+ mView.setWillExpand(willExpand);
+ }
+
/**
* Set a listener to when scrolling changes.
*/
@@ -1509,6 +1558,10 @@ public class NotificationStackScrollLayoutController {
mView.animateNextTopPaddingChange();
}
+ public void setNotificationActivityStarter(NotificationActivityStarter activityStarter) {
+ mNotificationActivityStarter = activityStarter;
+ }
+
/**
* Enum for UiEvent logged from this class
*/
@@ -1580,7 +1633,8 @@ public class NotificationStackScrollLayoutController {
@Override
public void setNotificationActivityStarter(
NotificationActivityStarter notificationActivityStarter) {
- mView.setNotificationActivityStarter(notificationActivityStarter);
+ NotificationStackScrollLayoutController.this
+ .setNotificationActivityStarter(notificationActivityStarter);
}
@Override
@@ -1694,17 +1748,23 @@ public class NotificationStackScrollLayoutController {
mView.handleEmptySpaceClick(ev);
NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
+
+ boolean longPressWantsIt = false;
+ if (mLongPressedView != null) {
+ longPressWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
+ }
boolean expandWantsIt = false;
- if (!mSwipeHelper.isSwiping()
+ if (mLongPressedView == null && !mSwipeHelper.isSwiping()
&& !mView.getOnlyScrollingInThisMotion() && guts == null) {
expandWantsIt = mView.getExpandHelper().onInterceptTouchEvent(ev);
}
boolean scrollWantsIt = false;
- if (!mSwipeHelper.isSwiping() && !mView.isExpandingNotification()) {
+ if (mLongPressedView == null && !mSwipeHelper.isSwiping()
+ && !mView.isExpandingNotification()) {
scrollWantsIt = mView.onInterceptTouchEventScroll(ev);
}
boolean swipeWantsIt = false;
- if (!mView.isBeingDragged()
+ if (mLongPressedView == null && !mView.isBeingDragged()
&& !mView.isExpandingNotification()
&& !mView.getExpandedInThisMotion()
&& !mView.getOnlyScrollingInThisMotion()
@@ -1732,7 +1792,7 @@ public class NotificationStackScrollLayoutController {
InteractionJankMonitor.getInstance().begin(mView,
CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
}
- return swipeWantsIt || scrollWantsIt || expandWantsIt;
+ return swipeWantsIt || scrollWantsIt || expandWantsIt || longPressWantsIt;
}
@Override
@@ -1741,11 +1801,15 @@ public class NotificationStackScrollLayoutController {
boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
|| ev.getActionMasked() == MotionEvent.ACTION_UP;
mView.handleEmptySpaceClick(ev);
+ boolean longPressWantsIt = false;
+ if (guts != null && mLongPressedView != null) {
+ longPressWantsIt = mSwipeHelper.onTouchEvent(ev);
+ }
boolean expandWantsIt = false;
boolean onlyScrollingInThisMotion = mView.getOnlyScrollingInThisMotion();
boolean expandingNotification = mView.isExpandingNotification();
- if (mView.getIsExpanded() && !mSwipeHelper.isSwiping() && !onlyScrollingInThisMotion
- && guts == null) {
+ if (mLongPressedView == null && mView.getIsExpanded()
+ && !mSwipeHelper.isSwiping() && !onlyScrollingInThisMotion && guts == null) {
ExpandHelper expandHelper = mView.getExpandHelper();
if (isCancelOrUp) {
expandHelper.onlyObserveMovements(false);
@@ -1759,12 +1823,12 @@ public class NotificationStackScrollLayoutController {
}
}
boolean scrollerWantsIt = false;
- if (mView.isExpanded() && !mSwipeHelper.isSwiping() && !expandingNotification
- && !mView.getDisallowScrollingInThisMotion()) {
+ if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
+ && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
scrollerWantsIt = mView.onScrollTouch(ev);
}
boolean horizontalSwipeWantsIt = false;
- if (!mView.isBeingDragged()
+ if (mLongPressedView == null && !mView.isBeingDragged()
&& !expandingNotification
&& !mView.getExpandedInThisMotion()
&& !onlyScrollingInThisMotion
@@ -1790,7 +1854,7 @@ public class NotificationStackScrollLayoutController {
mView.setCheckForLeaveBehind(true);
}
traceJankOnTouchEvent(ev.getActionMasked(), scrollerWantsIt);
- return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt;
+ return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || longPressWantsIt;
}
private void traceJankOnTouchEvent(int action, boolean scrollerWantsIt) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
index 99ec7548fb9d..baf09c70f936 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
@@ -85,8 +85,12 @@ public class SectionHeaderView extends StackScrollerDecorView {
return true;
}
- void setAreThereDismissableGentleNotifs(boolean areThereDismissableGentleNotifs) {
- mClearAllButton.setVisibility(areThereDismissableGentleNotifs ? View.VISIBLE : View.GONE);
+ /**
+ * Show the clear section [X] button
+ * @param enabled
+ */
+ public void setClearSectionButtonEnabled(boolean enabled) {
+ mClearAllButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 8be5de7ae56e..015edb8e5541 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -24,8 +24,11 @@ import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
+import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -54,8 +57,7 @@ public class StackScrollAlgorithm {
private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
private boolean mIsExpanded;
private boolean mClipNotificationScrollToTop;
- private int mStatusBarHeight;
- private float mHeadsUpInset;
+ @VisibleForTesting float mHeadsUpInset;
private int mPinnedZTranslationExtra;
private float mNotificationScrimPadding;
@@ -75,9 +77,9 @@ public class StackScrollAlgorithm {
mPaddingBetweenElements = res.getDimensionPixelSize(
R.dimen.notification_divider_height);
mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
- mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height);
mClipNotificationScrollToTop = res.getBoolean(R.bool.config_clipNotificationScrollToTop);
- mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize(
+ int statusBarHeight = SystemBarUtils.getStatusBarHeight(context);
+ mHeadsUpInset = statusBarHeight + res.getDimensionPixelSize(
R.dimen.heads_up_status_bar_padding);
mPinnedZTranslationExtra = res.getDimensionPixelSize(
R.dimen.heads_up_pinned_elevation);
@@ -108,6 +110,15 @@ public class StackScrollAlgorithm {
getNotificationChildrenStates(algorithmState, ambientState);
}
+ /**
+ * How expanded or collapsed notifications are when pulling down the shade.
+ * @param ambientState Current ambient state.
+ * @return 0 when fully collapsed, 1 when expanded.
+ */
+ public float getNotificationSquishinessFraction(AmbientState ambientState) {
+ return getExpansionFractionWithoutShelf(mTempAlgorithmState, ambientState);
+ }
+
private void resetChildViewStates() {
int numChildren = mHostView.getChildCount();
for (int i = 0; i < numChildren; i++) {
@@ -362,7 +373,6 @@ public class StackScrollAlgorithm {
final float stackHeight = ambientState.getStackHeight() - shelfHeight - scrimPadding;
final float stackEndHeight = ambientState.getStackEndHeight() - shelfHeight - scrimPadding;
-
return stackHeight / stackEndHeight;
}
@@ -407,8 +417,8 @@ public class StackScrollAlgorithm {
viewState.alpha = 1f - ambientState.getHideAmount();
} else if (ambientState.isExpansionChanging()) {
// Adjust alpha for shade open & close.
- viewState.alpha = Interpolators.getNotificationScrimAlpha(
- ambientState.getExpansionFraction(), true /* notification */);
+ float expansion = ambientState.getExpansionFraction();
+ viewState.alpha = ShadeInterpolation.getContentAlpha(expansion);
}
if (ambientState.isShadeExpanded() && view.mustStayOnScreen()
@@ -562,13 +572,14 @@ public class StackScrollAlgorithm {
// Move the tracked heads up into position during the appear animation, by interpolating
// between the HUN inset (where it will appear as a HUN) and the end position in the shade
+ float headsUpTranslation = mHeadsUpInset - ambientState.getStackTopMargin();
ExpandableNotificationRow trackedHeadsUpRow = ambientState.getTrackedHeadsUpRow();
if (trackedHeadsUpRow != null) {
ExpandableViewState childState = trackedHeadsUpRow.getViewState();
if (childState != null) {
float endPosition = childState.yTranslation - ambientState.getStackTranslation();
childState.yTranslation = MathUtils.lerp(
- mHeadsUpInset, endPosition, ambientState.getAppearFraction());
+ headsUpTranslation, endPosition, ambientState.getAppearFraction());
}
}
@@ -602,7 +613,7 @@ public class StackScrollAlgorithm {
}
}
if (row.isPinned()) {
- childState.yTranslation = Math.max(childState.yTranslation, mHeadsUpInset);
+ childState.yTranslation = Math.max(childState.yTranslation, headsUpTranslation);
childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
childState.hidden = false;
ExpandableViewState topState =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 4466cfe99fe1..e3a4bf0170fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -433,6 +433,7 @@ public class StackStateAnimator {
if (row.isDismissed()) {
needsAnimation = false;
}
+
NotificationEntry entry = row.getEntry();
StatusBarIconView icon = entry.getIcons().getStatusBarIcon();
final StatusBarIconView centeredIcon = entry.getIcons().getCenteredIcon();
@@ -442,7 +443,8 @@ public class StackStateAnimator {
if (icon.getParent() != null) {
icon.getLocationOnScreen(mTmpLocation);
float iconPosition = mTmpLocation[0] - icon.getTranslationX()
- + ViewState.getFinalTranslationX(icon) + icon.getWidth() * 0.25f;
+ + ViewState.getFinalTranslationX(icon)
+ + icon.getWidth() * 0.25f;
mHostLayout.getLocationOnScreen(mTmpLocation);
targetLocation = iconPosition - mTmpLocation[0];
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index aeb2efd2026a..111cbbe81755 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -37,6 +37,7 @@ public class AutoHideController {
private final Handler mHandler;
private AutoHideUiElement mStatusBar;
+ /** For tablets, this will represent the Taskbar */
private AutoHideUiElement mNavigationBar;
private int mDisplayId;
@@ -89,7 +90,7 @@ public class AutoHideController {
}
}
- void resumeSuspendedAutoHide() {
+ public void resumeSuspendedAutoHide() {
if (mAutoHideSuspended) {
scheduleAutoHide();
Runnable checkBarModesRunnable = getCheckBarModesRunnable();
@@ -99,7 +100,7 @@ public class AutoHideController {
}
}
- void suspendAutoHide() {
+ public void suspendAutoHide() {
mHandler.removeCallbacks(mAutoHide);
Runnable checkBarModesRunnable = getCheckBarModesRunnable();
if (checkBarModesRunnable != null) {
@@ -171,4 +172,23 @@ public class AutoHideController {
return false;
}
+
+ /**
+ * Injectable factory for creating a {@link AutoHideController}.
+ */
+ public static class Factory {
+ private final Handler mHandler;
+ private final IWindowManager mIWindowManager;
+
+ @Inject
+ public Factory(@Main Handler handler, IWindowManager iWindowManager) {
+ mHandler = handler;
+ mIWindowManager = iWindowManager;
+ }
+
+ /** Create an {@link AutoHideController} */
+ public AutoHideController create(Context context) {
+ return new AutoHideController(context, mHandler, mIWindowManager);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 6d12a1cf10f5..989f6b85668b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -39,10 +39,15 @@ import android.widget.LinearLayout;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.DisableFlagsLogger.DisableState;
+import com.android.systemui.statusbar.OperatorNameView;
+import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
@@ -50,16 +55,17 @@ import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
import com.android.systemui.statusbar.policy.EncryptionHelper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import javax.inject.Inject;
+import dagger.Lazy;
+
/**
* Contains the collapsed status bar and handles hiding/showing based on disable flags
* and keyguard state. Also manages lifecycle to make sure the views it contains are being
@@ -85,10 +91,11 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private View mCenteredIconArea;
private int mDisabled1;
private int mDisabled2;
- private final StatusBar mStatusBarComponent;
+ private Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
private DarkIconManager mDarkIconManager;
- private View mOperatorNameFrame;
private final CommandQueue mCommandQueue;
+ private final CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger;
+ private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
private final OngoingCallController mOngoingCallController;
private final SystemStatusAnimationScheduler mAnimationScheduler;
private final StatusBarLocationPublisher mLocationPublisher;
@@ -111,6 +118,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
disable(getContext().getDisplayId(), mDisabled1, mDisabled2, animate);
}
};
+ private OperatorNameViewController mOperatorNameViewController;
@Inject
public CollapsedStatusBarFragment(
@@ -119,24 +127,28 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
StatusBarLocationPublisher locationPublisher,
NotificationIconAreaController notificationIconAreaController,
FeatureFlags featureFlags,
+ Lazy<Optional<StatusBar>> statusBarOptionalLazy,
StatusBarIconController statusBarIconController,
KeyguardStateController keyguardStateController,
NetworkController networkController,
StatusBarStateController statusBarStateController,
- StatusBar statusBarComponent,
- CommandQueue commandQueue
+ CommandQueue commandQueue,
+ CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
+ OperatorNameViewController.Factory operatorNameViewControllerFactory
) {
mOngoingCallController = ongoingCallController;
mAnimationScheduler = animationScheduler;
mLocationPublisher = locationPublisher;
mNotificationIconAreaController = notificationIconAreaController;
mFeatureFlags = featureFlags;
+ mStatusBarOptionalLazy = statusBarOptionalLazy;
mStatusBarIconController = statusBarIconController;
mKeyguardStateController = keyguardStateController;
mNetworkController = networkController;
mStatusBarStateController = statusBarStateController;
- mStatusBarComponent = statusBarComponent;
mCommandQueue = commandQueue;
+ mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger;
+ mOperatorNameViewControllerFactory = operatorNameViewControllerFactory;
}
@Override
@@ -236,7 +248,14 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
if (displayId != getContext().getDisplayId()) {
return;
}
+
+ int state1BeforeAdjustment = state1;
state1 = adjustDisableFlags(state1);
+
+ mCollapsedStatusBarFragmentLogger.logDisableFlagChange(
+ new DisableState(state1BeforeAdjustment, state2),
+ new DisableState(state1, state2));
+
final int old1 = mDisabled1;
final int diff1 = state1 ^ old1;
final int old2 = mDisabled2;
@@ -272,7 +291,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
protected int adjustDisableFlags(int state) {
- boolean headsUpVisible = mStatusBarComponent.headsUpShouldBeVisible();
+ boolean headsUpVisible = mStatusBarOptionalLazy.get()
+ .map(StatusBar::headsUpShouldBeVisible).orElse(false);
if (headsUpVisible) {
state |= DISABLE_CLOCK;
}
@@ -300,7 +320,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
// The shelf will be hidden when dozing with a custom clock, we must show notification
// icons in this occasion.
if (mStatusBarStateController.isDozing()
- && mStatusBarComponent.getPanelController().hasCustomClock()) {
+ && mStatusBarOptionalLazy.get().map(
+ sb -> sb.getPanelController().hasCustomClock()).orElse(false)) {
state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO;
}
@@ -341,10 +362,13 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
private boolean shouldHideNotificationIcons() {
- if (!mStatusBar.isClosed() && mStatusBarComponent.hideStatusBarIconsWhenExpanded()) {
+ final Optional<StatusBar> statusBarOptional = mStatusBarOptionalLazy.get();
+ if (!mStatusBar.isClosed()
+ && statusBarOptional.map(
+ StatusBar::hideStatusBarIconsWhenExpanded).orElse(false)) {
return true;
}
- if (mStatusBarComponent.hideStatusBarIconsForBouncer()) {
+ if (statusBarOptional.map(StatusBar::hideStatusBarIconsForBouncer).orElse(false)) {
return true;
}
return false;
@@ -403,14 +427,14 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
public void hideOperatorName(boolean animate) {
- if (mOperatorNameFrame != null) {
- animateHide(mOperatorNameFrame, animate);
+ if (mOperatorNameViewController != null) {
+ animateHide(mOperatorNameViewController.getView(), animate);
}
}
public void showOperatorName(boolean animate) {
- if (mOperatorNameFrame != null) {
- animateShow(mOperatorNameFrame, animate);
+ if (mOperatorNameViewController != null) {
+ animateShow(mOperatorNameViewController.getView(), animate);
}
}
@@ -487,7 +511,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private void initOperatorName() {
if (getResources().getBoolean(R.bool.config_showOperatorNameInStatusBar)) {
ViewStub stub = mStatusBar.findViewById(R.id.operator_name);
- mOperatorNameFrame = stub.inflate();
+ mOperatorNameViewController =
+ mOperatorNameViewControllerFactory.create((OperatorNameView) stub.inflate());
+ mOperatorNameViewController.init();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLogger.kt
new file mode 100644
index 000000000000..3c2b555eea68
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLogger.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.systemui.statusbar.phone
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.CollapsedSbFragmentLog
+import com.android.systemui.statusbar.DisableFlagsLogger
+import javax.inject.Inject
+
+/** Used by [CollapsedStatusBarFragment] to log messages to a [LogBuffer]. */
+class CollapsedStatusBarFragmentLogger @Inject constructor(
+ @CollapsedSbFragmentLog private val buffer: LogBuffer,
+ private val disableFlagsLogger: DisableFlagsLogger,
+) {
+
+ /** Logs a string representing the old and new disable flag states to [buffer]. */
+ fun logDisableFlagChange(
+ oldState: DisableFlagsLogger.DisableState,
+ newState: DisableFlagsLogger.DisableState) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ int1 = oldState.disable1
+ int2 = oldState.disable2
+ long1 = newState.disable1.toLong()
+ long2 = newState.disable2.toLong()
+ },
+ {
+ disableFlagsLogger.getDisableFlagsString(
+ DisableFlagsLogger.DisableState(int1, int2),
+ DisableFlagsLogger.DisableState(long1.toInt(), long2.toInt())
+ )
+ }
+ )
+ }
+}
+
+private const val TAG = "CollapsedSbFragment" \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 07618da4451a..96fa8a5cfd71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.res.Configuration
+import android.graphics.Rect
import android.os.LocaleList
import android.view.View.LAYOUT_DIRECTION_RTL
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -29,6 +30,7 @@ class ConfigurationControllerImpl(context: Context) : ConfigurationController {
private val lastConfig = Configuration()
private var density: Int = 0
private var smallestScreenWidth: Int = 0
+ private var maxBounds: Rect? = null
private var fontScale: Float = 0.toFloat()
private val inCarMode: Boolean
private var uiMode: Int = 0
@@ -85,6 +87,14 @@ class ConfigurationControllerImpl(context: Context) : ConfigurationController {
}
}
+ val maxBounds = newConfig.windowConfiguration.maxBounds
+ if (maxBounds != this.maxBounds) {
+ this.maxBounds = maxBounds
+ listeners.filterForEach({ this.listeners.contains(it) }) {
+ it.onMaxBoundsChanged()
+ }
+ }
+
val localeList = newConfig.locales
if (localeList != this.localeList) {
this.localeList = localeList
@@ -113,7 +123,7 @@ class ConfigurationControllerImpl(context: Context) : ConfigurationController {
if (lastConfig.updateFrom(newConfig) and ActivityInfo.CONFIG_ASSETS_PATHS != 0) {
listeners.filterForEach({ this.listeners.contains(it) }) {
- it.onOverlayChanged()
+ it.onThemeChanged()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index f25359e5f481..d06de75056d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -25,6 +25,7 @@ import android.widget.ImageView;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.CommandQueue;
import java.io.FileDescriptor;
@@ -50,11 +51,16 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,
/**
*/
@Inject
- public DarkIconDispatcherImpl(Context context, CommandQueue commandQueue) {
+ public DarkIconDispatcherImpl(
+ Context context,
+ CommandQueue commandQueue,
+ DumpManager dumpManager) {
mDarkModeIconColorSingleTone = context.getColor(R.color.dark_mode_icon_color_single_tone);
mLightModeIconColorSingleTone = context.getColor(R.color.light_mode_icon_color_single_tone);
mTransitionsController = new LightBarTransitionsController(context, this, commandQueue);
+
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
public LightBarTransitionsController getTransitionsController() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index b4f8126042ce..908cd34d7cad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -29,9 +29,9 @@ import android.widget.LinearLayout;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.R;
import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index e67c6ac3a7c8..49e3fe7df2be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -22,6 +22,7 @@ import android.os.PowerManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.Log;
import android.util.MathUtils;
import androidx.annotation.NonNull;
@@ -33,8 +34,9 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.tuner.TunerService;
import java.io.FileDescriptor;
@@ -48,8 +50,10 @@ import javax.inject.Inject;
* Retrieve doze information
*/
@SysUISingleton
-public class DozeParameters implements TunerService.Tunable,
- com.android.systemui.plugins.statusbar.DozeParameters, Dumpable {
+public class DozeParameters implements
+ TunerService.Tunable,
+ com.android.systemui.plugins.statusbar.DozeParameters,
+ Dumpable {
private static final int MAX_DURATION = 60 * 1000;
public static final boolean FORCE_NO_BLANKING =
SystemProperties.getBoolean("debug.force_no_blanking", false);
@@ -254,9 +258,20 @@ public class DozeParameters implements TunerService.Tunable,
}
/**
+ * Whether the single tap sensor uses the proximity sensor for this device posture.
+ */
+ public boolean singleTapUsesProx(@DevicePostureController.DevicePostureInt int devicePosture) {
+ return getPostureSpecificBool(
+ mResources.getIntArray(R.array.doze_single_tap_uses_prox_posture_mapping),
+ singleTapUsesProx(),
+ devicePosture
+ );
+ }
+
+ /**
* Whether the single tap sensor uses the proximity sensor.
*/
- public boolean singleTapUsesProx() {
+ private boolean singleTapUsesProx() {
return mResources.getBoolean(R.bool.doze_single_tap_uses_prox);
}
@@ -268,6 +283,15 @@ public class DozeParameters implements TunerService.Tunable,
}
/**
+ * Gets the brightness string array per posture. Brightness names along with
+ * doze_brightness_sensor_type is used to determine the brightness sensor to use for
+ * the current posture.
+ */
+ public String[] brightnessNames() {
+ return mResources.getStringArray(R.array.doze_brightness_sensor_name_posture_mapping);
+ }
+
+ /**
* Callback to listen for DozeParameter changes.
*/
public void addCallback(Callback callback) {
@@ -306,6 +330,20 @@ public class DozeParameters implements TunerService.Tunable,
pw.println(getSelectivelyRegisterSensorsUsingProx());
}
+ private boolean getPostureSpecificBool(
+ int[] postureMapping,
+ boolean defaultSensorBool,
+ int posture) {
+ boolean bool = defaultSensorBool;
+ if (posture < postureMapping.length) {
+ bool = postureMapping[posture] != 0;
+ } else {
+ Log.e("DozeParameters", "Unsupported doze posture " + posture);
+ }
+
+ return bool;
+ }
+
interface Callback {
/**
* Invoked when the value of getAlwaysOn may have changed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index f289b9f20211..57b9c03ce576 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -226,11 +226,11 @@ public final class DozeServiceHost implements DozeHost {
return;
}
- if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+ if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH) {
mScrimController.setWakeLockScreenSensorActive(true);
}
- boolean passiveAuthInterrupt = reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN
+ boolean passiveAuthInterrupt = reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH
&& mWakeLockScreenPerformsAuth;
// Set the state to pulsing, so ScrimController will know what to do once we ask it to
// execute the transition. The pulse callback will then be invoked when the scrims
@@ -329,7 +329,7 @@ public final class DozeServiceHost implements DozeHost {
@Override
public void extendPulse(int reason) {
- if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+ if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH) {
mScrimController.setWakeLockScreenSensorActive(true);
}
if (mDozeScrimController.isPulsing() && mHeadsUpManagerPhone.hasNotifications()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 878fbbf39627..927b4c8cc919 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -163,7 +163,6 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener,
mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null);
mWakeUpCoordinator.removeListener(this);
mNotificationPanelViewController.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
- mNotificationPanelViewController.setVerticalTranslationListener(null);
mNotificationPanelViewController.setHeadsUpAppearanceController(null);
mStackScrollerController.removeOnExpandedHeightChangedListener(mSetExpandedHeight);
mDarkIconDispatcher.removeDarkReceiver(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 4b545ebf2a05..c81196d7a0f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -26,6 +26,7 @@ import android.util.Pools;
import androidx.collection.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -121,7 +122,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
updateResources();
}
});
@@ -137,9 +138,8 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
private void updateResources() {
Resources resources = mContext.getResources();
- mHeadsUpInset =
- resources.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height)
- + resources.getDimensionPixelSize(R.dimen.heads_up_status_bar_padding);
+ mHeadsUpInset = SystemBarUtils.getStatusBarHeight(mContext)
+ + resources.getDimensionPixelSize(R.dimen.heads_up_status_bar_padding);
}
///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 4701d8b32ee6..e26f75f7ce40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -80,6 +80,7 @@ import com.android.settingslib.Utils;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.camera.CameraIntents;
@@ -1046,11 +1047,13 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
return;
}
+ ActivityLaunchAnimator.Controller animationController = createLaunchAnimationController(v);
if (mHasCard) {
Intent intent = new Intent(mContext, WalletActivity.class)
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(intent);
+ mActivityStarter.startActivity(intent, true /* dismissShade */, animationController,
+ true /* showOverLockscreenWhenLocked */);
} else {
if (mQuickAccessWalletController.getWalletClient().createWalletIntent() == null) {
Log.w(TAG, "Could not get intent of the wallet app.");
@@ -1058,10 +1061,14 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
mActivityStarter.postStartActivityDismissingKeyguard(
mQuickAccessWalletController.getWalletClient().createWalletIntent(),
- /* delay= */ 0);
+ /* delay= */ 0, animationController);
}
}
+ protected ActivityLaunchAnimator.Controller createLaunchAnimationController(View view) {
+ return ActivityLaunchAnimator.Controller.fromView(view, null);
+ }
+
private void onControlsClick(View v) {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
@@ -1071,10 +1078,14 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(ControlsUiController.EXTRA_ANIMATE, true);
+ ActivityLaunchAnimator.Controller controller =
+ v != null ? ActivityLaunchAnimator.Controller.fromView(v, null /* cujType */)
+ : null;
if (mControlsComponent.getVisibility() == AVAILABLE) {
- mContext.startActivity(intent);
+ mActivityStarter.startActivity(intent, true /* dismissShade */, controller,
+ true /* showOverLockscreenWhenLocked */);
} else {
- mActivityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */);
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */, controller);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 8c0dfc5f7ab4..353868ba969f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -30,6 +30,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
+import com.android.internal.policy.SystemBarUtils;
import com.android.keyguard.KeyguardHostViewController;
import com.android.keyguard.KeyguardRootViewController;
import com.android.keyguard.KeyguardSecurityModel;
@@ -44,6 +45,7 @@ import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.ListenerSet;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -83,11 +85,11 @@ public class KeyguardBouncer {
private final Runnable mRemoveViewRunnable = this::removeView;
private final KeyguardBypassController mKeyguardBypassController;
private KeyguardHostViewController mKeyguardViewController;
- private final List<KeyguardResetCallback> mResetCallbacks = new ArrayList<>();
+ private final ListenerSet<KeyguardResetCallback> mResetCallbacks = new ListenerSet<>();
private final Runnable mResetRunnable = ()-> {
if (mKeyguardViewController != null) {
mKeyguardViewController.resetSecurityContainer();
- for (KeyguardResetCallback callback : new ArrayList<>(mResetCallbacks)) {
+ for (KeyguardResetCallback callback : mResetCallbacks) {
callback.onKeyguardReset();
}
}
@@ -126,6 +128,19 @@ public class KeyguardBouncer {
mExpansionCallbacks.add(expansionCallback);
}
+ /**
+ * Enable/disable only the back button
+ */
+ public void setBackButtonEnabled(boolean enabled) {
+ int vis = mContainer.getSystemUiVisibility();
+ if (enabled) {
+ vis &= ~View.STATUS_BAR_DISABLE_BACK;
+ } else {
+ vis |= View.STATUS_BAR_DISABLE_BACK;
+ }
+ mContainer.setSystemUiVisibility(vis);
+ }
+
public void show(boolean resetSecuritySelection) {
show(resetSecuritySelection, true /* scrimmed */);
}
@@ -252,8 +267,6 @@ public class KeyguardBouncer {
mKeyguardViewController.resetSecurityContainer();
showPromptReason(mBouncerPromptReason);
}
- SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
- SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN);
}
};
@@ -456,8 +469,7 @@ public class KeyguardBouncer {
mKeyguardViewController.init();
mContainer.addView(mRoot, mContainer.getChildCount());
- mStatusBarHeight = mRoot.getResources().getDimensionPixelOffset(
- com.android.systemui.R.dimen.status_bar_height);
+ mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
setVisibility(View.INVISIBLE);
final WindowInsets rootInsets = mRoot.getRootWindowInsets();
@@ -591,7 +603,7 @@ public class KeyguardBouncer {
}
public void addKeyguardResetCallback(KeyguardResetCallback callback) {
- mResetCallbacks.add(callback);
+ mResetCallbacks.addIfAbsent(callback);
}
public void removeKeyguardResetCallback(KeyguardResetCallback callback) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index 3a4a819bc623..3d5f7553348a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -43,6 +43,11 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
@BypassOverride private val bypassOverride: Int
private var hasFaceFeature: Boolean
private var pendingUnlock: PendingUnlock? = null
+ private val listeners = mutableListOf<OnBypassStateChangedListener>()
+
+ private val faceAuthEnabledChangedCallback = object : KeyguardStateController.Callback {
+ override fun onFaceAuthEnabledChanged() = notifyListeners()
+ }
var userHasDeviceEntryIntent: Boolean = false // ie: attempted udfps auth
@IntDef(
@@ -84,7 +89,10 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
}
return enabled && mKeyguardStateController.isFaceAuthEnabled
}
- private set
+ private set(value) {
+ field = value
+ notifyListeners()
+ }
var bouncerShowing: Boolean = false
var altBouncerShowing: Boolean = false
@@ -141,6 +149,8 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
})
}
+ private fun notifyListeners() = listeners.forEach { it.onBypassStateChanged(bypassEnabled) }
+
/**
* Notify that the biometric unlock has happened.
*
@@ -225,6 +235,32 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr
pw.println(" userHasDeviceEntryIntent: $userHasDeviceEntryIntent")
}
+ /** Registers a listener for bypass state changes. */
+ fun registerOnBypassStateChangedListener(listener: OnBypassStateChangedListener) {
+ val start = listeners.isEmpty()
+ listeners.add(listener)
+ if (start) {
+ mKeyguardStateController.addCallback(faceAuthEnabledChangedCallback)
+ }
+ }
+
+ /**
+ * Unregisters a listener for bypass state changes, previous registered with
+ * [registerOnBypassStateChangedListener]
+ */
+ fun unregisterOnBypassStateChangedListener(listener: OnBypassStateChangedListener) {
+ listeners.remove(listener)
+ if (listeners.isEmpty()) {
+ mKeyguardStateController.removeCallback(faceAuthEnabledChangedCallback)
+ }
+ }
+
+ /** Listener for bypass state change events. */
+ interface OnBypassStateChangedListener {
+ /** Invoked when bypass becomes enabled or disabled. */
+ fun onBypassStateChanged(isEnabled: Boolean)
+ }
+
companion object {
const val BYPASS_FADE_DURATION = 67
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index b58cab4f1ea4..4f3bbdbff030 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -32,12 +32,6 @@ import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
* Utility class to calculate the clock position and top padding of notifications on Keyguard.
*/
public class KeyguardClockPositionAlgorithm {
- /**
- * How much the clock height influences the shade position.
- * 0 means nothing, 1 means move the shade up by the height of the clock
- * 0.5f means move the shade up by half of the size of the clock.
- */
- private static float CLOCK_HEIGHT_WEIGHT = 0.7f;
/**
* Margin between the bottom of the status view and the notification shade.
@@ -45,11 +39,6 @@ public class KeyguardClockPositionAlgorithm {
private int mStatusViewBottomMargin;
/**
- * Height of the parent view - display size in px.
- */
- private int mHeight;
-
- /**
* Height of {@link KeyguardStatusView}.
*/
private int mKeyguardStatusHeight;
@@ -83,7 +72,8 @@ public class KeyguardClockPositionAlgorithm {
private int mNotificationStackHeight;
/**
- * Minimum top margin to avoid overlap with status bar, or multi-user switcher avatar.
+ * Minimum top margin to avoid overlap with status bar, lock icon, or multi-user switcher
+ * avatar.
*/
private int mMinTopMargin;
@@ -93,12 +83,6 @@ public class KeyguardClockPositionAlgorithm {
private int mCutoutTopInset = 0;
/**
- * Maximum bottom padding to avoid overlap with {@link KeyguardBottomAreaView} or
- * the ambient indication.
- */
- private int mMaxShadeBottom;
-
- /**
* Recommended distance from the status bar.
*/
private int mContainerTopPadding;
@@ -114,14 +98,9 @@ public class KeyguardClockPositionAlgorithm {
private int mBurnInPreventionOffsetX;
/**
- * Burn-in prevention y translation.
- */
- private int mBurnInPreventionOffsetY;
-
- /**
- * Burn-in prevention y translation for large clock layouts.
+ * Burn-in prevention y translation for clock layouts.
*/
- private int mBurnInPreventionOffsetYLargeClock;
+ private int mBurnInPreventionOffsetYClock;
/**
* Doze/AOD transition amount.
@@ -178,35 +157,26 @@ public class KeyguardClockPositionAlgorithm {
res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
mBurnInPreventionOffsetX = res.getDimensionPixelSize(
R.dimen.burn_in_prevention_offset_x);
- mBurnInPreventionOffsetY = res.getDimensionPixelSize(
- R.dimen.burn_in_prevention_offset_y);
- mBurnInPreventionOffsetYLargeClock = res.getDimensionPixelSize(
- R.dimen.burn_in_prevention_offset_y_large_clock);
+ mBurnInPreventionOffsetYClock = res.getDimensionPixelSize(
+ R.dimen.burn_in_prevention_offset_y_clock);
}
/**
* Sets up algorithm values.
*/
- public void setup(int keyguardStatusBarHeaderHeight, int maxShadeBottom,
- int notificationStackHeight, float panelExpansion, int parentHeight,
+ public void setup(int keyguardStatusBarHeaderHeight, float panelExpansion,
int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY,
- boolean hasCustomClock, boolean hasVisibleNotifs, float dark,
- float overStrechAmount, boolean bypassEnabled, int unlockedStackScrollerPadding,
- float qsExpansion, int cutoutTopInset, boolean isSplitShade, float udfpsTop,
- float clockBottom, boolean isClockTopAligned) {
+ float dark, float overStretchAmount, boolean bypassEnabled,
+ int unlockedStackScrollerPadding, float qsExpansion, int cutoutTopInset,
+ boolean isSplitShade, float udfpsTop, float clockBottom, boolean isClockTopAligned) {
mMinTopMargin = keyguardStatusBarHeaderHeight + Math.max(mContainerTopPadding,
userSwitchHeight);
- mMaxShadeBottom = maxShadeBottom;
- mNotificationStackHeight = notificationStackHeight;
mPanelExpansion = panelExpansion;
- mHeight = parentHeight;
mKeyguardStatusHeight = keyguardStatusHeight + mStatusViewBottomMargin;
mUserSwitchHeight = userSwitchHeight;
mUserSwitchPreferredY = userSwitchPreferredY;
- mHasCustomClock = hasCustomClock;
- mHasVisibleNotifs = hasVisibleNotifs;
mDarkAmount = dark;
- mOverStretchAmount = overStrechAmount;
+ mOverStretchAmount = overStretchAmount;
mBypassEnabled = bypassEnabled;
mUnlockedStackScrollerPadding = unlockedStackScrollerPadding;
mQsExpansion = qsExpansion;
@@ -225,12 +195,21 @@ public class KeyguardClockPositionAlgorithm {
1.0f /* panelExpansion */, 1.0f /* darkAmount */);
result.clockAlpha = getClockAlpha(y);
result.stackScrollerPadding = getStackScrollerPadding(y);
- result.stackScrollerPaddingExpanded = mBypassEnabled ? mUnlockedStackScrollerPadding
- : getClockY(1.0f, mDarkAmount) + mKeyguardStatusHeight;
+ result.stackScrollerPaddingExpanded = getStackScrollerPaddingExpanded();
result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount);
result.clockScale = interpolate(getBurnInScale(), 1.0f, 1.0f - mDarkAmount);
}
+ private int getStackScrollerPaddingExpanded() {
+ if (mBypassEnabled) {
+ return mUnlockedStackScrollerPadding;
+ } else if (mIsSplitShade) {
+ return getClockY(1.0f, mDarkAmount);
+ } else {
+ return getClockY(1.0f, mDarkAmount) + mKeyguardStatusHeight;
+ }
+ }
+
private int getStackScrollerPadding(int clockYPosition) {
if (mBypassEnabled) {
return (int) (mUnlockedStackScrollerPadding + mOverStretchAmount);
@@ -242,8 +221,13 @@ public class KeyguardClockPositionAlgorithm {
}
public float getMinStackScrollerPadding() {
- return mBypassEnabled ? mUnlockedStackScrollerPadding
- : mMinTopMargin + mKeyguardStatusHeight;
+ if (mBypassEnabled) {
+ return mUnlockedStackScrollerPadding;
+ } else if (mIsSplitShade) {
+ return mMinTopMargin;
+ } else {
+ return mMinTopMargin + mKeyguardStatusHeight;
+ }
}
private int getExpandedPreferredClockY() {
@@ -266,11 +250,11 @@ public class KeyguardClockPositionAlgorithm {
// This will keep the clock at the top but out of the cutout area
float shift = 0;
- if (clockY - mBurnInPreventionOffsetYLargeClock < mCutoutTopInset) {
- shift = mCutoutTopInset - (clockY - mBurnInPreventionOffsetYLargeClock);
+ if (clockY - mBurnInPreventionOffsetYClock < mCutoutTopInset) {
+ shift = mCutoutTopInset - (clockY - mBurnInPreventionOffsetYClock);
}
- int burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock; // requested offset
+ int burnInPreventionOffsetY = mBurnInPreventionOffsetYClock; // requested offset
final boolean hasUdfps = mUdfpsTop > -1;
if (hasUdfps && !mIsClockTopAligned) {
// ensure clock doesn't overlap with the udfps icon
@@ -278,8 +262,8 @@ public class KeyguardClockPositionAlgorithm {
// sometimes the clock textView extends beyond udfps, so let's just use the
// space above the KeyguardStatusView/clock as our burn-in offset
burnInPreventionOffsetY = (int) (clockY - mCutoutTopInset) / 2;
- if (mBurnInPreventionOffsetYLargeClock < burnInPreventionOffsetY) {
- burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock;
+ if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
+ burnInPreventionOffsetY = mBurnInPreventionOffsetYClock;
}
shift = -burnInPreventionOffsetY;
} else {
@@ -287,8 +271,8 @@ public class KeyguardClockPositionAlgorithm {
float lowerSpace = mUdfpsTop - mClockBottom;
// center the burn-in offset within the upper + lower space
burnInPreventionOffsetY = (int) (lowerSpace + upperSpace) / 2;
- if (mBurnInPreventionOffsetYLargeClock < burnInPreventionOffsetY) {
- burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock;
+ if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
+ burnInPreventionOffsetY = mBurnInPreventionOffsetYClock;
}
shift = (lowerSpace - upperSpace) / 2;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index a5b5f1cbf1e7..3a68b9c3d1b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -95,44 +95,85 @@ public class KeyguardIndicationTextView extends TextView {
}
/**
- * Changes the text with an animation and makes sure a single indication is shown long enough.
+ * Changes the text with an animation. Makes sure a single indication is shown long enough.
+ */
+ public void switchIndication(CharSequence text, KeyguardIndication indication) {
+ switchIndication(text, indication, true, null);
+ }
+
+ /**
+ * Changes the text with an optional animation. For animating text, makes sure a single
+ * indication is shown long enough.
*
* @param text The text to show.
* @param indication optional display information for the text
+ * @param animate whether to animate this indication in - we may not want this on AOD
+ * @param onAnimationEndCallback runnable called after this indication is animated in
*/
- public void switchIndication(CharSequence text, KeyguardIndication indication) {
+ public void switchIndication(CharSequence text, KeyguardIndication indication,
+ boolean animate, Runnable onAnimationEndCallback) {
if (text == null) text = "";
CharSequence lastPendingMessage = mMessages.peekLast();
if (TextUtils.equals(lastPendingMessage, text)
|| (lastPendingMessage == null && TextUtils.equals(text, getText()))) {
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
return;
}
mMessages.add(text);
mKeyguardIndicationInfo.add(indication);
- final boolean hasIcon = indication != null && indication.getIcon() != null;
- final AnimatorSet animSet = new AnimatorSet();
- final AnimatorSet.Builder animSetBuilder = animSet.play(getOutAnimator());
-
- // Make sure each animation is visible for a minimum amount of time, while not worrying
- // about fading in blank text
- long timeInMillis = System.currentTimeMillis();
- long delay = Math.max(0, mNextAnimationTime - timeInMillis);
- setNextAnimationTime(timeInMillis + delay + getFadeOutDuration());
-
- final long minDurationMillis =
- (indication != null && indication.getMinVisibilityMillis() != null)
- ? indication.getMinVisibilityMillis()
- : MSG_MIN_DURATION_MILLIS_DEFAULT;
+ if (animate) {
+ final boolean hasIcon = indication != null && indication.getIcon() != null;
+ final AnimatorSet animator = new AnimatorSet();
+ // Make sure each animation is visible for a minimum amount of time, while not worrying
+ // about fading in blank text
+ long timeInMillis = System.currentTimeMillis();
+ long delay = Math.max(0, mNextAnimationTime - timeInMillis);
+ setNextAnimationTime(timeInMillis + delay + getFadeOutDuration());
+ final long minDurationMillis =
+ (indication != null && indication.getMinVisibilityMillis() != null)
+ ? indication.getMinVisibilityMillis()
+ : MSG_MIN_DURATION_MILLIS_DEFAULT;
+ if (!text.equals("") || hasIcon) {
+ setNextAnimationTime(mNextAnimationTime + minDurationMillis);
+ Animator inAnimator = getInAnimator();
+ inAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
+ }
+ });
+ animator.playSequentially(getOutAnimator(), inAnimator);
+ } else {
+ Animator outAnimator = getOutAnimator();
+ outAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
+ }
+ });
+ animator.play(outAnimator);
+ }
- if (!text.equals("") || hasIcon) {
- setNextAnimationTime(mNextAnimationTime + minDurationMillis);
- animSetBuilder.before(getInAnimator());
+ animator.setStartDelay(delay);
+ animator.start();
+ } else {
+ setAlpha(1f);
+ setTranslationY(0f);
+ setNextIndication();
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
}
-
- animSet.setStartDelay(delay);
- animSet.start();
}
private AnimatorSet getOutAnimator() {
@@ -143,29 +184,8 @@ public class KeyguardIndicationTextView extends TextView {
fadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
- KeyguardIndication info = mKeyguardIndicationInfo.poll();
- if (info != null) {
- // First, update the style.
- // If a background is set on the text, we don't want shadow on the text
- if (info.getBackground() != null) {
- setTextAppearance(sButtonStyleId);
- } else {
- setTextAppearance(sStyleId);
- }
- setBackground(info.getBackground());
- setTextColor(info.getTextColor());
- setOnClickListener(info.getClickListener());
- setClickable(info.getClickListener() != null);
- final Drawable icon = info.getIcon();
- if (icon != null) {
- icon.setTint(getCurrentTextColor());
- if (icon instanceof AnimatedVectorDrawable) {
- ((AnimatedVectorDrawable) icon).start();
- }
- }
- setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
- }
- setText(mMessages.poll());
+ super.onAnimationEnd(animator);
+ setNextIndication();
}
});
@@ -177,6 +197,32 @@ public class KeyguardIndicationTextView extends TextView {
return animatorSet;
}
+ private void setNextIndication() {
+ KeyguardIndication info = mKeyguardIndicationInfo.poll();
+ if (info != null) {
+ // First, update the style.
+ // If a background is set on the text, we don't want shadow on the text
+ if (info.getBackground() != null) {
+ setTextAppearance(sButtonStyleId);
+ } else {
+ setTextAppearance(sStyleId);
+ }
+ setBackground(info.getBackground());
+ setTextColor(info.getTextColor());
+ setOnClickListener(info.getClickListener());
+ setClickable(info.getClickListener() != null);
+ final Drawable icon = info.getIcon();
+ if (icon != null) {
+ icon.setTint(getCurrentTextColor());
+ if (icon instanceof AnimatedVectorDrawable) {
+ ((AnimatedVectorDrawable) icon).start();
+ }
+ }
+ setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
+ }
+ setText(mMessages.poll());
+ }
+
private AnimatorSet getInAnimator() {
AnimatorSet animatorSet = new AnimatorSet();
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(this, View.ALPHA, 1f);
@@ -190,6 +236,7 @@ public class KeyguardIndicationTextView extends TextView {
yTranslate.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
setTranslationY(0);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 55e0c9b979c9..03f3b0cab65c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -18,10 +18,8 @@ package com.android.systemui.statusbar.phone;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
+import static com.android.systemui.util.Utils.getStatusBarHeaderHeightKeyguard;
-import android.animation.ValueAnimator;
import android.annotation.ColorInt;
import android.content.Context;
import android.content.res.Configuration;
@@ -45,36 +43,18 @@ import android.widget.RelativeLayout;
import android.widget.TextView;
import com.android.settingslib.Utils;
-import com.android.systemui.BatteryMeterView;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
-import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
-import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
/**
* The header group on Keyguard.
*/
-public class KeyguardStatusBarView extends RelativeLayout implements
- BatteryStateChangeCallback,
- OnUserInfoChangedListener,
- ConfigurationListener,
- SystemStatusAnimationCallback {
+public class KeyguardStatusBarView extends RelativeLayout {
private static final int LAYOUT_NONE = 0;
private static final int LAYOUT_CUTOUT = 1;
@@ -84,31 +64,25 @@ public class KeyguardStatusBarView extends RelativeLayout implements
private boolean mShowPercentAvailable;
private boolean mBatteryCharging;
- private boolean mBatteryListening;
private TextView mCarrierLabel;
private ImageView mMultiUserAvatar;
private BatteryMeterView mBatteryView;
private StatusIconContainer mStatusIconContainer;
- private BatteryController mBatteryController;
private boolean mKeyguardUserSwitcherEnabled;
private final UserManager mUserManager;
+ private boolean mIsPrivacyDotEnabled;
private int mSystemIconsSwitcherHiddenExpandedMargin;
private int mStatusBarPaddingEnd;
private int mMinDotWidth;
private View mSystemIconsContainer;
- private TintedIconManager mIconManager;
- private List<String> mBlockedIcons = new ArrayList<>();
private View mCutoutSpace;
private ViewGroup mStatusIconArea;
private int mLayoutState = LAYOUT_NONE;
- private SystemStatusAnimationScheduler mAnimationScheduler;
- private FeatureFlags mFeatureFlags;
-
/**
* Draw this many pixels into the left/right side of the cutout to optimally use the space
*/
@@ -140,17 +114,14 @@ public class KeyguardStatusBarView extends RelativeLayout implements
mCutoutSpace = findViewById(R.id.cutout_space_view);
mStatusIconArea = findViewById(R.id.status_icon_area);
mStatusIconContainer = findViewById(R.id.statusIcons);
-
+ mIsPrivacyDotEnabled = mContext.getResources().getBoolean(R.bool.config_enablePrivacyDot);
loadDimens();
- loadBlockList();
- mBatteryController = Dependency.get(BatteryController.class);
- mAnimationScheduler = Dependency.get(SystemStatusAnimationScheduler.class);
- mFeatureFlags = Dependency.get(FeatureFlags.class);
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+ loadDimens();
MarginLayoutParams lp = (MarginLayoutParams) mMultiUserAvatar.getLayoutParams();
lp.width = lp.height = getResources().getDimensionPixelSize(
@@ -160,6 +131,22 @@ public class KeyguardStatusBarView extends RelativeLayout implements
// System icons
updateSystemIconsLayoutParams();
+ // mStatusIconArea
+ mStatusIconArea.setPaddingRelative(
+ mStatusIconArea.getPaddingStart(),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_padding_top),
+ mStatusIconArea.getPaddingEnd(),
+ mStatusIconArea.getPaddingBottom()
+ );
+
+ // mStatusIconContainer
+ mStatusIconContainer.setPaddingRelative(
+ mStatusIconContainer.getPaddingStart(),
+ mStatusIconContainer.getPaddingTop(),
+ getResources().getDimensionPixelSize(R.dimen.signal_cluster_battery_padding),
+ mStatusIconContainer.getPaddingBottom()
+ );
+
// Respect font size setting.
mCarrierLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
getResources().getDimensionPixelSize(
@@ -176,15 +163,12 @@ public class KeyguardStatusBarView extends RelativeLayout implements
}
private void updateKeyguardStatusBarHeight() {
- final int waterfallTop =
- mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top;
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
- lp.height = getResources().getDimensionPixelSize(
- R.dimen.status_bar_header_height_keyguard) + waterfallTop;
+ lp.height = getStatusBarHeaderHeightKeyguard(mContext);
setLayoutParams(lp);
}
- private void loadDimens() {
+ void loadDimens() {
Resources res = getResources();
mSystemIconsSwitcherHiddenExpandedMargin = res.getDimensionPixelSize(
R.dimen.system_icons_switcher_hidden_expanded_margin);
@@ -200,14 +184,6 @@ public class KeyguardStatusBarView extends RelativeLayout implements
R.dimen.rounded_corner_content_padding);
}
- // Set hidden status bar items
- private void loadBlockList() {
- Resources r = getResources();
- mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_volume));
- mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_alarm_clock));
- mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_call_strength));
- }
-
private void updateVisibilities() {
if (mMultiUserAvatar.getParent() != mStatusIconArea
&& !mKeyguardUserSwitcherEnabled) {
@@ -293,9 +269,10 @@ public class KeyguardStatusBarView extends RelativeLayout implements
mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding);
// consider privacy dot space
- final int minLeft = isLayoutRtl() ? Math.max(mMinDotWidth, mPadding.first) : mPadding.first;
- final int minRight = isLayoutRtl() ? mPadding.second :
- Math.max(mMinDotWidth, mPadding.second);
+ final int minLeft = (isLayoutRtl() && mIsPrivacyDotEnabled)
+ ? Math.max(mMinDotWidth, mPadding.first) : mPadding.first;
+ final int minRight = (!isLayoutRtl() && mIsPrivacyDotEnabled)
+ ? Math.max(mMinDotWidth, mPadding.second) : mPadding.second;
setPadding(minLeft, waterfallTop, minRight, 0);
}
@@ -358,60 +335,20 @@ public class KeyguardStatusBarView extends RelativeLayout implements
return true;
}
- public void setListening(boolean listening) {
- if (listening == mBatteryListening) {
- return;
- }
- mBatteryListening = listening;
- if (mBatteryListening) {
- mBatteryController.addCallback(this);
- } else {
- mBatteryController.removeCallback(this);
- }
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- UserInfoController userInfoController = Dependency.get(UserInfoController.class);
- userInfoController.addCallback(this);
- userInfoController.reloadUserInfo();
- Dependency.get(ConfigurationController.class).addCallback(this);
- mIconManager = new TintedIconManager(findViewById(R.id.statusIcons), mFeatureFlags);
- mIconManager.setBlockList(mBlockedIcons);
- Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
- mAnimationScheduler.addCallback(this);
- onThemeChanged();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- Dependency.get(UserInfoController.class).removeCallback(this);
- Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager);
- Dependency.get(ConfigurationController.class).removeCallback(this);
- mAnimationScheduler.removeCallback(this);
- }
-
- @Override
- public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
+ /** Should only be called from {@link KeyguardStatusBarViewController}. */
+ void onUserInfoChanged(Drawable picture) {
mMultiUserAvatar.setImageDrawable(picture);
}
- @Override
- public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+ /** Should only be called from {@link KeyguardStatusBarViewController}. */
+ void onBatteryLevelChanged(boolean charging) {
if (mBatteryCharging != charging) {
mBatteryCharging = charging;
updateVisibilities();
}
}
- @Override
- public void onPowerSaveChanged(boolean isPowerSave) {
- // could not care less
- }
-
- public void setKeyguardUserSwitcherEnabled(boolean enabled) {
+ void setKeyguardUserSwitcherEnabled(boolean enabled) {
mKeyguardUserSwitcherEnabled = enabled;
}
@@ -477,28 +414,20 @@ public class KeyguardStatusBarView extends RelativeLayout implements
return false;
}
- public void onThemeChanged() {
+ /** Should only be called from {@link KeyguardStatusBarViewController}. */
+ void onThemeChanged(StatusBarIconController.TintedIconManager iconManager) {
mBatteryView.setColorsFromContext(mContext);
- updateIconsAndTextColors();
- // Reload user avatar
- ((UserInfoControllerImpl) Dependency.get(UserInfoController.class))
- .onDensityOrFontScaleChanged();
+ updateIconsAndTextColors(iconManager);
}
- @Override
- public void onDensityOrFontScaleChanged() {
- loadDimens();
- }
-
- @Override
- public void onOverlayChanged() {
+ /** Should only be called from {@link KeyguardStatusBarViewController}. */
+ void onOverlayChanged() {
mCarrierLabel.setTextAppearance(
Utils.getThemeAttr(mContext, com.android.internal.R.attr.textAppearanceSmall));
- onThemeChanged();
mBatteryView.updatePercentView();
}
- private void updateIconsAndTextColors() {
+ private void updateIconsAndTextColors(StatusBarIconController.TintedIconManager iconManager) {
@ColorInt int textColor = Utils.getColorAttrDefaultColor(mContext,
R.attr.wallpaperTextColor);
@ColorInt int iconColor = Utils.getColorStateListDefaultColor(mContext,
@@ -506,8 +435,8 @@ public class KeyguardStatusBarView extends RelativeLayout implements
R.color.light_mode_icon_color_single_tone);
float intensity = textColor == Color.WHITE ? 0 : 1;
mCarrierLabel.setTextColor(iconColor);
- if (mIconManager != null) {
- mIconManager.setTint(iconColor);
+ if (iconManager != null) {
+ iconManager.setTint(iconColor);
}
applyDarkness(R.id.battery, mEmptyRect, intensity, iconColor);
@@ -532,10 +461,10 @@ public class KeyguardStatusBarView extends RelativeLayout implements
}
}
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ /** Should only be called from {@link KeyguardStatusBarViewController}. */
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("KeyguardStatusBarView:");
pw.println(" mBatteryCharging: " + mBatteryCharging);
- pw.println(" mBatteryListening: " + mBatteryListening);
pw.println(" mLayoutState: " + mLayoutState);
pw.println(" mKeyguardUserSwitcherEnabled: " + mKeyguardUserSwitcherEnabled);
if (mBatteryView != null) {
@@ -543,19 +472,16 @@ public class KeyguardStatusBarView extends RelativeLayout implements
}
}
- /** SystemStatusAnimationCallback */
- @Override
- public void onSystemChromeAnimationStart() {
- if (mAnimationScheduler.getAnimationState() == ANIMATING_OUT) {
+ void onSystemChromeAnimationStart(boolean isAnimatingOut) {
+ if (isAnimatingOut) {
mSystemIconsContainer.setVisibility(View.VISIBLE);
mSystemIconsContainer.setAlpha(0f);
}
}
- @Override
- public void onSystemChromeAnimationEnd() {
+ void onSystemChromeAnimationEnd(boolean isAnimatingIn) {
// Make sure the system icons are out of the way
- if (mAnimationScheduler.getAnimationState() == ANIMATING_IN) {
+ if (isAnimatingIn) {
mSystemIconsContainer.setVisibility(View.INVISIBLE);
mSystemIconsContainer.setAlpha(0f);
} else {
@@ -564,9 +490,8 @@ public class KeyguardStatusBarView extends RelativeLayout implements
}
}
- @Override
- public void onSystemChromeAnimationUpdate(ValueAnimator anim) {
- mSystemIconsContainer.setAlpha((float) anim.getAnimatedValue());
+ void onSystemChromeAnimationUpdate(float animatedValue) {
+ mSystemIconsContainer.setAlpha(animatedValue);
}
@Override
@@ -577,8 +502,10 @@ public class KeyguardStatusBarView extends RelativeLayout implements
/**
* Set the clipping on the top of the view.
+ *
+ * Should only be called from {@link KeyguardStatusBarViewController}.
*/
- public void setTopClipping(int topClipping) {
+ void setTopClipping(int topClipping) {
if (topClipping != mTopClipping) {
mTopClipping = topClipping;
updateClipping();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 377fb92ac6ba..90550818bbdd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -16,33 +16,454 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.res.Resources;
+import android.hardware.biometrics.BiometricSourceType;
+import android.util.MathUtils;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
import com.android.keyguard.CarrierTextController;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
+import com.android.systemui.battery.BatteryMeterViewController;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.util.ViewController;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
import javax.inject.Inject;
/** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */
public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> {
+ private static final AnimationProperties KEYGUARD_HUN_PROPERTIES =
+ new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+
+ private float mKeyguardHeadsUpShowingAmount = 0.0f;
+ private final AnimatableProperty mHeadsUpShowingAmountAnimation = AnimatableProperty.from(
+ "KEYGUARD_HEADS_UP_SHOWING_AMOUNT",
+ (view, aFloat) -> {
+ mKeyguardHeadsUpShowingAmount = aFloat;
+ updateViewState();
+ },
+ view -> mKeyguardHeadsUpShowingAmount,
+ R.id.keyguard_hun_animator_tag,
+ R.id.keyguard_hun_animator_end_tag,
+ R.id.keyguard_hun_animator_start_tag);
+
private final CarrierTextController mCarrierTextController;
+ private final ConfigurationController mConfigurationController;
+ private final SystemStatusAnimationScheduler mAnimationScheduler;
+ private final BatteryController mBatteryController;
+ private final UserInfoController mUserInfoController;
+ private final StatusBarIconController mStatusBarIconController;
+ private final StatusBarIconController.TintedIconManager.Factory mTintedIconManagerFactory;
+ private final BatteryMeterViewController mBatteryMeterViewController;
+ private final NotificationPanelViewController.NotificationPanelViewStateProvider
+ mNotificationPanelViewStateProvider;
+ private final KeyguardStateController mKeyguardStateController;
+ private final KeyguardBypassController mKeyguardBypassController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final BiometricUnlockController mBiometricUnlockController;
+ private final SysuiStatusBarStateController mStatusBarStateController;
+
+ private final ConfigurationController.ConfigurationListener mConfigurationListener =
+ new ConfigurationController.ConfigurationListener() {
+ @Override
+ public void onDensityOrFontScaleChanged() {
+ mView.loadDimens();
+ }
+
+ @Override
+ public void onThemeChanged() {
+ mView.onOverlayChanged();
+ KeyguardStatusBarViewController.this.onThemeChanged();
+ }
+ };
+
+ private final SystemStatusAnimationCallback mAnimationCallback =
+ new SystemStatusAnimationCallback() {
+ @Override
+ public void onSystemChromeAnimationStart() {
+ mView.onSystemChromeAnimationStart(
+ mAnimationScheduler.getAnimationState() == ANIMATING_OUT);
+ }
+
+ @Override
+ public void onSystemChromeAnimationEnd() {
+ mView.onSystemChromeAnimationEnd(
+ mAnimationScheduler.getAnimationState() == ANIMATING_IN);
+ }
+
+ @Override
+ public void onSystemChromeAnimationUpdate(@NonNull ValueAnimator anim) {
+ mView.onSystemChromeAnimationUpdate((float) anim.getAnimatedValue());
+ }
+ };
+
+ private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback =
+ new BatteryController.BatteryStateChangeCallback() {
+ @Override
+ public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+ mView.onBatteryLevelChanged(charging);
+ }
+ };
+
+ private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener =
+ (name, picture, userAccount) -> mView.onUserInfoChanged(picture);
+
+ private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener =
+ animation -> {
+ mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue();
+ updateViewState();
+ };
+
+ private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onBiometricAuthenticated(
+ int userId,
+ BiometricSourceType biometricSourceType,
+ boolean isStrongBiometric) {
+ if (mFirstBypassAttempt
+ && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ isStrongBiometric)) {
+ mDelayShowingKeyguardStatusBar = true;
+ }
+ }
+
+ @Override
+ public void onBiometricRunningStateChanged(
+ boolean running,
+ BiometricSourceType biometricSourceType) {
+ boolean keyguardOrShadeLocked =
+ mStatusBarState == KEYGUARD
+ || mStatusBarState == StatusBarState.SHADE_LOCKED;
+ if (!running
+ && mFirstBypassAttempt
+ && keyguardOrShadeLocked
+ && !mDozing
+ && !mDelayShowingKeyguardStatusBar
+ && !mBiometricUnlockController.isBiometricUnlock()) {
+ mFirstBypassAttempt = false;
+ animateKeyguardStatusBarIn();
+ }
+ }
+
+ @Override
+ public void onFinishedGoingToSleep(int why) {
+ mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
+ mDelayShowingKeyguardStatusBar = false;
+ }
+ };
+
+ private final StatusBarStateController.StateListener mStatusBarStateListener =
+ new StatusBarStateController.StateListener() {
+ @Override
+ public void onStateChanged(int newState) {
+ mStatusBarState = newState;
+ }
+ };
+
+ private final List<String> mBlockedIcons;
+ private final int mNotificationsHeaderCollideDistance;
+
+ private boolean mBatteryListening;
+ private StatusBarIconController.TintedIconManager mTintedIconManager;
+
+ private float mKeyguardStatusBarAnimateAlpha = 1f;
+ /**
+ * If face auth with bypass is running for the first time after you turn on the screen.
+ * (From aod or screen off)
+ */
+ private boolean mFirstBypassAttempt;
+ /**
+ * If auth happens successfully during {@code mFirstBypassAttempt}, and we should wait until
+ * the keyguard is dismissed to show the status bar.
+ */
+ private boolean mDelayShowingKeyguardStatusBar;
+ private int mStatusBarState;
+ private boolean mDozing;
+ private boolean mShowingKeyguardHeadsUp;
@Inject
public KeyguardStatusBarViewController(
- KeyguardStatusBarView view, CarrierTextController carrierTextController) {
+ KeyguardStatusBarView view,
+ CarrierTextController carrierTextController,
+ ConfigurationController configurationController,
+ SystemStatusAnimationScheduler animationScheduler,
+ BatteryController batteryController,
+ UserInfoController userInfoController,
+ StatusBarIconController statusBarIconController,
+ StatusBarIconController.TintedIconManager.Factory tintedIconManagerFactory,
+ BatteryMeterViewController batteryMeterViewController,
+ NotificationPanelViewController.NotificationPanelViewStateProvider
+ notificationPanelViewStateProvider,
+ KeyguardStateController keyguardStateController,
+ KeyguardBypassController bypassController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ BiometricUnlockController biometricUnlockController,
+ SysuiStatusBarStateController statusBarStateController) {
super(view);
mCarrierTextController = carrierTextController;
+ mConfigurationController = configurationController;
+ mAnimationScheduler = animationScheduler;
+ mBatteryController = batteryController;
+ mUserInfoController = userInfoController;
+ mStatusBarIconController = statusBarIconController;
+ mTintedIconManagerFactory = tintedIconManagerFactory;
+ mBatteryMeterViewController = batteryMeterViewController;
+ mNotificationPanelViewStateProvider = notificationPanelViewStateProvider;
+ mKeyguardStateController = keyguardStateController;
+ mKeyguardBypassController = bypassController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mBiometricUnlockController = biometricUnlockController;
+ mStatusBarStateController = statusBarStateController;
+
+ mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
+ mKeyguardStateController.addCallback(
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardFadingAwayChanged() {
+ if (!mKeyguardStateController.isKeyguardFadingAway()) {
+ mFirstBypassAttempt = false;
+ mDelayShowingKeyguardStatusBar = false;
+ }
+ }
+ }
+ );
+
+ Resources r = getResources();
+ mBlockedIcons = Collections.unmodifiableList(Arrays.asList(
+ r.getString(com.android.internal.R.string.status_bar_volume),
+ r.getString(com.android.internal.R.string.status_bar_alarm_clock),
+ r.getString(com.android.internal.R.string.status_bar_call_strength)));
+ mNotificationsHeaderCollideDistance = r.getDimensionPixelSize(
+ R.dimen.header_notifications_collide_distance);
}
@Override
protected void onInit() {
super.onInit();
mCarrierTextController.init();
+ mBatteryMeterViewController.init();
}
@Override
protected void onViewAttached() {
+ mConfigurationController.addCallback(mConfigurationListener);
+ mAnimationScheduler.addCallback(mAnimationCallback);
+ mUserInfoController.addCallback(mOnUserInfoChangedListener);
+ mStatusBarStateController.addCallback(mStatusBarStateListener);
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
+ if (mTintedIconManager == null) {
+ mTintedIconManager =
+ mTintedIconManagerFactory.create(mView.findViewById(R.id.statusIcons));
+ mTintedIconManager.setBlockList(mBlockedIcons);
+ mStatusBarIconController.addIconGroup(mTintedIconManager);
+ }
+ onThemeChanged();
}
@Override
protected void onViewDetached() {
+ mConfigurationController.removeCallback(mConfigurationListener);
+ mAnimationScheduler.removeCallback(mAnimationCallback);
+ mUserInfoController.removeCallback(mOnUserInfoChangedListener);
+ mStatusBarStateController.removeCallback(mStatusBarStateListener);
+ mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
+ if (mTintedIconManager != null) {
+ mStatusBarIconController.removeIconGroup(mTintedIconManager);
+ }
+ }
+
+ /** Should be called when the theme changes. */
+ public void onThemeChanged() {
+ mView.onThemeChanged(mTintedIconManager);
+ }
+
+ /** Sets whether user switcher is enabled. */
+ public void setKeyguardUserSwitcherEnabled(boolean enabled) {
+ mView.setKeyguardUserSwitcherEnabled(enabled);
+ }
+
+ /** Sets whether this controller should listen to battery updates. */
+ public void setBatteryListening(boolean listening) {
+ if (listening == mBatteryListening) {
+ return;
+ }
+ mBatteryListening = listening;
+ if (mBatteryListening) {
+ mBatteryController.addCallback(mBatteryStateChangeCallback);
+ } else {
+ mBatteryController.removeCallback(mBatteryStateChangeCallback);
+ }
}
+
+ /** Set the view to have no top clipping. */
+ public void setNoTopClipping() {
+ mView.setTopClipping(0);
+ }
+
+ /**
+ * Update the view's top clipping based on the value of notificationPanelTop and the view's
+ * current top.
+ *
+ * @param notificationPanelTop the current top of the notification panel view.
+ */
+ public void updateTopClipping(int notificationPanelTop) {
+ mView.setTopClipping(notificationPanelTop - mView.getTop());
+ }
+
+ /** Sets the dozing state. */
+ public void setDozing(boolean dozing) {
+ mDozing = dozing;
+ }
+
+ /** Animate the keyguard status bar in. */
+ public void animateKeyguardStatusBarIn() {
+ mView.setVisibility(View.VISIBLE);
+ mView.setAlpha(0f);
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ anim.addUpdateListener(mAnimatorUpdateListener);
+ anim.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ anim.start();
+ }
+
+ /** Animate the keyguard status bar out. */
+ public void animateKeyguardStatusBarOut(long startDelay, long duration) {
+ ValueAnimator anim = ValueAnimator.ofFloat(mView.getAlpha(), 0f);
+ anim.addUpdateListener(mAnimatorUpdateListener);
+ anim.setStartDelay(startDelay);
+ anim.setDuration(duration);
+ anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mView.setVisibility(View.INVISIBLE);
+ mView.setAlpha(1f);
+ mKeyguardStatusBarAnimateAlpha = 1f;
+ }
+ });
+ anim.start();
+ }
+
+ /**
+ * Updates the {@link KeyguardStatusBarView} state based on what the
+ * {@link NotificationPanelViewController.NotificationPanelViewStateProvider} and other
+ * controllers provide.
+ */
+ public void updateViewState() {
+ if (!isKeyguardShowing()) {
+ return;
+ }
+
+ float alphaQsExpansion = 1 - Math.min(
+ 1, mNotificationPanelViewStateProvider.getQsExpansionFraction() * 2);
+ float newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
+ * mKeyguardStatusBarAnimateAlpha
+ * (1.0f - mKeyguardHeadsUpShowingAmount);
+
+ boolean hideForBypass =
+ mFirstBypassAttempt && mKeyguardUpdateMonitor.shouldListenForFace()
+ || mDelayShowingKeyguardStatusBar;
+ int newVisibility = newAlpha != 0f && !mDozing && !hideForBypass
+ ? View.VISIBLE : View.INVISIBLE;
+
+ updateViewState(newAlpha, newVisibility);
+ }
+
+ /**
+ * Updates the {@link KeyguardStatusBarView} state based on the provided values.
+ */
+ public void updateViewState(float alpha, int visibility) {
+ mView.setAlpha(alpha);
+ mView.setVisibility(visibility);
+ }
+
+ /**
+ * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area)
+ * during swiping up.
+ */
+ private float getKeyguardContentsAlpha() {
+ float alpha;
+ if (isKeyguardShowing()) {
+ // When on Keyguard, we hide the header as soon as we expanded close enough to the
+ // header
+ alpha = mNotificationPanelViewStateProvider.getPanelViewExpandedHeight()
+ / (mView.getHeight() + mNotificationsHeaderCollideDistance);
+ } else {
+ // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
+ // soon as we start translating the stack.
+ alpha = mNotificationPanelViewStateProvider.getPanelViewExpandedHeight()
+ / mView.getHeight();
+ }
+ alpha = MathUtils.saturate(alpha);
+ alpha = (float) Math.pow(alpha, 0.75);
+ return alpha;
+ }
+
+ /**
+ * Update {@link KeyguardStatusBarView}'s visibility based on whether keyguard is showing and
+ * whether heads up is visible.
+ */
+ public void updateForHeadsUp() {
+ updateForHeadsUp(true);
+ }
+
+ void updateForHeadsUp(boolean animate) {
+ boolean showingKeyguardHeadsUp =
+ isKeyguardShowing() && mNotificationPanelViewStateProvider.shouldHeadsUpBeVisible();
+ if (mShowingKeyguardHeadsUp != showingKeyguardHeadsUp) {
+ mShowingKeyguardHeadsUp = showingKeyguardHeadsUp;
+ if (isKeyguardShowing()) {
+ PropertyAnimator.setProperty(
+ mView,
+ mHeadsUpShowingAmountAnimation,
+ showingKeyguardHeadsUp ? 1.0f : 0.0f,
+ KEYGUARD_HUN_PROPERTIES,
+ animate);
+ } else {
+ PropertyAnimator.applyImmediately(mView, mHeadsUpShowingAmountAnimation, 0.0f);
+ }
+ }
+ }
+
+ private boolean isKeyguardShowing() {
+ return mStatusBarState == KEYGUARD;
+ }
+
+ /** */
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("KeyguardStatusBarView:");
+ pw.println(" mBatteryListening: " + mBatteryListening);
+ mView.dump(fd, pw, args);
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 24c902151d7c..570b0ca3564c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
@@ -33,6 +34,7 @@ import com.android.internal.view.AppearanceRegion;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.shared.system.QuickStepContract;
@@ -86,8 +88,12 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
private boolean mNavbarColorManagedByIme;
@Inject
- public LightBarController(Context ctx, DarkIconDispatcher darkIconDispatcher,
- BatteryController batteryController, NavigationModeController navModeController) {
+ public LightBarController(
+ Context ctx,
+ DarkIconDispatcher darkIconDispatcher,
+ BatteryController batteryController,
+ NavigationModeController navModeController,
+ DumpManager dumpManager) {
mDarkModeColor = Color.valueOf(ctx.getColor(R.color.dark_mode_icon_color_single_tone));
mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
mBatteryController = batteryController;
@@ -95,6 +101,10 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
mNavigationMode = navModeController.addListener((mode) -> {
mNavigationMode = mode;
});
+
+ if (ctx.getDisplayId() == DEFAULT_DISPLAY) {
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
+ }
}
public void setNavigationBar(LightBarTransitionsController navigationBar) {
@@ -218,19 +228,19 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
}
}
+ // If no one is light, all icons become white.
+ if (numLightStacks == 0) {
+ mStatusBarIconController.getTransitionsController().setIconsDark(
+ false, animateChange());
+ }
+
// If all stacks are light, all icons get dark.
- if (numLightStacks == numStacks) {
+ else if (numLightStacks == numStacks) {
mStatusBarIconController.setIconsDarkArea(null);
mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
}
- // If no one is light, all icons become white.
- else if (numLightStacks == 0) {
- mStatusBarIconController.getTransitionsController().setIconsDark(
- false, animateChange());
- }
-
// Not the same for every stack, magic!
else {
mStatusBarIconController.setIconsDarkArea(
@@ -291,4 +301,33 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
pw.println();
}
}
+
+ /**
+ * Injectable factory for creating a {@link LightBarController}.
+ */
+ public static class Factory {
+ private final DarkIconDispatcher mDarkIconDispatcher;
+ private final BatteryController mBatteryController;
+ private final NavigationModeController mNavModeController;
+ private final DumpManager mDumpManager;
+
+ @Inject
+ public Factory(
+ DarkIconDispatcher darkIconDispatcher,
+ BatteryController batteryController,
+ NavigationModeController navModeController,
+ DumpManager dumpManager) {
+
+ mDarkIconDispatcher = darkIconDispatcher;
+ mBatteryController = batteryController;
+ mNavModeController = navModeController;
+ mDumpManager = dumpManager;
+ }
+
+ /** Create an {@link LightBarController} */
+ public LightBarController create(Context context) {
+ return new LightBarController(context, mDarkIconDispatcher, mBatteryController,
+ mNavModeController, mDumpManager);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
index 7d134057ee76..3f3328172e12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
@@ -21,6 +21,7 @@ import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.Nullable;
+import android.view.InsetsVisibilities;
import android.view.View;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -149,7 +150,8 @@ public class LightsOutNotifController {
@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) {
if (displayId != mDisplayId) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
index 094ebb9ef0a7..5f222afd77e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
@@ -24,7 +24,6 @@ import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.Dependency;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.dagger.SysUISingleton;
@@ -88,10 +87,11 @@ public class LockscreenGestureLogger {
}
private ArrayMap<Integer, Integer> mLegacyMap;
- private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+ private final MetricsLogger mMetricsLogger;
@Inject
- public LockscreenGestureLogger() {
+ public LockscreenGestureLogger(MetricsLogger metricsLogger) {
+ mMetricsLogger = metricsLogger;
mLegacyMap = new ArrayMap<>(EventLogConstants.METRICS_GESTURE_TYPE_MAP.length);
for (int i = 0; i < EventLogConstants.METRICS_GESTURE_TYPE_MAP.length ; i++) {
mLegacyMap.put(EventLogConstants.METRICS_GESTURE_TYPE_MAP[i], i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 78fcd82dc1f5..e565a44e7e1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -45,7 +45,6 @@ import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
import com.android.systemui.statusbar.NotificationMediaManager;
import libcore.io.IoUtils;
@@ -53,7 +52,6 @@ import libcore.io.IoUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Objects;
-import java.util.Optional;
import javax.inject.Inject;
@@ -70,7 +68,6 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen
private final WallpaperManager mWallpaperManager;
private final KeyguardUpdateMonitor mUpdateMonitor;
private final Handler mH;
- private final Optional<FaceAuthScreenBrightnessController> mFaceAuthScreenBrightnessController;
private boolean mCached;
private Bitmap mCache;
@@ -86,14 +83,12 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen
KeyguardUpdateMonitor keyguardUpdateMonitor,
DumpManager dumpManager,
NotificationMediaManager mediaManager,
- Optional<FaceAuthScreenBrightnessController> faceAuthScreenBrightnessController,
@Main Handler mainHandler) {
dumpManager.registerDumpable(getClass().getSimpleName(), this);
mWallpaperManager = wallpaperManager;
mCurrentUserId = ActivityManager.getCurrentUser();
mUpdateMonitor = keyguardUpdateMonitor;
mMediaManager = mediaManager;
- mFaceAuthScreenBrightnessController = faceAuthScreenBrightnessController;
mH = mainHandler;
if (iWallpaperManager != null) {
@@ -119,7 +114,6 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen
LoaderResult result = loadBitmap(mCurrentUserId, mSelectedUser);
if (result.success) {
mCached = true;
- mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
mCache = result.bitmap;
}
return mCache;
@@ -133,14 +127,6 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen
return LoaderResult.success(null);
}
- Bitmap faceAuthWallpaper = null;
- if (mFaceAuthScreenBrightnessController.isPresent()) {
- faceAuthWallpaper = mFaceAuthScreenBrightnessController.get().getFaceAuthWallpaper();
- if (faceAuthWallpaper != null) {
- return LoaderResult.success(faceAuthWallpaper);
- }
- }
-
// Prefer the selected user (when specified) over the current user for the FLAG_SET_LOCK
// wallpaper.
final int lockWallpaperUserId =
@@ -235,7 +221,6 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen
if (result.success) {
mCached = true;
mCache = result.bitmap;
- mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
mMediaManager.updateMediaMetaData(
true /* metaDataChanged */, true /* allowEnterAnimation */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
index f27c7d28df44..dd21c8af37e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
@@ -23,22 +23,26 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.DetailAdapter;
+import com.android.systemui.qs.FooterActionsView;
import com.android.systemui.qs.QSDetailDisplayer;
import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.qs.user.UserSwitchDialogController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
/** View Controller for {@link MultiUserSwitch}. */
-@QSScope
public class MultiUserSwitchController extends ViewController<MultiUserSwitch> {
private final UserManager mUserManager;
private final UserSwitcherController mUserSwitcherController;
private final QSDetailDisplayer mQsDetailDisplayer;
private final FalsingManager mFalsingManager;
+ private final UserSwitchDialogController mUserSwitchDialogController;
+ private final FeatureFlags mFeatureFlags;
private UserSwitcherController.BaseUserAdapter mUserListener;
@@ -49,26 +53,61 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> {
return;
}
- View center = mView.getChildCount() > 0 ? mView.getChildAt(0) : mView;
+ if (mFeatureFlags.useNewUserSwitcher()) {
+ mUserSwitchDialogController.showDialog(v);
+ } else {
+ View center = mView.getChildCount() > 0 ? mView.getChildAt(0) : mView;
- int[] tmpInt = new int[2];
- center.getLocationInWindow(tmpInt);
- tmpInt[0] += center.getWidth() / 2;
- tmpInt[1] += center.getHeight() / 2;
+ int[] tmpInt = new int[2];
+ center.getLocationInWindow(tmpInt);
+ tmpInt[0] += center.getWidth() / 2;
+ tmpInt[1] += center.getHeight() / 2;
- mQsDetailDisplayer.showDetailAdapter(getUserDetailAdapter(), tmpInt[0], tmpInt[1]);
+ mQsDetailDisplayer.showDetailAdapter(getUserDetailAdapter(), tmpInt[0], tmpInt[1]);
+ }
}
};
- @Inject
- public MultiUserSwitchController(MultiUserSwitch view, UserManager userManager,
+ @QSScope
+ public static class Factory {
+ private final UserManager mUserManager;
+ private final UserSwitcherController mUserSwitcherController;
+ private final QSDetailDisplayer mQsDetailDisplayer;
+ private final FalsingManager mFalsingManager;
+ private final UserSwitchDialogController mUserSwitchDialogController;
+ private final FeatureFlags mFeatureFlags;
+
+ @Inject
+ public Factory(UserManager userManager, UserSwitcherController userSwitcherController,
+ QSDetailDisplayer qsDetailDisplayer, FalsingManager falsingManager,
+ UserSwitchDialogController userSwitchDialogController,
+ FeatureFlags featureFlags) {
+ mUserManager = userManager;
+ mUserSwitcherController = userSwitcherController;
+ mQsDetailDisplayer = qsDetailDisplayer;
+ mFalsingManager = falsingManager;
+ mUserSwitchDialogController = userSwitchDialogController;
+ mFeatureFlags = featureFlags;
+ }
+
+ public MultiUserSwitchController create(FooterActionsView view) {
+ return new MultiUserSwitchController(view.findViewById(R.id.multi_user_switch),
+ mUserManager, mUserSwitcherController, mQsDetailDisplayer,
+ mFalsingManager, mUserSwitchDialogController, mFeatureFlags);
+ }
+ }
+
+ private MultiUserSwitchController(MultiUserSwitch view, UserManager userManager,
UserSwitcherController userSwitcherController, QSDetailDisplayer qsDetailDisplayer,
- FalsingManager falsingManager) {
+ FalsingManager falsingManager, UserSwitchDialogController userSwitchDialogController,
+ FeatureFlags featureFlags) {
super(view);
mUserManager = userManager;
mUserSwitcherController = userSwitcherController;
mQsDetailDisplayer = qsDetailDisplayer;
mFalsingManager = falsingManager;
+ mUserSwitchDialogController = userSwitchDialogController;
+ mFeatureFlags = featureFlags;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 6516abd143ed..fbe59a2743b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -6,6 +6,7 @@ import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Trace;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
@@ -336,12 +337,14 @@ public class NotificationIconAreaController implements
}
private void updateNotificationIcons() {
+ Trace.beginSection("NotificationIconAreaController.updateNotificationIcons");
updateStatusBarIcons();
updateShelfIcons();
updateCenterIcon();
updateAodNotificationIcons();
applyNotificationIconsTint();
+ Trace.endSection();
}
private void updateShelfIcons() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 58cbe83d49f6..3fe393d99c7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -21,12 +21,22 @@ import static android.view.View.GONE;
import static androidx.constraintlayout.widget.ConstraintSet.END;
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
import static androidx.constraintlayout.widget.ConstraintSet.START;
+import static androidx.constraintlayout.widget.ConstraintSet.TOP;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
+import static com.android.keyguard.KeyguardClockSwitch.LARGE;
+import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
+import static com.android.systemui.statusbar.phone.PanelBar.STATE_CLOSED;
+import static com.android.systemui.statusbar.phone.PanelBar.STATE_OPEN;
+import static com.android.systemui.statusbar.phone.PanelBar.STATE_OPENING;
import static java.lang.Float.isNaN;
@@ -50,7 +60,7 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
-import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
import android.os.Handler;
@@ -59,6 +69,8 @@ import android.os.SystemClock;
import android.os.UserManager;
import android.os.VibrationEffect;
import android.provider.Settings;
+import android.transition.ChangeBounds;
+import android.transition.TransitionManager;
import android.util.Log;
import android.util.MathUtils;
import android.view.LayoutInflater;
@@ -70,6 +82,7 @@ import android.view.ViewPropertyAnimator;
import android.view.ViewStub;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
+import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
@@ -81,20 +94,21 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.policy.SystemBarUtils;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardStatusViewController;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.LockIconViewController;
import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
import com.android.systemui.DejankUtils;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
@@ -107,6 +121,7 @@ import com.android.systemui.fragments.FragmentService;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
@@ -118,7 +133,6 @@ import com.android.systemui.qs.QSDetailDisplayer;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -173,7 +187,6 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
-import java.util.function.Function;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -203,7 +216,7 @@ public class NotificationPanelViewController extends PanelViewController {
*/
private static final int FLING_HIDE = 2;
private static final long ANIMATION_DELAY_ICON_FADE_IN =
- ActivityLaunchAnimator.ANIMATION_DURATION - CollapsedStatusBarFragment.FADE_IN_DURATION
+ LaunchAnimator.ANIMATION_DURATION - CollapsedStatusBarFragment.FADE_IN_DURATION
- CollapsedStatusBarFragment.FADE_IN_DELAY - 48;
private final DozeParameters mDozeParameters;
@@ -227,7 +240,6 @@ public class NotificationPanelViewController extends PanelViewController {
@VisibleForTesting final StatusBarStateListener mStatusBarStateListener =
new StatusBarStateListener();
- private final BiometricUnlockController mBiometricUnlockController;
private final NotificationPanelView mView;
private final VibratorHelper mVibratorHelper;
private final MetricsLogger mMetricsLogger;
@@ -255,53 +267,6 @@ public class NotificationPanelViewController extends PanelViewController {
private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
private static final Rect EMPTY_RECT = new Rect();
- private final AnimatableProperty KEYGUARD_HEADS_UP_SHOWING_AMOUNT = AnimatableProperty.from(
- "KEYGUARD_HEADS_UP_SHOWING_AMOUNT",
- (notificationPanelView, aFloat) -> setKeyguardHeadsUpShowingAmount(aFloat),
- (Function<NotificationPanelView, Float>) notificationPanelView ->
- getKeyguardHeadsUpShowingAmount(),
- R.id.keyguard_hun_animator_tag, R.id.keyguard_hun_animator_end_tag,
- R.id.keyguard_hun_animator_start_tag);
- private static final AnimationProperties
- KEYGUARD_HUN_PROPERTIES =
- new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- @VisibleForTesting
- final KeyguardUpdateMonitorCallback
- mKeyguardUpdateCallback =
- new KeyguardUpdateMonitorCallback() {
-
- @Override
- public void onBiometricAuthenticated(int userId,
- BiometricSourceType biometricSourceType,
- boolean isStrongBiometric) {
- if (mFirstBypassAttempt
- && mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric)) {
- mDelayShowingKeyguardStatusBar = true;
- }
- }
-
- @Override
- public void onBiometricRunningStateChanged(boolean running,
- BiometricSourceType biometricSourceType) {
- boolean
- keyguardOrShadeLocked =
- mBarState == KEYGUARD
- || mBarState == StatusBarState.SHADE_LOCKED;
- if (!running && mFirstBypassAttempt && keyguardOrShadeLocked && !mDozing
- && !mDelayShowingKeyguardStatusBar
- && !mBiometricUnlockController.isBiometricUnlock()) {
- mFirstBypassAttempt = false;
- animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- }
- }
-
- @Override
- public void onFinishedGoingToSleep(int why) {
- mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
- mDelayShowingKeyguardStatusBar = false;
- }
- };
-
private final LayoutInflater mLayoutInflater;
private final PowerManager mPowerManager;
private final AccessibilityManager mAccessibilityManager;
@@ -319,7 +284,6 @@ public class NotificationPanelViewController extends PanelViewController {
private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
private final QSDetailDisplayer mQSDetailDisplayer;
private final FragmentService mFragmentService;
- private final FeatureFlags mFeatureFlags;
private final ScrimController mScrimController;
private final PrivacyDotViewController mPrivacyDotViewController;
private final QuickAccessWalletController mQuickAccessWalletController;
@@ -331,8 +295,11 @@ public class NotificationPanelViewController extends PanelViewController {
private final int mMaxKeyguardNotifications;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final TapAgainViewController mTapAgainViewController;
+ private final SplitShadeHeaderController mSplitShadeHeaderController;
private final RecordingController mRecordingController;
private boolean mShouldUseSplitNotificationShade;
+ // The bottom padding reserved for elements of the keyguard measuring notifications
+ private float mKeyguardNotificationBottomPadding;
// Current max allowed keyguard notifications determined by measuring the panel
private int mMaxAllowedKeyguardNotifications;
@@ -341,13 +308,13 @@ public class NotificationPanelViewController extends PanelViewController {
private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
private KeyguardStatusBarView mKeyguardStatusBar;
- private KeyguardStatusBarViewController mKeyguarStatusBarViewController;
- private ViewGroup mBigClockContainer;
+ private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
@VisibleForTesting QS mQs;
private FrameLayout mQsFrame;
private KeyguardStatusViewController mKeyguardStatusViewController;
private LockIconViewController mLockIconViewController;
private NotificationsQuickSettingsContainer mNotificationContainerParent;
+ private NotificationsQSContainerController mNotificationsQSContainerController;
private boolean mAnimateNextPositionUpdate;
private float mQuickQsOffsetHeight;
private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@@ -371,6 +338,7 @@ public class NotificationPanelViewController extends PanelViewController {
private boolean mKeyguardUserSwitcherEnabled;
private boolean mDozing;
private boolean mDozingOnDown;
+ private boolean mBouncerShowing;
private int mBarState;
private float mInitialHeightOnTouch;
private float mInitialTouchX;
@@ -388,13 +356,12 @@ public class NotificationPanelViewController extends PanelViewController {
private FlingAnimationUtils mFlingAnimationUtils;
private int mStatusBarMinHeight;
private int mStatusBarHeaderHeightKeyguard;
- private int mNotificationsHeaderCollideDistance;
private float mOverStretchAmount;
private float mDownX;
private float mDownY;
private int mDisplayTopInset = 0; // in pixels
private int mDisplayRightInset = 0; // in pixels
- private int mSplitShadeNotificationsTopPadding;
+ private int mSplitShadeStatusBarHeight;
private final KeyguardClockPositionAlgorithm
mClockPositionAlgorithm =
@@ -425,7 +392,6 @@ public class NotificationPanelViewController extends PanelViewController {
private boolean mQsTouchAboveFalsingThreshold;
private int mQsFalsingThreshold;
- private float mKeyguardStatusBarAnimateAlpha = 1f;
private HeadsUpTouchHelper mHeadsUpTouchHelper;
private boolean mListenForHeadsUp;
private int mNavigationBarBottomHeight;
@@ -443,7 +409,7 @@ public class NotificationPanelViewController extends PanelViewController {
private Runnable mHeadsUpExistenceChangedRunnable = () -> {
setHeadsUpAnimatingAway(false);
- notifyBarPanelExpansionChanged();
+ updatePanelExpansionAndVisibility();
};
// TODO (b/162832756): once migrated to the new pipeline, delete legacy group manager
private NotificationGroupManagerLegacy mGroupManager;
@@ -471,15 +437,12 @@ public class NotificationPanelViewController extends PanelViewController {
private float mLinearDarkAmount;
private boolean mPulsing;
- private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
private boolean mUserSetupComplete;
- private int mQsNotificationTopPadding;
private boolean mHideIconsDuringLaunchAnimation = true;
private int mStackScrollerMeasuringPass;
private ArrayList<Consumer<ExpandableNotificationRow>>
mTrackingHeadsUpListeners =
new ArrayList<>();
- private Runnable mVerticalTranslationListener;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private int mPanelAlpha;
@@ -507,6 +470,8 @@ public class NotificationPanelViewController extends PanelViewController {
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final UserManager mUserManager;
private final MediaDataManager mMediaDataManager;
+ private final SysUiState mSysUiState;
+
private NotificationShadeDepthController mDepthController;
private int mDisplayId;
@@ -522,8 +487,6 @@ public class NotificationPanelViewController extends PanelViewController {
private int mDarkIconSize;
private int mHeadsUpInset;
private boolean mHeadsUpPinnedMode;
- private float mKeyguardHeadsUpShowingAmount = 0.0f;
- private boolean mShowingKeyguardHeadsUp;
private boolean mAllowExpandForSmallExpansion;
private Runnable mExpandAfterLayoutRunnable;
@@ -582,17 +545,6 @@ public class NotificationPanelViewController extends PanelViewController {
*/
private boolean mIsPanelCollapseOnQQS;
- /**
- * If face auth with bypass is running for the first time after you turn on the screen.
- * (From aod or screen off)
- */
- private boolean mFirstBypassAttempt;
- /**
- * If auth happens successfully during {@code mFirstBypassAttempt}, and we should wait until
- * the keyguard is dismissed to show the status bar.
- */
- private boolean mDelayShowingKeyguardStatusBar;
-
private boolean mAnimatingQS;
/**
@@ -645,6 +597,8 @@ public class NotificationPanelViewController extends PanelViewController {
private KeyguardMediaController mKeyguardMediaController;
+ private boolean mStatusViewCentered = true;
+
private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
@@ -701,8 +655,8 @@ public class NotificationPanelViewController extends PanelViewController {
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
ConversationNotificationManager conversationNotificationManager,
MediaHierarchyManager mediaHierarchyManager,
- BiometricUnlockController biometricUnlockController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ NotificationsQSContainerController notificationsQSContainerController,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
KeyguardQsUserSwitchComponent.Factory keyguardQsUserSwitchComponentFactory,
@@ -719,7 +673,6 @@ public class NotificationPanelViewController extends PanelViewController {
NotificationShadeDepthController notificationShadeDepthController,
AmbientState ambientState,
LockIconViewController lockIconViewController,
- FeatureFlags featureFlags,
KeyguardMediaController keyguardMediaController,
PrivacyDotViewController privacyDotViewController,
TapAgainViewController tapAgainViewController,
@@ -730,13 +683,23 @@ public class NotificationPanelViewController extends PanelViewController {
RecordingController recordingController,
@Main Executor uiExecutor,
SecureSettings secureSettings,
+ SplitShadeHeaderController splitShadeHeaderController,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+ LockscreenGestureLogger lockscreenGestureLogger,
NotificationRemoteInputManager remoteInputManager,
ControlsComponent controlsComponent) {
- super(view, falsingManager, dozeLog, keyguardStateController,
- (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
- statusBarKeyguardViewManager, latencyTracker, flingAnimationUtilsBuilder.get(),
- statusBarTouchableRegionManager, ambientState);
+ super(view,
+ falsingManager,
+ dozeLog,
+ keyguardStateController,
+ (SysuiStatusBarStateController) statusBarStateController,
+ vibratorHelper,
+ statusBarKeyguardViewManager,
+ latencyTracker,
+ flingAnimationUtilsBuilder.get(),
+ statusBarTouchableRegionManager,
+ lockscreenGestureLogger,
+ ambientState);
mView = view;
mVibratorHelper = vibratorHelper;
mKeyguardMediaController = keyguardMediaController;
@@ -749,13 +712,14 @@ public class NotificationPanelViewController extends PanelViewController {
mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
mMediaHierarchyManager = mediaHierarchyManager;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mNotificationsQSContainerController = notificationsQSContainerController;
+ mNotificationsQSContainerController.init();
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
mGroupManager = groupManager;
mNotificationIconAreaController = notificationIconAreaController;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
mDepthController = notificationShadeDepthController;
- mFeatureFlags = featureFlags;
mContentResolver = contentResolver;
mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory;
mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory;
@@ -763,8 +727,9 @@ public class NotificationPanelViewController extends PanelViewController {
mFragmentService = fragmentService;
mSettingsChangeObserver = new SettingsChangeObserver(handler);
mShouldUseSplitNotificationShade =
- Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources);
+ Utils.shouldUseSplitNotificationShade(mResources);
mView.setWillNotDraw(!DEBUG);
+ mSplitShadeHeaderController = splitShadeHeaderController;
mLayoutInflater = layoutInflater;
mFalsingManager = falsingManager;
mFalsingCollector = falsingCollector;
@@ -778,7 +743,6 @@ public class NotificationPanelViewController extends PanelViewController {
mDisplayId = displayId;
mPulseExpansionHandler = pulseExpansionHandler;
mDozeParameters = dozeParameters;
- mBiometricUnlockController = biometricUnlockController;
mScrimController = scrimController;
mScrimController.setClipsQsScrim(!mShouldUseSplitNotificationShade);
mUserManager = userManager;
@@ -786,6 +750,8 @@ public class NotificationPanelViewController extends PanelViewController {
mTapAgainViewController = tapAgainViewController;
mUiExecutor = uiExecutor;
mSecureSettings = secureSettings;
+ // TODO: inject via dagger instead of Dependency
+ mSysUiState = Dependency.get(SysUiState.class);
pulseExpansionHandler.setPulseExpandAbortListener(() -> {
if (mQs != null) {
mQs.animateHeaderSlidingOut();
@@ -794,21 +760,8 @@ public class NotificationPanelViewController extends PanelViewController {
mThemeResId = mView.getContext().getThemeResId();
mKeyguardBypassController = bypassController;
mUpdateMonitor = keyguardUpdateMonitor;
- mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
- KeyguardStateController.Callback
- keyguardMonitorCallback =
- new KeyguardStateController.Callback() {
- @Override
- public void onKeyguardFadingAwayChanged() {
- if (!mKeyguardStateController.isKeyguardFadingAway()) {
- mFirstBypassAttempt = false;
- mDelayShowingKeyguardStatusBar = false;
- }
- }
- };
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
lockscreenShadeTransitionController.setNotificationPanelController(this);
- mKeyguardStateController.addCallback(keyguardMonitorCallback);
DynamicPrivacyControlListener
dynamicPrivacyControlListener =
new DynamicPrivacyControlListener();
@@ -854,7 +807,6 @@ public class NotificationPanelViewController extends PanelViewController {
private void onFinishInflate() {
loadDimens();
mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
- mBigClockContainer = mView.findViewById(R.id.big_clock_container);
FrameLayout userAvatarContainer = null;
KeyguardUserSwitcherView keyguardUserSwitcherView = null;
@@ -869,10 +821,16 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
+ mKeyguardStatusBarViewController =
+ mKeyguardStatusBarViewComponentFactory.build(
+ mKeyguardStatusBar,
+ mNotificationPanelViewStateProvider)
+ .getKeyguardStatusBarViewController();
+ mKeyguardStatusBarViewController.init();
+
updateViewControllers(
mView.findViewById(R.id.keyguard_status_view),
userAvatarContainer,
- mKeyguardStatusBar,
keyguardUserSwitcherView);
mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
@@ -900,7 +858,7 @@ public class NotificationPanelViewController extends PanelViewController {
mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() {
@Override
public void onFullyHiddenChanged(boolean isFullyHidden) {
- updateKeyguardStatusBarForHeadsUp();
+ mKeyguardStatusBarViewController.updateForHeadsUp();
}
@Override
@@ -932,13 +890,9 @@ public class NotificationPanelViewController extends PanelViewController {
super.loadDimens();
mFlingAnimationUtils = mFlingAnimationUtilsBuilder.get()
.setMaxLengthSeconds(0.4f).build();
- mStatusBarMinHeight = mResources.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
- mStatusBarHeaderHeightKeyguard = mResources.getDimensionPixelSize(
- R.dimen.status_bar_header_height_keyguard);
+ mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
+ mStatusBarHeaderHeightKeyguard = Utils.getStatusBarHeaderHeightKeyguard(mView.getContext());
mQsPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height);
- mNotificationsHeaderCollideDistance = mResources.getDimensionPixelSize(
- R.dimen.header_notifications_collide_distance);
mClockPositionAlgorithm.loadDimens(mResources);
mQsFalsingThreshold = mResources.getDimensionPixelSize(R.dimen.qs_falsing_threshold);
mPositionMinSideMargin = mResources.getDimensionPixelSize(
@@ -947,8 +901,7 @@ public class NotificationPanelViewController extends PanelViewController {
R.dimen.keyguard_indication_bottom_padding);
mShelfHeight = mResources.getDimensionPixelSize(R.dimen.notification_shelf_height);
mDarkIconSize = mResources.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size_dark);
- int statusbarHeight = mResources.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
+ int statusbarHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
mHeadsUpInset = statusbarHeight + mResources.getDimensionPixelSize(
R.dimen.heads_up_status_bar_padding);
mDistanceForQSFullShadeTransition = mResources.getDimensionPixelSize(
@@ -957,7 +910,8 @@ public class NotificationPanelViewController extends PanelViewController {
R.dimen.pulse_expansion_max_top_overshoot);
mScrimCornerRadius = mResources.getDimensionPixelSize(
R.dimen.notification_scrim_corner_radius);
- mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius(mResources);
+ mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius(
+ mView.getContext());
mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize(
R.dimen.notification_side_paddings);
mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
@@ -965,7 +919,6 @@ public class NotificationPanelViewController extends PanelViewController {
private void updateViewControllers(KeyguardStatusView keyguardStatusView,
FrameLayout userAvatarView,
- KeyguardStatusBarView keyguardStatusBarView,
KeyguardUserSwitcherView keyguardUserSwitcherView) {
// Re-associate the KeyguardStatusViewController
KeyguardStatusViewComponent statusViewComponent =
@@ -973,12 +926,6 @@ public class NotificationPanelViewController extends PanelViewController {
mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
mKeyguardStatusViewController.init();
- KeyguardStatusBarViewComponent statusBarViewComponent =
- mKeyguardStatusBarViewComponentFactory.build(keyguardStatusBarView);
- mKeyguarStatusBarViewController =
- statusBarViewComponent.getKeyguardStatusBarViewController();
- mKeyguarStatusBarViewController.init();
-
if (mKeyguardUserSwitcherController != null) {
// Try to close the switcher so that callbacks are triggered if necessary.
// Otherwise, NPV can get into a state where some of the views are still hidden
@@ -996,16 +943,16 @@ public class NotificationPanelViewController extends PanelViewController {
userSwitcherComponent.getKeyguardQsUserSwitchController();
mKeyguardQsUserSwitchController.setNotificationPanelViewController(this);
mKeyguardQsUserSwitchController.init();
- mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true);
+ mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(true);
} else if (keyguardUserSwitcherView != null) {
KeyguardUserSwitcherComponent userSwitcherComponent =
mKeyguardUserSwitcherComponentFactory.build(keyguardUserSwitcherView);
mKeyguardUserSwitcherController =
userSwitcherComponent.getKeyguardUserSwitcherController();
mKeyguardUserSwitcherController.init();
- mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(true);
+ mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(true);
} else {
- mKeyguardStatusBar.setKeyguardUserSwitcherEnabled(false);
+ mKeyguardStatusBarViewController.setKeyguardUserSwitcherEnabled(false);
}
}
@@ -1023,18 +970,21 @@ public class NotificationPanelViewController extends PanelViewController {
}
public void updateResources() {
- mQuickQsOffsetHeight = mResources.getDimensionPixelSize(
- com.android.internal.R.dimen.quick_qs_offset_height);
- mSplitShadeNotificationsTopPadding =
- mResources.getDimensionPixelSize(R.dimen.notifications_top_padding_split_shade);
+ mQuickQsOffsetHeight = SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
+ mSplitShadeStatusBarHeight = Utils.getSplitShadeStatusBarHeight(mView.getContext());
int qsWidth = mResources.getDimensionPixelSize(R.dimen.qs_panel_width);
int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
mShouldUseSplitNotificationShade =
- Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources);
+ Utils.shouldUseSplitNotificationShade(mResources);
mScrimController.setClipsQsScrim(!mShouldUseSplitNotificationShade);
if (mQs != null) {
- mQs.setTranslateWhileExpanding(mShouldUseSplitNotificationShade);
+ mQs.setInSplitShade(mShouldUseSplitNotificationShade);
}
+
+ int topMargin = mShouldUseSplitNotificationShade ? mSplitShadeStatusBarHeight :
+ mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
+ mSplitShadeHeaderController.setSplitShadeMode(mShouldUseSplitNotificationShade);
+
// To change the constraints at runtime, all children of the ConstraintLayout must have ids
ensureAllViewsHaveIds(mNotificationContainerParent);
ConstraintSet constraintSet = new ConstraintSet();
@@ -1047,15 +997,20 @@ public class NotificationPanelViewController extends PanelViewController {
constraintSet.connect(
R.id.notification_stack_scroller, START,
R.id.qs_edge_guideline, START);
- constraintSet.connect(R.id.keyguard_status_view, END, R.id.qs_edge_guideline, END);
+ constraintSet.constrainHeight(R.id.split_shade_status_bar, mSplitShadeStatusBarHeight);
} else {
constraintSet.connect(R.id.qs_frame, END, PARENT_ID, END);
constraintSet.connect(R.id.notification_stack_scroller, START, PARENT_ID, START);
- constraintSet.connect(R.id.keyguard_status_view, END, PARENT_ID, END);
}
constraintSet.getConstraint(R.id.notification_stack_scroller).layout.mWidth = panelWidth;
constraintSet.getConstraint(R.id.qs_frame).layout.mWidth = qsWidth;
+ constraintSet.setMargin(R.id.notification_stack_scroller, TOP, topMargin);
+ constraintSet.setMargin(R.id.qs_frame, TOP, topMargin);
constraintSet.applyTo(mNotificationContainerParent);
+ mAmbientState.setStackTopMargin(topMargin);
+ mNotificationsQSContainerController.setSplitShadeEnabled(mShouldUseSplitNotificationShade);
+
+ updateKeyguardStatusViewAlignment(/* animate= */false);
mKeyguardMediaController.refreshMediaPosition();
}
@@ -1102,6 +1057,9 @@ public class NotificationPanelViewController extends PanelViewController {
keyguardStatusView = (KeyguardStatusView) mLayoutInflater.inflate(
R.layout.keyguard_status_view, mNotificationContainerParent, false);
mNotificationContainerParent.addView(keyguardStatusView, statusIndex);
+ // When it's reinflated, this is centered by default. If it shouldn't be, this will update
+ // below when resources are updated.
+ mStatusViewCentered = true;
attachSplitShadeMediaPlayerContainer(
keyguardStatusView.findViewById(R.id.status_view_media_container));
@@ -1128,9 +1086,8 @@ public class NotificationPanelViewController extends PanelViewController {
R.layout.keyguard_user_switcher /* layoutId */,
showKeyguardUserSwitcher /* enabled */);
- mBigClockContainer.removeAllViews();
updateViewControllers(mView.findViewById(R.id.keyguard_status_view), userAvatarView,
- mKeyguardStatusBar, keyguardUserSwitcherView);
+ keyguardUserSwitcherView);
// Update keyguard bottom area
int index = mView.indexOfChild(mKeyguardBottomArea);
@@ -1146,10 +1103,6 @@ public class NotificationPanelViewController extends PanelViewController {
mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
mStatusBarStateController.getInterpolatedDozeAmount());
- if (mKeyguardStatusBar != null) {
- mKeyguardStatusBar.onThemeChanged();
- }
-
mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
mBarState,
false,
@@ -1195,9 +1148,12 @@ public class NotificationPanelViewController extends PanelViewController {
if (mKeyguardShowing && !mKeyguardBypassController.getBypassEnabled()) {
mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(
mMaxAllowedKeyguardNotifications);
+ mNotificationStackScrollLayoutController.setKeyguardBottomPadding(
+ mKeyguardNotificationBottomPadding);
} else {
// no max when not on the keyguard
mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(-1);
+ mNotificationStackScrollLayoutController.setKeyguardBottomPadding(-1f);
}
}
@@ -1275,11 +1231,19 @@ public class NotificationPanelViewController extends PanelViewController {
updateClockAppearance();
}
if (!onKeyguard) {
- stackScrollerPadding = getUnlockedStackScrollerPadding();
+ if (mShouldUseSplitNotificationShade) {
+ // Quick settings are not on the top of the notifications
+ // when in split shade mode (they are on the left side),
+ // so we should not add a padding for them
+ stackScrollerPadding = 0;
+ } else {
+ stackScrollerPadding = getUnlockedStackScrollerPadding();
+ }
} else {
stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
}
+ mSplitShadeHeaderController.setShadeExpandedFraction(getExpandedFraction());
mNotificationStackScrollLayoutController.setIntrinsicPadding(stackScrollerPadding);
mKeyguardBottomArea.setAntiBurnInOffsetX(mClockPositionResult.clockX);
@@ -1290,13 +1254,19 @@ public class NotificationPanelViewController extends PanelViewController {
}
private void updateClockAppearance() {
- int totalHeight = mView.getHeight();
- int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
int userSwitcherPreferredY = mStatusBarHeaderHeightKeyguard;
boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
final boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
.getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
- mKeyguardStatusViewController.setHasVisibleNotifications(hasVisibleNotifications);
+ boolean splitShadeWithActiveMedia =
+ mShouldUseSplitNotificationShade && mMediaDataManager.hasActiveMedia();
+ if ((hasVisibleNotifications && !mShouldUseSplitNotificationShade)
+ || (splitShadeWithActiveMedia && !mDozing)) {
+ mKeyguardStatusViewController.displayClock(SMALL);
+ } else {
+ mKeyguardStatusViewController.displayClock(LARGE);
+ }
+ updateKeyguardStatusViewAlignment(true /* animate */);
int userIconHeight = mKeyguardQsUserSwitchController != null
? mKeyguardQsUserSwitchController.getUserIconHeight() : 0;
float expandedFraction =
@@ -1309,20 +1279,18 @@ public class NotificationPanelViewController extends PanelViewController {
float udfpsAodTopLocation = -1f;
if (mUpdateMonitor.isUdfpsEnrolled() && mAuthController.getUdfpsProps().size() > 0) {
FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0);
- udfpsAodTopLocation = props.sensorLocationY - props.sensorRadius
+ final SensorLocationInternal location = props.getLocation();
+ udfpsAodTopLocation = location.sensorLocationY - location.sensorRadius
- mUdfpsMaxYBurnInOffset;
}
mClockPositionAlgorithm.setup(
mStatusBarHeaderHeightKeyguard,
- totalHeight - bottomPadding,
- mNotificationStackScrollLayoutController.getIntrinsicContentHeight(),
expandedFraction,
- totalHeight,
mKeyguardStatusViewController.getLockscreenHeight(),
userIconHeight,
- userSwitcherPreferredY, hasCustomClock(),
- hasVisibleNotifications, darkamount, mOverStretchAmount,
+ userSwitcherPreferredY,
+ darkamount, mOverStretchAmount,
bypassEnabled, getUnlockedStackScrollerPadding(),
computeQsExpansionFraction(),
mDisplayTopInset,
@@ -1352,6 +1320,27 @@ public class NotificationPanelViewController extends PanelViewController {
updateClock();
}
+ private void updateKeyguardStatusViewAlignment(boolean animate) {
+ boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
+ .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
+ boolean shouldBeCentered =
+ !mShouldUseSplitNotificationShade || !hasVisibleNotifications || mDozing;
+ if (mStatusViewCentered != shouldBeCentered) {
+ mStatusViewCentered = shouldBeCentered;
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(mNotificationContainerParent);
+ int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline;
+ constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END);
+ if (animate) {
+ ChangeBounds transition = new ChangeBounds();
+ transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ TransitionManager.beginDelayedTransition(mNotificationContainerParent, transition);
+ }
+ constraintSet.applyTo(mNotificationContainerParent);
+ }
+ }
+
/**
* @return the padding of the stackscroller when unlocked
*/
@@ -1379,6 +1368,7 @@ public class NotificationPanelViewController extends PanelViewController {
float bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
bottomPadding = Math.max(lockIconPadding, bottomPadding);
+ mKeyguardNotificationBottomPadding = bottomPadding;
float availableSpace =
mNotificationStackScrollLayoutController.getHeight()
@@ -1532,6 +1522,24 @@ public class NotificationPanelViewController extends PanelViewController {
mNotificationStackScrollLayoutController.resetScrollPosition();
}
+ /** Collapses the panel. */
+ public void collapsePanel(boolean animate, boolean delayed, float speedUpFactor) {
+ boolean waiting = false;
+ if (animate && !isFullyCollapsed()) {
+ collapse(delayed, speedUpFactor);
+ waiting = true;
+ } else {
+ resetViews(false /* animate */);
+ setExpandedFraction(0); // just in case
+ }
+ if (!waiting) {
+ // it's possible that nothing animated, so we replicate the termination
+ // conditions of panelExpansionChanged here
+ // TODO(b/200063118): This can likely go away in a future refactor CL.
+ mBar.updateState(STATE_CLOSED);
+ }
+ }
+
@Override
public void collapse(boolean delayed, float speedUpFactor) {
if (!canPanelBeCollapsed()) {
@@ -1575,7 +1583,7 @@ public class NotificationPanelViewController extends PanelViewController {
private boolean isQsExpansionEnabled() {
return mQsExpansionEnabledPolicy && mQsExpansionEnabledAmbient
- && !mRemoteInputManager.getController().isRemoteInputActive();
+ && !mRemoteInputManager.isRemoteInputActive();
}
public void expandWithQs() {
@@ -1812,15 +1820,6 @@ public class NotificationPanelViewController extends PanelViewController {
return !mQsTouchAboveFalsingThreshold;
}
- /**
- * Percentage of panel expansion offset, caused by pulling down on a heads-up.
- */
- @Override
- public void setMinFraction(float minFraction) {
- mMinFraction = minFraction;
- mDepthController.setPanelPullDownMinFraction(mMinFraction);
- }
-
private float computeQsExpansionFraction() {
if (mQSAnimatingHiddenFromCollapsed) {
// When hiding QS from collapsed state, the expansion can sometimes temporarily
@@ -1853,6 +1852,9 @@ public class NotificationPanelViewController extends PanelViewController {
private boolean handleQsTouch(MotionEvent event) {
+ if (mShouldUseSplitNotificationShade && touchXOutsideOfQs(event.getX())) {
+ return false;
+ }
final int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
&& mBarState != KEYGUARD && !mQsExpanded && isQsExpansionEnabled()) {
@@ -1894,8 +1896,12 @@ public class NotificationPanelViewController extends PanelViewController {
return false;
}
+ private boolean touchXOutsideOfQs(float touchX) {
+ return touchX < mQsFrame.getX() || touchX > mQsFrame.getX() + mQsFrame.getWidth();
+ }
+
private boolean isInQsArea(float x, float y) {
- if (x < mQsFrame.getX() || x > mQsFrame.getX() + mQsFrame.getWidth()) {
+ if (touchXOutsideOfQs(x)) {
return false;
}
// Let's reject anything at the very bottom around the home handle in gesture nav
@@ -2113,7 +2119,7 @@ public class NotificationPanelViewController extends PanelViewController {
requestPanelHeightUpdate();
mFalsingCollector.setQsExpanded(expanded);
mStatusBar.setQsExpanded(expanded);
- mNotificationContainerParent.setQsExpanded(expanded);
+ mNotificationsQSContainerController.setQsExpanded(expanded);
mPulseExpansionHandler.setQsExpanded(expanded);
mKeyguardBypassController.setQSExpanded(expanded);
mStatusBarKeyguardViewManager.setQsExpanded(expanded);
@@ -2132,59 +2138,6 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
- private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
- @Override
- public void run() {
- mKeyguardStatusBar.setVisibility(View.INVISIBLE);
- mKeyguardStatusBar.setAlpha(1f);
- mKeyguardStatusBarAnimateAlpha = 1f;
- }
- };
-
- private void animateKeyguardStatusBarOut() {
- ValueAnimator anim = ValueAnimator.ofFloat(mKeyguardStatusBar.getAlpha(), 0f);
- anim.addUpdateListener(mStatusBarAnimateAlphaListener);
- anim.setStartDelay(mKeyguardStateController.isKeyguardFadingAway()
- ? mKeyguardStateController.getKeyguardFadingAwayDelay() : 0);
-
- long duration;
- if (mKeyguardStateController.isKeyguardFadingAway()) {
- duration = mKeyguardStateController.getShortenedFadingAwayDuration();
- } else {
- duration = StackStateAnimator.ANIMATION_DURATION_STANDARD;
- }
- anim.setDuration(duration);
-
- anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mAnimateKeyguardStatusBarInvisibleEndRunnable.run();
- }
- });
- anim.start();
- }
-
- private final ValueAnimator.AnimatorUpdateListener
- mStatusBarAnimateAlphaListener =
- new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue();
- updateHeaderKeyguardAlpha();
- }
- };
-
- private void animateKeyguardStatusBarIn(long duration) {
- mKeyguardStatusBar.setVisibility(View.VISIBLE);
- mKeyguardStatusBar.setAlpha(0f);
- ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
- anim.addUpdateListener(mStatusBarAnimateAlphaListener);
- anim.setDuration(duration);
- anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- anim.start();
- }
-
private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
@Override
public void run() {
@@ -2237,11 +2190,10 @@ public class NotificationPanelViewController extends PanelViewController {
mQsExpansionHeight = height;
updateQsExpansion();
requestScrollerTopPaddingUpdate(false /* animate */);
- updateHeaderKeyguardAlpha();
+ mKeyguardStatusBarViewController.updateViewState();
if (mBarState == StatusBarState.SHADE_LOCKED || mBarState == KEYGUARD) {
updateKeyguardBottomAreaAlpha();
positionClockAndNotifications();
- updateBigClockAlpha();
}
if (mAccessibilityManager.isEnabled()) {
@@ -2265,7 +2217,8 @@ public class NotificationPanelViewController extends PanelViewController {
private void updateQsExpansion() {
if (mQs == null) return;
float qsExpansionFraction = computeQsExpansionFraction();
- mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
+ mQs.setQsExpansion(qsExpansionFraction, getExpandedFraction(), getHeaderTranslation(),
+ mNotificationStackScrollLayoutController.getNotificationSquishinessFraction());
mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
@@ -2289,15 +2242,10 @@ public class NotificationPanelViewController extends PanelViewController {
updateQSExpansionEnabledAmbient();
}
- @Override
- public void setIsShadeOpening(boolean opening) {
- mAmbientState.setIsShadeOpening(opening);
- updateQSExpansionEnabledAmbient();
- }
-
private void updateQSExpansionEnabledAmbient() {
final float scrollRangeToTop = mAmbientState.getTopPadding() - mQuickQsOffsetHeight;
- mQsExpansionEnabledAmbient = mAmbientState.getScrollY() <= scrollRangeToTop;
+ mQsExpansionEnabledAmbient = mShouldUseSplitNotificationShade
+ || (mAmbientState.getScrollY() <= scrollRangeToTop);
setQsExpansionEnabled();
}
@@ -2346,8 +2294,8 @@ public class NotificationPanelViewController extends PanelViewController {
left = 0;
right = getView().getRight() + mDisplayRightInset;
} else {
- top = Math.min(qsPanelBottomY, mSplitShadeNotificationsTopPadding);
- bottom = mNotificationStackScrollLayoutController.getHeight();
+ top = Math.min(qsPanelBottomY, mSplitShadeStatusBarHeight);
+ bottom = top + mNotificationStackScrollLayoutController.getHeight();
left = mNotificationStackScrollLayoutController.getLeft();
right = mNotificationStackScrollLayoutController.getRight();
}
@@ -2409,7 +2357,6 @@ public class NotificationPanelViewController extends PanelViewController {
boolean qsVisible) {
// Fancy clipping for quick settings
int radius = mScrimCornerRadius;
- int statusBarClipTop = 0;
boolean clipStatusView = false;
if (!mShouldUseSplitNotificationShade) {
// The padding on this area is large enough that we can use a cheaper clipping strategy
@@ -2418,7 +2365,6 @@ public class NotificationPanelViewController extends PanelViewController {
float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius;
radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
Math.min(top / (float) mScrimCornerRadius, 1f));
- statusBarClipTop = top - mKeyguardStatusBar.getTop();
}
if (mQs != null) {
float qsTranslation = 0;
@@ -2430,7 +2376,7 @@ public class NotificationPanelViewController extends PanelViewController {
// qsTranslation should only be positive during pulse expansion because it's
// already translating in from the top
qsTranslation = Math.max(0, (top - mQs.getHeader().getHeight()) / 2.0f);
- } else {
+ } else if (!mShouldUseSplitNotificationShade) {
qsTranslation = (top - mQs.getHeader().getHeight()) * QS_PARALLAX_AMOUNT;
}
}
@@ -2456,12 +2402,17 @@ public class NotificationPanelViewController extends PanelViewController {
mScrimController.setNotificationsBounds(left, top, right, bottom);
}
+ if (mShouldUseSplitNotificationShade) {
+ mKeyguardStatusBarViewController.setNoTopClipping();
+ } else {
+ mKeyguardStatusBarViewController.updateTopClipping(top);
+ }
+
mScrimController.setScrimCornerRadius(radius);
- mKeyguardStatusBar.setTopClipping(statusBarClipTop);
int nsslLeft = left - mNotificationStackScrollLayoutController.getLeft();
int nsslRight = right - mNotificationStackScrollLayoutController.getLeft();
int nsslTop = top - mNotificationStackScrollLayoutController.getTop();
- int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop();
+ int nsslBottom = bottom;
int bottomRadius = mShouldUseSplitNotificationShade ? radius : 0;
mNotificationStackScrollLayoutController.setRoundedClippingBounds(
nsslLeft, nsslTop, nsslRight, nsslBottom, radius, bottomRadius);
@@ -2502,7 +2453,7 @@ public class NotificationPanelViewController extends PanelViewController {
private float calculateNotificationsTopPadding() {
if (mShouldUseSplitNotificationShade && !mKeyguardShowing) {
- return mSplitShadeNotificationsTopPadding;
+ return 0;
}
if (mKeyguardShowing && (mQsExpandImmediate
|| mIsExpanding && mQsExpandedWhenExpandingStarted)) {
@@ -2761,8 +2712,9 @@ public class NotificationPanelViewController extends PanelViewController {
* @return Whether we should intercept a gesture to open Quick Settings.
*/
private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
- if (!isQsExpansionEnabled() || mCollapsedOnDown || (mKeyguardShowing
- && mKeyguardBypassController.getBypassEnabled())) {
+ if (!isQsExpansionEnabled() || mCollapsedOnDown
+ || (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled())
+ || (mKeyguardShowing && mShouldUseSplitNotificationShade)) {
return false;
}
View header = mKeyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader();
@@ -2963,7 +2915,7 @@ public class NotificationPanelViewController extends PanelViewController {
*/
private void updateHeader() {
if (mBarState == KEYGUARD) {
- updateHeaderKeyguardAlpha();
+ mKeyguardStatusBarViewController.updateViewState();
}
updateQsExpansion();
}
@@ -2987,47 +2939,6 @@ public class NotificationPanelViewController extends PanelViewController {
return Math.min(0, translation);
}
- /**
- * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area)
- * during swiping up
- */
- private float getKeyguardContentsAlpha() {
- float alpha;
- if (mBarState == KEYGUARD) {
-
- // When on Keyguard, we hide the header as soon as we expanded close enough to the
- // header
- alpha =
- getExpandedHeight() / (mKeyguardStatusBar.getHeight()
- + mNotificationsHeaderCollideDistance);
- } else {
-
- // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
- // soon as we start translating the stack.
- alpha = getExpandedHeight() / mKeyguardStatusBar.getHeight();
- }
- alpha = MathUtils.saturate(alpha);
- alpha = (float) Math.pow(alpha, 0.75);
- return alpha;
- }
-
- private void updateHeaderKeyguardAlpha() {
- if (!mKeyguardShowing) {
- return;
- }
- float alphaQsExpansion = 1 - Math.min(1, computeQsExpansionFraction() * 2);
- float newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
- * mKeyguardStatusBarAnimateAlpha;
- newAlpha *= 1.0f - mKeyguardHeadsUpShowingAmount;
- mKeyguardStatusBar.setAlpha(newAlpha);
- boolean
- hideForBypass =
- mFirstBypassAttempt && mUpdateMonitor.shouldListenForFace()
- || mDelayShowingKeyguardStatusBar;
- mKeyguardStatusBar.setVisibility(
- newAlpha != 0f && !mDozing && !hideForBypass ? View.VISIBLE : View.INVISIBLE);
- }
-
private void updateKeyguardBottomAreaAlpha() {
// There are two possible panel expansion behaviors:
// • User dragging up to unlock: we want to fade out as quick as possible
@@ -3051,20 +2962,6 @@ public class NotificationPanelViewController extends PanelViewController {
mLockIconViewController.setAlpha(alpha);
}
- /**
- * Custom clock fades away when user drags up to unlock or pulls down quick settings.
- *
- * Updates alpha of custom clock to match the alpha of the KeyguardBottomArea. See
- * {@link #updateKeyguardBottomAreaAlpha}.
- */
- private void updateBigClockAlpha() {
- float expansionAlpha = MathUtils.map(
- isUnlockHintRunning() ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f,
- getExpandedFraction());
- float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
- mBigClockContainer.setAlpha(alpha);
- }
-
@Override
protected void onExpandingStarted() {
super.onExpandingStarted();
@@ -3085,7 +2982,7 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
protected void onExpandingFinished() {
- super.onExpandingFinished();
+ mScrimController.onExpandingFinished();
mNotificationStackScrollLayoutController.onExpansionStopped();
mHeadsUpManager.onExpandingFinished();
mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
@@ -3128,7 +3025,7 @@ public class NotificationPanelViewController extends PanelViewController {
}
private void setListening(boolean listening) {
- mKeyguardStatusBar.setListening(listening);
+ mKeyguardStatusBarViewController.setBatteryListening(listening);
if (mQs == null) return;
mQs.setListening(listening);
}
@@ -3160,6 +3057,7 @@ public class NotificationPanelViewController extends PanelViewController {
protected void onTrackingStarted() {
mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
super.onTrackingStarted();
+ mScrimController.onTrackingStarted();
if (mQsFullyExpanded) {
mQsExpandImmediate = true;
if (!mShouldUseSplitNotificationShade) {
@@ -3170,6 +3068,7 @@ public class NotificationPanelViewController extends PanelViewController {
mAffordanceHelper.animateHideLeftRightIcon();
}
mNotificationStackScrollLayoutController.onPanelTrackingStarted();
+ cancelPendingPanelCollapse();
}
@Override
@@ -3254,7 +3153,7 @@ public class NotificationPanelViewController extends PanelViewController {
private void updateDozingVisibilities(boolean animate) {
mKeyguardBottomArea.setDozing(mDozing, animate);
if (!mDozing && animate) {
- animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
}
}
@@ -3323,39 +3222,22 @@ public class NotificationPanelViewController extends PanelViewController {
mPanelAlphaEndAction = r;
}
- private void updateKeyguardStatusBarForHeadsUp() {
- boolean
- showingKeyguardHeadsUp =
- mKeyguardShowing && mHeadsUpAppearanceController.shouldBeVisible();
- if (mShowingKeyguardHeadsUp != showingKeyguardHeadsUp) {
- mShowingKeyguardHeadsUp = showingKeyguardHeadsUp;
- if (mKeyguardShowing) {
- PropertyAnimator.setProperty(mView, KEYGUARD_HEADS_UP_SHOWING_AMOUNT,
- showingKeyguardHeadsUp ? 1.0f : 0.0f, KEYGUARD_HUN_PROPERTIES,
- true /* animate */);
- } else {
- PropertyAnimator.applyImmediately(mView, KEYGUARD_HEADS_UP_SHOWING_AMOUNT, 0.0f);
- }
- }
- }
-
- private void setKeyguardHeadsUpShowingAmount(float amount) {
- mKeyguardHeadsUpShowingAmount = amount;
- updateHeaderKeyguardAlpha();
- }
-
- private float getKeyguardHeadsUpShowingAmount() {
- return mKeyguardHeadsUpShowingAmount;
- }
-
public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
mHeadsUpAnimatingAway = headsUpAnimatingAway;
mNotificationStackScrollLayoutController.setHeadsUpAnimatingAway(headsUpAnimatingAway);
- updateHeadsUpVisibility();
+ updateVisibility();
}
- private void updateHeadsUpVisibility() {
- ((PhoneStatusBarView) mBar).setHeadsUpVisible(mHeadsUpAnimatingAway || mHeadsUpPinnedMode);
+ /** Set whether the bouncer is showing. */
+ public void setBouncerShowing(boolean bouncerShowing) {
+ mBouncerShowing = bouncerShowing;
+ updateVisibility();
+ }
+
+ @Override
+ protected boolean shouldPanelBeVisible() {
+ boolean headsUpVisible = mHeadsUpAnimatingAway || mHeadsUpPinnedMode;
+ return headsUpVisible || isExpanded() || mBouncerShowing;
}
@Override
@@ -3376,8 +3258,7 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
protected void onClosingFinished() {
- super.onClosingFinished();
- resetHorizontalPanelPosition();
+ mStatusBar.onClosingFinished();
setClosingWithAlphaFadeout(false);
mMediaHierarchyManager.closeGuts();
}
@@ -3387,47 +3268,6 @@ public class NotificationPanelViewController extends PanelViewController {
mNotificationStackScrollLayoutController.forceNoOverlappingRendering(closing);
}
- /**
- * Updates the horizontal position of the panel so it is positioned closer to the touch
- * responsible for opening the panel.
- *
- * @param x the x-coordinate the touch event
- */
- protected void updateHorizontalPanelPosition(float x) {
- if (mNotificationStackScrollLayoutController.getWidth() * 1.75f > mView.getWidth()
- || mShouldUseSplitNotificationShade) {
- resetHorizontalPanelPosition();
- return;
- }
- float leftMost = mPositionMinSideMargin
- + mNotificationStackScrollLayoutController.getWidth() / 2;
- float
- rightMost =
- mView.getWidth() - mPositionMinSideMargin
- - mNotificationStackScrollLayoutController.getWidth() / 2;
- if (Math.abs(x - mView.getWidth() / 2)
- < mNotificationStackScrollLayoutController.getWidth() / 4) {
- x = mView.getWidth() / 2;
- }
- x = Math.min(rightMost, Math.max(leftMost, x));
- float
- center = mNotificationStackScrollLayoutController.getLeft()
- + mNotificationStackScrollLayoutController.getWidth() / 2;
- setHorizontalPanelTranslation(x - center);
- }
-
- private void resetHorizontalPanelPosition() {
- setHorizontalPanelTranslation(0f);
- }
-
- protected void setHorizontalPanelTranslation(float translation) {
- mNotificationStackScrollLayoutController.setTranslationX(translation);
- mQsFrame.setTranslationX(translation);
- if (mVerticalTranslationListener != null) {
- mVerticalTranslationListener.run();
- }
- }
-
protected void updateExpandedHeight(float expandedHeight) {
if (mTracking) {
mNotificationStackScrollLayoutController
@@ -3439,7 +3279,6 @@ public class NotificationPanelViewController extends PanelViewController {
}
mNotificationStackScrollLayoutController.setExpandedHeight(expandedHeight);
updateKeyguardBottomAreaAlpha();
- updateBigClockAlpha();
updateStatusBarIcons();
}
@@ -3470,8 +3309,14 @@ public class NotificationPanelViewController extends PanelViewController {
return mBarState == KEYGUARD;
}
+ /**
+ * Sets the minimum fraction for the panel expansion offset. This may be non-zero in certain
+ * cases, such as if there's a heads-up notification.
+ */
public void setPanelScrimMinFraction(float minFraction) {
- mBar.onPanelMinFractionChanged(minFraction);
+ mMinFraction = minFraction;
+ mDepthController.setPanelPullDownMinFraction(mMinFraction);
+ mScrimController.setPanelScrimMinFraction(mMinFraction);
}
public void clearNotificationEffects() {
@@ -3587,7 +3432,7 @@ public class NotificationPanelViewController extends PanelViewController {
mQs.setExpandClickListener(mOnClickListener);
mQs.setHeaderClickable(isQsExpansionEnabled());
mQs.setOverscrolling(mStackScrollerOverscrolling);
- mQs.setTranslateWhileExpanding(mShouldUseSplitNotificationShade);
+ mQs.setInSplitShade(mShouldUseSplitNotificationShade);
// recompute internal state when qspanel height changes
mQs.getView().addOnLayoutChangeListener(
@@ -3650,6 +3495,7 @@ public class NotificationPanelViewController extends PanelViewController {
mDozing = dozing;
mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation);
mKeyguardBottomArea.setDozing(mDozing, animate);
+ mKeyguardStatusBarViewController.setDozing(mDozing);
if (dozing) {
mBottomAreaShadeAlphaAnimator.cancel();
@@ -3716,7 +3562,7 @@ public class NotificationPanelViewController extends PanelViewController {
}
public void applyLaunchAnimationProgress(float linearProgress) {
- boolean hideIcons = ActivityLaunchAnimator.getProgress(linearProgress,
+ boolean hideIcons = LaunchAnimator.getProgress(linearProgress,
ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
if (hideIcons != mHideIconsDuringLaunchAnimation) {
mHideIconsDuringLaunchAnimation = hideIcons;
@@ -3734,10 +3580,6 @@ public class NotificationPanelViewController extends PanelViewController {
mTrackingHeadsUpListeners.remove(listener);
}
- public void setVerticalTranslationListener(Runnable verticalTranslationListener) {
- mVerticalTranslationListener = verticalTranslationListener;
- }
-
public void setHeadsUpAppearanceController(
HeadsUpAppearanceController headsUpAppearanceController) {
mHeadsUpAppearanceController = headsUpAppearanceController;
@@ -3764,6 +3606,11 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
+ /** */
+ public void setImportantForAccessibility(int mode) {
+ mView.setImportantForAccessibility(mode);
+ }
+
/**
* Do not let the user drag the shade up and down for the current touch session.
* This is necessary to avoid shade expansion while/after the bouncer is dismissed.
@@ -3777,10 +3624,9 @@ public class NotificationPanelViewController extends PanelViewController {
super.dump(fd, pw, args);
pw.println(" gestureExclusionRect: " + calculateGestureExclusionRect()
+ " applyQSClippingImmediately: top(" + mQsClipTop + ") bottom(" + mQsClipBottom
- + ") qsVisible(" + mQsVisible
- );
- if (mKeyguardStatusBar != null) {
- mKeyguardStatusBar.dump(fd, pw, args);
+ + ") qsVisible(" + mQsVisible);
+ if (mKeyguardStatusBarViewController != null) {
+ mKeyguardStatusBarViewController.dump(fd, pw, args);
}
}
@@ -3842,13 +3688,27 @@ public class NotificationPanelViewController extends PanelViewController {
mNotificationStackScrollLayoutController.setScrollingEnabled(b);
}
+ private Runnable mHideExpandedRunnable;
+ private final Runnable mMaybeHideExpandedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (getExpansionFraction() == 0.0f) {
+ mView.post(mHideExpandedRunnable);
+ }
+ }
+ };
+
/**
* Initialize objects instead of injecting to avoid circular dependencies.
+ *
+ * @param hideExpandedRunnable a runnable to run when we need to hide the expanded panel.
*/
public void initDependencies(
StatusBar statusBar,
+ Runnable hideExpandedRunnable,
NotificationShelfController notificationShelfController) {
setStatusBar(statusBar);
+ mHideExpandedRunnable = hideExpandedRunnable;
mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
mNotificationShelfController = notificationShelfController;
mLockscreenShadeTransitionController.bindController(notificationShelfController);
@@ -3905,8 +3765,47 @@ public class NotificationPanelViewController extends PanelViewController {
private long mLastTouchDownTime = -1L;
@Override
+ public boolean onTouchForwardedFromStatusBar(MotionEvent event) {
+ // TODO(b/202981994): Move the touch debugging in this method to a central location.
+ // (Right now, it's split between StatusBar and here.)
+
+ // If panels aren't enabled, ignore the gesture and don't pass it down to the
+ // panel view.
+ if (!mCommandQueue.panelsEnabled()) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ Log.v(
+ TAG,
+ String.format(
+ "onTouchForwardedFromStatusBar: "
+ + "panel disabled, ignoring touch at (%d,%d)",
+ (int) event.getX(),
+ (int) event.getY()
+ )
+ );
+ }
+ return false;
+ }
+
+ // If the view that would receive the touch is disabled, just have status bar eat
+ // the gesture.
+ if (event.getAction() == MotionEvent.ACTION_DOWN && !mView.isEnabled()) {
+ Log.v(TAG,
+ String.format(
+ "onTouchForwardedFromStatusBar: "
+ + "panel view disabled, eating touch at (%d,%d)",
+ (int) event.getX(),
+ (int) event.getY()
+ )
+ );
+ return true;
+ }
+
+ return mView.dispatchTouchEvent(event);
+ }
+
+ @Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) {
+ if (mBlockTouches || mQs.disallowPanelTouches()) {
return false;
}
initDownStates(event);
@@ -3915,7 +3814,9 @@ public class NotificationPanelViewController extends PanelViewController {
if (mStatusBar.isBouncerShowing()) {
return true;
}
- if (mBar.panelEnabled() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+ if (mCommandQueue.panelsEnabled()
+ && !mNotificationStackScrollLayoutController.isLongPressInProgress()
+ && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
return true;
@@ -3980,6 +3881,7 @@ public class NotificationPanelViewController extends PanelViewController {
return true;
}
if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
+ && !mNotificationStackScrollLayoutController.isLongPressInProgress()
&& mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
}
@@ -3998,7 +3900,6 @@ public class NotificationPanelViewController extends PanelViewController {
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
- updateHorizontalPanelPosition(event.getX());
handled = true;
}
@@ -4059,6 +3960,20 @@ public class NotificationPanelViewController extends PanelViewController {
mContentResolver.unregisterContentObserver(mSettingsChangeObserver);
}
+ /**
+ * Updates notification panel-specific flags on {@link SysUiState}.
+ */
+ public void updateSystemUiStateFlags() {
+ if (SysUiState.DEBUG) {
+ Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
+ + isFullyExpanded() + " inQs=" + isInSettings());
+ }
+ mSysUiState.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
+ isFullyExpanded() && !isInSettings())
+ .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED, isInSettings())
+ .commitUpdate(mDisplayId);
+ }
+
private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
@Override
public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
@@ -4127,9 +4042,7 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
public void flingTopOverscroll(float velocity, boolean open) {
// in split shade mode we want to expand/collapse QS only when touch happens within QS
- if (mShouldUseSplitNotificationShade
- && (mInitialTouchX < mQsFrame.getX()
- || mInitialTouchX > mQsFrame.getX() + mQsFrame.getWidth())) {
+ if (mShouldUseSplitNotificationShade && touchXOutsideOfQs(mInitialTouchX)) {
return;
}
mLastOverscroll = 0f;
@@ -4324,8 +4237,8 @@ public class NotificationPanelViewController extends PanelViewController {
}
updateGestureExclusionRect();
mHeadsUpPinnedMode = inPinnedMode;
- updateHeadsUpVisibility();
- updateKeyguardStatusBarForHeadsUp();
+ updateVisibility();
+ mKeyguardStatusBarViewController.updateForHeadsUp();
}
@Override
@@ -4370,12 +4283,7 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
public void onThemeChanged() {
if (DEBUG) Log.d(TAG, "onThemeChanged");
- final int themeResId = mView.getContext().getThemeResId();
- if (mThemeResId == themeResId) {
- return;
- }
- mThemeResId = themeResId;
-
+ mThemeResId = mView.getContext().getThemeResId();
reInflateViews();
}
@@ -4389,12 +4297,6 @@ public class NotificationPanelViewController extends PanelViewController {
}
@Override
- public void onOverlayChanged() {
- if (DEBUG) Log.d(TAG, "onOverlayChanged");
- reInflateViews();
- }
-
- @Override
public void onDensityOrFontScaleChanged() {
if (DEBUG) Log.d(TAG, "onDensityOrFontScaleChanged");
reInflateViews();
@@ -4448,11 +4350,22 @@ public class NotificationPanelViewController extends PanelViewController {
if (oldState == KEYGUARD && (goingToFullShade
|| statusBarState == StatusBarState.SHADE_LOCKED)) {
- animateKeyguardStatusBarOut();
+
+ long startDelay;
+ long duration;
+ if (mKeyguardStateController.isKeyguardFadingAway()) {
+ startDelay = mKeyguardStateController.getKeyguardFadingAwayDelay();
+ duration = mKeyguardStateController.getShortenedFadingAwayDuration();
+ } else {
+ startDelay = 0;
+ duration = StackStateAnimator.ANIMATION_DURATION_STANDARD;
+ }
+ mKeyguardStatusBarViewController.animateKeyguardStatusBarOut(startDelay, duration);
updateQSMinHeight();
} else if (oldState == StatusBarState.SHADE_LOCKED
&& statusBarState == KEYGUARD) {
- animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
+
mNotificationStackScrollLayoutController.resetScrollPosition();
// Only animate header if the header is visible. If not, it will partially
// animate out
@@ -4464,15 +4377,23 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
} else {
- mKeyguardStatusBar.setAlpha(1f);
- mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
+ final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
+ && statusBarState == KEYGUARD
+ && mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying();
+ if (!animatingUnlockedShadeToKeyguard) {
+ // Only make the status bar visible if we're not animating the screen off, since
+ // we only want to be showing the clock/notifications during the animation.
+ mKeyguardStatusBarViewController.updateViewState(
+ /* alpha= */ 1f,
+ keyguardShowing ? View.VISIBLE : View.INVISIBLE);
+ }
if (keyguardShowing && oldState != mBarState) {
if (mQs != null) {
mQs.hideImmediately();
}
}
}
- updateKeyguardStatusBarForHeadsUp();
+ mKeyguardStatusBarViewController.updateForHeadsUp();
if (keyguardShowing) {
updateDozingVisibilities(false /* animate */);
}
@@ -4481,8 +4402,9 @@ public class NotificationPanelViewController extends PanelViewController {
// The update needs to happen after the headerSlide in above, otherwise the translation
// would reset
maybeAnimateBottomAreaAlpha();
- resetHorizontalPanelPosition();
updateQsState();
+ mSplitShadeHeaderController.setShadeExpanded(
+ mBarState == SHADE || mBarState == SHADE_LOCKED);
}
@Override
@@ -4496,6 +4418,43 @@ public class NotificationPanelViewController extends PanelViewController {
}
/**
+ * An interface that provides the current state of the notification panel and related views,
+ * which is needed to calculate {@link KeyguardStatusBarView}'s state in
+ * {@link KeyguardStatusBarViewController}.
+ */
+ public interface NotificationPanelViewStateProvider {
+ /** Returns the expanded height of the panel view. */
+ float getPanelViewExpandedHeight();
+ /** Returns the fraction of QS that's expanded. */
+ float getQsExpansionFraction();
+ /**
+ * Returns true if heads up should be visible.
+ *
+ * TODO(b/138786270): If HeadsUpAppearanceController was injectable, we could inject it into
+ * {@link KeyguardStatusBarViewController} and remove this method.
+ */
+ boolean shouldHeadsUpBeVisible();
+ }
+
+ private final NotificationPanelViewStateProvider mNotificationPanelViewStateProvider =
+ new NotificationPanelViewStateProvider() {
+ @Override
+ public float getPanelViewExpandedHeight() {
+ return getExpandedHeight();
+ }
+
+ @Override
+ public float getQsExpansionFraction() {
+ return computeQsExpansionFraction();
+ }
+
+ @Override
+ public boolean shouldHeadsUpBeVisible() {
+ return mHeadsUpAppearanceController.shouldBeVisible();
+ }
+ };
+
+ /**
* Reconfigures the shade to show the AOD UI (clock, smartspace, etc). This is called by the
* screen off animation controller in order to animate in AOD without "actually" fully switching
* to the KEYGUARD state, which is a heavy transition that causes jank as 10+ files react to the
@@ -4527,7 +4486,6 @@ public class NotificationPanelViewController extends PanelViewController {
.addTagListener(QS.TAG, mFragmentListener);
mStatusBarStateController.addCallback(mStatusBarStateListener);
mConfigurationController.addCallback(mConfigurationListener);
- mUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
// Theme might have changed between inflating this view and attaching it to the
// window, so
// force a call to onThemeChanged
@@ -4544,7 +4502,6 @@ public class NotificationPanelViewController extends PanelViewController {
.removeTagListener(QS.TAG, mFragmentListener);
mStatusBarStateController.removeCallback(mStatusBarStateListener);
mConfigurationController.removeCallback(mConfigurationListener);
- mUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
mFalsingManager.removeTapListener(mFalsingTapListener);
}
}
@@ -4670,9 +4627,6 @@ public class NotificationPanelViewController extends PanelViewController {
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mAffordanceHelper.onConfigurationChanged();
- if (newConfig.orientation != mLastOrientation) {
- resetHorizontalPanelPosition();
- }
mLastOrientation = newConfig.orientation;
}
}
@@ -4690,4 +4644,45 @@ public class NotificationPanelViewController extends PanelViewController {
return insets;
}
}
+
+ /** Removes any pending runnables that would collapse the panel. */
+ public void cancelPendingPanelCollapse() {
+ mView.removeCallbacks(mMaybeHideExpandedRunnable);
+ }
+
+ private final PanelBar.PanelStateChangeListener mPanelStateChangeListener =
+ new PanelBar.PanelStateChangeListener() {
+
+ @PanelBar.PanelState
+ private int mCurrentState = STATE_CLOSED;
+
+ @Override
+ public void onStateChanged(@PanelBar.PanelState int state) {
+ mAmbientState.setIsShadeOpening(state == STATE_OPENING);
+ updateQSExpansionEnabledAmbient();
+
+ if (state == STATE_OPEN && mCurrentState != state) {
+ mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+ if (state == STATE_OPENING) {
+ mStatusBar.makeExpandedVisible(false);
+ }
+ if (state == STATE_CLOSED) {
+ // Close the status bar in the next frame so we can show the end of the
+ // animation.
+ mView.post(mMaybeHideExpandedRunnable);
+ }
+ mCurrentState = state;
+ }
+ };
+
+ public PanelBar.PanelStateChangeListener getPanelStateChangeListener() {
+ return mPanelStateChangeListener;
+ }
+
+
+ /** Returns the handler that the status bar should forward touches to. */
+ public PhoneStatusBarView.TouchEventHandler getStatusBarTouchEventHandler() {
+ return getTouchHandler()::onTouchForwardedFromStatusBar;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index c26782b017c6..030a8951943d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -84,8 +84,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
private final WindowManager mWindowManager;
private final IActivityManager mActivityManager;
private final DozeParameters mDozeParameters;
+ private final KeyguardStateController mKeyguardStateController;
private final LayoutParams mLpChanged;
- private final boolean mKeyguardScreenRotation;
private final long mLockScreenDisplayTimeout;
private final float mKeyguardPreferredRefreshRate; // takes precedence over max
private final float mKeyguardMaxRefreshRate;
@@ -123,8 +123,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
mContext = context;
mWindowManager = windowManager;
mActivityManager = activityManager;
- mKeyguardScreenRotation = keyguardStateController.isKeyguardScreenRotationAllowed();
mDozeParameters = dozeParameters;
+ mKeyguardStateController = keyguardStateController;
mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
mLpChanged = new LayoutParams();
mKeyguardViewMediator = keyguardViewMediator;
@@ -323,7 +323,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
private void adjustScreenOrientation(State state) {
if (state.isKeyguardShowingAndNotOccluded() || state.mDozing) {
- if (mKeyguardScreenRotation) {
+ if (mKeyguardStateController.isKeyguardScreenRotationAllowed()) {
mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
} else {
mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
@@ -473,7 +473,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
for (StatusBarWindowCallback cb : activeCallbacks) {
cb.onStateChanged(mCurrentState.mKeyguardShowing,
mCurrentState.mKeyguardOccluded,
- mCurrentState.mBouncerShowing);
+ mCurrentState.mBouncerShowing,
+ mCurrentState.mDozing);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 3807b4647fe1..36bd31b2efe3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -48,7 +48,6 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -57,7 +56,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.InjectionInflationController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -69,7 +67,6 @@ import javax.inject.Inject;
*/
public class NotificationShadeWindowViewController {
private static final String TAG = "NotifShadeWindowVC";
- private final InjectionInflationController mInjectionInflationController;
private final NotificationWakeUpCoordinator mCoordinator;
private final PulseExpansionHandler mPulseExpansionHandler;
private final DynamicPrivacyController mDynamicPrivacyController;
@@ -108,7 +105,7 @@ public class NotificationShadeWindowViewController {
private boolean mExpandingBelowNotch;
private final DockManager mDockManager;
private final NotificationPanelViewController mNotificationPanelViewController;
- private final SuperStatusBarViewFactory mStatusBarViewFactory;
+ private final StatusBarWindowView mStatusBarWindowView;
// Used for determining view / touch intersection
private int[] mTempLocation = new int[2];
@@ -117,7 +114,6 @@ public class NotificationShadeWindowViewController {
@Inject
public NotificationShadeWindowViewController(
- InjectionInflationController injectionInflationController,
NotificationWakeUpCoordinator coordinator,
PulseExpansionHandler pulseExpansionHandler,
DynamicPrivacyController dynamicPrivacyController,
@@ -138,11 +134,10 @@ public class NotificationShadeWindowViewController {
NotificationShadeDepthController depthController,
NotificationShadeWindowView notificationShadeWindowView,
NotificationPanelViewController notificationPanelViewController,
- SuperStatusBarViewFactory statusBarViewFactory,
+ StatusBarWindowView statusBarWindowView,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
LockIconViewController lockIconViewController) {
- mInjectionInflationController = injectionInflationController;
mCoordinator = coordinator;
mPulseExpansionHandler = pulseExpansionHandler;
mDynamicPrivacyController = dynamicPrivacyController;
@@ -163,7 +158,7 @@ public class NotificationShadeWindowViewController {
mDockManager = dockManager;
mNotificationPanelViewController = notificationPanelViewController;
mDepthController = depthController;
- mStatusBarViewFactory = statusBarViewFactory;
+ mStatusBarWindowView = statusBarWindowView;
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mLockIconViewController = lockIconViewController;
@@ -172,6 +167,13 @@ public class NotificationShadeWindowViewController {
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
}
+ /**
+ * @return Location where to place the KeyguardBouncer
+ */
+ public ViewGroup getBouncerContainer() {
+ return mView.findViewById(R.id.keyguard_bouncer_container);
+ }
+
/** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
public void setupExpandedStatusBar() {
mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
@@ -491,11 +493,10 @@ public class NotificationShadeWindowViewController {
public void setStatusBarView(PhoneStatusBarView statusBarView) {
mStatusBarView = statusBarView;
- if (statusBarView != null && mStatusBarViewFactory != null) {
+ if (statusBarView != null) {
mBarTransitions = new PhoneStatusBarTransitions(
statusBarView,
- mStatusBarViewFactory.getStatusBarWindowView()
- .findViewById(R.id.status_bar_container));
+ mStatusBarWindowView.findViewById(R.id.status_bar_container));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
new file mode 100644
index 000000000000..34bb6d3e1a27
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
@@ -0,0 +1,141 @@
+package com.android.systemui.statusbar.phone
+
+import android.view.WindowInsets
+import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.plugins.qs.QS
+import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.util.ViewController
+import java.util.function.Consumer
+import javax.inject.Inject
+
+class NotificationsQSContainerController @Inject constructor(
+ view: NotificationsQuickSettingsContainer,
+ private val navigationModeController: NavigationModeController,
+ private val overviewProxyService: OverviewProxyService
+) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
+
+ var qsExpanded = false
+ set(value) {
+ if (field != value) {
+ field = value
+ mView.invalidate()
+ }
+ }
+ var splitShadeEnabled = false
+ set(value) {
+ if (field != value) {
+ field = value
+ // in case device configuration changed while showing QS details/customizer
+ updateBottomSpacing()
+ }
+ }
+
+ private var isQSDetailShowing = false
+ private var isQSCustomizing = false
+ private var isQSCustomizerAnimating = false
+
+ private var notificationsBottomMargin = 0
+ private var bottomStableInsets = 0
+ private var bottomCutoutInsets = 0
+
+ private var isGestureNavigation = true
+ private var taskbarVisible = false
+ private val taskbarVisibilityListener: OverviewProxyListener = object : OverviewProxyListener {
+ override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) {
+ taskbarVisible = visible
+ }
+ }
+ private val windowInsetsListener: Consumer<WindowInsets> = Consumer { insets ->
+ // when taskbar is visible, stableInsetBottom will include its height
+ bottomStableInsets = insets.stableInsetBottom
+ bottomCutoutInsets = insets.displayCutout?.safeInsetBottom ?: 0
+ updateBottomSpacing()
+ }
+
+ override fun onInit() {
+ val currentMode: Int = navigationModeController.addListener { mode: Int ->
+ isGestureNavigation = QuickStepContract.isGesturalMode(mode)
+ }
+ isGestureNavigation = QuickStepContract.isGesturalMode(currentMode)
+ }
+
+ public override fun onViewAttached() {
+ notificationsBottomMargin = mView.defaultNotificationsMarginBottom
+ overviewProxyService.addCallback(taskbarVisibilityListener)
+ mView.setInsetsChangedListener(windowInsetsListener)
+ mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) }
+ }
+
+ override fun onViewDetached() {
+ overviewProxyService.removeCallback(taskbarVisibilityListener)
+ mView.removeOnInsetsChangedListener()
+ mView.removeQSFragmentAttachedListener()
+ }
+
+ override fun setCustomizerAnimating(animating: Boolean) {
+ if (isQSCustomizerAnimating != animating) {
+ isQSCustomizerAnimating = animating
+ mView.invalidate()
+ }
+ }
+
+ override fun setCustomizerShowing(showing: Boolean) {
+ isQSCustomizing = showing
+ updateBottomSpacing()
+ }
+
+ override fun setDetailShowing(showing: Boolean) {
+ isQSDetailShowing = showing
+ updateBottomSpacing()
+ }
+
+ private fun updateBottomSpacing() {
+ val (containerPadding, notificationsMargin) = calculateBottomSpacing()
+ var qsScrollPaddingBottom = 0
+ if (!(splitShadeEnabled || isQSCustomizing || isQSDetailShowing || isGestureNavigation ||
+ taskbarVisible)) {
+ // no taskbar, portrait, navigation buttons enabled:
+ // padding is needed so QS can scroll up over bottom insets - to reach the point when
+ // the whole QS is above bottom insets
+ qsScrollPaddingBottom = bottomStableInsets
+ }
+ mView.setPadding(0, 0, 0, containerPadding)
+ mView.setNotificationsMarginBottom(notificationsMargin)
+ mView.setQSScrollPaddingBottom(qsScrollPaddingBottom)
+ }
+
+ private fun calculateBottomSpacing(): Pair<Int, Int> {
+ val containerPadding: Int
+ var stackScrollMargin = notificationsBottomMargin
+ if (splitShadeEnabled) {
+ if (isGestureNavigation) {
+ // only default cutout padding, taskbar always hides
+ containerPadding = bottomCutoutInsets
+ } else if (taskbarVisible) {
+ // navigation buttons + visible taskbar means we're NOT on homescreen
+ containerPadding = bottomStableInsets
+ } else {
+ // navigation buttons + hidden taskbar means we're on homescreen
+ containerPadding = 0
+ // we need extra margin for notifications as navigation buttons are below them
+ stackScrollMargin = bottomStableInsets + notificationsBottomMargin
+ }
+ } else {
+ if (isQSCustomizing || isQSDetailShowing) {
+ // Clear out bottom paddings/margins so the qs customization can be full height.
+ containerPadding = 0
+ stackScrollMargin = 0
+ } else if (isGestureNavigation) {
+ containerPadding = bottomCutoutInsets
+ } else if (taskbarVisible) {
+ containerPadding = bottomStableInsets
+ } else {
+ containerPadding = 0
+ }
+ }
+ return containerPadding to stackScrollMargin
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index ed8fb31aea32..9210a8b5db80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -22,7 +22,6 @@ import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowInsets;
-import android.widget.FrameLayout;
import androidx.constraintlayout.widget.ConstraintLayout;
@@ -31,10 +30,10 @@ import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.function.Consumer;
/**
* The container with notification stack scroller and quick settings inside.
@@ -42,20 +41,18 @@ import java.util.Comparator;
public class NotificationsQuickSettingsContainer extends ConstraintLayout
implements FragmentListener, AboveShelfObserver.HasViewAboveShelfChangedListener {
- private FrameLayout mQsFrame;
- private NotificationStackScrollLayout mStackScroller;
+ private View mQsFrame;
+ private View mStackScroller;
private View mKeyguardStatusBar;
- private boolean mQsExpanded;
- private boolean mCustomizerAnimating;
- private boolean mCustomizing;
- private boolean mDetailShowing;
- private int mBottomPadding;
private int mStackScrollerMargin;
- private boolean mHasViewsAboveShelf;
private ArrayList<View> mDrawingOrderedChildren = new ArrayList<>();
private ArrayList<View> mLayoutDrawingOrder = new ArrayList<>();
private final Comparator<View> mIndexComparator = Comparator.comparingInt(this::indexOfChild);
+ private Consumer<WindowInsets> mInsetsChangedListener = insets -> {};
+ private Consumer<QS> mQSFragmentAttachedListener = qs -> {};
+ private QS mQs;
+ private View mQSScrollView;
public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -71,6 +68,59 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
}
@Override
+ public void onFragmentViewCreated(String tag, Fragment fragment) {
+ mQs = (QS) fragment;
+ mQSFragmentAttachedListener.accept(mQs);
+ mQSScrollView = mQs.getView().findViewById(R.id.expanded_qs_scroll_view);
+ }
+
+ @Override
+ public void onHasViewsAboveShelfChanged(boolean hasViewsAboveShelf) {
+ invalidate();
+ }
+
+ public void setNotificationsMarginBottom(int margin) {
+ LayoutParams params = (LayoutParams) mStackScroller.getLayoutParams();
+ params.bottomMargin = margin;
+ mStackScroller.setLayoutParams(params);
+ }
+
+ public void setQSScrollPaddingBottom(int paddingBottom) {
+ if (mQSScrollView != null) {
+ mQSScrollView.setPaddingRelative(
+ mQSScrollView.getPaddingLeft(),
+ mQSScrollView.getPaddingTop(),
+ mQSScrollView.getPaddingRight(),
+ paddingBottom
+ );
+ }
+ }
+
+ public int getDefaultNotificationsMarginBottom() {
+ return mStackScrollerMargin;
+ }
+
+ public void setInsetsChangedListener(Consumer<WindowInsets> onInsetsChangedListener) {
+ mInsetsChangedListener = onInsetsChangedListener;
+ }
+
+ public void removeOnInsetsChangedListener() {
+ mInsetsChangedListener = insets -> {};
+ }
+
+ public void setQSFragmentAttachedListener(Consumer<QS> qsFragmentAttachedListener) {
+ mQSFragmentAttachedListener = qsFragmentAttachedListener;
+ // listener might be attached after fragment is attached
+ if (mQs != null) {
+ mQSFragmentAttachedListener.accept(mQs);
+ }
+ }
+
+ public void removeQSFragmentAttachedListener() {
+ mQSFragmentAttachedListener = qs -> {};
+ }
+
+ @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
FragmentHostManager.get(this).addTagListener(QS.TAG, this);
@@ -84,8 +134,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- mBottomPadding = insets.getStableInsetBottom();
- setPadding(0, 0, 0, mBottomPadding);
+ mInsetsChangedListener.accept(insets);
return insets;
}
@@ -121,57 +170,4 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
}
}
- @Override
- public void onFragmentViewCreated(String tag, Fragment fragment) {
- QS container = (QS) fragment;
- container.setContainer(this);
- }
-
- public void setQsExpanded(boolean expanded) {
- if (mQsExpanded != expanded) {
- mQsExpanded = expanded;
- invalidate();
- }
- }
-
- public void setCustomizerAnimating(boolean isAnimating) {
- if (mCustomizerAnimating != isAnimating) {
- mCustomizerAnimating = isAnimating;
- invalidate();
- }
- }
-
- public void setCustomizerShowing(boolean isShowing) {
- mCustomizing = isShowing;
- updateBottomMargin();
- mStackScroller.setQsCustomizerShowing(isShowing);
- }
-
- public void setDetailShowing(boolean isShowing) {
- mDetailShowing = isShowing;
- updateBottomMargin();
- }
-
- private void updateBottomMargin() {
- if (mCustomizing || mDetailShowing) {
- // Clear out bottom paddings/margins so the qs customization can be full height.
- setPadding(0, 0, 0, 0);
- setBottomMargin(mStackScroller, 0);
- } else {
- setPadding(0, 0, 0, mBottomPadding);
- setBottomMargin(mStackScroller, mStackScrollerMargin);
- }
- }
-
- private void setBottomMargin(View v, int bottomMargin) {
- LayoutParams params = (LayoutParams) v.getLayoutParams();
- params.bottomMargin = bottomMargin;
- v.setLayoutParams(params);
- }
-
- @Override
- public void onHasViewsAboveShelfChanged(boolean hasViewsAboveShelf) {
- mHasViewsAboveShelf = hasViewsAboveShelf;
- invalidate();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index eca91a3f6fb7..e90258db8571 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -17,24 +17,26 @@
package com.android.systemui.statusbar.phone;
import static java.lang.Float.isNaN;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
-import android.annotation.CallSuper;
+import android.annotation.IntDef;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.MotionEvent;
import android.widget.FrameLayout;
+import androidx.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+
public abstract class PanelBar extends FrameLayout {
public static final boolean DEBUG = false;
public static final String TAG = PanelBar.class.getSimpleName();
private static final boolean SPEW = false;
private static final String PANEL_BAR_SUPER_PARCELABLE = "panel_bar_super_parcelable";
private static final String STATE = "state";
- private boolean mBouncerShowing;
- private boolean mExpanded;
protected float mPanelFraction;
public static final void LOG(String fmt, Object... args) {
@@ -42,24 +44,32 @@ public abstract class PanelBar extends FrameLayout {
Log.v(TAG, String.format(fmt, args));
}
+ /** Enum for the current state of the panel. */
+ @Retention(SOURCE)
+ @IntDef({STATE_CLOSED, STATE_OPENING, STATE_OPEN})
+ @interface PanelState {}
public static final int STATE_CLOSED = 0;
public static final int STATE_OPENING = 1;
public static final int STATE_OPEN = 2;
- PanelViewController mPanel;
+ @Nullable private PanelStateChangeListener mPanelStateChangeListener;
private int mState = STATE_CLOSED;
private boolean mTracking;
- public void go(int state) {
- if (DEBUG) LOG("go state: %d -> %d", mState, state);
- mState = state;
- if (mPanel != null) {
- mPanel.setIsShadeOpening(state == STATE_OPENING);
+ /** Updates the panel state if necessary. */
+ public void updateState(@PanelState int state) {
+ if (DEBUG) LOG("update state: %d -> %d", mState, state);
+ if (mState != state) {
+ go(state);
}
}
- protected boolean isShadeOpening() {
- return mState == STATE_OPENING;
+ private void go(@PanelState int state) {
+ if (DEBUG) LOG("go state: %d -> %d", mState, state);
+ mState = state;
+ if (mPanelStateChangeListener != null) {
+ mPanelStateChangeListener.onStateChanged(state);
+ }
}
@Override
@@ -93,82 +103,9 @@ public abstract class PanelBar extends FrameLayout {
super.onFinishInflate();
}
- /** Set the PanelViewController */
- public void setPanel(PanelViewController pv) {
- mPanel = pv;
- pv.setBar(this);
- }
-
- public void setBouncerShowing(boolean showing) {
- mBouncerShowing = showing;
- int important = showing ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- : IMPORTANT_FOR_ACCESSIBILITY_AUTO;
-
- setImportantForAccessibility(important);
- updateVisibility();
-
- if (mPanel != null) mPanel.getView().setImportantForAccessibility(important);
- }
-
- public float getExpansionFraction() {
- return mPanelFraction;
- }
-
- public boolean isExpanded() {
- return mExpanded;
- }
-
- protected void updateVisibility() {
- mPanel.getView().setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
- }
-
- protected boolean shouldPanelBeVisible() {
- return mExpanded || mBouncerShowing;
- }
-
- public boolean panelEnabled() {
- return true;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- // Allow subclasses to implement enable/disable semantics
- if (!panelEnabled()) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- Log.v(TAG, String.format("onTouch: all panels disabled, ignoring touch at (%d,%d)",
- (int) event.getX(), (int) event.getY()));
- }
- return false;
- }
-
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- final PanelViewController panel = mPanel;
- if (panel == null) {
- // panel is not there, so we'll eat the gesture
- Log.v(TAG, String.format("onTouch: no panel for touch at (%d,%d)",
- (int) event.getX(), (int) event.getY()));
- return true;
- }
- boolean enabled = panel.isEnabled();
- if (DEBUG) LOG("PanelBar.onTouch: state=%d ACTION_DOWN: panel %s %s", mState, panel,
- (enabled ? "" : " (disabled)"));
- if (!enabled) {
- // panel is disabled, so we'll eat the gesture
- Log.v(TAG, String.format(
- "onTouch: panel (%s) is disabled, ignoring touch at (%d,%d)",
- panel, (int) event.getX(), (int) event.getY()));
- return true;
- }
- }
- return mPanel == null || mPanel.getView().dispatchTouchEvent(event);
- }
-
- /**
- * Percentage of panel expansion offset, caused by pulling down on a heads-up.
- */
- @CallSuper
- public void onPanelMinFractionChanged(float minFraction) {
- mPanel.setMinFraction(minFraction);
+ /** Sets the listener that will be notified of panel state changes. */
+ public void setPanelStateChangeListener(PanelStateChangeListener listener) {
+ mPanelStateChangeListener = listener;
}
/**
@@ -182,69 +119,30 @@ public abstract class PanelBar extends FrameLayout {
}
boolean fullyClosed = true;
boolean fullyOpened = false;
- if (SPEW) LOG("panelExpansionChanged: start state=%d", mState);
- PanelViewController pv = mPanel;
- mExpanded = expanded;
+ if (SPEW) LOG("panelExpansionChanged: start state=%d, f=%.1f", mState, frac);
mPanelFraction = frac;
- updateVisibility();
// adjust any other panels that may be partially visible
if (expanded) {
if (mState == STATE_CLOSED) {
go(STATE_OPENING);
- onPanelPeeked();
}
fullyClosed = false;
- final float thisFrac = pv.getExpandedFraction();
- if (SPEW) LOG("panelExpansionChanged: -> %s: f=%.1f", pv.getName(), thisFrac);
- fullyOpened = thisFrac >= 1f;
+ fullyOpened = frac >= 1f;
}
if (fullyOpened && !mTracking) {
go(STATE_OPEN);
- onPanelFullyOpened();
} else if (fullyClosed && !mTracking && mState != STATE_CLOSED) {
go(STATE_CLOSED);
- onPanelCollapsed();
}
if (SPEW) LOG("panelExpansionChanged: end state=%d [%s%s ]", mState,
fullyOpened?" fullyOpened":"", fullyClosed?" fullyClosed":"");
}
- public void collapsePanel(boolean animate, boolean delayed, float speedUpFactor) {
- boolean waiting = false;
- PanelViewController pv = mPanel;
- if (animate && !pv.isFullyCollapsed()) {
- pv.collapse(delayed, speedUpFactor);
- waiting = true;
- } else {
- pv.resetViews(false /* animate */);
- pv.setExpandedFraction(0); // just in case
- }
- if (DEBUG) LOG("collapsePanel: animate=%s waiting=%s", animate, waiting);
- if (!waiting && mState != STATE_CLOSED) {
- // it's possible that nothing animated, so we replicate the termination
- // conditions of panelExpansionChanged here
- go(STATE_CLOSED);
- onPanelCollapsed();
- }
- }
-
- public void onPanelPeeked() {
- if (DEBUG) LOG("onPanelPeeked");
- }
-
public boolean isClosed() {
return mState == STATE_CLOSED;
}
- public void onPanelCollapsed() {
- if (DEBUG) LOG("onPanelCollapsed");
- }
-
- public void onPanelFullyOpened() {
- if (DEBUG) LOG("onPanelFullyOpened");
- }
-
public void onTrackingStarted() {
mTracking = true;
}
@@ -253,11 +151,9 @@ public abstract class PanelBar extends FrameLayout {
mTracking = false;
}
- public void onExpandingFinished() {
- if (DEBUG) LOG("onExpandingFinished");
- }
-
- public void onClosingFinished() {
-
+ /** An interface that will be notified of panel state changes. */
+ public interface PanelStateChangeListener {
+ /** Called when the state changes. */
+ void onStateChanged(@PanelState int state);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 99c0c13ad3d5..e5296af86b81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.phone;
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.GENERIC;
@@ -79,7 +82,6 @@ public abstract class PanelViewController {
protected long mDownTime;
protected boolean mTouchSlopExceededBeforeDown;
private float mMinExpandHeight;
- private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
private boolean mPanelUpdateWhenAnimatorEnds;
private boolean mVibrateOnOpening;
protected boolean mIsLaunchAnimationRunning;
@@ -182,10 +184,10 @@ public abstract class PanelViewController {
protected final KeyguardStateController mKeyguardStateController;
protected final SysuiStatusBarStateController mStatusBarStateController;
protected final AmbientState mAmbientState;
+ protected final LockscreenGestureLogger mLockscreenGestureLogger;
+ private final TouchHandler mTouchHandler;
- protected void onExpandingFinished() {
- mBar.onExpandingFinished();
- }
+ protected abstract void onExpandingFinished();
protected void onExpandingStarted() {
}
@@ -217,10 +219,13 @@ public abstract class PanelViewController {
LatencyTracker latencyTracker,
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+ LockscreenGestureLogger lockscreenGestureLogger,
AmbientState ambientState) {
mAmbientState = ambientState;
mView = view;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mLockscreenGestureLogger = lockscreenGestureLogger;
+ mTouchHandler = createTouchHandler();
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -233,7 +238,7 @@ public abstract class PanelViewController {
});
mView.addOnLayoutChangeListener(createLayoutChangeListener());
- mView.setOnTouchListener(createTouchHandler());
+ mView.setOnTouchListener(mTouchHandler);
mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
mResources = mView.getResources();
@@ -284,6 +289,10 @@ public abstract class PanelViewController {
: mTouchSlop;
}
+ protected TouchHandler getTouchHandler() {
+ return mTouchHandler;
+ }
+
private void addMovement(MotionEvent event) {
// Add movement to velocity tracker using raw screen X and Y coordinates instead
// of window coordinates because the window frame may be moving at the same time.
@@ -313,7 +322,7 @@ public abstract class PanelViewController {
}
private void startOpening(MotionEvent event) {
- notifyBarPanelExpansionChanged();
+ updatePanelExpansionAndVisibility();
maybeVibrateOnOpening();
//TODO: keyguard opens QS a different way; log that too?
@@ -338,13 +347,6 @@ public abstract class PanelViewController {
protected abstract float getOpeningHeight();
/**
- * Minimum fraction from where expansion should start. This is set when pulling down on a
- * heads-up notification.
- * @param minFraction Fraction from 0 to 1.
- */
- public abstract void setMinFraction(float minFraction);
-
- /**
* @return whether the swiping direction is upwards and above a 45 degree angle compared to the
* horizontal direction
*/
@@ -387,11 +389,22 @@ public abstract class PanelViewController {
final boolean expand;
if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
- // If we get a cancel, put the shade back to the state it was in when the gesture
- // started
- if (onKeyguard) {
+ // If the keyguard is fading away, don't expand it again. This can happen if you're
+ // swiping to unlock, the app below the keyguard is in landscape, and the screen
+ // rotates while your finger is still down after the swipe to unlock.
+ if (mKeyguardStateController.isKeyguardFadingAway()) {
+ expand = false;
+ } else if (onKeyguard) {
expand = true;
+ } else if (mKeyguardStateController.isKeyguardFadingAway()) {
+ // If we're in the middle of dismissing the keyguard, don't expand due to the
+ // cancelled gesture. Gesture cancellation during an unlock is expected in some
+ // situations, such keeping your finger down while swiping to unlock to an app
+ // that is locked in landscape (the rotation will cancel the touch event).
+ expand = false;
} else {
+ // If we get a cancel, put the shade back to the state it was in when the
+ // gesture started
expand = !mPanelClosedOnDown;
}
} else {
@@ -445,15 +458,17 @@ public abstract class PanelViewController {
protected void onTrackingStopped(boolean expand) {
mTracking = false;
mBar.onTrackingStopped(expand);
- notifyBarPanelExpansionChanged();
+ mStatusBar.onTrackingStopped(expand);
+ updatePanelExpansionAndVisibility();
}
protected void onTrackingStarted() {
endClosing();
mTracking = true;
mBar.onTrackingStarted();
+ mStatusBar.onTrackingStarted();
notifyExpandingStarted();
- notifyBarPanelExpansionChanged();
+ updatePanelExpansionAndVisibility();
}
/**
@@ -683,7 +698,7 @@ public abstract class PanelViewController {
} else {
cancelJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
}
- notifyBarPanelExpansionChanged();
+ updatePanelExpansionAndVisibility();
}
protected abstract boolean shouldUseDismissingAnimation();
@@ -758,7 +773,7 @@ public abstract class PanelViewController {
mExpandedFraction = Math.min(1f,
maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
onHeightUpdated(mExpandedHeight);
- notifyBarPanelExpansionChanged();
+ updatePanelExpansionAndVisibility();
}
/**
@@ -876,7 +891,7 @@ public abstract class PanelViewController {
if (mExpanding) {
notifyExpandingFinished();
}
- notifyBarPanelExpansionChanged();
+ updatePanelExpansionAndVisibility();
// Wait for window manager to pickup the change, so we know the maximum height of the panel
// then.
@@ -914,7 +929,7 @@ public abstract class PanelViewController {
}
if (mInstantExpanding) {
mInstantExpanding = false;
- notifyBarPanelExpansionChanged();
+ updatePanelExpansionAndVisibility();
}
}
@@ -924,10 +939,7 @@ public abstract class PanelViewController {
mView.removeCallbacks(mFlingCollapseRunnable);
}
- protected void onClosingFinished() {
- mBar.onClosingFinished();
- }
-
+ protected abstract void onClosingFinished();
protected void startUnlockHintAnimation() {
@@ -1020,7 +1032,7 @@ public abstract class PanelViewController {
public void onAnimationEnd(Animator animation) {
setAnimator(null);
onAnimationFinished.run();
- notifyBarPanelExpansionChanged();
+ updatePanelExpansionAndVisibility();
}
});
animator.start();
@@ -1057,19 +1069,39 @@ public abstract class PanelViewController {
return animator;
}
- protected void notifyBarPanelExpansionChanged() {
+ /** Update the visibility of {@link PanelView} if necessary. */
+ public void updateVisibility() {
+ mView.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
+ }
+
+ /** Returns true if {@link PanelView} should be visible. */
+ abstract boolean shouldPanelBeVisible();
+
+ /**
+ * Updates the panel expansion and {@link PanelView} visibility if necessary.
+ *
+ * TODO(b/200063118): Could public calls to this method be replaced with calls to
+ * {@link #updateVisibility()}? That would allow us to make this method private.
+ */
+ public void updatePanelExpansionAndVisibility() {
if (mBar != null) {
- mBar.panelExpansionChanged(
- mExpandedFraction,
- mExpandedFraction > 0f || mInstantExpanding
- || isPanelVisibleBecauseOfHeadsUp() || mTracking
- || mHeightAnimator != null && !mIsSpringBackAnimation);
+ mBar.panelExpansionChanged(mExpandedFraction, isExpanded());
}
+ updateVisibility();
for (int i = 0; i < mExpansionListeners.size(); i++) {
mExpansionListeners.get(i).onPanelExpansionChanged(mExpandedFraction, mTracking);
}
}
+ public boolean isExpanded() {
+ return mExpandedFraction > 0f
+ || mInstantExpanding
+ || isPanelVisibleBecauseOfHeadsUp()
+ || mTracking
+ || mHeightAnimator != null
+ && !mIsSpringBackAnimation;
+ }
+
public void addExpansionListener(PanelExpansionListener panelExpansionListener) {
mExpansionListeners.add(panelExpansionListener);
}
@@ -1130,28 +1162,28 @@ public abstract class PanelViewController {
return mView;
}
- public boolean isEnabled() {
- return mView.isEnabled();
- }
-
public OnLayoutChangeListener createLayoutChangeListener() {
return new OnLayoutChangeListener();
}
- protected TouchHandler createTouchHandler() {
- return new TouchHandler();
- }
+ protected abstract TouchHandler createTouchHandler();
protected OnConfigurationChangedListener createOnConfigurationChangedListener() {
return new OnConfigurationChangedListener();
}
- /**
- * Set that the panel is currently opening and not fully opened or closed.
- */
- public abstract void setIsShadeOpening(boolean opening);
+ public abstract class TouchHandler implements View.OnTouchListener {
+ /**
+ * Method called when a touch has occurred on {@link PhoneStatusBarView}.
+ *
+ * Touches that occur on the status bar view may have ramifications for the notification
+ * panel (e.g. a touch that pulls down the shade could start on the status bar), so we need
+ * to notify the panel controller when these touches occur.
+ *
+ * Returns true if the event was handled and false otherwise.
+ */
+ public abstract boolean onTouchForwardedFromStatusBar(MotionEvent event);
- public class TouchHandler implements View.OnTouchListener {
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
&& event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
@@ -1405,8 +1437,7 @@ public abstract class PanelViewController {
private void beginJankMonitoring(int cuj) {
InteractionJankMonitor.Configuration.Builder builder =
- new InteractionJankMonitor.Configuration.Builder(cuj)
- .setView(mView)
+ InteractionJankMonitor.Configuration.Builder.withView(cuj, mView)
.setTag(isFullyCollapsed() ? "Expand" : "Collapse");
InteractionJankMonitor.getInstance().begin(builder);
}
@@ -1418,4 +1449,8 @@ public abstract class PanelViewController {
private void cancelJankMonitoring(int cuj) {
InteractionJankMonitor.getInstance().cancel(cuj);
}
+
+ protected float getExpansionFraction() {
+ return mExpandedFraction;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 7b110a0ec01f..d19ed28bd823 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -604,8 +604,7 @@ public class PhoneStatusBarPolicy
@Override
public void onUserSetupChanged() {
- boolean userSetup = mProvisionedController.isUserSetup(
- mProvisionedController.getCurrentUser());
+ boolean userSetup = mProvisionedController.isCurrentUserSetup();
if (mCurrentUserSetup == userSetup) return;
mCurrentUserSetup = userSetup;
updateAlarm();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 2ca36b648b76..883313bdc096 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -18,15 +18,13 @@ package com.android.systemui.statusbar.phone;
import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
-import static java.lang.Float.isNaN;
-
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.EventLog;
+import android.util.Log;
import android.util.Pair;
import android.view.DisplayCutout;
import android.view.Gravity;
@@ -37,12 +35,11 @@ import android.view.WindowInsets;
import android.view.accessibility.AccessibilityEvent;
import android.widget.LinearLayout;
+import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.Dependency;
-import com.android.systemui.EventLogTags;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.leak.RotationUtils;
import java.util.List;
@@ -52,22 +49,11 @@ public class PhoneStatusBarView extends PanelBar {
private static final String TAG = "PhoneStatusBarView";
private static final boolean DEBUG = StatusBar.DEBUG;
private static final boolean DEBUG_GESTURES = false;
- private final CommandQueue mCommandQueue;
private final StatusBarContentInsetsProvider mContentInsetsProvider;
StatusBar mBar;
- boolean mIsFullyOpenedPanel = false;
private ScrimController mScrimController;
- private float mMinFraction;
- private Runnable mHideExpandedRunnable = new Runnable() {
- @Override
- public void run() {
- if (mPanelFraction == 0.0f) {
- mBar.makeExpandedInvisible();
- }
- }
- };
private DarkReceiver mBattery;
private DarkReceiver mClock;
private int mRotationOrientation = -1;
@@ -80,16 +66,16 @@ public class PhoneStatusBarView extends PanelBar {
private int mStatusBarHeight;
@Nullable
private List<StatusBar.ExpansionChangedListener> mExpansionChangedListeners;
+ @Nullable
+ private TouchEventHandler mTouchEventHandler;
/**
* Draw this many pixels into the left/right side of the cutout to optimally use the space
*/
private int mCutoutSideNudge = 0;
- private boolean mHeadsUpVisible;
public PhoneStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
- mCommandQueue = Dependency.get(CommandQueue.class);
mContentInsetsProvider = Dependency.get(StatusBarContentInsetsProvider.class);
}
@@ -102,6 +88,10 @@ public class PhoneStatusBarView extends PanelBar {
mExpansionChangedListeners = listeners;
}
+ void setTouchEventHandler(TouchEventHandler handler) {
+ mTouchEventHandler = handler;
+ }
+
public void setScrimController(ScrimController scrimController) {
mScrimController = scrimController;
}
@@ -176,11 +166,6 @@ public class PhoneStatusBarView extends PanelBar {
}
@Override
- public boolean panelEnabled() {
- return mCommandQueue.panelsEnabled();
- }
-
- @Override
public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
if (super.onRequestSendAccessibilityEventInternal(child, event)) {
// The status bar is very small so augment the view that the user is touching
@@ -196,98 +181,31 @@ public class PhoneStatusBarView extends PanelBar {
}
@Override
- public void onPanelPeeked() {
- super.onPanelPeeked();
- mBar.makeExpandedVisible(false);
- }
-
- @Override
- public void onPanelCollapsed() {
- super.onPanelCollapsed();
- // Close the status bar in the next frame so we can show the end of the animation.
- post(mHideExpandedRunnable);
- mIsFullyOpenedPanel = false;
- }
-
- public void removePendingHideExpandedRunnables() {
- removeCallbacks(mHideExpandedRunnable);
- }
-
- @Override
- public void onPanelFullyOpened() {
- super.onPanelFullyOpened();
- if (!mIsFullyOpenedPanel) {
- mPanel.getView().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- }
- mIsFullyOpenedPanel = true;
- }
-
- @Override
public boolean onTouchEvent(MotionEvent event) {
- boolean barConsumedEvent = mBar.interceptTouchEvent(event);
-
- if (DEBUG_GESTURES) {
- if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
- EventLog.writeEvent(EventLogTags.SYSUI_PANELBAR_TOUCH,
- event.getActionMasked(), (int) event.getX(), (int) event.getY(),
- barConsumedEvent ? 1 : 0);
- }
+ mBar.onTouchEvent(event);
+ if (mTouchEventHandler == null) {
+ Log.w(
+ TAG,
+ String.format(
+ "onTouch: No touch handler provided; eating gesture at (%d,%d)",
+ (int) event.getX(),
+ (int) event.getY()
+ )
+ );
+ return true;
}
-
- return barConsumedEvent || super.onTouchEvent(event);
- }
-
- @Override
- public void onTrackingStarted() {
- super.onTrackingStarted();
- mBar.onTrackingStarted();
- mScrimController.onTrackingStarted();
- removePendingHideExpandedRunnables();
- }
-
- @Override
- public void onClosingFinished() {
- super.onClosingFinished();
- mBar.onClosingFinished();
- }
-
- @Override
- public void onTrackingStopped(boolean expand) {
- super.onTrackingStopped(expand);
- mBar.onTrackingStopped(expand);
- }
-
- @Override
- public void onExpandingFinished() {
- super.onExpandingFinished();
- mScrimController.onExpandingFinished();
+ return mTouchEventHandler.handleTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- return mBar.interceptTouchEvent(event) || super.onInterceptTouchEvent(event);
- }
-
- @Override
- public void onPanelMinFractionChanged(float minFraction) {
- if (isNaN(minFraction)) {
- throw new IllegalArgumentException("minFraction cannot be NaN");
- }
- super.onPanelMinFractionChanged(minFraction);
- if (mMinFraction != minFraction) {
- mMinFraction = minFraction;
- updateScrimFraction();
- }
+ mBar.onTouchEvent(event);
+ return super.onInterceptTouchEvent(event);
}
@Override
public void panelExpansionChanged(float frac, boolean expanded) {
super.panelExpansionChanged(frac, expanded);
- updateScrimFraction();
- if ((frac == 0 || frac == 1) && mBar.getNavigationBarView() != null) {
- mBar.getNavigationBarView().onStatusBarPanelStateChanged();
- }
-
if (mExpansionChangedListeners != null) {
for (StatusBar.ExpansionChangedListener listener : mExpansionChangedListeners) {
listener.onExpansionChanged(frac, expanded);
@@ -295,15 +213,6 @@ public class PhoneStatusBarView extends PanelBar {
}
}
- private void updateScrimFraction() {
- float scrimFraction = mPanelFraction;
- if (mMinFraction < 1.0f) {
- scrimFraction = Math.max((mPanelFraction - mMinFraction) / (1.0f - mMinFraction),
- 0);
- }
- mScrimController.setPanelExpansion(scrimFraction);
- }
-
public void updateResources() {
mCutoutSideNudge = getResources().getDimensionPixelSize(
R.dimen.display_cutout_margin_consumption);
@@ -315,7 +224,7 @@ public class PhoneStatusBarView extends PanelBar {
final int waterfallTopInset =
mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top;
ViewGroup.LayoutParams layoutParams = getLayoutParams();
- mStatusBarHeight = getResources().getDimensionPixelSize(R.dimen.status_bar_height);
+ mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
layoutParams.height = mStatusBarHeight - waterfallTopInset;
int statusBarPaddingTop = getResources().getDimensionPixelSize(
@@ -383,13 +292,14 @@ public class PhoneStatusBarView extends PanelBar {
getPaddingBottom());
}
- public void setHeadsUpVisible(boolean headsUpVisible) {
- mHeadsUpVisible = headsUpVisible;
- updateVisibility();
- }
-
- @Override
- protected boolean shouldPanelBeVisible() {
- return mHeadsUpVisible || super.shouldPanelBeVisible();
+ /**
+ * A handler repsonsible for all touch event handling on the status bar.
+ *
+ * The handler will be notified each time {@link this#onTouchEvent} is called, and the return
+ * value from the handler will be returned from {@link this#onTouchEvent}.
+ **/
+ public interface TouchEventHandler {
+ /** Called each time {@link this#onTouchEvent} is called. */
+ boolean handleTouchEvent(MotionEvent event);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
new file mode 100644
index 000000000000..de21e73f8100
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -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.systemui.statusbar.phone
+
+import android.graphics.Point
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import com.android.systemui.R
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.unfold.UNFOLD_STATUS_BAR
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.util.ViewController
+import javax.inject.Inject
+import javax.inject.Named
+import dagger.Lazy
+
+/** Controller for [PhoneStatusBarView]. */
+class PhoneStatusBarViewController private constructor(
+ view: PhoneStatusBarView,
+ @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
+ private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
+ touchEventHandler: PhoneStatusBarView.TouchEventHandler,
+) : ViewController<PhoneStatusBarView>(view) {
+
+ override fun onViewAttached() {
+ moveFromCenterAnimationController?.let { animationController ->
+ val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
+ val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area)
+
+ val viewCenterProvider = StatusBarViewsCenterProvider()
+ val viewsToAnimate = arrayOf(
+ statusBarLeftSide,
+ systemIconArea
+ )
+
+ mView.viewTreeObserver.addOnPreDrawListener(object :
+ ViewTreeObserver.OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ animationController.onViewsReady(viewsToAnimate, viewCenterProvider)
+ mView.viewTreeObserver.removeOnPreDrawListener(this)
+ return true
+ }
+ })
+
+ mView.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ ->
+ val widthChanged = right - left != oldRight - oldLeft
+ if (widthChanged) {
+ moveFromCenterAnimationController.onStatusBarWidthChanged()
+ }
+ }
+ }
+
+ progressProvider?.setReadyToHandleTransition(true)
+ }
+
+ override fun onViewDetached() {
+ progressProvider?.setReadyToHandleTransition(false)
+ moveFromCenterAnimationController?.onViewDetached()
+ }
+
+ init {
+ mView.setTouchEventHandler(touchEventHandler)
+ }
+
+ fun setImportantForAccessibility(mode: Int) {
+ mView.importantForAccessibility = mode
+ }
+
+ private class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
+ override fun getViewCenter(view: View, outPoint: Point) =
+ when (view.id) {
+ R.id.status_bar_left_side -> {
+ // items aligned to the start, return start center point
+ getViewEdgeCenter(view, outPoint, isStart = true)
+ }
+ R.id.system_icon_area -> {
+ // items aligned to the end, return end center point
+ getViewEdgeCenter(view, outPoint, isStart = false)
+ }
+ else -> super.getViewCenter(view, outPoint)
+ }
+
+ /**
+ * Returns start or end (based on [isStart]) center point of the view
+ */
+ private fun getViewEdgeCenter(view: View, outPoint: Point, isStart: Boolean) {
+ val isRtl = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
+ val isLeftEdge = isRtl xor isStart
+
+ val viewLocation = IntArray(2)
+ view.getLocationOnScreen(viewLocation)
+
+ val viewX = viewLocation[0]
+ val viewY = viewLocation[1]
+
+ outPoint.x = viewX + if (isLeftEdge) view.height / 2 else view.width - view.height / 2
+ outPoint.y = viewY + view.height / 2
+ }
+ }
+
+ class Factory @Inject constructor(
+ @Named(UNFOLD_STATUS_BAR)
+ private val progressProvider: Lazy<ScopedUnfoldTransitionProgressProvider>,
+ private val moveFromCenterController: Lazy<StatusBarMoveFromCenterAnimationController>,
+ private val unfoldConfig: UnfoldTransitionConfig,
+ ) {
+ fun create(
+ view: PhoneStatusBarView,
+ touchEventHandler: PhoneStatusBarView.TouchEventHandler
+ ): PhoneStatusBarViewController {
+ return PhoneStatusBarViewController(
+ view,
+ if (unfoldConfig.isEnabled) progressProvider.get() else null,
+ if (unfoldConfig.isEnabled) moveFromCenterController.get() else null,
+ touchEventHandler
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 27c129ad34c5..1921357ddf7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -34,6 +34,7 @@ import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
+import androidx.annotation.FloatRange;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -46,7 +47,7 @@ import com.android.settingslib.Utils;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
+import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
@@ -150,12 +151,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
public static final float BUSY_SCRIM_ALPHA = 1f;
/**
- * The default scrim under the expanded bubble stack.
- * This should not be lower than 0.54, otherwise we won't pass GAR.
- */
- public static final float BUBBLE_SCRIM_ALPHA = 0.6f;
-
- /**
* Scrim opacity that can have text on top.
*/
public static final float GAR_SCRIM_ALPHA = 0.6f;
@@ -170,8 +165,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private ScrimView mScrimInFront;
private ScrimView mNotificationsScrim;
private ScrimView mScrimBehind;
- @Nullable
- private ScrimView mScrimForBubble;
private Runnable mScrimBehindChangeRunnable;
@@ -191,8 +184,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA;
private final float mDefaultScrimAlpha;
- // Assuming the shade is expanded during initialization
- private float mPanelExpansion = 1f;
+ private float mRawPanelExpansionFraction;
+ private float mPanelScrimMinFraction;
+ // Calculated based on mRawPanelExpansionFraction and mPanelScrimMinFraction
+ private float mPanelExpansionFraction = 1f; // Assume shade is expanded during initialization
private float mQsExpansion;
private boolean mQsBottomVisible;
@@ -209,12 +204,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private float mInFrontAlpha = NOT_INITIALIZED;
private float mBehindAlpha = NOT_INITIALIZED;
private float mNotificationsAlpha = NOT_INITIALIZED;
- private float mBubbleAlpha = NOT_INITIALIZED;
private int mInFrontTint;
private int mBehindTint;
private int mNotificationsTint;
- private int mBubbleTint;
private boolean mWallpaperVisibilityTimedOut;
private int mScrimsVisibility;
@@ -243,7 +236,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
mScrimStateListener = lightBarController::setScrimState;
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
- ScrimState.BUBBLE_EXPANDED.setBubbleAlpha(BUBBLE_SCRIM_ALPHA);
mKeyguardStateController = keyguardStateController;
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
@@ -273,11 +265,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
@Override
- public void onOverlayChanged() {
- ScrimController.this.onThemeChanged();
- }
-
- @Override
public void onUiModeChanged() {
ScrimController.this.onThemeChanged();
}
@@ -290,11 +277,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
* Attach the controller to the supplied views.
*/
public void attachViews(ScrimView behindScrim, ScrimView notificationsScrim,
- ScrimView scrimInFront, @Nullable ScrimView scrimForBubble) {
+ ScrimView scrimInFront) {
mNotificationsScrim = notificationsScrim;
mScrimBehind = behindScrim;
mScrimInFront = scrimInFront;
- mScrimForBubble = scrimForBubble;
updateThemeColors();
behindScrim.enableBottomEdgeConcave(mClipsQsScrim);
@@ -307,8 +293,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
final ScrimState[] states = ScrimState.values();
for (int i = 0; i < states.length; i++) {
- states[i].init(mScrimInFront, mScrimBehind, mScrimForBubble, mDozeParameters,
- mDockManager);
+ states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager);
states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
states[i].setDefaultScrimAlpha(mDefaultScrimAlpha);
}
@@ -316,9 +301,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mScrimBehind.setDefaultFocusHighlightEnabled(false);
mNotificationsScrim.setDefaultFocusHighlightEnabled(false);
mScrimInFront.setDefaultFocusHighlightEnabled(false);
- if (mScrimForBubble != null) {
- mScrimForBubble.setDefaultFocusHighlightEnabled(false);
- }
updateScrims();
mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
}
@@ -373,21 +355,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mAnimateChange = state.getAnimateChange();
mAnimationDuration = state.getAnimationDuration();
- mInFrontTint = state.getFrontTint();
- mBehindTint = state.getBehindTint();
- mNotificationsTint = state.getNotifTint();
- mBubbleTint = state.getBubbleTint();
-
- mInFrontAlpha = state.getFrontAlpha();
- mBehindAlpha = state.getBehindAlpha();
- mBubbleAlpha = state.getBubbleAlpha();
- mNotificationsAlpha = state.getNotifAlpha();
- if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) {
- throw new IllegalStateException("Scrim opacity is NaN for state: " + state + ", front: "
- + mInFrontAlpha + ", back: " + mBehindAlpha + ", notif: "
- + mNotificationsAlpha);
- }
- applyStateToAlpha();
+ applyState();
// Scrim might acquire focus when user is navigating with a D-pad or a keyboard.
// We need to disable focus otherwise AOD would end up with a gray overlay.
@@ -513,20 +481,44 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
*
* The expansion fraction is tied to the scrim opacity.
*
- * @param fraction From 0 to 1 where 0 means collapsed and 1 expanded.
+ * See {@link PanelBar#panelExpansionChanged}.
+ *
+ * @param rawPanelExpansionFraction From 0 to 1 where 0 means collapsed and 1 expanded.
*/
- public void setPanelExpansion(float fraction) {
- if (isNaN(fraction)) {
- throw new IllegalArgumentException("Fraction should not be NaN");
+ public void setRawPanelExpansionFraction(
+ @FloatRange(from = 0.0, to = 1.0) float rawPanelExpansionFraction) {
+ if (isNaN(rawPanelExpansionFraction)) {
+ throw new IllegalArgumentException("rawPanelExpansionFraction should not be NaN");
+ }
+ mRawPanelExpansionFraction = rawPanelExpansionFraction;
+ calculateAndUpdatePanelExpansion();
+ }
+
+ /** See {@link NotificationPanelViewController#setPanelScrimMinFraction(float)}. */
+ public void setPanelScrimMinFraction(float minFraction) {
+ if (isNaN(minFraction)) {
+ throw new IllegalArgumentException("minFraction should not be NaN");
+ }
+ mPanelScrimMinFraction = minFraction;
+ calculateAndUpdatePanelExpansion();
+ }
+
+ private void calculateAndUpdatePanelExpansion() {
+ float panelExpansionFraction = mRawPanelExpansionFraction;
+ if (mPanelScrimMinFraction < 1.0f) {
+ panelExpansionFraction = Math.max(
+ (mRawPanelExpansionFraction - mPanelScrimMinFraction)
+ / (1.0f - mPanelScrimMinFraction),
+ 0);
}
- if (mPanelExpansion != fraction) {
- mPanelExpansion = fraction;
+
+ if (mPanelExpansionFraction != panelExpansionFraction) {
+ mPanelExpansionFraction = panelExpansionFraction;
boolean relevantState = (mState == ScrimState.UNLOCKED
|| mState == ScrimState.KEYGUARD
|| mState == ScrimState.SHADE_LOCKED
- || mState == ScrimState.PULSING
- || mState == ScrimState.BUBBLE_EXPANDED);
+ || mState == ScrimState.PULSING);
if (!(relevantState && mExpansionAffectsAlpha)) {
return;
}
@@ -587,16 +579,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
if (isNaN(expansionFraction)) {
return;
}
- expansionFraction = Interpolators
- .getNotificationScrimAlpha(expansionFraction, false /* notification */);
+ expansionFraction = ShadeInterpolation.getNotificationScrimAlpha(expansionFraction);
boolean qsBottomVisible = qsPanelBottomY > 0;
if (mQsExpansion != expansionFraction || mQsBottomVisible != qsBottomVisible) {
mQsExpansion = expansionFraction;
mQsBottomVisible = qsBottomVisible;
boolean relevantState = (mState == ScrimState.SHADE_LOCKED
|| mState == ScrimState.KEYGUARD
- || mState == ScrimState.PULSING
- || mState == ScrimState.BUBBLE_EXPANDED);
+ || mState == ScrimState.PULSING);
if (!(relevantState && mExpansionAffectsAlpha)) {
return;
}
@@ -654,12 +644,22 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
}
- private void applyStateToAlpha() {
+ private void applyState() {
+ mInFrontTint = mState.getFrontTint();
+ mBehindTint = mState.getBehindTint();
+ mNotificationsTint = mState.getNotifTint();
+
+ mInFrontAlpha = mState.getFrontAlpha();
+ mBehindAlpha = mState.getBehindAlpha();
+ mNotificationsAlpha = mState.getNotifAlpha();
+
+ assertAlphasValid();
+
if (!mExpansionAffectsAlpha) {
return;
}
- if (mState == ScrimState.UNLOCKED || mState == ScrimState.BUBBLE_EXPANDED) {
+ if (mState == ScrimState.UNLOCKED) {
// Darken scrim as you pull down the shade when unlocked, unless the shade is expanding
// because we're doing the screen off animation.
if (!mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) {
@@ -674,6 +674,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
mInFrontAlpha = 0;
}
+ } else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
+ float behindFraction = getInterpolatedFraction();
+ behindFraction = (float) Math.pow(behindFraction, 0.8f);
+
+ mBehindAlpha = behindFraction * mDefaultScrimAlpha;
+ mNotificationsAlpha = mBehindAlpha;
} else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
|| mState == ScrimState.PULSING) {
Pair<Integer, Float> result = calculateBackStateForState(mState);
@@ -711,12 +717,24 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mNotificationsTint = mState.getNotifTint();
mBehindTint = behindTint;
}
+
+ // At the end of a launch animation over the lockscreen, the state is either KEYGUARD or
+ // SHADE_LOCKED and this code is called. We have to set the notification alpha to 0
+ // otherwise there is a flicker to its previous value.
+ if (mKeyguardOccluded) {
+ mNotificationsAlpha = 0;
+ }
if (mUnOcclusionAnimationRunning && mState == ScrimState.KEYGUARD) {
// We're unoccluding the keyguard and don't want to have a bright flash.
mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA;
mNotificationsTint = ScrimState.KEYGUARD.getNotifTint();
}
}
+
+ assertAlphasValid();
+ }
+
+ private void assertAlphasValid() {
if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) {
throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
+ ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha + ", notif: "
@@ -756,14 +774,13 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private void applyAndDispatchState() {
- applyStateToAlpha();
+ applyState();
if (mUpdatePending) {
return;
}
setOrAdaptCurrentAnimation(mScrimBehind);
setOrAdaptCurrentAnimation(mNotificationsScrim);
setOrAdaptCurrentAnimation(mScrimInFront);
- setOrAdaptCurrentAnimation(mScrimForBubble);
dispatchBackScrimState(mScrimBehind.getViewAlpha());
// Reset wallpaper timeout if it's already timeout like expanding panel while PULSING
@@ -871,11 +888,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
setScrimAlpha(mScrimBehind, mBehindAlpha);
setScrimAlpha(mNotificationsScrim, mNotificationsAlpha);
- if (mScrimForBubble != null) {
- boolean animateScrimForBubble = mScrimForBubble.getViewAlpha() != 0 && !mBlankScreen;
- mScrimForBubble.setColors(mColors, animateScrimForBubble);
- setScrimAlpha(mScrimForBubble, mBubbleAlpha);
- }
// The animation could have all already finished, let's call onFinished just in case
onFinished(mState);
dispatchScrimsVisible();
@@ -908,7 +920,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
private float getInterpolatedFraction() {
- return Interpolators.getNotificationScrimAlpha(mPanelExpansion, false /* notification */);
+ return ShadeInterpolation.getNotificationScrimAlpha(mPanelExpansionFraction);
}
private void setScrimAlpha(ScrimView scrim, float alpha) {
@@ -928,8 +940,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
return "behind_scrim";
} else if (scrim == mNotificationsScrim) {
return "notifications_scrim";
- } else if (scrim == mScrimForBubble) {
- return "bubble_scrim";
}
return "unknown_scrim";
}
@@ -1002,8 +1012,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
return mBehindAlpha;
} else if (scrim == mNotificationsScrim) {
return mNotificationsAlpha;
- } else if (scrim == mScrimForBubble) {
- return mBubbleAlpha;
} else {
throw new IllegalArgumentException("Unknown scrim view");
}
@@ -1016,8 +1024,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
return mBehindTint;
} else if (scrim == mNotificationsScrim) {
return mNotificationsTint;
- } else if (scrim == mScrimForBubble) {
- return mBubbleTint;
} else {
throw new IllegalArgumentException("Unknown scrim view");
}
@@ -1049,8 +1055,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
if (isAnimating(mScrimBehind)
|| isAnimating(mNotificationsScrim)
- || isAnimating(mScrimInFront)
- || isAnimating(mScrimForBubble)) {
+ || isAnimating(mScrimInFront)) {
if (callback != null && callback != mCallback) {
// Since we only notify the callback that we're finished once everything has
// finished, we need to make sure that any changing callbacks are also invoked
@@ -1077,13 +1082,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mInFrontTint = Color.TRANSPARENT;
mBehindTint = mState.getBehindTint();
mNotificationsTint = mState.getNotifTint();
- mBubbleTint = Color.TRANSPARENT;
updateScrimColor(mScrimInFront, mInFrontAlpha, mInFrontTint);
updateScrimColor(mScrimBehind, mBehindAlpha, mBehindTint);
updateScrimColor(mNotificationsScrim, mNotificationsAlpha, mNotificationsTint);
- if (mScrimForBubble != null) {
- updateScrimColor(mScrimForBubble, mBubbleAlpha, mBubbleTint);
- }
}
}
@@ -1225,6 +1226,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
pw.println(" ScrimController: ");
pw.print(" state: ");
pw.println(mState);
+ pw.println(" mClipQsScrim = " + mState.mClipQsScrim);
pw.print(" frontScrim:");
pw.print(" viewAlpha=");
@@ -1250,20 +1252,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
pw.print(" tint=0x");
pw.println(Integer.toHexString(mNotificationsScrim.getTint()));
- pw.print(" bubbleScrim:");
- pw.print(" viewAlpha=");
- pw.print(mScrimForBubble.getViewAlpha());
- pw.print(" alpha=");
- pw.print(mBubbleAlpha);
- pw.print(" tint=0x");
- pw.println(Integer.toHexString(mScrimForBubble.getTint()));
-
pw.print(" mTracking=");
pw.println(mTracking);
pw.print(" mDefaultScrimAlpha=");
pw.println(mDefaultScrimAlpha);
- pw.print(" mExpansionFraction=");
- pw.println(mPanelExpansion);
+ pw.print(" mPanelExpansionFraction=");
+ pw.println(mPanelExpansionFraction);
pw.print(" mExpansionAffectsAlpha=");
pw.println(mExpansionAffectsAlpha);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 15b8c67fd4ba..9246c0e73289 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -19,8 +19,6 @@ package com.android.systemui.statusbar.phone;
import android.graphics.Color;
import android.os.Trace;
-import androidx.annotation.Nullable;
-
import com.android.systemui.dock.DockManager;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -43,11 +41,9 @@ public enum ScrimState {
public void prepare(ScrimState previousState) {
mFrontTint = Color.BLACK;
mBehindTint = Color.BLACK;
- mBubbleTint = previousState.mBubbleTint;
mFrontAlpha = 1f;
mBehindAlpha = 1f;
- mBubbleAlpha = previousState.mBubbleAlpha;
mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG;
}
@@ -81,18 +77,27 @@ public enum ScrimState {
mFrontTint = Color.BLACK;
mBehindTint = Color.BLACK;
mNotifTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT;
- mBubbleTint = Color.TRANSPARENT;
mFrontAlpha = 0;
mBehindAlpha = mClipQsScrim ? 1 : mScrimBehindAlphaKeyguard;
mNotifAlpha = mClipQsScrim ? mScrimBehindAlphaKeyguard : 0;
- mBubbleAlpha = 0;
if (mClipQsScrim) {
updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
}
}
},
+ AUTH_SCRIMMED_SHADE {
+ @Override
+ public void prepare(ScrimState previousState) {
+ // notif & behind scrim alpha values are determined by ScrimController#applyState
+ // based on the shade expansion
+
+ mFrontTint = Color.BLACK;
+ mFrontAlpha = .66f;
+ }
+ },
+
AUTH_SCRIMMED {
@Override
public void prepare(ScrimState previousState) {
@@ -118,7 +123,6 @@ public enum ScrimState {
mNotifAlpha = mClipQsScrim ? mDefaultScrimAlpha : 0;
mNotifTint = Color.TRANSPARENT;
mFrontAlpha = 0f;
- mBubbleAlpha = 0f;
}
},
@@ -129,7 +133,6 @@ public enum ScrimState {
@Override
public void prepare(ScrimState previousState) {
mBehindAlpha = 0;
- mBubbleAlpha = 0f;
mFrontAlpha = mDefaultScrimAlpha;
}
},
@@ -139,7 +142,6 @@ public enum ScrimState {
public void prepare(ScrimState previousState) {
mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
mNotifAlpha = 1f;
- mBubbleAlpha = 0f;
mFrontAlpha = 0f;
mBehindTint = Color.BLACK;
@@ -163,7 +165,6 @@ public enum ScrimState {
public void prepare(ScrimState previousState) {
mBehindAlpha = 0;
mFrontAlpha = 0;
- mBubbleAlpha = 0;
}
},
@@ -185,9 +186,6 @@ public enum ScrimState {
mBehindTint = Color.BLACK;
mBehindAlpha = ScrimController.TRANSPARENT;
- mBubbleTint = Color.TRANSPARENT;
- mBubbleAlpha = ScrimController.TRANSPARENT;
-
mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG;
// DisplayPowerManager may blank the screen for us, or we might blank it for ourselves
// by animating the screen off via the LightRevelScrim. In either case we just need to
@@ -214,7 +212,6 @@ public enum ScrimState {
@Override
public void prepare(ScrimState previousState) {
mFrontAlpha = mAodFrontScrimAlpha;
- mBubbleAlpha = 0f;
mBehindTint = Color.BLACK;
mFrontTint = Color.BLACK;
mBlankScreen = mDisplayRequiresBlanking;
@@ -238,7 +235,6 @@ public enum ScrimState {
mBehindAlpha = mClipQsScrim ? 1 : 0;
mNotifAlpha = 0;
mFrontAlpha = 0;
- mBubbleAlpha = 0;
mAnimationDuration = mKeyguardFadingAway
? mKeyguardFadingAwayDuration
@@ -249,21 +245,16 @@ public enum ScrimState {
mFrontTint = Color.TRANSPARENT;
mBehindTint = Color.BLACK;
- mBubbleTint = Color.TRANSPARENT;
mBlankScreen = false;
if (previousState == ScrimState.AOD) {
// Set all scrims black, before they fade transparent.
updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */);
updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */);
- if (mScrimForBubble != null) {
- updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */);
- }
// Scrims should still be black at the end of the transition.
mFrontTint = Color.BLACK;
mBehindTint = Color.BLACK;
- mBubbleTint = Color.BLACK;
mBlankScreen = true;
}
@@ -271,71 +262,24 @@ public enum ScrimState {
updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
}
}
- },
-
- /**
- * Unlocked with a bubble expanded.
- */
- BUBBLE_EXPANDED {
- @Override
- public void prepare(ScrimState previousState) {
- mBehindAlpha = mClipQsScrim ? 1 : 0;
- mNotifAlpha = 0;
- mFrontAlpha = 0;
-
- mAnimationDuration = mKeyguardFadingAway
- ? mKeyguardFadingAwayDuration
- : StatusBar.FADE_KEYGUARD_DURATION;
-
- mAnimateChange = !mLaunchingAffordanceWithPreview;
-
- mFrontTint = Color.TRANSPARENT;
- mBehindTint = Color.BLACK;
- mBubbleTint = Color.BLACK;
- mBlankScreen = false;
-
- if (previousState == ScrimState.AOD) {
- // Set all scrims black, before they fade transparent.
- updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */);
- updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */);
- if (mScrimForBubble != null) {
- updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */);
- }
-
- // Scrims should still be black at the end of the transition.
- mFrontTint = Color.BLACK;
- mBehindTint = Color.BLACK;
- mBubbleTint = Color.BLACK;
- mBlankScreen = true;
- }
-
- if (mClipQsScrim) {
- updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
- }
-
- mAnimationDuration = ScrimController.ANIMATION_DURATION;
- }
};
boolean mBlankScreen = false;
long mAnimationDuration = ScrimController.ANIMATION_DURATION;
int mFrontTint = Color.TRANSPARENT;
int mBehindTint = Color.TRANSPARENT;
- int mBubbleTint = Color.TRANSPARENT;
int mNotifTint = Color.TRANSPARENT;
boolean mAnimateChange = true;
float mAodFrontScrimAlpha;
float mFrontAlpha;
float mBehindAlpha;
- float mBubbleAlpha;
float mNotifAlpha;
float mScrimBehindAlphaKeyguard;
float mDefaultScrimAlpha;
ScrimView mScrimInFront;
ScrimView mScrimBehind;
- @Nullable ScrimView mScrimForBubble;
DozeParameters mDozeParameters;
DockManager mDockManager;
@@ -348,11 +292,10 @@ public enum ScrimState {
long mKeyguardFadingAwayDuration;
boolean mClipQsScrim;
- public void init(ScrimView scrimInFront, ScrimView scrimBehind, ScrimView scrimForBubble,
- DozeParameters dozeParameters, DockManager dockManager) {
+ public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters,
+ DockManager dockManager) {
mScrimInFront = scrimInFront;
mScrimBehind = scrimBehind;
- mScrimForBubble = scrimForBubble;
mDozeParameters = dozeParameters;
mDockManager = dockManager;
@@ -379,10 +322,6 @@ public enum ScrimState {
return mNotifAlpha;
}
- public float getBubbleAlpha() {
- return mBubbleAlpha;
- }
-
public int getFrontTint() {
return mFrontTint;
}
@@ -395,10 +334,6 @@ public enum ScrimState {
return mNotifTint;
}
- public int getBubbleTint() {
- return mBubbleTint;
- }
-
public long getAnimationDuration() {
return mAnimationDuration;
}
@@ -436,10 +371,6 @@ public enum ScrimState {
mDefaultScrimAlpha = defaultScrimAlpha;
}
- public void setBubbleAlpha(float alpha) {
- mBubbleAlpha = alpha;
- }
-
public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index d4458e29a306..a54251a46901 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -48,7 +48,7 @@ public class ShadeControllerImpl implements ShadeController {
protected final NotificationShadeWindowController mNotificationShadeWindowController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final int mDisplayId;
- protected final Lazy<StatusBar> mStatusBarLazy;
+ protected final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
private final Lazy<AssistManager> mAssistManagerLazy;
private final Optional<Bubbles> mBubblesOptional;
@@ -61,7 +61,7 @@ public class ShadeControllerImpl implements ShadeController {
NotificationShadeWindowController notificationShadeWindowController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
WindowManager windowManager,
- Lazy<StatusBar> statusBarLazy,
+ Lazy<Optional<StatusBar>> statusBarOptionalLazy,
Lazy<AssistManager> assistManagerLazy,
Optional<Bubbles> bubblesOptional
) {
@@ -71,7 +71,7 @@ public class ShadeControllerImpl implements ShadeController {
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mDisplayId = windowManager.getDefaultDisplay().getDisplayId();
// TODO: Remove circular reference to StatusBar when possible.
- mStatusBarLazy = statusBarLazy;
+ mStatusBarOptionalLazy = statusBarOptionalLazy;
mAssistManagerLazy = assistManagerLazy;
mBubblesOptional = bubblesOptional;
}
@@ -118,10 +118,6 @@ public class ShadeControllerImpl implements ShadeController {
+ " flags=" + flags);
}
- if ((flags & CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL) == 0) {
- getStatusBar().postHideRecentApps();
- }
-
// TODO(b/62444020): remove when this bug is fixed
Log.v(TAG, "NotificationShadeWindow: " + getNotificationShadeWindowView()
+ " canPanelBeCollapsed(): "
@@ -132,7 +128,8 @@ public class ShadeControllerImpl implements ShadeController {
mNotificationShadeWindowController.setNotificationShadeFocusable(false);
getStatusBar().getNotificationShadeWindowViewController().cancelExpandHelper();
- getStatusBarView().collapsePanel(true /* animate */, delayed, speedUpFactor);
+ getNotificationPanelViewController()
+ .collapsePanel(true /* animate */, delayed, speedUpFactor);
} else if (mBubblesOptional.isPresent()) {
mBubblesOptional.get().collapseStack();
}
@@ -210,7 +207,7 @@ public class ShadeControllerImpl implements ShadeController {
}
private StatusBar getStatusBar() {
- return mStatusBarLazy.get();
+ return mStatusBarOptionalLazy.get().get();
}
private NotificationPresenter getPresenter() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
new file mode 100644
index 000000000000..873289130139
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.systemui.statusbar.phone
+
+import android.view.View
+import com.android.systemui.R
+import com.android.systemui.animation.ShadeInterpolation
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.qs.carrier.QSCarrierGroupController
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope
+import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.SPLIT_SHADE_HEADER
+import javax.inject.Inject
+import javax.inject.Named
+
+@StatusBarScope
+class SplitShadeHeaderController @Inject constructor(
+ @Named(SPLIT_SHADE_HEADER) private val statusBar: View,
+ private val statusBarIconController: StatusBarIconController,
+ qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
+ featureFlags: FeatureFlags,
+ batteryMeterViewController: BatteryMeterViewController
+) {
+
+ // TODO(b/194178072) Handle RSSI hiding when multi carrier
+ private val iconManager: StatusBarIconController.IconManager
+ private val qsCarrierGroupController: QSCarrierGroupController
+ private var visible = false
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ updateListeners()
+ }
+
+ var shadeExpanded = false
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ updateVisibility()
+ }
+
+ var splitShadeMode = false
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ updateVisibility()
+ }
+
+ var shadeExpandedFraction = -1f
+ set(value) {
+ if (visible && field != value) {
+ statusBar.alpha = ShadeInterpolation.getContentAlpha(value)
+ field = value
+ }
+ }
+
+ init {
+ batteryMeterViewController.init()
+ val batteryIcon: BatteryMeterView = statusBar.findViewById(R.id.batteryRemainingIcon)
+
+ // battery settings same as in QS icons
+ batteryMeterViewController.ignoreTunerUpdates()
+ batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+
+ val iconContainer: StatusIconContainer = statusBar.findViewById(R.id.statusIcons)
+ iconManager = StatusBarIconController.IconManager(iconContainer, featureFlags)
+ qsCarrierGroupController = qsCarrierGroupControllerBuilder
+ .setQSCarrierGroup(statusBar.findViewById(R.id.carrier_group))
+ .build()
+ }
+
+ private fun updateVisibility() {
+ val visibility = if (!splitShadeMode) {
+ View.GONE
+ } else if (shadeExpanded) {
+ View.VISIBLE
+ } else {
+ View.INVISIBLE
+ }
+ if (statusBar.visibility != visibility) {
+ statusBar.visibility = visibility
+ visible = visibility == View.VISIBLE
+ }
+ }
+
+ private fun updateListeners() {
+ qsCarrierGroupController.setListening(visible)
+ if (visible) {
+ statusBarIconController.addIconGroup(iconManager)
+ } else {
+ statusBarIconController.removeIconGroup(iconManager)
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 6cf1d82149ce..711e9412df7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.phone;
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-import static android.app.StatusBarManager.WindowType;
import static android.app.StatusBarManager.WindowVisibleState;
import static android.app.StatusBarManager.windowStateToString;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -27,21 +26,19 @@ import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
+import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
import static androidx.lifecycle.Lifecycle.State.RESUMED;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import static com.android.systemui.charging.WirelessChargingLayout.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
-import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
-import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING;
import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import static com.android.wm.shell.bubbles.BubbleController.TASKBAR_CHANGED_BROADCAST;
@@ -56,6 +53,7 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.TaskInfo;
+import android.app.TaskStackBuilder;
import android.app.UiModeManager;
import android.app.WallpaperInfo;
import android.app.WallpaperManager;
@@ -71,17 +69,13 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
-import android.media.AudioAttributes;
import android.metrics.LogMaker;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -89,8 +83,6 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
import android.provider.Settings;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
@@ -105,7 +97,6 @@ import android.util.Slog;
import android.view.Display;
import android.view.IRemoteAnimationRunner;
import android.view.IWindowManager;
-import android.view.InsetsState.InternalInsetsType;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationAdapter;
@@ -113,7 +104,6 @@ import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsetsController.Appearance;
-import android.view.WindowInsetsController.Behavior;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
@@ -133,37 +123,36 @@ import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.RegisterStatusBarResult;
-import com.android.internal.view.AppearanceRegion;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.AutoReinflateContainer;
import com.android.systemui.DejankUtils;
-import com.android.systemui.Dumpable;
import com.android.systemui.EventLogTags;
import com.android.systemui.InitController;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
-import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DelegateLaunchAnimatorController;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.camera.CameraIntents;
import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
-import com.android.systemui.demomode.DemoMode;
-import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.emergency.EmergencyGesture;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.ExtensionFragmentListener;
import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -184,13 +173,12 @@ import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.scrim.ScrimView;
-import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CircleReveal;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyboardShortcuts;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -205,20 +193,20 @@ import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
+import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.PowerButtonReveal;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.charging.WiredChargingRippleController;
+import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
@@ -239,11 +227,17 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
+import com.android.systemui.unfold.UnfoldTransitionWallpaperController;
+import com.android.systemui.unfold.config.UnfoldTransitionConfig;
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
+import com.android.systemui.util.DumpUtilsKt;
+import com.android.systemui.util.WallpaperController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.MessageRouter;
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubbles;
@@ -261,27 +255,18 @@ import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Named;
-import javax.inject.Provider;
import dagger.Lazy;
-public class StatusBar extends SystemUI implements DemoMode,
- ActivityStarter, KeyguardStateController.Callback,
- OnHeadsUpChangedListener, CommandQueue.Callbacks,
- ColorExtractor.OnColorsChangedListener, ConfigurationListener,
- StatusBarStateController.StateListener,
- LifecycleOwner, BatteryController.BatteryStateChangeCallback,
- ActivityLaunchAnimator.Callback {
+/** */
+public class StatusBar extends SystemUI implements
+ ActivityStarter,
+ LifecycleOwner {
public static final boolean MULTIUSER_DEBUG = false;
- protected static final int MSG_HIDE_RECENT_APPS = 1020;
- protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
- protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
- protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026;
protected static final int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027;
// Should match the values in PhoneWindowManager
- public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
static public final String SYSTEM_DIALOG_REASON_SCREENSHOT = "screenshot";
@@ -306,14 +291,12 @@ public class StatusBar extends SystemUI implements DemoMode,
public static final String ACTION_FAKE_ARTWORK = "fake_artwork";
- private static final int MSG_OPEN_NOTIFICATION_PANEL = 1000;
- private static final int MSG_CLOSE_PANELS = 1001;
private static final int MSG_OPEN_SETTINGS_PANEL = 1002;
private static final int MSG_LAUNCH_TRANSITION_TIMEOUT = 1003;
// 1020-1040 reserved for BaseStatusBar
// Time after we abort the launch transition.
- private static final long LAUNCH_TRANSITION_TIMEOUT_MS = 5000;
+ static final long LAUNCH_TRANSITION_TIMEOUT_MS = 5000;
protected static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
@@ -322,11 +305,6 @@ public class StatusBar extends SystemUI implements DemoMode,
*/
private static final int HINT_RESET_DELAY_MS = 1200;
- private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .build();
-
public static final int FADE_KEYGUARD_START_DELAY = 100;
public static final int FADE_KEYGUARD_DURATION = 300;
public static final int FADE_KEYGUARD_DURATION_PULSING = 96;
@@ -352,15 +330,115 @@ public class StatusBar extends SystemUI implements DemoMode,
try {
IPackageManager packageManager =
IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
- onlyCoreApps = packageManager.isOnlyCoreApps();
+ onlyCoreApps = packageManager != null && packageManager.isOnlyCoreApps();
} catch (RemoteException e) {
onlyCoreApps = false;
}
ONLY_CORE_APPS = onlyCoreApps;
}
- private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private boolean mCallingFadingAwayAfterReveal;
+ private StatusBarCommandQueueCallbacks mCommandQueueCallbacks;
+
+ void setWindowState(int state) {
+ mStatusBarWindowState = state;
+ mStatusBarWindowHidden = state == WINDOW_STATE_HIDDEN;
+ }
+
+ void acquireGestureWakeLock(long time) {
+ mGestureWakeLock.acquire(time);
+ }
+
+ boolean setAppearance(int appearance) {
+ if (mAppearance != appearance) {
+ mAppearance = appearance;
+ return updateBarMode(barMode(isTransientShown(), appearance));
+ }
+
+ return false;
+ }
+
+ int getBarMode() {
+ return mStatusBarMode;
+ }
+
+ boolean getWereIconsJustHidden() {
+ return mWereIconsJustHidden;
+ }
+
+ void setWereIconsJustHidden(boolean justHidden) {
+ mWereIconsJustHidden = justHidden;
+ }
+
+ void resendMessage(int msg) {
+ mMessageRouter.cancelMessages(msg);
+ mMessageRouter.sendMessage(msg);
+ }
+
+ void resendMessage(Object msg) {
+ mMessageRouter.cancelMessages(msg.getClass());
+ mMessageRouter.sendMessage(msg);
+ }
+
+ int getDisabled1() {
+ return mDisabled1;
+ }
+
+ void setDisabled1(int disabled) {
+ mDisabled1 = disabled;
+ }
+
+ int getDisabled2() {
+ return mDisabled2;
+ }
+
+ void setDisabled2(int disabled) {
+ mDisabled2 = disabled;
+ }
+
+ void setLastCameraLaunchSource(int source) {
+ mLastCameraLaunchSource = source;
+ }
+
+ void setLaunchCameraOnFinishedGoingToSleep(boolean launch) {
+ mLaunchCameraOnFinishedGoingToSleep = launch;
+ }
+
+ void setLaunchCameraOnFinishedWaking(boolean launch) {
+ mLaunchCameraWhenFinishedWaking = launch;
+ }
+
+ void setLaunchEmergencyActionOnFinishedGoingToSleep(boolean launch) {
+ mLaunchEmergencyActionOnFinishedGoingToSleep = launch;
+ }
+
+ void setLaunchEmergencyActionOnFinishedWaking(boolean launch) {
+ mLaunchEmergencyActionWhenFinishedWaking = launch;
+ }
+
+ void setTopHidesStatusBar(boolean hides) {
+ mTopHidesStatusBar = hides;
+ }
+
+ QSPanelController getQSPanelController() {
+ return mQSPanelController;
+ }
+
+ /** */
+ public void animateExpandNotificationsPanel() {
+ mCommandQueueCallbacks.animateExpandNotificationsPanel();
+ }
+
+ /** */
+ public void animateExpandSettingsPanel(@Nullable String subpanel) {
+ mCommandQueueCallbacks.animateExpandSettingsPanel(subpanel);
+ }
+
+ /** */
+ public void animateCollapsePanels(int flags, boolean force) {
+ mCommandQueueCallbacks.animateCollapsePanels(flags, force);
+ }
public interface ExpansionChangedListener {
void onExpansionChanged(float expansion, boolean expanded);
@@ -372,8 +450,7 @@ public class StatusBar extends SystemUI implements DemoMode,
protected int mState; // TODO: remove this. Just use StatusBarStateController
protected boolean mBouncerShowing;
- private PhoneStatusBarPolicy mIconPolicy;
- private StatusBarSignalPolicy mSignalPolicy;
+ private final PhoneStatusBarPolicy mIconPolicy;
private final VolumeComponent mVolumeComponent;
private BrightnessMirrorController mBrightnessMirrorController;
@@ -381,17 +458,17 @@ public class StatusBar extends SystemUI implements DemoMode,
private BiometricUnlockController mBiometricUnlockController;
private final LightBarController mLightBarController;
private final Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
+ private final LockscreenGestureLogger mLockscreenGestureLogger;
@Nullable
protected LockscreenWallpaper mLockscreenWallpaper;
private final AutoHideController mAutoHideController;
- @Nullable
- private final KeyguardLiftController mKeyguardLiftController;
+ private final CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger;
private final Point mCurrentDisplaySize = new Point();
protected NotificationShadeWindowView mNotificationShadeWindowView;
- protected StatusBarWindowView mPhoneStatusBarWindow;
protected PhoneStatusBarView mStatusBarView;
+ private PhoneStatusBarViewController mPhoneStatusBarViewController;
private AuthRippleController mAuthRippleController;
private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
protected NotificationShadeWindowController mNotificationShadeWindowController;
@@ -402,7 +479,6 @@ public class StatusBar extends SystemUI implements DemoMode,
private boolean mWakeUpComingFromTouch;
private PointF mWakeUpTouchLocation;
private LightRevealScrim mLightRevealScrim;
- private WiredChargingRippleController mChargingRippleAnimationController;
private PowerButtonReveal mPowerButtonReveal;
private final Object mQueueLock = new Object();
@@ -422,13 +498,13 @@ public class StatusBar extends SystemUI implements DemoMode,
protected NotificationShadeWindowViewController mNotificationShadeWindowViewController;
private final DozeParameters mDozeParameters;
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
- private final Provider<StatusBarComponent.Builder> mStatusBarComponentBuilder;
+ private final StatusBarComponent.Factory mStatusBarComponentFactory;
private final PluginManager mPluginManager;
private final Optional<LegacySplitScreen> mSplitScreenOptional;
private final StatusBarNotificationActivityStarter.Builder
mStatusBarNotificationActivityStarterBuilder;
private final ShadeController mShadeController;
- private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
+ private final StatusBarWindowView mStatusBarWindowView;
private final LightsOutNotifController mLightsOutNotifController;
private final InitController mInitController;
@@ -436,9 +512,8 @@ public class StatusBar extends SystemUI implements DemoMode,
private final KeyguardDismissUtil mKeyguardDismissUtil;
private final ExtensionController mExtensionController;
private final UserInfoControllerImpl mUserInfoControllerImpl;
- private final DismissCallbackRegistry mDismissCallbackRegistry;
private final DemoModeController mDemoModeController;
- private NotificationsController mNotificationsController;
+ private final NotificationsController mNotificationsController;
private final OngoingCallController mOngoingCallController;
private final SystemStatusAnimationScheduler mAnimationScheduler;
private final StatusBarLocationPublisher mStatusBarLocationPublisher;
@@ -451,33 +526,44 @@ public class StatusBar extends SystemUI implements DemoMode,
// settings
private QSPanelController mQSPanelController;
+ private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
+ private final PhoneStatusBarViewController.Factory mPhoneStatusBarViewControllerFactory;
KeyguardIndicationController mKeyguardIndicationController;
- private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
-
private View mReportRejectedTouch;
private boolean mExpandedVisible;
private final int[] mAbsPos = new int[2];
+ private final NotifShadeEventSource mNotifShadeEventSource;
+ protected final NotificationEntryManager mEntryManager;
private final NotificationGutsManager mGutsManager;
private final NotificationLogger mNotificationLogger;
private final NotificationViewHierarchyManager mViewHierarchyManager;
private final KeyguardViewMediator mKeyguardViewMediator;
protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
- private final BrightnessSlider.Factory mBrightnessSliderFactory;
+ private final BrightnessSliderController.Factory mBrightnessSliderFactory;
private final FeatureFlags mFeatureFlags;
+ private final UnfoldTransitionConfig mUnfoldTransitionConfig;
+ private final Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimation;
+ private final Lazy<NaturalRotationUnfoldProgressProvider> mNaturalUnfoldProgressProvider;
+ private final Lazy<UnfoldTransitionWallpaperController> mUnfoldWallpaperController;
+ private final WallpaperController mWallpaperController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+ private final MessageRouter mMessageRouter;
+ private final WallpaperManager mWallpaperManager;
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ private final TunerService mTunerService;
private final List<ExpansionChangedListener> mExpansionChangedListeners;
- // for disabling the status bar
+ // Flags for disabling the status bar
+ // Two variables becaseu the first one evidently ran out of room for new flags.
private int mDisabled1 = 0;
private int mDisabled2 = 0;
- /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int) */
+ /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */
private @Appearance int mAppearance;
private boolean mTransientShown;
@@ -496,29 +582,6 @@ public class StatusBar extends SystemUI implements DemoMode,
// ensure quick settings is disabled until the current user makes it through the setup wizard
@VisibleForTesting
protected boolean mUserSetup = false;
- private final DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() {
- @Override
- public void onUserSetupChanged() {
- final boolean userSetup = mDeviceProvisionedController.isUserSetup(
- mDeviceProvisionedController.getCurrentUser());
- Log.d(TAG, "mUserSetupObserver - DeviceProvisionedListener called for user "
- + mDeviceProvisionedController.getCurrentUser());
- if (MULTIUSER_DEBUG) {
- Log.d(TAG, String.format("User setup changed: userSetup=%s mUserSetup=%s",
- userSetup, mUserSetup));
- }
-
- if (userSetup != mUserSetup) {
- mUserSetup = userSetup;
- if (!mUserSetup && mStatusBarView != null)
- animateCollapseQuickSettings();
- if (mNotificationPanelViewController != null) {
- mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
- }
- updateQsExpansionEnabled();
- }
- }
- };
@VisibleForTesting
public enum StatusBarUiEvent implements UiEventLogger.UiEventEnum {
@@ -558,64 +621,32 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- protected final H mHandler = createHandler();
+ private Handler mMainHandler;
+ private final DelayableExecutor mMainExecutor;
private int mInteractingWindows;
private @TransitionMode int mStatusBarMode;
- private ViewMediatorCallback mKeyguardViewMediatorCallback;
+ private final ViewMediatorCallback mKeyguardViewMediatorCallback;
private final ScrimController mScrimController;
protected DozeScrimController mDozeScrimController;
private final Executor mUiBgExecutor;
protected boolean mDozing;
+ private boolean mIsFullscreen;
private final NotificationMediaManager mMediaManager;
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final NotificationRemoteInputManager mRemoteInputManager;
private boolean mWallpaperSupported;
- private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!mWallpaperSupported) {
- // Receiver should not have been registered at all...
- Log.wtf(TAG, "WallpaperManager not supported");
- return;
- }
- WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class);
- WallpaperInfo info = wallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT);
- final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
- // If WallpaperInfo is null, it must be ImageWallpaper.
- final boolean supportsAmbientMode = deviceSupportsAodWallpaper
- && (info != null && info.supportsAmbientMode());
-
- mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
- mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
- mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode);
- }
- };
-
- BroadcastReceiver mTaskbarChangeReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (mBubblesOptional.isPresent()) {
- mBubblesOptional.get().onTaskbarChanged(intent.getExtras());
- }
- }
- };
-
private Runnable mLaunchTransitionEndRunnable;
- private NotificationEntry mDraggedDownEntry;
private boolean mLaunchCameraWhenFinishedWaking;
private boolean mLaunchCameraOnFinishedGoingToSleep;
private boolean mLaunchEmergencyActionWhenFinishedWaking;
private boolean mLaunchEmergencyActionOnFinishedGoingToSleep;
private int mLastCameraLaunchSource;
protected PowerManager.WakeLock mGestureWakeLock;
- private Vibrator mVibrator;
- private VibrationEffect mCameraLaunchGestureVibrationEffect;
private final int[] mTmpInt2 = new int[2];
@@ -627,29 +658,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private boolean mIsOccluded;
private boolean mWereIconsJustHidden;
private boolean mBouncerWasShowingWhenHidden;
-
- // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over,
- // this animation is tied to the scrim for historic reasons.
- // TODO: notify when keyguard has faded away instead of the scrim.
- private final ScrimController.Callback mUnlockScrimCallback = new ScrimController
- .Callback() {
- @Override
- public void onFinished() {
- if (mStatusBarKeyguardViewManager == null) {
- Log.w(TAG, "Tried to notify keyguard visibility when "
- + "mStatusBarKeyguardViewManager was null");
- return;
- }
- if (mKeyguardStateController.isKeyguardFadingAway() && !mCallingFadingAwayAfterReveal) {
- mStatusBarKeyguardViewManager.onKeyguardFadedAway();
- }
- }
-
- @Override
- public void onCancelled() {
- onFinished();
- }
- };
+ private boolean mIsLaunchingActivityOverLockscreen;
private final UserSwitcherController mUserSwitcherController;
private final NetworkController mNetworkController;
@@ -668,51 +677,25 @@ public class StatusBar extends SystemUI implements DemoMode,
private boolean mNoAnimationOnNextBarModeChange;
private final SysuiStatusBarStateController mStatusBarStateController;
- private final KeyguardUpdateMonitorCallback mUpdateCallback =
- new KeyguardUpdateMonitorCallback() {
- @Override
- public void onDreamingStateChanged(boolean dreaming) {
- if (dreaming) {
- maybeEscalateHeadsUp();
- }
- }
-
- // TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by
- // KeyguardCoordinator
- @Override
- public void onStrongAuthStateChanged(int userId) {
- super.onStrongAuthStateChanged(userId);
- mNotificationsController.requestNotificationUpdate("onStrongAuthStateChanged");
- }
- };
-
-
- private final FalsingManager.FalsingBeliefListener mFalsingBeliefListener =
- new FalsingManager.FalsingBeliefListener() {
- @Override
- public void onFalse() {
- // Hides quick settings, bouncer, and quick-quick settings.
- mStatusBarKeyguardViewManager.reset(true);
- }
- };
-
- private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
-
private HeadsUpAppearanceController mHeadsUpAppearanceController;
- private boolean mVibrateOnOpening;
- private final VibratorHelper mVibratorHelper;
- private ActivityLaunchAnimator mActivityLaunchAnimator;
+ private final ActivityLaunchAnimator mActivityLaunchAnimator;
+ private final DialogLaunchAnimator mDialogLaunchAnimator;
private NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
protected StatusBarNotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
- private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
+ private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
private final Optional<BubblesManager> mBubblesManagerOptional;
private final Optional<Bubbles> mBubblesOptional;
private final Bubbles.BubbleExpandListener mBubbleExpandListener;
private final Optional<StartingSurface> mStartingSurfaceOptional;
- private ActivityIntentHelper mActivityIntentHelper;
+ private final ActivityIntentHelper mActivityIntentHelper;
private NotificationStackScrollLayoutController mStackScrollerController;
+ private BatteryMeterViewController mBatteryMeterViewController;
+
+ private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
+ (extractor, which) -> updateTheme();
+
/**
* Public constructor for StatusBar.
@@ -727,7 +710,6 @@ public class StatusBar extends SystemUI implements DemoMode,
LightBarController lightBarController,
AutoHideController autoHideController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- StatusBarSignalPolicy signalPolicy,
PulseExpansionHandler pulseExpansionHandler,
NotificationWakeUpCoordinator notificationWakeUpCoordinator,
KeyguardBypassController keyguardBypassController,
@@ -738,7 +720,8 @@ public class StatusBar extends SystemUI implements DemoMode,
FalsingManager falsingManager,
FalsingCollector falsingCollector,
BroadcastDispatcher broadcastDispatcher,
- RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
+ NotifShadeEventSource notifShadeEventSource,
+ NotificationEntryManager notificationEntryManager,
NotificationGutsManager notificationGutsManager,
NotificationLogger notificationLogger,
NotificationInterruptStateProvider notificationInterruptStateProvider,
@@ -757,20 +740,18 @@ public class StatusBar extends SystemUI implements DemoMode,
ScreenLifecycle screenLifecycle,
WakefulnessLifecycle wakefulnessLifecycle,
SysuiStatusBarStateController statusBarStateController,
- VibratorHelper vibratorHelper,
Optional<BubblesManager> bubblesManagerOptional,
Optional<Bubbles> bubblesOptional,
VisualStabilityManager visualStabilityManager,
DeviceProvisionedController deviceProvisionedController,
NavigationBarController navigationBarController,
- AccessibilityFloatingMenuController accessibilityFloatingMenuController,
Lazy<AssistManager> assistManagerLazy,
ConfigurationController configurationController,
NotificationShadeWindowController notificationShadeWindowController,
DozeParameters dozeParameters,
ScrimController scrimController,
- @Nullable KeyguardLiftController keyguardLiftController,
Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
+ LockscreenGestureLogger lockscreenGestureLogger,
Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
DozeServiceHost dozeServiceHost,
PowerManager powerManager,
@@ -778,14 +759,15 @@ public class StatusBar extends SystemUI implements DemoMode,
DozeScrimController dozeScrimController,
VolumeComponent volumeComponent,
CommandQueue commandQueue,
- Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
+ CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
+ StatusBarComponent.Factory statusBarComponentFactory,
PluginManager pluginManager,
Optional<LegacySplitScreen> splitScreenOptional,
LightsOutNotifController lightsOutNotifController,
StatusBarNotificationActivityStarter.Builder
statusBarNotificationActivityStarterBuilder,
ShadeController shadeController,
- SuperStatusBarViewFactory superStatusBarViewFactory,
+ StatusBarWindowView statusBarWindowView,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
ViewMediatorCallback viewMediatorCallback,
InitController initController,
@@ -794,15 +776,20 @@ public class StatusBar extends SystemUI implements DemoMode,
KeyguardDismissUtil keyguardDismissUtil,
ExtensionController extensionController,
UserInfoControllerImpl userInfoControllerImpl,
+ OperatorNameViewController.Factory operatorNameViewControllerFactory,
+ PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory,
PhoneStatusBarPolicy phoneStatusBarPolicy,
KeyguardIndicationController keyguardIndicationController,
- DismissCallbackRegistry dismissCallbackRegistry,
DemoModeController demoModeController,
Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
NotificationIconAreaController notificationIconAreaController,
- BrightnessSlider.Factory brightnessSliderFactory,
- WiredChargingRippleController chargingRippleAnimationController,
+ BrightnessSliderController.Factory brightnessSliderFactory,
+ UnfoldTransitionConfig unfoldTransitionConfig,
+ Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
+ Lazy<UnfoldTransitionWallpaperController> unfoldTransitionWallpaperController,
+ Lazy<NaturalRotationUnfoldProgressProvider> naturalRotationUnfoldProgressProvider,
+ WallpaperController wallpaperController,
OngoingCallController ongoingCallController,
SystemStatusAnimationScheduler animationScheduler,
StatusBarLocationPublisher locationPublisher,
@@ -810,19 +797,28 @@ public class StatusBar extends SystemUI implements DemoMode,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
FeatureFlags featureFlags,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+ @Main Handler mainHandler,
+ @Main DelayableExecutor delayableExecutor,
+ @Main MessageRouter messageRouter,
+ WallpaperManager wallpaperManager,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
- Optional<StartingSurface> startingSurfaceOptional) {
+ Optional<StartingSurface> startingSurfaceOptional,
+ TunerService tunerService,
+ DumpManager dumpManager,
+ ActivityLaunchAnimator activityLaunchAnimator,
+ DialogLaunchAnimator dialogLaunchAnimator) {
super(context);
mNotificationsController = notificationsController;
mLightBarController = lightBarController;
mAutoHideController = autoHideController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mSignalPolicy = signalPolicy;
mPulseExpansionHandler = pulseExpansionHandler;
mWakeUpCoordinator = notificationWakeUpCoordinator;
mKeyguardBypassController = keyguardBypassController;
mKeyguardStateController = keyguardStateController;
mHeadsUpManager = headsUpManagerPhone;
+ mOperatorNameViewControllerFactory = operatorNameViewControllerFactory;
+ mPhoneStatusBarViewControllerFactory = phoneStatusBarViewControllerFactory;
mKeyguardIndicationController = keyguardIndicationController;
mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
mDynamicPrivacyController = dynamicPrivacyController;
@@ -830,7 +826,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mFalsingCollector = falsingCollector;
mFalsingManager = falsingManager;
mBroadcastDispatcher = broadcastDispatcher;
- mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler;
+ mNotifShadeEventSource = notifShadeEventSource;
+ mEntryManager = notificationEntryManager;
mGutsManager = notificationGutsManager;
mNotificationLogger = notificationLogger;
mNotificationInterruptStateProvider = notificationInterruptStateProvider;
@@ -849,7 +846,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mScreenLifecycle = screenLifecycle;
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
- mVibratorHelper = vibratorHelper;
mBubblesManagerOptional = bubblesManagerOptional;
mBubblesOptional = bubblesOptional;
mVisualStabilityManager = visualStabilityManager;
@@ -862,20 +858,21 @@ public class StatusBar extends SystemUI implements DemoMode,
mPowerManager = powerManager;
mDozeParameters = dozeParameters;
mScrimController = scrimController;
- mKeyguardLiftController = keyguardLiftController;
mLockscreenWallpaperLazy = lockscreenWallpaperLazy;
+ mLockscreenGestureLogger = lockscreenGestureLogger;
mScreenPinningRequest = screenPinningRequest;
mDozeScrimController = dozeScrimController;
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy;
mVolumeComponent = volumeComponent;
mCommandQueue = commandQueue;
- mStatusBarComponentBuilder = statusBarComponentBuilder;
+ mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger;
+ mStatusBarComponentFactory = statusBarComponentFactory;
mPluginManager = pluginManager;
mSplitScreenOptional = splitScreenOptional;
mStatusBarNotificationActivityStarterBuilder = statusBarNotificationActivityStarterBuilder;
mShadeController = shadeController;
- mSuperStatusBarViewFactory = superStatusBarViewFactory;
+ mStatusBarWindowView = statusBarWindowView;
mLightsOutNotifController = lightsOutNotifController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardViewMediatorCallback = viewMediatorCallback;
@@ -885,35 +882,60 @@ public class StatusBar extends SystemUI implements DemoMode,
mExtensionController = extensionController;
mUserInfoControllerImpl = userInfoControllerImpl;
mIconPolicy = phoneStatusBarPolicy;
- mDismissCallbackRegistry = dismissCallbackRegistry;
mDemoModeController = demoModeController;
mNotificationIconAreaController = notificationIconAreaController;
mBrightnessSliderFactory = brightnessSliderFactory;
- mChargingRippleAnimationController = chargingRippleAnimationController;
+ mUnfoldTransitionConfig = unfoldTransitionConfig;
+ mUnfoldLightRevealOverlayAnimation = unfoldLightRevealOverlayAnimation;
+ mNaturalUnfoldProgressProvider = naturalRotationUnfoldProgressProvider;
+ mUnfoldWallpaperController = unfoldTransitionWallpaperController;
+ mWallpaperController = wallpaperController;
mOngoingCallController = ongoingCallController;
mAnimationScheduler = animationScheduler;
mStatusBarLocationPublisher = locationPublisher;
mStatusBarIconController = statusBarIconController;
mFeatureFlags = featureFlags;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
+ mMainHandler = mainHandler;
+ mMainExecutor = delayableExecutor;
+ mMessageRouter = messageRouter;
+ mWallpaperManager = wallpaperManager;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+ mTunerService = tunerService;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
lockscreenShadeTransitionController.setStatusbar(this);
mExpansionChangedListeners = new ArrayList<>();
+ addExpansionChangedListener(
+ (expansion, expanded) -> mScrimController.setRawPanelExpansionFraction(expansion));
+ addExpansionChangedListener(this::onPanelExpansionChanged);
mBubbleExpandListener =
- (isExpanding, key) -> {
- mContext.getMainExecutor().execute(() -> {
- mNotificationsController.requestNotificationUpdate("onBubbleExpandChanged");
- updateScrimController();
- });
- };
+ (isExpanding, key) -> mContext.getMainExecutor().execute(() -> {
+ mNotificationsController.requestNotificationUpdate("onBubbleExpandChanged");
+ updateScrimController();
+ });
mActivityIntentHelper = new ActivityIntentHelper(mContext);
+ mActivityLaunchAnimator = activityLaunchAnimator;
+ mDialogLaunchAnimator = dialogLaunchAnimator;
+
+ // The status bar background may need updating when the ongoing call status changes.
+ mOngoingCallController.addCallback((animate) -> maybeUpdateBarMode());
+
+ // TODO(b/190746471): Find a better home for this.
DateTimeView.setReceiverHandler(timeTickHandler);
+
+ mMessageRouter.subscribeTo(KeyboardShortcutsMessage.class,
+ data -> toggleKeyboardShortcuts(data.mDeviceId));
+ mMessageRouter.subscribeTo(MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU,
+ id -> dismissKeyboardShortcuts());
+ mMessageRouter.subscribeTo(AnimateExpandSettingsPanelMessage.class,
+ data -> mCommandQueueCallbacks.animateExpandSettingsPanel(data.mSubpanel));
+ mMessageRouter.subscribeTo(MSG_LAUNCH_TRANSITION_TIMEOUT,
+ id -> onLaunchTransitionTimeout());
}
@Override
@@ -930,21 +952,18 @@ public class StatusBar extends SystemUI implements DemoMode,
mKeyguardIndicationController.init();
- mColorExtractor.addOnColorsChangedListener(this);
- mStatusBarStateController.addCallback(this,
+ mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener);
+ mStatusBarStateController.addCallback(mStateListener,
SysuiStatusBarStateController.RANK_STATUS_BAR);
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mDreamManager = IDreamManager.Stub.asInterface(
ServiceManager.checkService(DreamService.DREAM_SERVICE));
- mDisplay = mWindowManager.getDefaultDisplay();
+ mDisplay = mContext.getDisplay();
mDisplayId = mDisplay.getDisplayId();
updateDisplaySize();
- mVibrateOnOpening = mContext.getResources().getBoolean(
- R.bool.config_vibrateOnIconAnimation);
-
// start old BaseStatusBar.start().
mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
@@ -958,14 +977,7 @@ public class StatusBar extends SystemUI implements DemoMode,
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- mWallpaperSupported =
- mContext.getSystemService(WallpaperManager.class).isWallpaperSupported();
-
- // Connect in to the status bar manager service
- mCommandQueue.addCallback(this);
-
- // Listen for demo mode changes
- mDemoModeController.addCallback(this);
+ mWallpaperSupported = mWallpaperManager.isWallpaperSupported();
RegisterStatusBarResult result = null;
try {
@@ -992,12 +1004,13 @@ public class StatusBar extends SystemUI implements DemoMode,
if (containsType(result.mTransientBarTypes, ITYPE_STATUS_BAR)) {
showTransientUnchecked();
}
- onSystemBarAttributesChanged(mDisplayId, result.mAppearance, result.mAppearanceRegions,
- result.mNavbarColorManagedByIme, result.mBehavior, result.mAppFullscreen);
+ mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance,
+ result.mAppearanceRegions, result.mNavbarColorManagedByIme, result.mBehavior,
+ result.mRequestedVisibilities, result.mPackageName);
// StatusBarManagerService has a back up of IME token and it's restored here.
- setImeWindowStatus(mDisplayId, result.mImeToken, result.mImeWindowVis,
- result.mImeBackDisposition, result.mShowImeSwitcher);
+ mCommandQueueCallbacks.setImeWindowStatus(mDisplayId, result.mImeToken,
+ result.mImeWindowVis, result.mImeBackDisposition, result.mShowImeSwitcher);
// Set up the initial icon state
int numIcons = result.mIcons.size();
@@ -1036,7 +1049,13 @@ public class StatusBar extends SystemUI implements DemoMode,
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy.init();
- mKeyguardStateController.addCallback(this);
+ mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
+ @Override
+ public void onUnlockedChanged() {
+ updateKeyguardState();
+ logStateToEventlog();
+ }
+ });
startKeyguard();
mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
@@ -1048,9 +1067,9 @@ public class StatusBar extends SystemUI implements DemoMode,
mAmbientIndicationContainer);
updateLightRevealScrimVisibility();
- mConfigurationController.addCallback(this);
+ mConfigurationController.addCallback(mConfigurationListener);
- mBatteryController.observe(mLifecycle, this);
+ mBatteryController.observe(mLifecycle, mBatteryStateChangeCallback);
mLifecycle.setCurrentState(RESUMED);
// set the initial view visibility
@@ -1061,13 +1080,19 @@ public class StatusBar extends SystemUI implements DemoMode,
mFalsingManager.addFalsingBeliefListener(mFalsingBeliefListener);
+ if (mUnfoldTransitionConfig.isEnabled()) {
+ mUnfoldLightRevealOverlayAnimation.get().init();
+ mUnfoldWallpaperController.get().init();
+ mNaturalUnfoldProgressProvider.get().init();
+ }
+
mPluginManager.addPluginListener(
new PluginListener<OverlayPlugin>() {
- private ArraySet<OverlayPlugin> mOverlays = new ArraySet<>();
+ private final ArraySet<OverlayPlugin> mOverlays = new ArraySet<>();
@Override
public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) {
- mMainThreadHandler.post(
+ mMainExecutor.execute(
() -> plugin.setup(getNotificationShadeWindowView(),
getNavigationBarView(),
new Callback(plugin), mDozeParameters));
@@ -1075,7 +1100,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void onPluginDisconnected(OverlayPlugin plugin) {
- mMainThreadHandler.post(() -> {
+ mMainExecutor.execute(() -> {
mOverlays.remove(plugin);
mNotificationShadeWindowController
.setForcePluginOpen(mOverlays.size() != 0, this);
@@ -1096,7 +1121,7 @@ public class StatusBar extends SystemUI implements DemoMode,
} else {
mOverlays.remove(mPlugin);
}
- mMainThreadHandler.post(() -> {
+ mMainExecutor.execute(() -> {
mNotificationShadeWindowController
.setStateListener(b -> mOverlays.forEach(
o -> o.setCollapseDesired(b)));
@@ -1106,13 +1131,17 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
}, OverlayPlugin.class, true /* Allow multiple plugins */);
+
+ mStartingSurfaceOptional.ifPresent(startingSurface -> startingSurface.setSysuiProxy(
+ (requestTopUi, componentTag) -> mMainExecutor.execute(() ->
+ mNotificationShadeWindowController.setRequestTopUi(
+ requestTopUi, componentTag))));
}
// ================================================================================
// Constructing the view
// ================================================================================
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
- final Context context = mContext;
updateDisplaySize(); // populates mDisplayMetrics
updateResources();
updateTheme();
@@ -1120,26 +1149,25 @@ public class StatusBar extends SystemUI implements DemoMode,
inflateStatusBarWindow();
mNotificationShadeWindowViewController.setService(this, mNotificationShadeWindowController);
mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
+ mWallpaperController.setRootView(mNotificationShadeWindowView);
// TODO: Deal with the ugliness that comes from having some of the statusbar broken out
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
- mStackScrollerController =
- mNotificationPanelViewController.getNotificationStackScrollLayoutController();
- mStackScroller = mStackScrollerController.getView();
NotificationListContainer notifListContainer =
mStackScrollerController.getNotificationListContainer();
mNotificationLogger.setUpWithContainer(notifListContainer);
- inflateShelf();
mNotificationIconAreaController.setupShelf(mNotificationShelfController);
mNotificationPanelViewController.addExpansionListener(mWakeUpCoordinator);
mNotificationPanelViewController.addExpansionListener(
this::dispatchPanelExpansionForKeyguardDismiss);
+ mUserSwitcherController.init(mNotificationShadeWindowView);
+
// Allow plugins to reference DarkIconDispatcher and StatusBarStateController
mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
mPluginDependencyProvider.allowPluginDependency(StatusBarStateController.class);
- FragmentHostManager.get(mPhoneStatusBarWindow)
+ FragmentHostManager.get(mStatusBarWindowView)
.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
CollapsedStatusBarFragment statusBarFragment =
(CollapsedStatusBarFragment) fragment;
@@ -1147,29 +1175,39 @@ public class StatusBar extends SystemUI implements DemoMode,
PhoneStatusBarView oldStatusBarView = mStatusBarView;
mStatusBarView = (PhoneStatusBarView) statusBarFragment.getView();
mStatusBarView.setBar(this);
- mStatusBarView.setPanel(mNotificationPanelViewController);
+ mStatusBarView.setPanelStateChangeListener(
+ mNotificationPanelViewController.getPanelStateChangeListener());
mStatusBarView.setScrimController(mScrimController);
mStatusBarView.setExpansionChangedListeners(mExpansionChangedListeners);
for (ExpansionChangedListener listener : mExpansionChangedListeners) {
sendInitialExpansionAmount(listener);
}
- // CollapsedStatusBarFragment re-inflated PhoneStatusBarView and both of
- // mStatusBarView.mExpanded and mStatusBarView.mBouncerShowing are false.
- // PhoneStatusBarView's new instance will set to be gone in
- // PanelBar.updateVisibility after calling mStatusBarView.setBouncerShowing
- // that will trigger PanelBar.updateVisibility. If there is a heads up showing,
- // it needs to notify PhoneStatusBarView's new instance to update the correct
- // status by calling mNotificationPanel.notifyBarPanelExpansionChanged().
- if (mHeadsUpManager.hasPinnedHeadsUp()) {
- mNotificationPanelViewController.notifyBarPanelExpansionChanged();
- }
- mStatusBarView.setBouncerShowing(mBouncerShowing);
- if (oldStatusBarView != null) {
- float fraction = oldStatusBarView.getExpansionFraction();
- boolean expanded = oldStatusBarView.isExpanded();
- mStatusBarView.panelExpansionChanged(fraction, expanded);
- }
+ mNotificationPanelViewController.setBar(mStatusBarView);
+
+ mPhoneStatusBarViewController = mPhoneStatusBarViewControllerFactory
+ .create(mStatusBarView, mNotificationPanelViewController
+ .getStatusBarTouchEventHandler());
+ mPhoneStatusBarViewController.init();
+
+ mBatteryMeterViewController = new BatteryMeterViewController(
+ mStatusBarView.findViewById(R.id.battery),
+ mConfigurationController,
+ mTunerService,
+ mBroadcastDispatcher,
+ mMainHandler,
+ mContext.getContentResolver(),
+ mBatteryController
+ );
+ mBatteryMeterViewController.init();
+
+ // Ensure we re-propagate panel expansion values to the panel controller and
+ // any listeners it may have, such as PanelBar. This will also ensure we
+ // re-display the notification panel if necessary (for example, if
+ // a heads-up notification was being displayed and should continue being
+ // displayed).
+ mNotificationPanelViewController.updatePanelExpansionAndVisibility();
+ setBouncerShowingForStatusBarComponents(mBouncerShowing);
HeadsUpAppearanceController oldController = mHeadsUpAppearanceController;
if (mHeadsUpAppearanceController != null) {
@@ -1180,7 +1218,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// TODO (b/136993073) Separate notification shade and status bar
mHeadsUpAppearanceController = new HeadsUpAppearanceController(
mNotificationIconAreaController, mHeadsUpManager,
- mStackScroller.getController(),
+ mStackScrollerController,
mStatusBarStateController, mKeyguardBypassController,
mKeyguardStateController, mWakeUpCoordinator, mCommandQueue,
mNotificationPanelViewController, mStatusBarView);
@@ -1199,19 +1237,20 @@ public class StatusBar extends SystemUI implements DemoMode,
mStatusBarLocationPublisher,
mNotificationIconAreaController,
mFeatureFlags,
+ () -> Optional.of(this),
mStatusBarIconController,
mKeyguardStateController,
mNetworkController,
mStatusBarStateController,
- this,
- mCommandQueue
+ mCommandQueue,
+ mCollapsedStatusBarFragmentLogger,
+ mOperatorNameViewControllerFactory
),
CollapsedStatusBarFragment.TAG)
.commit();
mHeadsUpManager.setup(mVisualStabilityManager);
mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
- mHeadsUpManager.addListener(this);
mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener());
mHeadsUpManager.addListener(mVisualStabilityManager);
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
@@ -1236,7 +1275,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public boolean shouldHideOnTouch() {
- return !mRemoteInputManager.getController().isRemoteInputActive();
+ return !mRemoteInputManager.isRemoteInputActive();
}
@Override
@@ -1254,13 +1293,11 @@ public class StatusBar extends SystemUI implements DemoMode,
ScrimView notificationsScrim = mNotificationShadeWindowView
.findViewById(R.id.scrim_notifications);
ScrimView scrimInFront = mNotificationShadeWindowView.findViewById(R.id.scrim_in_front);
- ScrimView scrimForBubble = mBubblesManagerOptional.isPresent()
- ? mBubblesManagerOptional.get().getScrimForBubble() : null;
mScrimController.setScrimVisibleListener(scrimsVisible -> {
mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible);
});
- mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront, scrimForBubble);
+ mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront);
mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
mLightRevealScrim.setScrimOpaqueChangedListener((opaque) -> {
@@ -1281,6 +1318,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationPanelViewController.initDependencies(
this,
+ this::makeExpandedInvisible,
mNotificationShelfController);
BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
@@ -1321,7 +1359,7 @@ public class StatusBar extends SystemUI implements DemoMode,
QS qs = (QS) f;
if (qs instanceof QSFragment) {
mQSPanelController = ((QSFragment) qs).getQSPanelController();
- mQSPanelController.setBrightnessMirror(mBrightnessMirrorController);
+ ((QSFragment) qs).setBrightnessMirrorController(mBrightnessMirrorController);
}
});
}
@@ -1352,14 +1390,11 @@ public class StatusBar extends SystemUI implements DemoMode,
});
}
- if (!mPowerManager.isScreenOn()) {
+ if (!mPowerManager.isInteractive()) {
mBroadcastReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
}
mGestureWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
- "GestureWakeLock");
- mVibrator = mContext.getSystemService(Vibrator.class);
- mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
- mVibrator, context.getResources());
+ "sysui:GestureWakeLock");
// receive broadcasts
registerBroadcastReceiver();
@@ -1368,13 +1403,19 @@ public class StatusBar extends SystemUI implements DemoMode,
if (DEBUG_MEDIA_FAKE_ARTWORK) {
demoFilter.addAction(ACTION_FAKE_ARTWORK);
}
- context.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter,
+ mContext.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter,
android.Manifest.permission.DUMP, null);
// listen for USER_SETUP_COMPLETE setting (per-user)
mDeviceProvisionedController.addCallback(mUserSetupObserver);
mUserSetupObserver.onUserSetupChanged();
+ for (ExpansionChangedListener listener : mExpansionChangedListeners) {
+ // The initial expansion amount comes from mNotificationPanelViewController, so we
+ // should send the amount once we've fully set up that controller.
+ sendInitialExpansionAmount(listener);
+ }
+
// disable profiling bars, since they overlap and clutter the output on app windows
ThreadedRenderer.overrideProperty("disableProfileBars", "true");
@@ -1415,23 +1456,21 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- @NonNull
- @Override
- public Lifecycle getLifecycle() {
- return mLifecycle;
- }
-
- @Override
- public void onPowerSaveChanged(boolean isPowerSave) {
- mHandler.post(mCheckBarModes);
- if (mDozeServiceHost != null) {
- mDozeServiceHost.firePowerSaveChanged(isPowerSave);
+ private void onPanelExpansionChanged(float frac, boolean expanded) {
+ if (frac == 0 || frac == 1) {
+ if (getNavigationBarView() != null) {
+ getNavigationBarView().onStatusBarPanelStateChanged();
+ }
+ if (getNotificationPanelViewController() != null) {
+ getNotificationPanelViewController().updateSystemUiStateFlags();
+ }
}
}
+ @NonNull
@Override
- public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- // noop
+ public Lifecycle getLifecycle() {
+ return mLifecycle;
}
@VisibleForTesting
@@ -1449,7 +1488,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private void setUpPresenter() {
// Set up the initial notification state.
- mActivityLaunchAnimator = new ActivityLaunchAnimator(this, mContext);
+ mActivityLaunchAnimator.setCallback(mKeyguardHandler);
mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider(
mNotificationShadeWindowViewController,
mStackScrollerController.getNotificationListContainer(),
@@ -1457,17 +1496,39 @@ public class StatusBar extends SystemUI implements DemoMode,
);
// TODO: inject this.
- mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController,
- mHeadsUpManager, mNotificationShadeWindowView, mStackScrollerController,
- mDozeScrimController, mScrimController, mNotificationShadeWindowController,
- mDynamicPrivacyController, mKeyguardStateController,
+ mPresenter = new StatusBarNotificationPresenter(
+ mContext,
+ mNotificationPanelViewController,
+ mHeadsUpManager,
+ mNotificationShadeWindowView,
+ mStackScrollerController,
+ mDozeScrimController,
+ mScrimController,
+ mNotificationShadeWindowController,
+ mDynamicPrivacyController,
+ mKeyguardStateController,
mKeyguardIndicationController,
- this /* statusBar */, mShadeController,
- mLockscreenShadeTransitionController, mCommandQueue, mInitController,
- mNotificationInterruptStateProvider);
+ mFeatureFlags,
+ this /* statusBar */,
+ mShadeController,
+ mLockscreenShadeTransitionController,
+ mCommandQueue,
+ mViewHierarchyManager,
+ mLockscreenUserManager,
+ mStatusBarStateController,
+ mNotifShadeEventSource,
+ mEntryManager,
+ mMediaManager,
+ mGutsManager,
+ mKeyguardUpdateMonitor,
+ mLockscreenGestureLogger,
+ mInitController,
+ mNotificationInterruptStateProvider,
+ mRemoteInputManager,
+ mConfigurationController);
mNotificationShelfController.setOnActivatedListener(mPresenter);
- mRemoteInputManager.getController().addCallback(mNotificationShadeWindowController);
+ mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController);
mNotificationActivityStarter =
mStatusBarNotificationActivityStarterBuilder
@@ -1477,7 +1538,7 @@ public class StatusBar extends SystemUI implements DemoMode,
.setNotificationPresenter(mPresenter)
.setNotificationPanelViewController(mNotificationPanelViewController)
.build();
- mStackScroller.setNotificationActivityStarter(mNotificationActivityStarter);
+ mStackScrollerController.setNotificationActivityStarter(mNotificationActivityStarter);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
mNotificationsController.initialize(
@@ -1511,6 +1572,9 @@ public class StatusBar extends SystemUI implements DemoMode,
time, PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:" + why);
mWakeUpComingFromTouch = true;
where.getLocationInWindow(mTmpInt2);
+
+ // NOTE, the incoming view can sometimes be the entire container... unsure if
+ // this location is valuable enough
mWakeUpTouchLocation = new PointF(mTmpInt2[0] + where.getWidth() / 2,
mTmpInt2[1] + where.getHeight() / 2);
mFalsingCollector.onScreenOnFromTouch();
@@ -1540,68 +1604,36 @@ public class StatusBar extends SystemUI implements DemoMode,
};
}
- private void inflateShelf() {
- mNotificationShelfController = mSuperStatusBarViewFactory
- .getNotificationShelfController(mStackScroller);
- }
-
- @Override
- public void onDensityOrFontScaleChanged() {
- // TODO: Remove this.
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.onDensityOrFontScaleChanged();
- }
- // TODO: Bring these out of StatusBar.
- mUserInfoControllerImpl.onDensityOrFontScaleChanged();
- mUserSwitcherController.onDensityOrFontScaleChanged();
- mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
- mHeadsUpManager.onDensityOrFontScaleChanged();
- }
-
- @Override
- public void onThemeChanged() {
- if (mStatusBarKeyguardViewManager != null) {
- mStatusBarKeyguardViewManager.onThemeChanged();
- }
- if (mAmbientIndicationContainer instanceof AutoReinflateContainer) {
- ((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout();
- }
- mNotificationIconAreaController.onThemeChanged();
- }
-
- @Override
- public void onOverlayChanged() {
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.onOverlayChanged();
- }
- // We need the new R.id.keyguard_indication_area before recreating
- // mKeyguardIndicationController
- mNotificationPanelViewController.onThemeChanged();
- onThemeChanged();
- }
-
- @Override
- public void onUiModeChanged() {
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.onUiModeChanged();
- }
- }
-
private void inflateStatusBarWindow() {
- mNotificationShadeWindowView = mSuperStatusBarViewFactory.getNotificationShadeWindowView();
- StatusBarComponent statusBarComponent = mStatusBarComponentBuilder.get()
- .statusBarWindowView(mNotificationShadeWindowView).build();
+ StatusBarComponent statusBarComponent = mStatusBarComponentFactory.create();
+ mNotificationShadeWindowView = statusBarComponent.getNotificationShadeWindowView();
mNotificationShadeWindowViewController = statusBarComponent
.getNotificationShadeWindowViewController();
mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
mNotificationShadeWindowViewController.setupExpandedStatusBar();
mStatusBarWindowController = statusBarComponent.getStatusBarWindowController();
- mPhoneStatusBarWindow = mSuperStatusBarViewFactory.getStatusBarWindowView();
mNotificationPanelViewController = statusBarComponent.getNotificationPanelViewController();
statusBarComponent.getLockIconViewController().init();
+ mStackScrollerController = statusBarComponent.getNotificationStackScrollLayoutController();
+ mStackScroller = mStackScrollerController.getView();
+ mNotificationShelfController = statusBarComponent.getNotificationShelfController();
mAuthRippleController = statusBarComponent.getAuthRippleController();
mAuthRippleController.init();
+
+ mHeadsUpManager.addListener(statusBarComponent.getStatusBarHeadsUpChangeListener());
+
+ mHeadsUpManager.addListener(statusBarComponent.getStatusBarHeadsUpChangeListener());
+
+ // Listen for demo mode changes
+ mDemoModeController.addCallback(statusBarComponent.getStatusBarDemoMode());
+
+ if (mCommandQueueCallbacks != null) {
+ mCommandQueue.removeCallback(mCommandQueueCallbacks);
+ }
+ mCommandQueueCallbacks = statusBarComponent.getStatusBarCommandQueueCallbacks();
+ // Connect in to the status bar manager service
+ mCommandQueue.addCallback(mCommandQueueCallbacks);
}
protected void startKeyguard() {
@@ -1636,13 +1668,13 @@ public class StatusBar extends SystemUI implements DemoMode,
}
});
mStatusBarKeyguardViewManager.registerStatusBar(
- /* statusBar= */ this, getBouncerContainer(),
+ /* statusBar= */ this,
mNotificationPanelViewController, mBiometricUnlockController,
mStackScroller, mKeyguardBypassController);
mKeyguardIndicationController
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
- mRemoteInputManager.getController().addCallback(mStatusBarKeyguardViewManager);
+ mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager);
mDynamicPrivacyController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
@@ -1651,7 +1683,7 @@ public class StatusBar extends SystemUI implements DemoMode,
Trace.endSection();
}
- protected View getStatusBarView() {
+ protected PhoneStatusBarView getStatusBarView() {
return mStatusBarView;
}
@@ -1660,7 +1692,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
public StatusBarWindowView getStatusBarWindow() {
- return mPhoneStatusBarWindow;
+ return mStatusBarWindowView;
}
public NotificationShadeWindowViewController getNotificationShadeWindowViewController() {
@@ -1671,8 +1703,8 @@ public class StatusBar extends SystemUI implements DemoMode,
return mNotificationPanelViewController;
}
- protected ViewGroup getBouncerContainer() {
- return mNotificationShadeWindowView;
+ public ViewGroup getBouncerContainer() {
+ return mNotificationShadeWindowViewController.getBouncerContainer();
}
public int getStatusBarHeight() {
@@ -1713,7 +1745,7 @@ public class StatusBar extends SystemUI implements DemoMode,
* If the user switcher is simple then disable QS during setup because
* the user intends to use the lock screen user switcher, QS in not needed.
*/
- private void updateQsExpansionEnabled() {
+ void updateQsExpansionEnabled() {
final boolean expandEnabled = mDeviceProvisionedController.isDeviceProvisioned()
&& (mUserSetup || mUserSwitcherController == null
|| !mUserSwitcherController.isSimpleUserSwitcher())
@@ -1729,22 +1761,6 @@ public class StatusBar extends SystemUI implements DemoMode,
return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0;
}
- public void addQsTile(ComponentName tile) {
- if (mQSPanelController != null && mQSPanelController.getHost() != null) {
- mQSPanelController.getHost().addTile(tile);
- }
- }
-
- public void remQsTile(ComponentName tile) {
- if (mQSPanelController != null && mQSPanelController.getHost() != null) {
- mQSPanelController.getHost().removeTile(tile);
- }
- }
-
- public void clickTile(ComponentName tile) {
- mQSPanelController.clickTile(tile);
- }
-
/**
* Request a notification update
* @param reason why we're requesting a notification update
@@ -1770,102 +1786,10 @@ public class StatusBar extends SystemUI implements DemoMode,
&& mFalsingCollector.isReportingEnabled() ? View.VISIBLE : View.INVISIBLE);
}
- /**
- * State is one or more of the DISABLE constants from StatusBarManager.
- */
- @Override
- public void disable(int displayId, int state1, int state2, boolean animate) {
- if (displayId != mDisplayId) {
- return;
- }
- state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2);
-
- animate &= mStatusBarWindowState != WINDOW_STATE_HIDDEN;
- final int old1 = mDisabled1;
- final int diff1 = state1 ^ old1;
- mDisabled1 = state1;
-
- final int old2 = mDisabled2;
- final int diff2 = state2 ^ old2;
- mDisabled2 = state2;
-
- if (DEBUG) {
- Log.d(TAG, String.format("disable1: 0x%08x -> 0x%08x (diff1: 0x%08x)",
- old1, state1, diff1));
- Log.d(TAG, String.format("disable2: 0x%08x -> 0x%08x (diff2: 0x%08x)",
- old2, state2, diff2));
- }
-
- StringBuilder flagdbg = new StringBuilder();
- flagdbg.append("disable<");
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_EXPAND)) ? 'E' : 'e');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_EXPAND)) ? '!' : ' ');
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS)) ? 'I' : 'i');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS)) ? '!' : ' ');
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS)) ? 'A' : 'a');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS)) ? '!' : ' ');
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_SYSTEM_INFO)) ? 'S' : 's');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_SYSTEM_INFO)) ? '!' : ' ');
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_BACK)) ? 'B' : 'b');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_BACK)) ? '!' : ' ');
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_HOME)) ? 'H' : 'h');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_HOME)) ? '!' : ' ');
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_RECENT)) ? 'R' : 'r');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_RECENT)) ? '!' : ' ');
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_CLOCK)) ? 'C' : 'c');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_CLOCK)) ? '!' : ' ');
- flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_SEARCH)) ? 'S' : 's');
- flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_SEARCH)) ? '!' : ' ');
- flagdbg.append("> disable2<");
- flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_QUICK_SETTINGS)) ? 'Q' : 'q');
- flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS)) ? '!' : ' ');
- flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_SYSTEM_ICONS)) ? 'I' : 'i');
- flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_SYSTEM_ICONS)) ? '!' : ' ');
- flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE)) ? 'N' : 'n');
- flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE)) ? '!' : ' ');
- flagdbg.append('>');
- Log.d(TAG, flagdbg.toString());
-
- if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
- if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
- mShadeController.animateCollapsePanels();
- }
- }
-
- if ((diff1 & StatusBarManager.DISABLE_RECENT) != 0) {
- if ((state1 & StatusBarManager.DISABLE_RECENT) != 0) {
- // close recents if it's visible
- mHandler.removeMessages(MSG_HIDE_RECENT_APPS);
- mHandler.sendEmptyMessage(MSG_HIDE_RECENT_APPS);
- }
- }
-
- if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
- if (areNotificationAlertsDisabled()) {
- mHeadsUpManager.releaseAllImmediately();
- }
- }
-
- if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) {
- updateQsExpansionEnabled();
- }
-
- if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
- updateQsExpansionEnabled();
- if ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
- mShadeController.animateCollapsePanels();
- }
- }
- }
-
boolean areNotificationAlertsDisabled() {
return (mDisabled1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
}
- protected H createHandler() {
- return new StatusBar.H();
- }
-
@Override
public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade,
int flags) {
@@ -1879,10 +1803,77 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void startActivity(Intent intent, boolean dismissShade,
- ActivityLaunchAnimator.Controller animationController) {
- startActivityDismissingKeyguard(intent, false, dismissShade,
+ @Nullable ActivityLaunchAnimator.Controller animationController,
+ boolean showOverLockscreenWhenLocked) {
+ // Make sure that we dismiss the keyguard if it is directly dismissable or when we don't
+ // want to show the activity above it.
+ if (mKeyguardStateController.isUnlocked() || !showOverLockscreenWhenLocked) {
+ startActivityDismissingKeyguard(intent, false, dismissShade,
false /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */,
0 /* flags */, animationController);
+ return;
+ }
+
+ boolean animate =
+ animationController != null && shouldAnimateLaunch(true /* isActivityIntent */,
+ showOverLockscreenWhenLocked);
+
+ ActivityLaunchAnimator.Controller controller = null;
+ if (animate) {
+ // Wrap the animation controller to dismiss the shade and set
+ // mIsLaunchingActivityOverLockscreen during the animation.
+ ActivityLaunchAnimator.Controller delegate = wrapAnimationController(
+ animationController, dismissShade);
+ controller = new DelegateLaunchAnimatorController(delegate) {
+ @Override
+ public void onIntentStarted(boolean willAnimate) {
+ getDelegate().onIntentStarted(willAnimate);
+
+ if (willAnimate) {
+ StatusBar.this.mIsLaunchingActivityOverLockscreen = true;
+ }
+ }
+
+ @Override
+ public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) {
+ // Set mIsLaunchingActivityOverLockscreen to false before actually finishing the
+ // animation so that we can assume that mIsLaunchingActivityOverLockscreen
+ // being true means that we will collapse the shade (or at least run the
+ // post collapse runnables) later on.
+ StatusBar.this.mIsLaunchingActivityOverLockscreen = false;
+ getDelegate().onLaunchAnimationEnd(isExpandingFullyAbove);
+ }
+
+ @Override
+ public void onLaunchAnimationCancelled() {
+ // Set mIsLaunchingActivityOverLockscreen to false before actually finishing the
+ // animation so that we can assume that mIsLaunchingActivityOverLockscreen
+ // being true means that we will collapse the shade (or at least run the
+ // post collapse runnables) later on.
+ StatusBar.this.mIsLaunchingActivityOverLockscreen = false;
+ getDelegate().onLaunchAnimationCancelled();
+ }
+ };
+ } else if (dismissShade) {
+ // The animation will take care of dismissing the shade at the end of the animation. If
+ // we don't animate, collapse it directly.
+ collapseShade();
+ }
+
+ mActivityLaunchAnimator.startIntentWithAnimation(controller, animate,
+ intent.getPackage(), showOverLockscreenWhenLocked, (adapter) -> TaskStackBuilder
+ .create(mContext)
+ .addNextIntent(intent)
+ .startActivities(getActivityOptions(getDisplayId(), adapter),
+ UserHandle.CURRENT));
+ }
+
+ /**
+ * Whether we are currently animating an activity launch above the lockscreen (occluding
+ * activity).
+ */
+ public boolean isLaunchingActivityOverLockscreen() {
+ return mIsLaunchingActivityOverLockscreen;
}
@Override
@@ -1902,6 +1893,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationPanelViewController.setStatusAccessibilityImportance(expanded
? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
: View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ mNotificationPanelViewController.updateSystemUiStateFlags();
if (getNavigationBarView() != null) {
getNavigationBarView().onStatusBarPanelStateChanged();
}
@@ -1922,70 +1914,6 @@ public class StatusBar extends SystemUI implements DemoMode,
logStateToEventlog();
}
- @Override
- public void onUnlockedChanged() {
- updateKeyguardState();
- logStateToEventlog();
- }
-
- @Override
- public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
- if (inPinnedMode) {
- mNotificationShadeWindowController.setHeadsUpShowing(true);
- mStatusBarWindowController.setForceStatusBarVisible(true);
- if (mNotificationPanelViewController.isFullyCollapsed()) {
- // We need to ensure that the touchable region is updated before the window will be
- // resized, in order to not catch any touches. A layout will ensure that
- // onComputeInternalInsets will be called and after that we can resize the layout. Let's
- // make sure that the window stays small for one frame until the touchableRegion is set.
- mNotificationPanelViewController.getView().requestLayout();
- mNotificationShadeWindowController.setForceWindowCollapsed(true);
- mNotificationPanelViewController.getView().post(() -> {
- mNotificationShadeWindowController.setForceWindowCollapsed(false);
- });
- }
- } else {
- boolean bypassKeyguard = mKeyguardBypassController.getBypassEnabled()
- && mState == StatusBarState.KEYGUARD;
- if (!mNotificationPanelViewController.isFullyCollapsed()
- || mNotificationPanelViewController.isTracking() || bypassKeyguard) {
- // We are currently tracking or is open and the shade doesn't need to be kept
- // open artificially.
- mNotificationShadeWindowController.setHeadsUpShowing(false);
- if (bypassKeyguard) {
- mStatusBarWindowController.setForceStatusBarVisible(false);
- }
- } else {
- // we need to keep the panel open artificially, let's wait until the animation
- // is finished.
- mHeadsUpManager.setHeadsUpGoingAway(true);
- mNotificationPanelViewController.runAfterAnimationFinished(() -> {
- if (!mHeadsUpManager.hasPinnedHeadsUp()) {
- mNotificationShadeWindowController.setHeadsUpShowing(false);
- mHeadsUpManager.setHeadsUpGoingAway(false);
- }
- mRemoteInputManager.onPanelCollapsed();
- });
- }
- }
- }
-
- @Override
- public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
- mNotificationsController.requestNotificationUpdate("onHeadsUpStateChanged");
- if (mStatusBarStateController.isDozing() && isHeadsUp) {
- entry.setPulseSuppressed(false);
- mDozeServiceHost.fireNotificationPulse(entry);
- if (mDozeServiceHost.isPulsing()) {
- mDozeScrimController.cancelPendingPulseTimeout();
- }
- }
- if (!isHeadsUp && !mHeadsUpManager.hasNotifications()) {
- // There are no longer any notifications to show. We should end the pulse now.
- mDozeScrimController.pulseOutNow();
- }
- }
-
public void setPanelExpanded(boolean isExpanded) {
if (mPanelExpanded != isExpanded) {
mNotificationLogger.onPanelExpandedChanged(isExpanded);
@@ -2018,11 +1946,6 @@ public class StatusBar extends SystemUI implements DemoMode,
return mNotificationPanelViewController.hideStatusBarIconsWhenExpanded();
}
- @Override
- public void onColorsChanged(ColorExtractor extractor, int which) {
- updateTheme();
- }
-
@Nullable
public View getAmbientIndicationContainer() {
return mAmbientIndicationContainer;
@@ -2058,7 +1981,7 @@ public class StatusBar extends SystemUI implements DemoMode,
*
* @param animate should the change of the icons be animated.
*/
- private void updateHideIconsForBouncer(boolean animate) {
+ void updateHideIconsForBouncer(boolean animate) {
boolean hideBecauseApp = mTopHidesStatusBar && mIsOccluded
&& (mStatusBarWindowHidden || mBouncerShowing);
boolean hideBecauseKeyguard = !mPanelExpanded && !mIsOccluded && mBouncerShowing;
@@ -2069,7 +1992,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// We're delaying the showing, since most of the time the fullscreen app will
// hide the icons again and we don't want them to fade in and out immediately again.
mWereIconsJustHidden = true;
- mHandler.postDelayed(() -> {
+ mMainExecutor.executeDelayed(() -> {
mWereIconsJustHidden = false;
mCommandQueue.recomputeDisableFlags(mDisplayId, true);
}, 500);
@@ -2112,51 +2035,28 @@ public class StatusBar extends SystemUI implements DemoMode,
*
* Note: This method must be called *before* dismissing the keyguard.
*/
- public boolean shouldAnimateLaunch(boolean isActivityIntent) {
+ public boolean shouldAnimateLaunch(boolean isActivityIntent, boolean showOverLockscreen) {
// TODO(b/184121838): Support launch animations when occluded.
if (isOccluded()) {
return false;
}
- // Always animate if we are unlocked.
- if (!mKeyguardStateController.isShowing()) {
+ // Always animate if we are not showing the keyguard or if we animate over the lockscreen
+ // (without unlocking it).
+ if (showOverLockscreen || !mKeyguardStateController.isShowing()) {
return true;
}
- // If we are locked, only animate if remote unlock animations are enabled. We also don't
- // animate non-activity launches as they can break the animation.
+ // If we are locked and have to dismiss the keyguard, only animate if remote unlock
+ // animations are enabled. We also don't animate non-activity launches as they can break the
+ // animation.
// TODO(b/184121838): Support non activity launches on the lockscreen.
return isActivityIntent && KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation;
}
- @Override
- public boolean isOnKeyguard() {
- return mKeyguardStateController.isShowing();
- }
-
- @Override
- public void hideKeyguardWithAnimation(IRemoteAnimationRunner runner) {
- // We post to the main thread for 2 reasons:
- // 1. KeyguardViewMediator is not thread-safe.
- // 2. To ensure that ViewMediatorCallback#keyguardDonePending is called before
- // ViewMediatorCallback#readyForKeyguardDone. The wrong order could occur when doing
- // dismissKeyguardThenExecute { hideKeyguardWithAnimation(runner) }.
- mMainThreadHandler.post(() -> mKeyguardViewMediator.hideWithAnimation(runner));
- }
-
- @Override
- public void setBlursDisabledForAppLaunch(boolean disabled) {
- mKeyguardViewMediator.setBlursDisabledForAppLaunch(disabled);
- }
-
- @Override
- public int getBackgroundColor(TaskInfo task) {
- if (!mStartingSurfaceOptional.isPresent()) {
- Log.w(TAG, "No starting surface, defaulting to SystemBGColor");
- return SplashscreenContentDrawer.getSystemBGColor();
- }
-
- return mStartingSurfaceOptional.get().getBackgroundColor(task);
+ /** Whether we should animate an activity launch. */
+ public boolean shouldAnimateLaunch(boolean isActivityIntent) {
+ return shouldAnimateLaunch(isActivityIntent, false /* showOverLockscreen */);
}
public boolean isDeviceInVrMode() {
@@ -2172,38 +2072,19 @@ public class StatusBar extends SystemUI implements DemoMode,
mState = state;
}
- @VisibleForTesting
- void setUserSetupForTest(boolean userSetup) {
- mUserSetup = userSetup;
+ static class KeyboardShortcutsMessage {
+ final int mDeviceId;
+
+ KeyboardShortcutsMessage(int deviceId) {
+ mDeviceId = deviceId;
+ }
}
- /**
- * All changes to the status bar and notifications funnel through here and are batched.
- */
- protected class H extends Handler {
- @Override
- public void handleMessage(Message m) {
- switch (m.what) {
- case MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU:
- toggleKeyboardShortcuts(m.arg1);
- break;
- case MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU:
- dismissKeyboardShortcuts();
- break;
- // End old BaseStatusBar.H handling.
- case MSG_OPEN_NOTIFICATION_PANEL:
- animateExpandNotificationsPanel();
- break;
- case MSG_OPEN_SETTINGS_PANEL:
- animateExpandSettingsPanel((String) m.obj);
- break;
- case MSG_CLOSE_PANELS:
- mShadeController.animateCollapsePanels();
- break;
- case MSG_LAUNCH_TRANSITION_TIMEOUT:
- onLaunchTransitionTimeout();
- break;
- }
+ static class AnimateExpandSettingsPanelMessage {
+ final String mSubpanel;
+
+ AnimateExpandSettingsPanelMessage(String subpanel) {
+ mSubpanel = subpanel;
}
}
@@ -2227,59 +2108,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mHeadsUpManager.releaseAllImmediately();
}
- /**
- * Called for system navigation gestures. First action opens the panel, second opens
- * settings. Down action closes the entire panel.
- */
- @Override
- public void handleSystemKey(int key) {
- if (SPEW) Log.d(TAG, "handleNavigationKey: " + key);
- if (!mCommandQueue.panelsEnabled() || !mKeyguardUpdateMonitor.isDeviceInteractive()
- || mKeyguardStateController.isShowing() && !mKeyguardStateController.isOccluded()) {
- return;
- }
-
- // Panels are not available in setup
- if (!mUserSetup) return;
-
- if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP == key) {
- mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_UP);
- mNotificationPanelViewController.collapse(
- false /* delayed */, 1.0f /* speedUpFactor */);
- } else if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN == key) {
- mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN);
- if (mNotificationPanelViewController.isFullyCollapsed()) {
- if (mVibrateOnOpening) {
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
- }
- mNotificationPanelViewController.expand(true /* animate */);
- mStackScroller.setWillExpand(true);
- mHeadsUpManager.unpinAll(true /* userUnpinned */);
- mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN, 1);
- } else if (!mNotificationPanelViewController.isInSettings()
- && !mNotificationPanelViewController.isExpanding()) {
- mNotificationPanelViewController.flingSettings(0 /* velocity */,
- NotificationPanelView.FLING_EXPAND);
- mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN_QS, 1);
- }
- }
-
- }
-
- @Override
- public void showPinningEnterExitToast(boolean entering) {
- if (getNavigationBarView() != null) {
- getNavigationBarView().showPinningEnterExitToast(entering);
- }
- }
-
- @Override
- public void showPinningEscapeToast() {
- if (getNavigationBarView() != null) {
- getNavigationBarView().showPinningEscapeToast();
- }
- }
-
void makeExpandedVisible(boolean force) {
if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
@@ -2298,42 +2126,17 @@ public class StatusBar extends SystemUI implements DemoMode,
}
public void postAnimateCollapsePanels() {
- mHandler.post(mShadeController::animateCollapsePanels);
+ mMainExecutor.execute(mShadeController::animateCollapsePanels);
}
public void postAnimateForceCollapsePanels() {
- mHandler.post(() -> mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE,
+ mMainExecutor.execute(
+ () -> mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE,
true /* force */));
}
public void postAnimateOpenPanels() {
- mHandler.sendEmptyMessage(MSG_OPEN_SETTINGS_PANEL);
- }
-
- @Override
- public void togglePanel() {
- if (mPanelExpanded) {
- mShadeController.animateCollapsePanels();
- } else {
- animateExpandNotificationsPanel();
- }
- }
-
- @Override
- public void animateCollapsePanels(int flags, boolean force) {
- mShadeController.animateCollapsePanels(flags, force, false /* delayed */,
- 1.0f /* speedUpFactor */);
- }
-
- /**
- * Called by {@link ShadeController} when it calls
- * {@link ShadeController#animateCollapsePanels(int, boolean, boolean, float)}.
- */
- void postHideRecentApps() {
- if (!mHandler.hasMessages(MSG_HIDE_RECENT_APPS)) {
- mHandler.removeMessages(MSG_HIDE_RECENT_APPS);
- mHandler.sendEmptyMessage(MSG_HIDE_RECENT_APPS);
- }
+ mMessageRouter.sendMessage(MSG_OPEN_SETTINGS_PANEL);
}
public boolean isExpandedVisible() {
@@ -2359,39 +2162,10 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- @Override
- public void animateExpandNotificationsPanel() {
- if (SPEW) Log.d(TAG, "animateExpand: mExpandedVisible=" + mExpandedVisible);
- if (!mCommandQueue.panelsEnabled()) {
- return ;
- }
-
- mNotificationPanelViewController.expandWithoutQs();
-
- if (false) postStartTracing();
- }
-
- @Override
- public void animateExpandSettingsPanel(@Nullable String subPanel) {
- if (SPEW) Log.d(TAG, "animateExpand: mExpandedVisible=" + mExpandedVisible);
- if (!mCommandQueue.panelsEnabled()) {
- return;
- }
-
- // Settings are not available in setup
- if (!mUserSetup) return;
-
- if (subPanel != null) {
- mQSPanelController.openDetails(subPanel);
- }
- mNotificationPanelViewController.expandWithQs();
-
- if (false) postStartTracing();
- }
-
public void animateCollapseQuickSettings() {
if (mState == StatusBarState.SHADE) {
- mStatusBarView.collapsePanel(true, false /* delayed */, 1.0f /* speedUpFactor */);
+ mNotificationPanelViewController.collapsePanel(
+ true, false /* delayed */, 1.0f /* speedUpFactor */);
}
}
@@ -2404,7 +2178,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
// Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
- mStatusBarView.collapsePanel(/*animate=*/ false, false /* delayed*/,
+ mNotificationPanelViewController.collapsePanel(/*animate=*/ false, false /* delayed*/,
1.0f /* speedUpFactor */);
mNotificationPanelViewController.closeQs();
@@ -2438,7 +2212,10 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- public boolean interceptTouchEvent(MotionEvent event) {
+ /** Called when a touch event occurred on {@link PhoneStatusBarView}. */
+ public void onTouchEvent(MotionEvent event) {
+ // TODO(b/202981994): Move this touch debugging to a central location. (Right now, it's
+ // split between NotificationPanelViewController and here.)
if (DEBUG_GESTURES) {
if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH,
@@ -2468,13 +2245,8 @@ public class StatusBar extends SystemUI implements DemoMode,
final boolean upOrCancel =
event.getAction() == MotionEvent.ACTION_UP ||
event.getAction() == MotionEvent.ACTION_CANCEL;
- if (upOrCancel && !mExpandedVisible) {
- setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
- } else {
- setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
- }
+ setInteracting(StatusBarManager.WINDOW_STATUS_BAR, !upOrCancel || mExpandedVisible);
}
- return false;
}
boolean isSameStatusBarState(int state) {
@@ -2489,88 +2261,23 @@ public class StatusBar extends SystemUI implements DemoMode,
return mBiometricUnlockController;
}
- @Override // CommandQueue
- public void setWindowState(
- int displayId, @WindowType int window, @WindowVisibleState int state) {
- if (displayId != mDisplayId) {
- return;
- }
- boolean showing = state == WINDOW_STATE_SHOWING;
- if (mNotificationShadeWindowView != null
- && window == StatusBarManager.WINDOW_STATUS_BAR
- && mStatusBarWindowState != state) {
- mStatusBarWindowState = state;
- if (DEBUG_WINDOW_STATE) Log.d(TAG, "Status bar " + windowStateToString(state));
- if (mStatusBarView != null) {
- if (!showing && mState == StatusBarState.SHADE) {
- mStatusBarView.collapsePanel(false /* animate */, false /* delayed */,
- 1.0f /* speedUpFactor */);
- }
- mStatusBarWindowHidden = state == WINDOW_STATE_HIDDEN;
- updateHideIconsForBouncer(false /* animate */);
- }
- }
-
- updateBubblesVisibility();
- }
-
- @Override
- public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
- AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, boolean isFullscreen) {
- if (displayId != mDisplayId) {
- return;
- }
- boolean barModeChanged = false;
- if (mAppearance != appearance) {
- mAppearance = appearance;
- barModeChanged = updateBarMode(barMode(mTransientShown, appearance));
- }
- mLightBarController.onStatusBarAppearanceChanged(appearanceRegions, barModeChanged,
- mStatusBarMode, navbarColorManagedByIme);
-
- updateBubblesVisibility();
- mStatusBarStateController.setFullscreenState(isFullscreen);
- }
-
- @Override
- public void showTransient(int displayId, @InternalInsetsType int[] types) {
- if (displayId != mDisplayId) {
- return;
- }
- if (!containsType(types, ITYPE_STATUS_BAR)) {
- return;
- }
- showTransientUnchecked();
- }
-
- private void showTransientUnchecked() {
+ void showTransientUnchecked() {
if (!mTransientShown) {
mTransientShown = true;
mNoAnimationOnNextBarModeChange = true;
- handleTransientChanged();
+ maybeUpdateBarMode();
}
}
- @Override
- public void abortTransient(int displayId, @InternalInsetsType int[] types) {
- if (displayId != mDisplayId) {
- return;
- }
- if (!containsType(types, ITYPE_STATUS_BAR)) {
- return;
- }
- clearTransient();
- }
- private void clearTransient() {
+ void clearTransient() {
if (mTransientShown) {
mTransientShown = false;
- handleTransientChanged();
+ maybeUpdateBarMode();
}
}
- private void handleTransientChanged() {
+ private void maybeUpdateBarMode() {
final int barMode = barMode(mTransientShown, mAppearance);
if (updateBarMode(barMode)) {
mLightBarController.onStatusBarModeChanged(barMode);
@@ -2588,9 +2295,11 @@ public class StatusBar extends SystemUI implements DemoMode,
return false;
}
- private static @TransitionMode int barMode(boolean isTransient, int appearance) {
+ private @TransitionMode int barMode(boolean isTransient, int appearance) {
final int lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS | APPEARANCE_OPAQUE_STATUS_BARS;
- if (isTransient) {
+ if (mOngoingCallController.hasOngoingCall() && mIsFullscreen) {
+ return MODE_SEMI_TRANSPARENT;
+ } else if (isTransient) {
return MODE_SEMI_TRANSPARENT;
} else if ((appearance & lightsOutOpaque) == lightsOutOpaque) {
return MODE_LIGHTS_OUT;
@@ -2605,8 +2314,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- @Override
- public void showWirelessChargingAnimation(int batteryLevel) {
+ protected void showWirelessChargingAnimation(int batteryLevel) {
showChargingAnimation(batteryLevel, UNKNOWN_BATTERY_LEVEL, 0);
}
@@ -2627,11 +2335,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}, false, sUiEventLogger).show(animationDelay);
}
- @Override
- public void onRecentsAnimationStateChanged(boolean running) {
- setInteracting(StatusBarManager.WINDOW_NAVIGATION_BAR, running);
- }
-
protected BarTransitions getStatusBarTransitions() {
return mNotificationShadeWindowViewController.getBarTransitions();
}
@@ -2651,13 +2354,11 @@ public class StatusBar extends SystemUI implements DemoMode,
}
/** Temporarily hides Bubbles if the status bar is hidden. */
- private void updateBubblesVisibility() {
- if (mBubblesOptional.isPresent()) {
- mBubblesOptional.get().onStatusBarVisibilityChanged(
- mStatusBarMode != MODE_LIGHTS_OUT
- && mStatusBarMode != MODE_LIGHTS_OUT_TRANSPARENT
- && !mStatusBarWindowHidden);
- }
+ void updateBubblesVisibility() {
+ mBubblesOptional.ifPresent(bubbles -> bubbles.onStatusBarVisibilityChanged(
+ mStatusBarMode != MODE_LIGHTS_OUT
+ && mStatusBarMode != MODE_LIGHTS_OUT_TRANSPARENT
+ && !mStatusBarWindowHidden));
}
void checkBarMode(@TransitionMode int mode, @WindowVisibleState int windowState,
@@ -2741,9 +2442,15 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationPanelViewController.dump(fd, pw, args);
}
pw.println(" mStackScroller: ");
- if (mStackScroller instanceof Dumpable) {
- pw.print (" ");
- ((Dumpable) mStackScroller).dump(fd, pw, args);
+ if (mStackScroller != null) {
+ DumpUtilsKt.withIndenting(pw, ipw -> {
+ // Triple indent until we rewrite the rest of this dump()
+ ipw.increaseIndent();
+ ipw.increaseIndent();
+ mStackScroller.dump(fd, ipw, args);
+ ipw.decreaseIndent();
+ ipw.decreaseIndent();
+ });
}
pw.println(" Theme:");
String nightMode = mUiModeManager == null ? "null" : mUiModeManager.getNightMode() + "";
@@ -2774,19 +2481,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationsController.dump(fd, pw, args, DUMPTRUCK);
- if (DUMPTRUCK) {
- if (false) {
- pw.println("see the logcat for a dump of the views we have created.");
- // must happen on ui thread
- mHandler.post(() -> {
- mStatusBarView.getLocationOnScreen(mAbsPos);
- Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] +
- ") " + mStatusBarView.getWidth() + "x" + getStatusBarHeight());
- mStatusBarView.debug();
- });
- }
- }
-
if (DEBUG_GESTURES) {
pw.print(" status bar gestures: ");
mGestureRec.dump(fd, pw, args);
@@ -2817,7 +2511,7 @@ public class StatusBar extends SystemUI implements DemoMode,
pw.println(" Insecure camera: " + CameraIntents.getInsecureCameraIntent(mContext));
pw.println(" Secure camera: " + CameraIntents.getSecureCameraIntent(mContext));
pw.println(" Override package: "
- + String.valueOf(CameraIntents.getOverrideCameraPackage(mContext)));
+ + CameraIntents.getOverrideCameraPackage(mContext));
}
public static void dumpBarTransitions(
@@ -2878,7 +2572,7 @@ public class StatusBar extends SystemUI implements DemoMode,
startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, 0);
}
- private void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
+ void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
final boolean dismissShade, final boolean disallowEnterPictureInPictureWhileLaunching,
final Callback callback, int flags,
@Nullable ActivityLaunchAnimator.Controller animationController) {
@@ -2892,7 +2586,8 @@ public class StatusBar extends SystemUI implements DemoMode,
animationController != null && !willLaunchResolverActivity && shouldAnimateLaunch(
true /* isActivityIntent */);
ActivityLaunchAnimator.Controller animController =
- animate ? wrapAnimationController(animationController, dismissShade) : null;
+ animationController != null ? wrapAnimationController(animationController,
+ dismissShade) : null;
// If we animate, we will dismiss the shade only once the animation is done. This is taken
// care of by the StatusBarLaunchAnimationController.
@@ -2923,7 +2618,7 @@ public class StatusBar extends SystemUI implements DemoMode,
options.setRotationAnimationHint(
WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
}
- if (intent.getAction() == Settings.Panel.ACTION_VOLUME) {
+ if (Settings.Panel.ACTION_VOLUME.equals(intent.getAction())) {
// Settings Panel is implemented as activity(not a dialog), so
// underlying app is paused and may enter picture-in-picture mode
// as a result.
@@ -2963,7 +2658,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private ActivityLaunchAnimator.Controller wrapAnimationController(
ActivityLaunchAnimator.Controller animationController, boolean dismissShade) {
View rootView = animationController.getLaunchContainer().getRootView();
- if (rootView == mSuperStatusBarViewFactory.getStatusBarWindowView()) {
+ if (rootView == mStatusBarWindowView) {
// We are animating a view in the status bar. We have to make sure that the status bar
// window matches the full screen during the animation and that we are expanding the
// view below the other status bar text.
@@ -3022,7 +2717,7 @@ public class StatusBar extends SystemUI implements DemoMode,
&& mStatusBarKeyguardViewManager.isOccluded()) {
mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
} else {
- AsyncTask.execute(runnable);
+ mMainExecutor.execute(runnable);
}
}
if (dismissShade) {
@@ -3034,7 +2729,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// Do it after DismissAction has been processed to conserve the needed
// ordering.
- mHandler.post(mShadeController::runPostCollapseRunnables);
+ mMainExecutor.execute(mShadeController::runPostCollapseRunnables);
}
} else if (StatusBar.this.isInLaunchTransition()
&& mNotificationPanelViewController.isLaunchTransitionFinished()) {
@@ -3043,7 +2738,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// finished,
// so nobody will call readyForKeyguardDone anymore. Post it such that
// keyguardDonePending gets called first.
- mHandler.post(mStatusBarKeyguardViewManager::readyForKeyguardDone);
+ mMainExecutor.execute(mStatusBarKeyguardViewManager::readyForKeyguardDone);
}
return deferred;
}
@@ -3064,9 +2759,7 @@ public class StatusBar extends SystemUI implements DemoMode,
String action = intent.getAction();
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
KeyboardShortcuts.dismiss();
- if (mRemoteInputManager.getController() != null) {
- mRemoteInputManager.getController().closeRemoteInputs();
- }
+ mRemoteInputManager.closeRemoteInputs();
if (mBubblesOptional.isPresent() && mBubblesOptional.get().isStackExpanded()) {
mBubblesOptional.get().collapseStack();
}
@@ -3084,8 +2777,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationShadeWindowController.setNotTouchable(false);
}
if (mBubblesOptional.isPresent() && mBubblesOptional.get().isStackExpanded()) {
- // Post to main thread handler, since updating the UI.
- mMainThreadHandler.post(() -> mBubblesOptional.get().collapseStack());
+ // Post to main thread, since updating the UI.
+ mMainExecutor.execute(() -> mBubblesOptional.get().collapseStack());
}
finishBarAnimations();
resetUserExpandedStates();
@@ -3146,20 +2839,6 @@ public class StatusBar extends SystemUI implements DemoMode,
action.onDismiss();
}
}
-
- @Override
- public void onConfigChanged(Configuration newConfig) {
- updateResources();
- updateDisplaySize(); // populates mDisplayMetrics
-
- if (DEBUG) {
- Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
- }
-
- mViewHierarchyManager.updateRowStates();
- mScreenPinningRequest.onConfigurationChanged();
- }
-
/**
* Notify the shade controller that the current user changed
*
@@ -3307,43 +2986,12 @@ public class StatusBar extends SystemUI implements DemoMode,
| ((currentlyInsecure ? 1 : 0) << 12);
}
- //
- // tracing
- //
-
- void postStartTracing() {
- mHandler.postDelayed(mStartTracing, 3000);
- }
-
- void vibrate() {
- android.os.Vibrator vib = (android.os.Vibrator)mContext.getSystemService(
- Context.VIBRATOR_SERVICE);
- vib.vibrate(250, VIBRATION_ATTRIBUTES);
- }
-
- final Runnable mStartTracing = new Runnable() {
- @Override
- public void run() {
- vibrate();
- SystemClock.sleep(250);
- Log.d(TAG, "startTracing");
- android.os.Debug.startMethodTracing("/data/statusbar-traces/trace");
- mHandler.postDelayed(mStopTracing, 10000);
- }
- };
-
- final Runnable mStopTracing = () -> {
- android.os.Debug.stopMethodTracing();
- Log.d(TAG, "stopTracing");
- vibrate();
- };
-
@Override
public void postQSRunnableDismissingKeyguard(final Runnable runnable) {
- mHandler.post(() -> {
+ mMainExecutor.execute(() -> {
mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
- executeRunnableDismissingKeyguard(() -> mHandler.post(runnable), null, false, false,
- false);
+ executeRunnableDismissingKeyguard(
+ () -> mMainExecutor.execute(runnable), null, false, false, false);
});
}
@@ -3355,7 +3003,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void postStartActivityDismissingKeyguard(final PendingIntent intent,
@Nullable ActivityLaunchAnimator.Controller animationController) {
- mHandler.post(() -> startPendingIntentDismissingKeyguard(intent,
+ mMainExecutor.execute(() -> startPendingIntentDismissingKeyguard(intent,
null /* intentSentUiThreadCallback */, animationController));
}
@@ -3367,7 +3015,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void postStartActivityDismissingKeyguard(Intent intent, int delay,
@Nullable ActivityLaunchAnimator.Controller animationController) {
- mHandler.postDelayed(
+ mMainExecutor.executeDelayed(
() ->
startActivityDismissingKeyguard(intent, true /* onlyProvisioned */,
true /* dismissShade */,
@@ -3378,82 +3026,6 @@ public class StatusBar extends SystemUI implements DemoMode,
delay);
}
- @Override
- public List<String> demoCommands() {
- List<String> s = new ArrayList<>();
- s.add(DemoMode.COMMAND_BARS);
- s.add(DemoMode.COMMAND_CLOCK);
- s.add(DemoMode.COMMAND_OPERATOR);
- return s;
- }
-
- @Override
- public void onDemoModeStarted() {
- // Must send this message to any view that we delegate to via dispatchDemoCommandToView
- dispatchDemoModeStartedToView(R.id.clock);
- dispatchDemoModeStartedToView(R.id.operator_name);
- }
-
- @Override
- public void onDemoModeFinished() {
- dispatchDemoModeFinishedToView(R.id.clock);
- dispatchDemoModeFinishedToView(R.id.operator_name);
- checkBarModes();
- }
-
- @Override
- public void dispatchDemoCommand(String command, @NonNull Bundle args) {
- if (command.equals(COMMAND_CLOCK)) {
- dispatchDemoCommandToView(command, args, R.id.clock);
- }
- if (command.equals(COMMAND_BARS)) {
- String mode = args.getString("mode");
- int barMode = "opaque".equals(mode) ? MODE_OPAQUE :
- "translucent".equals(mode) ? MODE_TRANSLUCENT :
- "semi-transparent".equals(mode) ? MODE_SEMI_TRANSPARENT :
- "transparent".equals(mode) ? MODE_TRANSPARENT :
- "warning".equals(mode) ? MODE_WARNING :
- -1;
- if (barMode != -1) {
- boolean animate = true;
- if (mNotificationShadeWindowController != null
- && mNotificationShadeWindowViewController.getBarTransitions() != null) {
- mNotificationShadeWindowViewController.getBarTransitions().transitionTo(
- barMode, animate);
- }
- mNavigationBarController.transitionTo(mDisplayId, barMode, animate);
- }
- }
- if (command.equals(COMMAND_OPERATOR)) {
- dispatchDemoCommandToView(command, args, R.id.operator_name);
- }
- }
-
- //TODO: these should have controllers, and this method should be removed
- private void dispatchDemoCommandToView(String command, Bundle args, int id) {
- if (mStatusBarView == null) return;
- View v = mStatusBarView.findViewById(id);
- if (v instanceof DemoModeCommandReceiver) {
- ((DemoModeCommandReceiver) v).dispatchDemoCommand(command, args);
- }
- }
-
- private void dispatchDemoModeStartedToView(int id) {
- if (mStatusBarView == null) return;
- View v = mStatusBarView.findViewById(id);
- if (v instanceof DemoModeCommandReceiver) {
- ((DemoModeCommandReceiver) v).onDemoModeStarted();
- }
- }
-
- private void dispatchDemoModeFinishedToView(int id) {
- if (mStatusBarView == null) return;
- View v = mStatusBarView.findViewById(id);
- if (v instanceof DemoModeCommandReceiver) {
- ((DemoModeCommandReceiver) v).onDemoModeFinished();
- }
- }
-
public void showKeyguard() {
mStatusBarStateController.setKeyguardRequested(true);
mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
@@ -3513,7 +3085,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationPanelViewController.cancelAnimation();
onLaunchTransitionFadingEnded();
}
- mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
+ mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
if (mUserSwitcherController != null && mUserSwitcherController.useFullscreenUserSwitcher()) {
mStatusBarStateController.setState(StatusBarState.FULLSCREEN_USER_SWITCHER);
} else if (!mPulseExpansionHandler.isWakingToShadeLocked()) {
@@ -3554,7 +3126,7 @@ public class StatusBar extends SystemUI implements DemoMode,
*/
public void fadeKeyguardAfterLaunchTransition(final Runnable beforeFading,
Runnable endRunnable) {
- mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
+ mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
mLaunchTransitionEndRunnable = endRunnable;
Runnable hideRunnable = () -> {
mKeyguardStateController.setLaunchTransitionFadingAway(true);
@@ -3591,7 +3163,8 @@ public class StatusBar extends SystemUI implements DemoMode,
};
if (mLightRevealScrim.getRevealAmount() != 1.0f) {
mCallingFadingAwayAfterReveal = true;
- // we're still revealing the Light reveal, let's only go to keyguard once
+ // We're still revealing the Light reveal, let's only go to keyguard once
+ // that has finished and nothing moves anymore.
// Going there introduces lots of jank
mLightRevealScrim.setFullyRevealedRunnable(finishFading);
} else {
@@ -3605,7 +3178,7 @@ public class StatusBar extends SystemUI implements DemoMode,
*/
public void animateKeyguardUnoccluding() {
mNotificationPanelViewController.setExpandedFraction(0f);
- animateExpandNotificationsPanel();
+ mCommandQueueCallbacks.animateExpandNotificationsPanel();
mScrimController.setUnocclusionAnimationRunning(true);
}
@@ -3615,8 +3188,8 @@ public class StatusBar extends SystemUI implements DemoMode,
* because the launched app crashed or something else went wrong.
*/
public void startLaunchTransitionTimeout() {
- mHandler.sendEmptyMessageDelayed(MSG_LAUNCH_TRANSITION_TIMEOUT,
- LAUNCH_TRANSITION_TIMEOUT_MS);
+ mMessageRouter.sendMessageDelayed(
+ MSG_LAUNCH_TRANSITION_TIMEOUT, LAUNCH_TRANSITION_TIMEOUT_MS);
}
private void onLaunchTransitionTimeout() {
@@ -3671,7 +3244,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mQSPanelController != null) {
mQSPanelController.refreshAllTiles();
}
- mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
+ mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
releaseGestureWakeLock();
mNotificationPanelViewController.onAffordanceLaunchEnded();
mNotificationPanelViewController.cancelAnimation();
@@ -3730,16 +3303,16 @@ public class StatusBar extends SystemUI implements DemoMode,
* Switches theme from light to dark and vice-versa.
*/
protected void updateTheme() {
-
// Lock wallpaper defines the color of the majority of the views, hence we'll use it
// to set our default theme.
final boolean lockDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
final int themeResId = lockDarkText ? R.style.Theme_SystemUI_LightWallpaper
: R.style.Theme_SystemUI;
- if (mContext.getThemeResId() != themeResId) {
- mContext.setTheme(themeResId);
- mConfigurationController.notifyThemeChanged();
+ if (mContext.getThemeResId() == themeResId) {
+ return;
}
+ mContext.setTheme(themeResId);
+ mConfigurationController.notifyThemeChanged();
}
private void updateDozingState() {
@@ -3777,7 +3350,7 @@ public class StatusBar extends SystemUI implements DemoMode,
/**
* While IME is active and a BACK event is detected, check with
- * {@link StatusBarKeyguardViewManager#dispatchBackKeyEventPreIme(KeyEvent)} to see if the event
+ * {@link StatusBarKeyguardViewManager#dispatchBackKeyEventPreIme()} to see if the event
* should be handled before routing to IME, in order to prevent the user having to hit back
* twice to exit bouncer.
*/
@@ -3899,71 +3472,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationPanelViewController.collapseWithDuration(duration);
}
- @Override
- public void onStatePreChange(int oldState, int newState) {
- // If we're visible and switched to SHADE_LOCKED (the user dragged
- // down on the lockscreen), clear notification LED, vibration,
- // ringing.
- // Other transitions are covered in handleVisibleToUserChanged().
- if (mVisible && (newState == StatusBarState.SHADE_LOCKED
- || mStatusBarStateController.goingToFullShade())) {
- clearNotificationEffects();
- }
- if (newState == StatusBarState.KEYGUARD) {
- mRemoteInputManager.onPanelCollapsed();
- maybeEscalateHeadsUp();
- }
- }
-
- @Override
- public void onStateChanged(int newState) {
- mState = newState;
- updateReportRejectedTouchVisibility();
- mDozeServiceHost.updateDozing();
- updateTheme();
- mNavigationBarController.touchAutoDim(mDisplayId);
- Trace.beginSection("StatusBar#updateKeyguardState");
- if (mState == StatusBarState.KEYGUARD && mStatusBarView != null) {
- mStatusBarView.removePendingHideExpandedRunnables();
- }
- updateDozingState();
- checkBarModes();
- updateScrimController();
- mPresenter.updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
- updateKeyguardState();
- Trace.endSection();
- }
-
- @Override
- public void onDozeAmountChanged(float linear, float eased) {
- if (mFeatureFlags.useNewLockscreenAnimations()
- && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)
- && !mBiometricUnlockController.isWakeAndUnlock()) {
- mLightRevealScrim.setRevealAmount(1f - linear);
- }
- }
-
- @Override
- public void onDozingChanged(boolean isDozing) {
- Trace.beginSection("StatusBar#updateDozing");
- mDozing = isDozing;
-
- // Collapse the notification panel if open
- boolean dozingAnimated = mDozeServiceHost.getDozingRequested()
- && mDozeParameters.shouldControlScreenOff();
- mNotificationPanelViewController.resetViews(dozingAnimated);
-
- updateQsExpansionEnabled();
- mKeyguardViewMediator.setDozing(mDozing);
-
- mNotificationsController.requestNotificationUpdate("onDozingChanged");
- updateDozingState();
- mDozeServiceHost.updateDozing();
- updateScrimController();
- updateReportRejectedTouchVisibility();
- Trace.endSection();
- }
-
/**
* Updates the light reveal effect to reflect the reason we're waking or sleeping (for example,
* from the power button).
@@ -4064,7 +3572,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mBouncerShowing = bouncerShowing;
mKeyguardBypassController.setBouncerShowing(bouncerShowing);
mPulseExpansionHandler.setBouncerShowing(bouncerShowing);
- if (mStatusBarView != null) mStatusBarView.setBouncerShowing(bouncerShowing);
+ setBouncerShowingForStatusBarComponents(bouncerShowing);
updateHideIconsForBouncer(true /* animate */);
mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
updateScrimController();
@@ -4074,6 +3582,23 @@ public class StatusBar extends SystemUI implements DemoMode,
}
/**
+ * Propagate the bouncer state to status bar components.
+ *
+ * Separate from {@link #setBouncerShowing} because we sometimes re-create the status bar and
+ * should update only the status bar components.
+ */
+ private void setBouncerShowingForStatusBarComponents(boolean bouncerShowing) {
+ int importance = bouncerShowing
+ ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+ if (mPhoneStatusBarViewController != null) {
+ mPhoneStatusBarViewController.setImportantForAccessibility(importance);
+ }
+ mNotificationPanelViewController.setImportantForAccessibility(importance);
+ mNotificationPanelViewController.setBouncerShowing(bouncerShowing);
+ }
+
+ /**
* Collapses the notification shade if it is tracking or expanded.
*/
public void collapseShade() {
@@ -4104,7 +3629,8 @@ public class StatusBar extends SystemUI implements DemoMode,
// This gets executed before we will show Keyguard, so post it in order that the state
// is correct.
- mHandler.post(() -> onCameraLaunchGestureDetected(mLastCameraLaunchSource));
+ mMainExecutor.execute(() -> mCommandQueueCallbacks.onCameraLaunchGestureDetected(
+ mLastCameraLaunchSource));
}
if (mLaunchEmergencyActionOnFinishedGoingToSleep) {
@@ -4112,7 +3638,8 @@ public class StatusBar extends SystemUI implements DemoMode,
// This gets executed before we will show Keyguard, so post it in order that the
// state is correct.
- mHandler.post(() -> onEmergencyActionLaunchGestureDetected());
+ mMainExecutor.execute(
+ () -> mCommandQueueCallbacks.onEmergencyActionLaunchGestureDetected());
}
updateIsKeyguard();
}
@@ -4225,36 +3752,6 @@ public class StatusBar extends SystemUI implements DemoMode,
return mWakefulnessLifecycle.getWakefulness();
}
- private void vibrateForCameraGesture() {
- // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
- mVibrator.vibrate(mCameraLaunchGestureVibrationEffect, VIBRATION_ATTRIBUTES);
- }
-
- private static VibrationEffect getCameraGestureVibrationEffect(Vibrator vibrator,
- Resources resources) {
- if (vibrator.areAllPrimitivesSupported(
- VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
- VibrationEffect.Composition.PRIMITIVE_CLICK)) {
- return VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 50)
- .compose();
- }
- if (vibrator.hasAmplitudeControl()) {
- return VibrationEffect.createWaveform(
- CAMERA_LAUNCH_GESTURE_VIBRATION_TIMINGS,
- CAMERA_LAUNCH_GESTURE_VIBRATION_AMPLITUDES,
- /* repeat= */ -1);
- }
-
- int[] pattern = resources.getIntArray(R.array.config_cameraLaunchGestureVibePattern);
- long[] timings = new long[pattern.length];
- for (int i = 0; i < pattern.length; i++) {
- timings[i] = pattern[i];
- }
- return VibrationEffect.createWaveform(timings, /* repeat= */ -1);
- }
-
/**
* @return true if the screen is currently fully off, i.e. has finished turning off and has
* since not started turning on.
@@ -4263,139 +3760,11 @@ public class StatusBar extends SystemUI implements DemoMode,
return mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_OFF;
}
- @Override
- public void showScreenPinningRequest(int taskId) {
- if (mKeyguardStateController.isShowing()) {
- // Don't allow apps to trigger this from keyguard.
- return;
- }
- // Show screen pinning request, since this comes from an app, show 'no thanks', button.
- showScreenPinningRequest(taskId, true);
- }
-
public void showScreenPinningRequest(int taskId, boolean allowCancel) {
mScreenPinningRequest.showPrompt(taskId, allowCancel);
}
- @Override
- public void appTransitionCancelled(int displayId) {
- if (displayId == mDisplayId) {
- mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.onAppTransitionFinished());
- }
- }
-
- @Override
- public void appTransitionFinished(int displayId) {
- if (displayId == mDisplayId) {
- mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.onAppTransitionFinished());
- }
- }
-
- @Override
- public void onCameraLaunchGestureDetected(int source) {
- mLastCameraLaunchSource = source;
- if (isGoingToSleep()) {
- if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Finish going to sleep before launching camera");
- mLaunchCameraOnFinishedGoingToSleep = true;
- return;
- }
- if (!mNotificationPanelViewController.canCameraGestureBeLaunched()) {
- if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Can't launch camera right now");
- return;
- }
- if (!mDeviceInteractive) {
- mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_CAMERA_LAUNCH,
- "com.android.systemui:CAMERA_GESTURE");
- }
- vibrateForCameraGesture();
-
- if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) {
- Log.v(TAG, "Camera launch");
- mKeyguardUpdateMonitor.onCameraLaunched();
- }
-
- if (!mStatusBarKeyguardViewManager.isShowing()) {
- final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext);
- startActivityDismissingKeyguard(cameraIntent,
- false /* onlyProvisioned */, true /* dismissShade */,
- true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
- null /* animationController */);
- } else {
- if (!mDeviceInteractive) {
- // Avoid flickering of the scrim when we instant launch the camera and the bouncer
- // comes on.
- mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L);
- }
- if (isWakingUpOrAwake()) {
- if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Launching camera");
- if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
- mStatusBarKeyguardViewManager.reset(true /* hide */);
- }
- mNotificationPanelViewController.launchCamera(
- mDeviceInteractive /* animate */, source);
- updateScrimController();
- } else {
- // We need to defer the camera launch until the screen comes on, since otherwise
- // we will dismiss us too early since we are waiting on an activity to be drawn and
- // incorrectly get notified because of the screen on event (which resumes and pauses
- // some activities)
- if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Deferring until screen turns on");
- mLaunchCameraWhenFinishedWaking = true;
- }
- }
- }
-
- @Override
- public void onEmergencyActionLaunchGestureDetected() {
- Intent emergencyIntent = getEmergencyActionIntent();
-
- if (emergencyIntent == null) {
- Log.wtf(TAG, "Couldn't find an app to process the emergency intent.");
- return;
- }
-
- if (isGoingToSleep()) {
- mLaunchEmergencyActionOnFinishedGoingToSleep = true;
- return;
- }
-
- if (!mDeviceInteractive) {
- mPowerManager.wakeUp(SystemClock.uptimeMillis(),
- PowerManager.WAKE_REASON_GESTURE,
- "com.android.systemui:EMERGENCY_GESTURE");
- }
- // TODO(b/169087248) Possibly add haptics here for emergency action. Currently disabled for
- // app-side haptic experimentation.
-
- if (!mStatusBarKeyguardViewManager.isShowing()) {
- startActivityDismissingKeyguard(emergencyIntent,
- false /* onlyProvisioned */, true /* dismissShade */,
- true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
- null /* animationController */);
- return;
- }
-
- if (!mDeviceInteractive) {
- // Avoid flickering of the scrim when we instant launch the camera and the bouncer
- // comes on.
- mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L);
- }
-
- if (isWakingUpOrAwake()) {
- if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
- mStatusBarKeyguardViewManager.reset(true /* hide */);
- }
- mContext.startActivityAsUser(emergencyIntent, UserHandle.CURRENT);
- return;
- }
- // We need to defer the emergency action launch until the screen comes on, since otherwise
- // we will dismiss us too early since we are waiting on an activity to be drawn and
- // incorrectly get notified because of the screen on event (which resumes and pauses
- // some activities)
- mLaunchEmergencyActionWhenFinishedWaking = true;
- }
-
- private @Nullable Intent getEmergencyActionIntent() {
+ @Nullable Intent getEmergencyActionIntent() {
Intent emergencyIntent = new Intent(EmergencyGesture.ACTION_LAUNCH_EMERGENCY);
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> emergencyActivities = pm.queryIntentActivities(emergencyIntent,
@@ -4453,16 +3822,11 @@ public class StatusBar extends SystemUI implements DemoMode,
return true;
}
- private boolean isGoingToSleep() {
+ boolean isGoingToSleep() {
return mWakefulnessLifecycle.getWakefulness()
== WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP;
}
- private boolean isWakingUpOrAwake() {
- return mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_AWAKE
- || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_WAKING;
- }
-
public void notifyBiometricAuthModeChanged() {
mDozeServiceHost.updateDozing();
updateScrimController();
@@ -4489,7 +3853,11 @@ public class StatusBar extends SystemUI implements DemoMode,
mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
- mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
+ if (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED) {
+ mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
+ } else {
+ mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
+ }
} else if (mBouncerShowing) {
// Bouncer needs the front scrim when it's on top of an activity,
// tapping on a notification, editing QS or being dismissed by
@@ -4518,8 +3886,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mScrimController.transitionTo(ScrimState.AOD);
} else if (mIsKeyguard && !unlocking) {
mScrimController.transitionTo(ScrimState.KEYGUARD);
- } else if (mBubblesOptional.isPresent() && mBubblesOptional.get().isStackExpanded()) {
- mScrimController.transitionTo(ScrimState.BUBBLE_EXPANDED, mUnlockScrimCallback);
} else {
mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
}
@@ -4616,10 +3982,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
}
- @Override
- public void toggleSplitScreen() {
- toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
- }
public void awakenDreams() {
mUiBgExecutor.execute(() -> {
@@ -4631,46 +3993,6 @@ public class StatusBar extends SystemUI implements DemoMode,
});
}
- @Override
- public void preloadRecentApps() {
- int msg = MSG_PRELOAD_RECENT_APPS;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessage(msg);
- }
-
- @Override
- public void cancelPreloadRecentApps() {
- int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessage(msg);
- }
-
- @Override
- public void dismissKeyboardShortcutsMenu() {
- int msg = MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU;
- mHandler.removeMessages(msg);
- mHandler.sendEmptyMessage(msg);
- }
-
- @Override
- public void toggleKeyboardShortcutsMenu(int deviceId) {
- int msg = MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU;
- mHandler.removeMessages(msg);
- mHandler.obtainMessage(msg, deviceId, 0).sendToTarget();
- }
-
- @Override
- public void setTopAppHidesStatusBar(boolean topAppHidesStatusBar) {
- mTopHidesStatusBar = topAppHidesStatusBar;
- if (!topAppHidesStatusBar && mWereIconsJustHidden) {
- // Immediately update the icon hidden state, since that should only apply if we're
- // staying fullscreen.
- mWereIconsJustHidden = false;
- mCommandQueue.recomputeDisableFlags(mDisplayId, true);
- }
- updateHideIconsForBouncer(true /* animate */);
- }
-
protected void toggleKeyboardShortcuts(int deviceId) {
KeyboardShortcuts.toggle(mContext, deviceId);
}
@@ -4790,7 +4112,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
private void postOnUiThread(Runnable runnable) {
- mMainThreadHandler.post(runnable);
+ mMainExecutor.execute(runnable);
}
/**
@@ -4932,34 +4254,19 @@ public class StatusBar extends SystemUI implements DemoMode,
}
return mStatusBarKeyguardViewManager.isSecure();
}
-
- @Override
- public void showAssistDisclosure() {
- mAssistManagerLazy.get().showDisclosure();
- }
-
public NotificationPanelViewController getPanelController() {
return mNotificationPanelViewController;
}
-
- @Override
- public void startAssist(Bundle args) {
- mAssistManagerLazy.get().startAssist(args);
- }
// End Extra BaseStatusBarMethods.
public NotificationGutsManager getGutsManager() {
return mGutsManager;
}
- private boolean isTransientShown() {
+ boolean isTransientShown() {
return mTransientShown;
}
- @Override
- public void suppressAmbientDisplay(boolean suppressed) {
- mDozeServiceHost.setDozeSuppressed(suppressed);
- }
public void addExpansionChangedListener(@NonNull ExpansionChangedListener listener) {
mExpansionChangedListeners.add(listener);
@@ -4967,9 +4274,10 @@ public class StatusBar extends SystemUI implements DemoMode,
}
private void sendInitialExpansionAmount(ExpansionChangedListener expansionChangedListener) {
- if (mStatusBarView != null) {
- expansionChangedListener.onExpansionChanged(mStatusBarView.getExpansionFraction(),
- mStatusBarView.isExpanded());
+ if (mNotificationPanelViewController != null) {
+ expansionChangedListener.onExpansionChanged(
+ mNotificationPanelViewController.getExpandedFraction(),
+ mNotificationPanelViewController.isExpanded());
}
}
@@ -4985,4 +4293,284 @@ public class StatusBar extends SystemUI implements DemoMode,
mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha());
}
+
+ private final KeyguardUpdateMonitorCallback mUpdateCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onDreamingStateChanged(boolean dreaming) {
+ if (dreaming) {
+ maybeEscalateHeadsUp();
+ }
+ }
+
+ // TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by
+ // KeyguardCoordinator
+ @Override
+ public void onStrongAuthStateChanged(int userId) {
+ super.onStrongAuthStateChanged(userId);
+ mNotificationsController.requestNotificationUpdate("onStrongAuthStateChanged");
+ }
+ };
+
+
+ private final FalsingManager.FalsingBeliefListener mFalsingBeliefListener =
+ new FalsingManager.FalsingBeliefListener() {
+ @Override
+ public void onFalse() {
+ // Hides quick settings, bouncer, and quick-quick settings.
+ mStatusBarKeyguardViewManager.reset(true);
+ }
+ };
+
+ // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over,
+ // this animation is tied to the scrim for historic reasons.
+ // TODO: notify when keyguard has faded away instead of the scrim.
+ private final ScrimController.Callback mUnlockScrimCallback = new ScrimController
+ .Callback() {
+ @Override
+ public void onFinished() {
+ if (mStatusBarKeyguardViewManager == null) {
+ Log.w(TAG, "Tried to notify keyguard visibility when "
+ + "mStatusBarKeyguardViewManager was null");
+ return;
+ }
+ if (mKeyguardStateController.isKeyguardFadingAway() && !mCallingFadingAwayAfterReveal) {
+ mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ }
+ }
+
+ @Override
+ public void onCancelled() {
+ onFinished();
+ }
+ };
+
+ private final DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() {
+ @Override
+ public void onUserSetupChanged() {
+ final boolean userSetup = mDeviceProvisionedController.isCurrentUserSetup();
+ Log.d(TAG, "mUserSetupObserver - DeviceProvisionedListener called for "
+ + "current user");
+ if (MULTIUSER_DEBUG) {
+ Log.d(TAG, String.format("User setup changed: userSetup=%s mUserSetup=%s",
+ userSetup, mUserSetup));
+ }
+
+ if (userSetup != mUserSetup) {
+ mUserSetup = userSetup;
+ if (!mUserSetup && mStatusBarView != null) {
+ animateCollapseQuickSettings();
+ }
+ if (mNotificationPanelViewController != null) {
+ mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
+ }
+ updateQsExpansionEnabled();
+ }
+ }
+ };
+
+ private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!mWallpaperSupported) {
+ // Receiver should not have been registered at all...
+ Log.wtf(TAG, "WallpaperManager not supported");
+ return;
+ }
+ WallpaperInfo info = mWallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT);
+ mWallpaperController.onWallpaperInfoUpdated(info);
+
+ final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
+ // If WallpaperInfo is null, it must be ImageWallpaper.
+ final boolean supportsAmbientMode = deviceSupportsAodWallpaper
+ && (info != null && info.supportsAmbientMode());
+
+ mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
+ mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
+ mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode);
+ }
+ };
+
+ BroadcastReceiver mTaskbarChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mBubblesOptional.ifPresent(bubbles -> bubbles.onTaskbarChanged(intent.getExtras()));
+ }
+ };
+
+ private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ updateResources();
+ updateDisplaySize(); // populates mDisplayMetrics
+
+ if (DEBUG) {
+ Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
+ }
+
+ mViewHierarchyManager.updateRowStates();
+ mScreenPinningRequest.onConfigurationChanged();
+ }
+
+ @Override
+ public void onDensityOrFontScaleChanged() {
+ // TODO: Remove this.
+ if (mBrightnessMirrorController != null) {
+ mBrightnessMirrorController.onDensityOrFontScaleChanged();
+ }
+ // TODO: Bring these out of StatusBar.
+ mUserInfoControllerImpl.onDensityOrFontScaleChanged();
+ mUserSwitcherController.onDensityOrFontScaleChanged();
+ mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
+ mHeadsUpManager.onDensityOrFontScaleChanged();
+ }
+
+ @Override
+ public void onThemeChanged() {
+ if (mBrightnessMirrorController != null) {
+ mBrightnessMirrorController.onOverlayChanged();
+ }
+ // We need the new R.id.keyguard_indication_area before recreating
+ // mKeyguardIndicationController
+ mNotificationPanelViewController.onThemeChanged();
+
+ if (mStatusBarKeyguardViewManager != null) {
+ mStatusBarKeyguardViewManager.onThemeChanged();
+ }
+ if (mAmbientIndicationContainer instanceof AutoReinflateContainer) {
+ ((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout();
+ }
+ mNotificationIconAreaController.onThemeChanged();
+ }
+
+ @Override
+ public void onUiModeChanged() {
+ if (mBrightnessMirrorController != null) {
+ mBrightnessMirrorController.onUiModeChanged();
+ }
+ }
+ };
+
+ private StatusBarStateController.StateListener mStateListener =
+ new StatusBarStateController.StateListener() {
+ @Override
+ public void onStatePreChange(int oldState, int newState) {
+ // If we're visible and switched to SHADE_LOCKED (the user dragged
+ // down on the lockscreen), clear notification LED, vibration,
+ // ringing.
+ // Other transitions are covered in handleVisibleToUserChanged().
+ if (mVisible && (newState == StatusBarState.SHADE_LOCKED
+ || mStatusBarStateController.goingToFullShade())) {
+ clearNotificationEffects();
+ }
+ if (newState == StatusBarState.KEYGUARD) {
+ mRemoteInputManager.onPanelCollapsed();
+ maybeEscalateHeadsUp();
+ }
+ }
+
+ @Override
+ public void onStateChanged(int newState) {
+ mState = newState;
+ updateReportRejectedTouchVisibility();
+ mDozeServiceHost.updateDozing();
+ updateTheme();
+ mNavigationBarController.touchAutoDim(mDisplayId);
+ Trace.beginSection("StatusBar#updateKeyguardState");
+ if (mState == StatusBarState.KEYGUARD && mStatusBarView != null) {
+ mNotificationPanelViewController.cancelPendingPanelCollapse();
+ }
+ updateDozingState();
+ checkBarModes();
+ updateScrimController();
+ mPresenter.updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
+ updateKeyguardState();
+ Trace.endSection();
+ }
+
+ @Override
+ public void onDozeAmountChanged(float linear, float eased) {
+ if (mFeatureFlags.useNewLockscreenAnimations()
+ && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)
+ && !mBiometricUnlockController.isWakeAndUnlock()) {
+ mLightRevealScrim.setRevealAmount(1f - linear);
+ }
+
+ mDialogLaunchAnimator.onDozeAmountChanged(linear);
+ }
+
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ Trace.beginSection("StatusBar#updateDozing");
+ mDozing = isDozing;
+
+ // Collapse the notification panel if open
+ boolean dozingAnimated = mDozeServiceHost.getDozingRequested()
+ && mDozeParameters.shouldControlScreenOff();
+ mNotificationPanelViewController.resetViews(dozingAnimated);
+
+ updateQsExpansionEnabled();
+ mKeyguardViewMediator.setDozing(mDozing);
+
+ mNotificationsController.requestNotificationUpdate("onDozingChanged");
+ updateDozingState();
+ mDozeServiceHost.updateDozing();
+ updateScrimController();
+ updateReportRejectedTouchVisibility();
+ Trace.endSection();
+ }
+
+ @Override
+ public void onFullscreenStateChanged(boolean isFullscreen) {
+ mIsFullscreen = isFullscreen;
+ maybeUpdateBarMode();
+ }
+ };
+
+ private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback =
+ new BatteryController.BatteryStateChangeCallback() {
+ @Override
+ public void onPowerSaveChanged(boolean isPowerSave) {
+ mMainExecutor.execute(mCheckBarModes);
+ if (mDozeServiceHost != null) {
+ mDozeServiceHost.firePowerSaveChanged(isPowerSave);
+ }
+ }
+ };
+
+ private final ActivityLaunchAnimator.Callback mKeyguardHandler =
+ new ActivityLaunchAnimator.Callback() {
+ @Override
+ public boolean isOnKeyguard() {
+ return mKeyguardStateController.isShowing();
+ }
+
+ @Override
+ public void hideKeyguardWithAnimation(IRemoteAnimationRunner runner) {
+ // We post to the main thread for 2 reasons:
+ // 1. KeyguardViewMediator is not thread-safe.
+ // 2. To ensure that ViewMediatorCallback#keyguardDonePending is called before
+ // ViewMediatorCallback#readyForKeyguardDone. The wrong order could occur
+ // when doing
+ // dismissKeyguardThenExecute { hideKeyguardWithAnimation(runner) }.
+ mMainExecutor.execute(() -> mKeyguardViewMediator.hideWithAnimation(runner));
+ }
+
+ @Override
+ public void setBlursDisabledForAppLaunch(boolean disabled) {
+ mKeyguardViewMediator.setBlursDisabledForAppLaunch(disabled);
+ }
+
+ @Override
+ public int getBackgroundColor(TaskInfo task) {
+ if (!mStartingSurfaceOptional.isPresent()) {
+ Log.w(TAG, "No starting surface, defaulting to SystemBGColor");
+ return SplashscreenContentDrawer.getSystemBGColor();
+ }
+
+ return mStartingSurfaceOptional.get().getBackgroundColor(task);
+ }
+ };
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
new file mode 100644
index 000000000000..bb1daa252cdf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
@@ -0,0 +1,648 @@
+/*
+ * 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.systemui.statusbar.phone;
+
+import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static android.app.StatusBarManager.windowStateToString;
+import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.containsType;
+
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+
+import android.annotation.Nullable;
+import android.app.StatusBarManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.media.AudioAttributes;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.util.Log;
+import android.util.Slog;
+import android.view.InsetsState.InternalInsetsType;
+import android.view.InsetsVisibilities;
+import android.view.KeyEvent;
+import android.view.WindowInsetsController.Appearance;
+import android.view.WindowInsetsController.Behavior;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.view.AppearanceRegion;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.R;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.camera.CameraIntents;
+import com.android.systemui.dagger.qualifiers.DisplayId;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.DisableFlagsLogger;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+/** */
+@StatusBarComponent.StatusBarScope
+public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks {
+ private final StatusBar mStatusBar;
+ private final Context mContext;
+ private final ShadeController mShadeController;
+ private final CommandQueue mCommandQueue;
+ private final NotificationPanelViewController mNotificationPanelViewController;
+ private final Optional<LegacySplitScreen> mSplitScreenOptional;
+ private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
+ private final MetricsLogger mMetricsLogger;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final KeyguardStateController mKeyguardStateController;
+ private final HeadsUpManager mHeadsUpManager;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
+ private final DeviceProvisionedController mDeviceProvisionedController;
+ private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final AssistManager mAssistManager;
+ private final DozeServiceHost mDozeServiceHost;
+ private final SysuiStatusBarStateController mStatusBarStateController;
+ private final NotificationShadeWindowView mNotificationShadeWindowView;
+ private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
+ private final PowerManager mPowerManager;
+ private final VibratorHelper mVibratorHelper;
+ private final Optional<Vibrator> mVibratorOptional;
+ private final LightBarController mLightBarController;
+ private final DisableFlagsLogger mDisableFlagsLogger;
+ private final int mDisplayId;
+ private final boolean mVibrateOnOpening;
+ private final VibrationEffect mCameraLaunchGestureVibrationEffect;
+
+
+ private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .build();
+
+ @Inject
+ StatusBarCommandQueueCallbacks(
+ StatusBar statusBar,
+ Context context,
+ @Main Resources resources,
+ ShadeController shadeController,
+ CommandQueue commandQueue,
+ NotificationPanelViewController notificationPanelViewController,
+ Optional<LegacySplitScreen> splitScreenOptional,
+ RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
+ MetricsLogger metricsLogger,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ KeyguardStateController keyguardStateController,
+ HeadsUpManager headsUpManager,
+ WakefulnessLifecycle wakefulnessLifecycle,
+ DeviceProvisionedController deviceProvisionedController,
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ AssistManager assistManager,
+ DozeServiceHost dozeServiceHost,
+ SysuiStatusBarStateController statusBarStateController,
+ NotificationShadeWindowView notificationShadeWindowView,
+ NotificationStackScrollLayoutController notificationStackScrollLayoutController,
+ PowerManager powerManager,
+ VibratorHelper vibratorHelper,
+ Optional<Vibrator> vibratorOptional,
+ LightBarController lightBarController,
+ DisableFlagsLogger disableFlagsLogger,
+ @DisplayId int displayId) {
+
+ mStatusBar = statusBar;
+ mContext = context;
+ mShadeController = shadeController;
+ mCommandQueue = commandQueue;
+ mNotificationPanelViewController = notificationPanelViewController;
+ mSplitScreenOptional = splitScreenOptional;
+ mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler;
+ mMetricsLogger = metricsLogger;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mKeyguardStateController = keyguardStateController;
+ mHeadsUpManager = headsUpManager;
+ mWakefulnessLifecycle = wakefulnessLifecycle;
+ mDeviceProvisionedController = deviceProvisionedController;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mAssistManager = assistManager;
+ mDozeServiceHost = dozeServiceHost;
+ mStatusBarStateController = statusBarStateController;
+ mNotificationShadeWindowView = notificationShadeWindowView;
+ mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
+ mPowerManager = powerManager;
+ mVibratorHelper = vibratorHelper;
+ mVibratorOptional = vibratorOptional;
+ mLightBarController = lightBarController;
+ mDisableFlagsLogger = disableFlagsLogger;
+ mDisplayId = displayId;
+
+ mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
+ mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
+ mVibratorOptional, resources);
+ }
+
+ @Override
+ public void abortTransient(int displayId, @InternalInsetsType int[] types) {
+ if (displayId != mDisplayId) {
+ return;
+ }
+ if (!containsType(types, ITYPE_STATUS_BAR)) {
+ return;
+ }
+ mStatusBar.clearTransient();
+ }
+
+ @Override
+ public void addQsTile(ComponentName tile) {
+ QSPanelController qsPanelController = mStatusBar.getQSPanelController();
+ if (qsPanelController != null && qsPanelController.getHost() != null) {
+ qsPanelController.getHost().addTile(tile);
+ }
+ }
+
+ @Override
+ public void remQsTile(ComponentName tile) {
+ QSPanelController qsPanelController = mStatusBar.getQSPanelController();
+ if (qsPanelController != null && qsPanelController.getHost() != null) {
+ qsPanelController.getHost().removeTile(tile);
+ }
+ }
+
+ @Override
+ public void clickTile(ComponentName tile) {
+ QSPanelController qsPanelController = mStatusBar.getQSPanelController();
+ if (qsPanelController != null) {
+ qsPanelController.clickTile(tile);
+ }
+ }
+
+ @Override
+ public void animateCollapsePanels(int flags, boolean force) {
+ mShadeController.animateCollapsePanels(flags, force, false /* delayed */,
+ 1.0f /* speedUpFactor */);
+ }
+
+ @Override
+ public void animateExpandNotificationsPanel() {
+ if (StatusBar.SPEW) {
+ Log.d(StatusBar.TAG,
+ "animateExpand: mExpandedVisible=" + mStatusBar.isExpandedVisible());
+ }
+ if (!mCommandQueue.panelsEnabled()) {
+ return;
+ }
+
+ mNotificationPanelViewController.expandWithoutQs();
+ }
+
+ @Override
+ public void animateExpandSettingsPanel(@Nullable String subPanel) {
+ if (StatusBar.SPEW) {
+ Log.d(StatusBar.TAG,
+ "animateExpand: mExpandedVisible=" + mStatusBar.isExpandedVisible());
+ }
+ if (!mCommandQueue.panelsEnabled()) {
+ return;
+ }
+
+ // Settings are not available in setup
+ if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
+
+
+ QSPanelController qsPanelController = mStatusBar.getQSPanelController();
+ if (subPanel != null && qsPanelController != null) {
+ qsPanelController.openDetails(subPanel);
+ }
+ mNotificationPanelViewController.expandWithQs();
+ }
+
+ @Override
+ public void appTransitionCancelled(int displayId) {
+ if (displayId == mDisplayId) {
+ mSplitScreenOptional.ifPresent(LegacySplitScreen::onAppTransitionFinished);
+ }
+ }
+
+ @Override
+ public void appTransitionFinished(int displayId) {
+ if (displayId == mDisplayId) {
+ mSplitScreenOptional.ifPresent(LegacySplitScreen::onAppTransitionFinished);
+ }
+ }
+
+ @Override
+ public void dismissKeyboardShortcutsMenu() {
+ mStatusBar.resendMessage(StatusBar.MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU);
+ }
+ /**
+ * State is one or more of the DISABLE constants from StatusBarManager.
+ */
+ @Override
+ public void disable(int displayId, int state1, int state2, boolean animate) {
+ if (displayId != mDisplayId) {
+ return;
+ }
+
+ int state2BeforeAdjustment = state2;
+ state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2);
+ Log.d(StatusBar.TAG,
+ mDisableFlagsLogger.getDisableFlagsString(
+ /* old= */ new DisableFlagsLogger.DisableState(
+ mStatusBar.getDisabled1(), mStatusBar.getDisabled2()),
+ /* new= */ new DisableFlagsLogger.DisableState(
+ state1, state2BeforeAdjustment),
+ /* newStateAfterLocalModification= */ new DisableFlagsLogger.DisableState(
+ state1, state2)));
+
+ final int old1 = mStatusBar.getDisabled1();
+ final int diff1 = state1 ^ old1;
+ mStatusBar.setDisabled1(state1);
+
+ final int old2 = mStatusBar.getDisabled2();
+ final int diff2 = state2 ^ old2;
+ mStatusBar.setDisabled2(state2);
+
+ if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
+ if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
+ mShadeController.animateCollapsePanels();
+ }
+ }
+
+ if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
+ if (mStatusBar.areNotificationAlertsDisabled()) {
+ mHeadsUpManager.releaseAllImmediately();
+ }
+ }
+
+ if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) {
+ mStatusBar.updateQsExpansionEnabled();
+ }
+
+ if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+ mStatusBar.updateQsExpansionEnabled();
+ if ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+ mShadeController.animateCollapsePanels();
+ }
+ }
+ }
+
+ /**
+ * Called for system navigation gestures. First action opens the panel, second opens
+ * settings. Down action closes the entire panel.
+ */
+ @Override
+ public void handleSystemKey(int key) {
+ if (StatusBar.SPEW) {
+ Log.d(StatusBar.TAG, "handleNavigationKey: " + key);
+ }
+ if (!mCommandQueue.panelsEnabled() || !mKeyguardUpdateMonitor.isDeviceInteractive()
+ || mKeyguardStateController.isShowing() && !mKeyguardStateController.isOccluded()) {
+ return;
+ }
+
+ // Panels are not available in setup
+ if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
+
+ if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP == key) {
+ mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_UP);
+ mNotificationPanelViewController.collapse(
+ false /* delayed */, 1.0f /* speedUpFactor */);
+ } else if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN == key) {
+ mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN);
+ if (mNotificationPanelViewController.isFullyCollapsed()) {
+ if (mVibrateOnOpening) {
+ mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+ }
+ mNotificationPanelViewController.expand(true /* animate */);
+ mNotificationStackScrollLayoutController.setWillExpand(true);
+ mHeadsUpManager.unpinAll(true /* userUnpinned */);
+ mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN, 1);
+ } else if (!mNotificationPanelViewController.isInSettings()
+ && !mNotificationPanelViewController.isExpanding()) {
+ mNotificationPanelViewController.flingSettings(0 /* velocity */,
+ NotificationPanelView.FLING_EXPAND);
+ mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN_QS, 1);
+ }
+ }
+
+ }
+
+ @Override
+ public void onCameraLaunchGestureDetected(int source) {
+ mStatusBar.setLastCameraLaunchSource(source);
+ if (mStatusBar.isGoingToSleep()) {
+ if (StatusBar.DEBUG_CAMERA_LIFT) {
+ Slog.d(StatusBar.TAG, "Finish going to sleep before launching camera");
+ }
+ mStatusBar.setLaunchCameraOnFinishedGoingToSleep(true);
+ return;
+ }
+ if (!mNotificationPanelViewController.canCameraGestureBeLaunched()) {
+ if (StatusBar.DEBUG_CAMERA_LIFT) {
+ Slog.d(StatusBar.TAG, "Can't launch camera right now");
+ }
+ return;
+ }
+ if (!mStatusBar.isDeviceInteractive()) {
+ mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_CAMERA_LAUNCH,
+ "com.android.systemui:CAMERA_GESTURE");
+ }
+ vibrateForCameraGesture();
+
+ if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) {
+ Log.v(StatusBar.TAG, "Camera launch");
+ mKeyguardUpdateMonitor.onCameraLaunched();
+ }
+
+ if (!mStatusBarKeyguardViewManager.isShowing()) {
+ final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext);
+ mStatusBar.startActivityDismissingKeyguard(cameraIntent,
+ false /* onlyProvisioned */, true /* dismissShade */,
+ true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
+ null /* animationController */);
+ } else {
+ if (!mStatusBar.isDeviceInteractive()) {
+ // Avoid flickering of the scrim when we instant launch the camera and the bouncer
+ // comes on.
+ mStatusBar.acquireGestureWakeLock(StatusBar.LAUNCH_TRANSITION_TIMEOUT_MS + 1000L);
+ }
+ if (isWakingUpOrAwake()) {
+ if (StatusBar.DEBUG_CAMERA_LIFT) {
+ Slog.d(StatusBar.TAG, "Launching camera");
+ }
+ if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
+ mStatusBarKeyguardViewManager.reset(true /* hide */);
+ }
+ mNotificationPanelViewController.launchCamera(
+ mStatusBar.isDeviceInteractive() /* animate */, source);
+ mStatusBar.updateScrimController();
+ } else {
+ // We need to defer the camera launch until the screen comes on, since otherwise
+ // we will dismiss us too early since we are waiting on an activity to be drawn and
+ // incorrectly get notified because of the screen on event (which resumes and pauses
+ // some activities)
+ if (StatusBar.DEBUG_CAMERA_LIFT) {
+ Slog.d(StatusBar.TAG, "Deferring until screen turns on");
+ }
+ mStatusBar.setLaunchCameraOnFinishedWaking(true);
+ }
+ }
+ }
+
+ @Override
+ public void onEmergencyActionLaunchGestureDetected() {
+ Intent emergencyIntent = mStatusBar.getEmergencyActionIntent();
+
+ if (emergencyIntent == null) {
+ Log.wtf(StatusBar.TAG, "Couldn't find an app to process the emergency intent.");
+ return;
+ }
+
+ if (isGoingToSleep()) {
+ mStatusBar.setLaunchEmergencyActionOnFinishedGoingToSleep(true);
+ return;
+ }
+
+ if (!mStatusBar.isDeviceInteractive()) {
+ mPowerManager.wakeUp(SystemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_GESTURE,
+ "com.android.systemui:EMERGENCY_GESTURE");
+ }
+ // TODO(b/169087248) Possibly add haptics here for emergency action. Currently disabled for
+ // app-side haptic experimentation.
+
+ if (!mStatusBarKeyguardViewManager.isShowing()) {
+ mStatusBar.startActivityDismissingKeyguard(emergencyIntent,
+ false /* onlyProvisioned */, true /* dismissShade */,
+ true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
+ null /* animationController */);
+ return;
+ }
+
+ if (!mStatusBar.isDeviceInteractive()) {
+ // Avoid flickering of the scrim when we instant launch the camera and the bouncer
+ // comes on.
+ mStatusBar.acquireGestureWakeLock(StatusBar.LAUNCH_TRANSITION_TIMEOUT_MS + 1000L);
+ }
+
+ if (isWakingUpOrAwake()) {
+ if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
+ mStatusBarKeyguardViewManager.reset(true /* hide */);
+ }
+ mContext.startActivityAsUser(emergencyIntent, UserHandle.CURRENT);
+ return;
+ }
+ // We need to defer the emergency action launch until the screen comes on, since otherwise
+ // we will dismiss us too early since we are waiting on an activity to be drawn and
+ // incorrectly get notified because of the screen on event (which resumes and pauses
+ // some activities)
+ mStatusBar.setLaunchEmergencyActionOnFinishedWaking(true);
+ }
+
+ @Override
+ public void onRecentsAnimationStateChanged(boolean running) {
+ mStatusBar.setInteracting(StatusBarManager.WINDOW_NAVIGATION_BAR, running);
+ }
+
+
+ @Override
+ public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
+ AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
+ @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName) {
+ if (displayId != mDisplayId) {
+ return;
+ }
+ boolean barModeChanged = mStatusBar.setAppearance(appearance);
+
+ mLightBarController.onStatusBarAppearanceChanged(appearanceRegions, barModeChanged,
+ mStatusBar.getBarMode(), navbarColorManagedByIme);
+
+ mStatusBar.updateBubblesVisibility();
+ mStatusBarStateController.setSystemBarAttributes(
+ appearance, behavior, requestedVisibilities, packageName);
+ }
+
+ @Override
+ public void showTransient(int displayId, @InternalInsetsType int[] types) {
+ if (displayId != mDisplayId) {
+ return;
+ }
+ if (!containsType(types, ITYPE_STATUS_BAR)) {
+ return;
+ }
+ mStatusBar.showTransientUnchecked();
+ }
+
+ @Override
+ public void toggleKeyboardShortcutsMenu(int deviceId) {
+ mStatusBar.resendMessage(new StatusBar.KeyboardShortcutsMessage(deviceId));
+ }
+
+ @Override
+ public void setTopAppHidesStatusBar(boolean topAppHidesStatusBar) {
+ mStatusBar.setTopHidesStatusBar(topAppHidesStatusBar);
+ if (!topAppHidesStatusBar && mStatusBar.getWereIconsJustHidden()) {
+ // Immediately update the icon hidden state, since that should only apply if we're
+ // staying fullscreen.
+ mStatusBar.setWereIconsJustHidden(false);
+ mCommandQueue.recomputeDisableFlags(mDisplayId, true);
+ }
+ mStatusBar.updateHideIconsForBouncer(true /* animate */);
+ }
+
+ @Override
+ public void setWindowState(
+ int displayId, @StatusBarManager.WindowType int window,
+ @StatusBarManager.WindowVisibleState int state) {
+ if (displayId != mDisplayId) {
+ return;
+ }
+ boolean showing = state == WINDOW_STATE_SHOWING;
+ if (mNotificationShadeWindowView != null
+ && window == StatusBarManager.WINDOW_STATUS_BAR
+ && !mStatusBar.isSameStatusBarState(state)) {
+ mStatusBar.setWindowState(state);
+ if (StatusBar.DEBUG_WINDOW_STATE) {
+ Log.d(StatusBar.TAG, "Status bar " + windowStateToString(state));
+ }
+ if (mStatusBar.getStatusBarView() != null) {
+ if (!showing && mStatusBarStateController.getState() == StatusBarState.SHADE) {
+ mNotificationPanelViewController.collapsePanel(
+ false /* animate */, false /* delayed */, 1.0f /* speedUpFactor */);
+ }
+
+ mStatusBar.updateHideIconsForBouncer(false /* animate */);
+ }
+ }
+
+ mStatusBar.updateBubblesVisibility();
+ }
+
+ @Override
+ public void showAssistDisclosure() {
+ mAssistManager.showDisclosure();
+ }
+
+ @Override
+ public void showPinningEnterExitToast(boolean entering) {
+ if (mStatusBar.getNavigationBarView() != null) {
+ mStatusBar.getNavigationBarView().showPinningEnterExitToast(entering);
+ }
+ }
+
+ @Override
+ public void showPinningEscapeToast() {
+ if (mStatusBar.getNavigationBarView() != null) {
+ mStatusBar.getNavigationBarView().showPinningEscapeToast();
+ }
+ }
+
+ @Override
+ public void showScreenPinningRequest(int taskId) {
+ if (mKeyguardStateController.isShowing()) {
+ // Don't allow apps to trigger this from keyguard.
+ return;
+ }
+ // Show screen pinning request, since this comes from an app, show 'no thanks', button.
+ mStatusBar.showScreenPinningRequest(taskId, true);
+ }
+
+ @Override
+ public void showWirelessChargingAnimation(int batteryLevel) {
+ mStatusBar.showWirelessChargingAnimation(batteryLevel);
+ }
+
+ @Override
+ public void startAssist(Bundle args) {
+ mAssistManager.startAssist(args);
+ }
+
+ @Override
+ public void suppressAmbientDisplay(boolean suppressed) {
+ mDozeServiceHost.setDozeSuppressed(suppressed);
+ }
+
+ @Override
+ public void togglePanel() {
+ if (mStatusBar.isPanelExpanded()) {
+ mShadeController.animateCollapsePanels();
+ } else {
+ animateExpandNotificationsPanel();
+ }
+ }
+
+ @Override
+ public void toggleSplitScreen() {
+ mStatusBar.toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
+ }
+
+ private boolean isGoingToSleep() {
+ return mWakefulnessLifecycle.getWakefulness()
+ == WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP;
+ }
+
+ private boolean isWakingUpOrAwake() {
+ return mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_AWAKE
+ || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_WAKING;
+ }
+
+ private void vibrateForCameraGesture() {
+ // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
+ mVibratorOptional.ifPresent(
+ v -> v.vibrate(mCameraLaunchGestureVibrationEffect, VIBRATION_ATTRIBUTES));
+ }
+
+ private static VibrationEffect getCameraGestureVibrationEffect(
+ Optional<Vibrator> vibratorOptional, Resources resources) {
+ if (vibratorOptional.isPresent() && vibratorOptional.get().areAllPrimitivesSupported(
+ VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
+ VibrationEffect.Composition.PRIMITIVE_CLICK)) {
+ return VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 50)
+ .compose();
+ }
+ if (vibratorOptional.isPresent() && vibratorOptional.get().hasAmplitudeControl()) {
+ return VibrationEffect.createWaveform(
+ StatusBar.CAMERA_LAUNCH_GESTURE_VIBRATION_TIMINGS,
+ StatusBar.CAMERA_LAUNCH_GESTURE_VIBRATION_AMPLITUDES,
+ /* repeat= */ -1);
+ }
+
+ int[] pattern = resources.getIntArray(R.array.config_cameraLaunchGestureVibePattern);
+ long[] timings = new long[pattern.length];
+ for (int i = 0; i < pattern.length; i++) {
+ timings[i] = pattern[i];
+ }
+ return VibrationEffect.createWaveform(timings, /* repeat= */ -1);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index fe1f63a34acd..6ae8331dfde5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -19,13 +19,13 @@ package com.android.systemui.statusbar.phone
import android.content.Context
import android.content.res.Resources
import android.graphics.Rect
-import android.util.Log
+import android.util.LruCache
import android.util.Pair
import android.view.DisplayCutout
import android.view.View.LAYOUT_DIRECTION_RTL
-import android.view.WindowManager
import android.view.WindowMetrics
import androidx.annotation.VisibleForTesting
+import com.android.internal.policy.SystemBarUtils
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
@@ -38,6 +38,7 @@ import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE
import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
import com.android.systemui.util.leak.RotationUtils.Rotation
+import com.android.systemui.util.leak.RotationUtils.getResourcesForRotation
import java.io.FileDescriptor
import java.io.PrintWriter
import java.lang.Math.max
@@ -61,14 +62,18 @@ import javax.inject.Inject
class StatusBarContentInsetsProvider @Inject constructor(
val context: Context,
val configurationController: ConfigurationController,
- val windowManager: WindowManager,
val dumpManager: DumpManager
) : CallbackController<StatusBarContentInsetsChangedListener>,
ConfigurationController.ConfigurationListener,
Dumpable {
- // Indexed by @Rotation
- private val insetsByCorner = arrayOfNulls<Rect>(4)
+
+ // Limit cache size as potentially we may connect large number of displays
+ // (e.g. network displays)
+ private val insetsCache = LruCache<CacheKey, Rect>(MAX_CACHE_SIZE)
private val listeners = mutableSetOf<StatusBarContentInsetsChangedListener>()
+ private val isPrivacyDotEnabled: Boolean by lazy(LazyThreadSafetyMode.PUBLICATION) {
+ context.resources.getBoolean(R.bool.config_enablePrivacyDot)
+ }
init {
configurationController.addCallback(this)
@@ -87,16 +92,16 @@ class StatusBarContentInsetsProvider @Inject constructor(
clearCachedInsets()
}
- override fun onOverlayChanged() {
+ override fun onThemeChanged() {
clearCachedInsets()
}
- private fun clearCachedInsets() {
- insetsByCorner[0] = null
- insetsByCorner[1] = null
- insetsByCorner[2] = null
- insetsByCorner[3] = null
+ override fun onMaxBoundsChanged() {
+ notifyInsetsChanged()
+ }
+ private fun clearCachedInsets() {
+ insetsCache.evictAll()
notifyInsetsChanged()
}
@@ -111,10 +116,10 @@ class StatusBarContentInsetsProvider @Inject constructor(
* dot in the coordinates relative to the given rotation.
*/
fun getBoundingRectForPrivacyChipForRotation(@Rotation rotation: Int): Rect {
- var insets = insetsByCorner[rotation]
- val rotatedResources = RotationUtils.getResourcesForRotation(rotation, context)
+ var insets = insetsCache[getCacheKey(rotation = rotation)]
+ val rotatedResources = getResourcesForRotation(rotation, context)
if (insets == null) {
- insets = getAndSetInsetsForRotation(rotation, rotatedResources)
+ insets = getStatusBarContentInsetsForRotation(rotation, rotatedResources)
}
val dotWidth = rotatedResources.getDimensionPixelSize(R.dimen.ongoing_appops_dot_diameter)
@@ -129,24 +134,16 @@ class StatusBarContentInsetsProvider @Inject constructor(
* Calculates the necessary left and right locations for the status bar contents invariant of
* the current device rotation, in the target rotation's coordinates
*/
- fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Rect {
- var insets = insetsByCorner[rotation]
- if (insets == null) {
- val rotatedResources = RotationUtils.getResourcesForRotation(rotation, context)
- insets = getAndSetInsetsForRotation(rotation, rotatedResources)
- }
-
- return insets
- }
-
- private fun getAndSetInsetsForRotation(
- @Rotation rot: Int,
- rotatedResources: Resources
+ @JvmOverloads
+ fun getStatusBarContentInsetsForRotation(
+ @Rotation rotation: Int,
+ rotatedResources: Resources = getResourcesForRotation(rotation, context)
): Rect {
- val insets = getCalculatedInsetsForRotation(rot, rotatedResources)
- insetsByCorner[rot] = insets
-
- return insets
+ val key = getCacheKey(rotation = rotation)
+ return insetsCache[key] ?: getCalculatedInsetsForRotation(rotation, rotatedResources)
+ .also {
+ insetsCache.put(key, it)
+ }
}
private fun getCalculatedInsetsForRotation(
@@ -159,34 +156,57 @@ class StatusBarContentInsetsProvider @Inject constructor(
val isRtl = rotatedResources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL
val roundedCornerPadding = rotatedResources
.getDimensionPixelSize(R.dimen.rounded_corner_content_padding)
- val minDotWidth = rotatedResources
- .getDimensionPixelSize(R.dimen.ongoing_appops_dot_min_padding)
+ val minDotPadding = if (isPrivacyDotEnabled)
+ rotatedResources.getDimensionPixelSize(R.dimen.ongoing_appops_dot_min_padding)
+ else 0
+ val dotWidth = if (isPrivacyDotEnabled)
+ rotatedResources.getDimensionPixelSize(R.dimen.ongoing_appops_dot_diameter)
+ else 0
val minLeft: Int
val minRight: Int
if (isRtl) {
- minLeft = max(minDotWidth, roundedCornerPadding)
+ minLeft = max(minDotPadding, roundedCornerPadding)
minRight = roundedCornerPadding
} else {
minLeft = roundedCornerPadding
- minRight = max(minDotWidth, roundedCornerPadding)
+ minRight = max(minDotPadding, roundedCornerPadding)
}
return calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
dc,
- windowManager.maximumWindowMetrics,
- rotatedResources.getDimensionPixelSize(R.dimen.status_bar_height),
+ context.resources.configuration.windowConfiguration.maxBounds,
+ SystemBarUtils.getStatusBarHeight(context),
minLeft,
- minRight)
+ minRight,
+ isRtl,
+ dotWidth)
+ }
+
+ fun getStatusBarPaddingTop(@Rotation rotation: Int? = null): Int {
+ val res = rotation?.let { it -> getResourcesForRotation(it, context) } ?: context.resources
+ return res.getDimensionPixelSize(R.dimen.status_bar_padding_top)
}
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
- insetsByCorner.forEachIndexed { index, rect ->
- pw.println("${RotationUtils.toString(index)} -> $rect")
+ insetsCache.snapshot().forEach { (key, rect) ->
+ pw.println("$key -> $rect")
}
+ pw.println(insetsCache)
}
+
+ private fun getCacheKey(@Rotation rotation: Int): CacheKey =
+ CacheKey(
+ uniqueDisplayId = context.display.uniqueId,
+ rotation = rotation
+ )
+
+ private data class CacheKey(
+ val uniqueDisplayId: String,
+ @Rotation val rotation: Int
+ )
}
interface StatusBarContentInsetsChangedListener {
@@ -194,10 +214,9 @@ interface StatusBarContentInsetsChangedListener {
}
private const val TAG = "StatusBarInsetsProvider"
+private const val MAX_CACHE_SIZE = 16
-private fun getRotationZeroDisplayBounds(wm: WindowMetrics, @Rotation exactRotation: Int): Rect {
- val bounds = wm.bounds
-
+private fun getRotationZeroDisplayBounds(bounds: Rect, @Rotation exactRotation: Int): Rect {
if (exactRotation == ROTATION_NONE || exactRotation == ROTATION_UPSIDE_DOWN) {
return bounds
}
@@ -232,10 +251,13 @@ fun getPrivacyChipBoundingRectForInsets(
*
* @param currentRotation current device rotation
* @param targetRotation rotation for which to calculate the status bar content rect
- * @param displayCutout [DisplayCutout] for the curren display. possibly null
+ * @param displayCutout [DisplayCutout] for the current display. possibly null
* @param windowMetrics [WindowMetrics] for the current window
* @param statusBarHeight height of the status bar for the target rotation
- * @param roundedCornerPadding from rounded_corner_content_padding
+ * @param minLeft the minimum padding to enforce on the left
+ * @param minRight the minimum padding to enforce on the right
+ * @param isRtl current layout direction is Right-To-Left or not
+ * @param dotWidth privacy dot image width (0 if privacy dot is disabled)
*
* @see [RotationUtils#getResourcesForRotation]
*/
@@ -243,10 +265,12 @@ fun calculateInsetsForRotationWithRotatedResources(
@Rotation currentRotation: Int,
@Rotation targetRotation: Int,
displayCutout: DisplayCutout?,
- windowMetrics: WindowMetrics,
+ maxBounds: Rect,
statusBarHeight: Int,
minLeft: Int,
- minRight: Int
+ minRight: Int,
+ isRtl: Boolean,
+ dotWidth: Int
): Rect {
/*
TODO: Check if this is ever used for devices with no rounded corners
@@ -254,18 +278,19 @@ fun calculateInsetsForRotationWithRotatedResources(
val right = if (isRtl) paddingStart else paddingEnd
*/
- val rotZeroBounds = getRotationZeroDisplayBounds(windowMetrics, currentRotation)
- val currentBounds = windowMetrics.bounds
+ val rotZeroBounds = getRotationZeroDisplayBounds(maxBounds, currentRotation)
val sbLeftRight = getStatusBarLeftRight(
displayCutout,
statusBarHeight,
rotZeroBounds.right,
rotZeroBounds.bottom,
- currentBounds.width(),
- currentBounds.height(),
+ maxBounds.width(),
+ maxBounds.height(),
minLeft,
minRight,
+ isRtl,
+ dotWidth,
targetRotation,
currentRotation)
@@ -283,6 +308,8 @@ fun calculateInsetsForRotationWithRotatedResources(
* @param cHeight display height in our current rotation
* @param minLeft the minimum padding to enforce on the left
* @param minRight the minimum padding to enforce on the right
+ * @param isRtl current layout direction is Right-To-Left or not
+ * @param dotWidth privacy dot image width (0 if privacy dot is disabled)
* @param targetRotation the rotation for which to calculate margins
* @param currentRotation the rotation from which the display cutout was generated
*
@@ -298,6 +325,8 @@ private fun getStatusBarLeftRight(
cHeight: Int,
minLeft: Int,
minRight: Int,
+ isRtl: Boolean,
+ dotWidth: Int,
@Rotation targetRotation: Int,
@Rotation currentRotation: Int
): Rect {
@@ -332,13 +361,16 @@ private fun getStatusBarLeftRight(
}
if (cutoutRect.touchesLeftEdge(relativeRotation, cWidth, cHeight)) {
-
- val l = max(minLeft, cutoutRect.logicalWidth(relativeRotation))
- leftMargin = max(l, leftMargin)
+ var logicalWidth = cutoutRect.logicalWidth(relativeRotation)
+ if (isRtl) logicalWidth += dotWidth
+ leftMargin = max(logicalWidth, leftMargin)
} else if (cutoutRect.touchesRightEdge(relativeRotation, cWidth, cHeight)) {
- val logicalWidth = cutoutRect.logicalWidth(relativeRotation)
- rightMargin = max(minRight, logicalWidth)
+ var logicalWidth = cutoutRect.logicalWidth(relativeRotation)
+ if (!isRtl) logicalWidth += dotWidth
+ rightMargin = max(rightMargin, logicalWidth)
}
+ // TODO(b/203626889): Fix the scenario when config_mainBuiltInDisplayCutoutRectApproximation
+ // is very close to but not directly touch edges.
}
return Rect(leftMargin, 0, logicalDisplayWidth - rightMargin, sbHeight)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java
new file mode 100644
index 000000000000..e642b2e244e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java
@@ -0,0 +1,143 @@
+/*
+ * 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.systemui.statusbar.phone;
+
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.DisplayId;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeCommandReceiver;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+/** */
+@StatusBarComponent.StatusBarScope
+public class StatusBarDemoMode implements DemoMode {
+ private final StatusBar mStatusBar;
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+ private final NavigationBarController mNavigationBarController;
+ private final int mDisplayId;
+
+ @Inject
+ StatusBarDemoMode(
+ StatusBar statusBar,
+ NotificationShadeWindowController notificationShadeWindowController,
+ NotificationShadeWindowViewController notificationShadeWindowViewController,
+ NavigationBarController navigationBarController,
+ @DisplayId int displayId) {
+ mStatusBar = statusBar;
+ mNotificationShadeWindowController = notificationShadeWindowController;
+ mNotificationShadeWindowViewController = notificationShadeWindowViewController;
+ mNavigationBarController = navigationBarController;
+ mDisplayId = displayId;
+ }
+
+ @Override
+ public List<String> demoCommands() {
+ List<String> s = new ArrayList<>();
+ s.add(DemoMode.COMMAND_BARS);
+ s.add(DemoMode.COMMAND_CLOCK);
+ s.add(DemoMode.COMMAND_OPERATOR);
+ return s;
+ }
+
+ @Override
+ public void onDemoModeStarted() {
+ // Must send this message to any view that we delegate to via dispatchDemoCommandToView
+ dispatchDemoModeStartedToView(R.id.clock);
+ dispatchDemoModeStartedToView(R.id.operator_name);
+ }
+
+ @Override
+ public void onDemoModeFinished() {
+ dispatchDemoModeFinishedToView(R.id.clock);
+ dispatchDemoModeFinishedToView(R.id.operator_name);
+ mStatusBar.checkBarModes();
+ }
+
+ @Override
+ public void dispatchDemoCommand(String command, @NonNull Bundle args) {
+ if (command.equals(COMMAND_CLOCK)) {
+ dispatchDemoCommandToView(command, args, R.id.clock);
+ }
+ if (command.equals(COMMAND_BARS)) {
+ String mode = args.getString("mode");
+ int barMode = "opaque".equals(mode) ? MODE_OPAQUE :
+ "translucent".equals(mode) ? MODE_TRANSLUCENT :
+ "semi-transparent".equals(mode) ? MODE_SEMI_TRANSPARENT :
+ "transparent".equals(mode) ? MODE_TRANSPARENT :
+ "warning".equals(mode) ? MODE_WARNING :
+ -1;
+ if (barMode != -1) {
+ boolean animate = true;
+ if (mNotificationShadeWindowController != null
+ && mNotificationShadeWindowViewController.getBarTransitions() != null) {
+ mNotificationShadeWindowViewController.getBarTransitions().transitionTo(
+ barMode, animate);
+ }
+ mNavigationBarController.transitionTo(mDisplayId, barMode, animate);
+ }
+ }
+ if (command.equals(COMMAND_OPERATOR)) {
+ dispatchDemoCommandToView(command, args, R.id.operator_name);
+ }
+ }
+
+ private void dispatchDemoModeStartedToView(int id) {
+ View statusBarView = mStatusBar.getStatusBarView();
+ if (statusBarView == null) return;
+ View v = statusBarView.findViewById(id);
+ if (v instanceof DemoModeCommandReceiver) {
+ ((DemoModeCommandReceiver) v).onDemoModeStarted();
+ }
+ }
+
+ //TODO: these should have controllers, and this method should be removed
+ private void dispatchDemoCommandToView(String command, Bundle args, int id) {
+ View statusBarView = mStatusBar.getStatusBarView();
+ if (statusBarView == null) return;
+ View v = statusBarView.findViewById(id);
+ if (v instanceof DemoModeCommandReceiver) {
+ ((DemoModeCommandReceiver) v).dispatchDemoCommand(command, args);
+ }
+ }
+
+ private void dispatchDemoModeFinishedToView(int id) {
+ View statusBarView = mStatusBar.getStatusBarView();
+ if (statusBarView == null) return;
+ View v = statusBarView.findViewById(id);
+ if (v instanceof DemoModeCommandReceiver) {
+ ((DemoModeCommandReceiver) v).onDemoModeFinished();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
new file mode 100644
index 000000000000..ca877af150da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -0,0 +1,133 @@
+/*
+ * 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.systemui.statusbar.phone;
+
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+
+import javax.inject.Inject;
+
+/** Ties the {@link StatusBar} to {@link com.android.systemui.statusbar.policy.HeadsUpManager}. */
+@StatusBarComponent.StatusBarScope
+public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener {
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final StatusBarWindowController mStatusBarWindowController;
+ private final NotificationPanelViewController mNotificationPanelViewController;
+ private final KeyguardBypassController mKeyguardBypassController;
+ private final HeadsUpManagerPhone mHeadsUpManager;
+ private final StatusBarStateController mStatusBarStateController;
+ private final NotificationRemoteInputManager mNotificationRemoteInputManager;
+ private final NotificationsController mNotificationsController;
+ private final DozeServiceHost mDozeServiceHost;
+ private final DozeScrimController mDozeScrimController;
+
+ @Inject
+ StatusBarHeadsUpChangeListener(
+ NotificationShadeWindowController notificationShadeWindowController,
+ StatusBarWindowController statusBarWindowController,
+ NotificationPanelViewController notificationPanelViewController,
+ KeyguardBypassController keyguardBypassController,
+ HeadsUpManagerPhone headsUpManager,
+ StatusBarStateController statusBarStateController,
+ NotificationRemoteInputManager notificationRemoteInputManager,
+ NotificationsController notificationsController,
+ DozeServiceHost dozeServiceHost,
+ DozeScrimController dozeScrimController) {
+
+ mNotificationShadeWindowController = notificationShadeWindowController;
+ mStatusBarWindowController = statusBarWindowController;
+ mNotificationPanelViewController = notificationPanelViewController;
+ mKeyguardBypassController = keyguardBypassController;
+ mHeadsUpManager = headsUpManager;
+ mStatusBarStateController = statusBarStateController;
+ mNotificationRemoteInputManager = notificationRemoteInputManager;
+ mNotificationsController = notificationsController;
+ mDozeServiceHost = dozeServiceHost;
+ mDozeScrimController = dozeScrimController;
+ }
+
+ @Override
+ public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
+ if (inPinnedMode) {
+ mNotificationShadeWindowController.setHeadsUpShowing(true);
+ mStatusBarWindowController.setForceStatusBarVisible(true);
+ if (mNotificationPanelViewController.isFullyCollapsed()) {
+ // We need to ensure that the touchable region is updated before the
+ //window will be
+ // resized, in order to not catch any touches. A layout will ensure that
+ // onComputeInternalInsets will be called and after that we can
+ //resize the layout. Let's
+ // make sure that the window stays small for one frame until the
+ //touchableRegion is set.
+ mNotificationPanelViewController.getView().requestLayout();
+ mNotificationShadeWindowController.setForceWindowCollapsed(true);
+ mNotificationPanelViewController.getView().post(() -> {
+ mNotificationShadeWindowController.setForceWindowCollapsed(false);
+ });
+ }
+ } else {
+ boolean bypassKeyguard = mKeyguardBypassController.getBypassEnabled()
+ && mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+ if (!mNotificationPanelViewController.isFullyCollapsed()
+ || mNotificationPanelViewController.isTracking()
+ || bypassKeyguard) {
+ // We are currently tracking or is open and the shade doesn't need to
+ //be kept
+ // open artificially.
+ mNotificationShadeWindowController.setHeadsUpShowing(false);
+ if (bypassKeyguard) {
+ mStatusBarWindowController.setForceStatusBarVisible(false);
+ }
+ } else {
+ // we need to keep the panel open artificially, let's wait until the
+ //animation
+ // is finished.
+ mHeadsUpManager.setHeadsUpGoingAway(true);
+ mNotificationPanelViewController.runAfterAnimationFinished(() -> {
+ if (!mHeadsUpManager.hasPinnedHeadsUp()) {
+ mNotificationShadeWindowController.setHeadsUpShowing(false);
+ mHeadsUpManager.setHeadsUpGoingAway(false);
+ }
+ mNotificationRemoteInputManager.onPanelCollapsed();
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
+ mNotificationsController.requestNotificationUpdate("onHeadsUpStateChanged");
+ if (mStatusBarStateController.isDozing() && isHeadsUp) {
+ entry.setPulseSuppressed(false);
+ mDozeServiceHost.fireNotificationPulse(entry);
+ if (mDozeServiceHost.isPulsing()) {
+ mDozeScrimController.cancelPendingPulseTimeout();
+ }
+ }
+ if (!isHeadsUp && !mHeadsUpManager.hasNotifications()) {
+ // There are no longer any notifications to show. We should end the
+ //pulse now.
+ mDozeScrimController.pulseOutNow();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 2c7553487067..48fe77482340 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -35,10 +35,11 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoModeCommandReceiver;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
@@ -50,6 +51,8 @@ import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import java.util.ArrayList;
import java.util.List;
+import javax.inject.Inject;
+
public interface StatusBarIconController {
/**
@@ -213,6 +216,20 @@ public interface StatusBarIconController {
icons.setColor(mColor);
return icons;
}
+
+ @SysUISingleton
+ public static class Factory {
+ private final FeatureFlags mFeatureFlags;
+
+ @Inject
+ public Factory(FeatureFlags featureFlags) {
+ mFeatureFlags = featureFlags;
+ }
+
+ public TintedIconManager create(ViewGroup group) {
+ return new TintedIconManager(group, mFeatureFlags);
+ }
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 75900a2bffa1..88a7dc7bcd75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -23,6 +23,7 @@ import android.os.Bundle;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import android.view.ViewGroup;
import com.android.internal.statusbar.StatusBarIcon;
@@ -32,6 +33,7 @@ import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
@@ -71,7 +73,8 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
public StatusBarIconControllerImpl(
Context context,
CommandQueue commandQueue,
- DemoModeController demoModeController) {
+ DemoModeController demoModeController,
+ DumpManager dumpManager) {
super(context.getResources().getStringArray(
com.android.internal.R.array.config_statusBarIcons));
Dependency.get(ConfigurationController.class).addCallback(this);
@@ -83,11 +86,19 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
commandQueue.addCallback(this);
Dependency.get(TunerService.class).addTunable(this, ICON_HIDE_LIST);
demoModeController.addCallback(this);
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
/** */
@Override
public void addIconGroup(IconManager group) {
+ for (IconManager existingIconManager : mIconGroups) {
+ if (existingIconManager.mGroup == group.mGroup) {
+ Log.e(TAG, "Adding new IconManager for the same ViewGroup. This could cause "
+ + "unexpected results.");
+ }
+ }
+
mIconGroups.add(group);
List<Slot> allSlots = getSlots();
for (int i = 0; i < allSlots.size(); i++) {
@@ -103,6 +114,14 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
}
}
+ private void refreshIconGroups() {
+ for (int i = mIconGroups.size() - 1; i >= 0; --i) {
+ IconManager group = mIconGroups.get(i);
+ removeIconGroup(group);
+ addIconGroup(group);
+ }
+ }
+
/** */
@Override
public void removeIconGroup(IconManager group) {
@@ -457,5 +476,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
@Override
public void onDensityOrFontScaleChanged() {
loadDimens();
+ refreshIconGroups();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 9a2ebcfeea98..cac66a3186ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -51,7 +51,6 @@ import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.DejankUtils;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dock.DockManager;
-import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -70,10 +69,11 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Objects;
-import java.util.Optional;
import javax.inject.Inject;
+import dagger.Lazy;
+
/**
* Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
* via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -107,17 +107,18 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private final ConfigurationController mConfigurationController;
private final NavigationModeController mNavigationModeController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
- private final Optional<FaceAuthScreenBrightnessController> mFaceAuthScreenBrightnessController;
private final KeyguardBouncer.Factory mKeyguardBouncerFactory;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
private KeyguardMessageAreaController mKeyguardMessageAreaController;
+ private final Lazy<ShadeController> mShadeController;
private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
@Override
public void onFullyShown() {
updateStates();
- mStatusBar.wakeUpIfDozing(SystemClock.uptimeMillis(), mContainer, "BOUNCER_VISIBLE");
+ mStatusBar.wakeUpIfDozing(SystemClock.uptimeMillis(),
+ mStatusBar.getBouncerContainer(), "BOUNCER_VISIBLE");
}
@Override
@@ -171,7 +172,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private NotificationPanelViewController mNotificationPanelViewController;
private BiometricUnlockController mBiometricUnlockController;
- private ViewGroup mContainer;
private View mNotificationContainer;
protected KeyguardBouncer mBouncer;
@@ -239,12 +239,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
DockManager dockManager,
NotificationShadeWindowController notificationShadeWindowController,
KeyguardStateController keyguardStateController,
- Optional<FaceAuthScreenBrightnessController> faceAuthScreenBrightnessController,
NotificationMediaManager notificationMediaManager,
KeyguardBouncer.Factory keyguardBouncerFactory,
WakefulnessLifecycle wakefulnessLifecycle,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
- KeyguardMessageAreaController.Factory keyguardMessageAreaFactory) {
+ KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
+ Lazy<ShadeController> shadeController) {
mContext = context;
mViewMediatorCallback = callback;
mLockPatternUtils = lockPatternUtils;
@@ -256,23 +256,23 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mKeyguardUpdateManager = keyguardUpdateMonitor;
mStatusBarStateController = sysuiStatusBarStateController;
mDockManager = dockManager;
- mFaceAuthScreenBrightnessController = faceAuthScreenBrightnessController;
mKeyguardBouncerFactory = keyguardBouncerFactory;
mWakefulnessLifecycle = wakefulnessLifecycle;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
+ mShadeController = shadeController;
}
@Override
public void registerStatusBar(StatusBar statusBar,
- ViewGroup container,
NotificationPanelViewController notificationPanelViewController,
BiometricUnlockController biometricUnlockController,
View notificationContainer,
KeyguardBypassController bypassController) {
mStatusBar = statusBar;
- mContainer = container;
mBiometricUnlockController = biometricUnlockController;
+
+ ViewGroup container = mStatusBar.getBouncerContainer();
mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
mNotificationPanelViewController = notificationPanelViewController;
notificationPanelViewController.addExpansionListener(this);
@@ -280,11 +280,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mNotificationContainer = notificationContainer;
mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create(
KeyguardMessageArea.findSecurityMessageDisplay(container));
- mFaceAuthScreenBrightnessController.ifPresent((it) -> {
- View overlay = new View(mContext);
- container.addView(overlay);
- it.attach(overlay);
- });
registerListeners();
}
@@ -351,7 +346,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
} else if (mPulsing && expansion == KeyguardBouncer.EXPANSION_VISIBLE) {
// Panel expanded while pulsing but didn't translate the bouncer (because we are
// unlocked.) Let's simply wake-up to dismiss the lock screen.
- mStatusBar.wakeUpIfDozing(SystemClock.uptimeMillis(), mContainer, "BOUNCER_VISIBLE");
+ mStatusBar.wakeUpIfDozing(SystemClock.uptimeMillis(), mStatusBar.getBouncerContainer(),
+ "BOUNCER_VISIBLE");
}
}
@@ -637,6 +633,18 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
});
return;
}
+
+ if (mStatusBar.isLaunchingActivityOverLockscreen()) {
+ mOccluded = true;
+
+ // When isLaunchingActivityOverLockscreen() is true, we know for sure that the post
+ // collapse runnables will be run.
+ mShadeController.get().addPostCollapseAction(() -> {
+ mNotificationShadeWindowController.setKeyguardOccluded(mOccluded);
+ reset(true /* hideBouncerWhenShowing */);
+ });
+ return;
+ }
} else if (!occluded && mOccluded && mShowing) {
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
@@ -816,7 +824,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
public void onKeyguardFadedAway() {
- mContainer.postDelayed(() -> mNotificationShadeWindowController
+ mNotificationContainer.postDelayed(() -> mNotificationShadeWindowController
.setKeyguardFadingAway(false), 100);
ViewGroupFadeHelper.reset(mNotificationPanelViewController.getView());
mStatusBar.finishKeyguardFadingAway();
@@ -941,10 +949,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
};
protected void updateStates() {
- if (mContainer == null ) {
- return;
- }
- int vis = mContainer.getSystemUiVisibility();
boolean showing = mShowing;
boolean occluded = mOccluded;
boolean bouncerShowing = mBouncer.isShowing();
@@ -956,9 +960,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
(mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive)
|| mFirstUpdate) {
if (bouncerDismissible || !showing || remoteInputActive) {
- mContainer.setSystemUiVisibility(vis & ~View.STATUS_BAR_DISABLE_BACK);
+ mBouncer.setBackButtonEnabled(true);
} else {
- mContainer.setSystemUiVisibility(vis | View.STATUS_BAR_DISABLE_BACK);
+ mBouncer.setBackButtonEnabled(false);
}
}
@@ -973,6 +977,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mStatusBar.setBouncerShowing(bouncerShowing);
}
+ if (occluded != mLastOccluded || mFirstUpdate) {
+ mKeyguardUpdateManager.onKeyguardOccludedChanged(occluded);
+ }
if ((showing && !occluded) != (mLastShowing && !mLastOccluded) || mFirstUpdate) {
mKeyguardUpdateManager.onKeyguardVisibilityChanged(showing && !occluded);
}
@@ -1022,11 +1029,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (delay == 0) {
mMakeNavigationBarVisibleRunnable.run();
} else {
- mContainer.postOnAnimationDelayed(mMakeNavigationBarVisibleRunnable,
+ mNotificationContainer.postOnAnimationDelayed(mMakeNavigationBarVisibleRunnable,
delay);
}
} else {
- mContainer.removeCallbacks(mMakeNavigationBarVisibleRunnable);
+ mNotificationContainer.removeCallbacks(mMakeNavigationBarVisibleRunnable);
mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
.hide(navigationBars());
}
@@ -1129,7 +1136,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
public void showBouncerMessage(String message, ColorStateList colorState) {
if (isShowingAlternateAuth()) {
if (mKeyguardMessageAreaController != null) {
- mKeyguardMessageAreaController.setNextMessageColor(colorState);
mKeyguardMessageAreaController.setMessage(message);
}
} else {
@@ -1352,4 +1358,4 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
void requestUdfps(boolean requestUdfps, int color);
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index 14e513a0556d..32aae6c05df6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -1,6 +1,7 @@
package com.android.systemui.statusbar.phone
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.LaunchAnimator
/**
* A [ActivityLaunchAnimator.Controller] that takes care of collapsing the status bar at the right
@@ -22,7 +23,7 @@ class StatusBarLaunchAnimatorController(
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
statusBar.notificationPanelViewController.setIsLaunchAnimationRunning(true)
if (!isExpandingFullyAbove) {
- statusBar.collapsePanelWithDuration(ActivityLaunchAnimator.ANIMATION_DURATION.toInt())
+ statusBar.collapsePanelWithDuration(LaunchAnimator.ANIMATION_DURATION.toInt())
}
}
@@ -33,7 +34,7 @@ class StatusBarLaunchAnimatorController(
}
override fun onLaunchAnimationProgress(
- state: ActivityLaunchAnimator.State,
+ state: LaunchAnimator.State,
progress: Float,
linearProgress: Float
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
new file mode 100644
index 000000000000..8ef186c316f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.systemui.statusbar.phone
+
+import android.view.View
+import android.view.WindowManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.ViewCenterProvider
+import com.android.systemui.unfold.UNFOLD_STATUS_BAR
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import javax.inject.Inject
+import javax.inject.Named
+
+@SysUISingleton
+class StatusBarMoveFromCenterAnimationController @Inject constructor(
+ @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider,
+ private val windowManager: WindowManager,
+) {
+
+ private val transitionListener = TransitionListener()
+ private var moveFromCenterAnimator: UnfoldMoveFromCenterAnimator? = null
+
+ fun onViewsReady(viewsToAnimate: Array<View>, viewCenterProvider: ViewCenterProvider) {
+ moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(windowManager,
+ viewCenterProvider = viewCenterProvider)
+
+ moveFromCenterAnimator?.updateDisplayProperties()
+
+ viewsToAnimate.forEach {
+ moveFromCenterAnimator?.registerViewForAnimation(it)
+ }
+
+ progressProvider.addCallback(transitionListener)
+ }
+
+ fun onViewDetached() {
+ progressProvider.removeCallback(transitionListener)
+ moveFromCenterAnimator?.clearRegisteredViews()
+ moveFromCenterAnimator = null
+ }
+
+ fun onStatusBarWidthChanged() {
+ moveFromCenterAnimator?.updateDisplayProperties()
+ moveFromCenterAnimator?.updateViewPositions()
+ }
+
+ private inner class TransitionListener : TransitionProgressListener {
+ override fun onTransitionProgress(progress: Float) {
+ moveFromCenterAnimator?.onTransitionProgress(progress)
+ }
+
+ override fun onTransitionFinished() {
+ // Reset translations when transition is stopped/cancelled
+ // (e.g. the transition could be cancelled mid-way when rotating the screen)
+ moveFromCenterAnimator?.onTransitionProgress(1f)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 9a6dd38ffca5..dba3b2418790 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -52,15 +52,14 @@ import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -72,6 +71,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowDragController;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -230,12 +230,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mLogger.logStartingActivityFromClick(sbn.getKey());
final NotificationEntry entry = row.getEntry();
- RemoteInputController controller = mRemoteInputManager.getController();
- if (controller.isRemoteInputActive(entry)
+ if (mRemoteInputManager.isRemoteInputActive(entry)
&& !TextUtils.isEmpty(row.getActiveRemoteInputText())) {
// We have an active remote input typed and the user clicked on the notification.
// this was probably unintentional, so we're closing the edit text instead.
- controller.closeRemoteInputs();
+ mRemoteInputManager.closeRemoteInputs();
return;
}
Notification notification = sbn.getNotification();
@@ -265,8 +264,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Override
public boolean onDismiss() {
return handleNotificationClickAfterKeyguardDismissed(
- entry, row, controller, intent,
- isActivityIntent, animate, showOverLockscreen);
+ entry, row, intent, isActivityIntent, animate, showOverLockscreen);
}
@Override
@@ -286,7 +284,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private boolean handleNotificationClickAfterKeyguardDismissed(
NotificationEntry entry,
ExpandableNotificationRow row,
- RemoteInputController controller,
PendingIntent intent,
boolean isActivityIntent,
boolean animate,
@@ -294,8 +291,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mLogger.logHandleClickAfterKeyguardDismissed(entry.getKey());
final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed(
- entry, row, controller, intent,
- isActivityIntent, animate);
+ entry, row, intent, isActivityIntent, animate);
if (showOverLockscreen) {
mShadeController.addPostCollapseAction(runnable);
@@ -315,7 +311,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private void handleNotificationClickAfterPanelCollapsed(
NotificationEntry entry,
ExpandableNotificationRow row,
- RemoteInputController controller,
PendingIntent intent,
boolean isActivityIntent,
boolean animate) {
@@ -354,7 +349,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
if (!TextUtils.isEmpty(entry.remoteInputText)) {
remoteInputText = entry.remoteInputText;
}
- if (!TextUtils.isEmpty(remoteInputText) && !controller.isSpinning(notificationKey)) {
+ if (!TextUtils.isEmpty(remoteInputText)
+ && !mRemoteInputManager.isSpinning(notificationKey)) {
fillInIntent = new Intent().putExtra(Notification.EXTRA_REMOTE_INPUT_DRAFT,
remoteInputText.toString());
}
@@ -407,6 +403,53 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mIsCollapsingToShowActivityOverLockscreen = false;
}
+ /**
+ * Called when a notification is dropped on proper target window.
+ * Intent that is included in this entry notification,
+ * will be sent by {@link ExpandableNotificationRowDragController}
+ *
+ * @param entry notification entry that is dropped.
+ */
+ @Override
+ public void onDragSuccess(NotificationEntry entry) {
+ // this method is not responsible for intent sending.
+ // will focus follow operation only after drag-and-drop that notification.
+ NotificationVisibility.NotificationLocation location =
+ NotificationLogger.getNotificationLocation(entry);
+ final NotificationVisibility nv = NotificationVisibility.obtain(entry.getKey(),
+ entry.getRanking().getRank(), getVisibleNotificationsCount(), true, location);
+
+ // retrieve the group summary to remove with this entry before we tell NMS the
+ // notification was clicked to avoid a race condition
+ final boolean shouldAutoCancel = shouldAutoCancel(entry.getSbn());
+ final NotificationEntry summaryToRemove = shouldAutoCancel
+ ? mOnUserInteractionCallback.getGroupSummaryToDismiss(entry) : null;
+
+ String notificationKey = entry.getKey();
+ // inform NMS that the notification was clicked
+ mClickNotifier.onNotificationClick(notificationKey, nv);
+
+ if (shouldAutoCancel || mRemoteInputManager.isNotificationKeptForRemoteInputHistory(
+ notificationKey)) {
+ // Immediately remove notification from visually showing.
+ // We have to post the removal to the UI thread for synchronization.
+ mMainThreadHandler.post(() -> {
+ final Runnable removeNotification = () ->
+ mOnUserInteractionCallback.onDismiss(
+ entry, REASON_CLICK, summaryToRemove);
+ if (mPresenter.isCollapsing()) {
+ // To avoid lags we're only performing the remove
+ // after the shade is collapsed
+ mShadeController.addPostCollapseAction(removeNotification);
+ } else {
+ removeNotification.run();
+ }
+ });
+ }
+
+ mIsCollapsingToShowActivityOverLockscreen = false;
+ }
+
private void expandBubbleStackOnMainThread(NotificationEntry entry) {
if (!mBubblesManagerOptional.isPresent()) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 8821de077ec3..ecd5c985154c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -17,15 +17,12 @@ package com.android.systemui.statusbar.phone;
import static com.android.systemui.statusbar.phone.StatusBar.CLOSE_PANEL_WHEN_EMPTIED;
import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
import static com.android.systemui.statusbar.phone.StatusBar.MULTIUSER_DEBUG;
-import static com.android.systemui.statusbar.phone.StatusBar.SPEW;
-import android.annotation.Nullable;
import android.app.KeyguardManager;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
@@ -37,7 +34,6 @@ import android.widget.TextView;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.MessagingGroup;
import com.android.internal.widget.MessagingMessage;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -45,9 +41,9 @@ import com.android.systemui.Dependency;
import com.android.systemui.ForegroundServiceNotificationListener;
import com.android.systemui.InitController;
import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -61,11 +57,10 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -84,28 +79,19 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
ConfigurationController.ConfigurationListener,
NotificationRowBinderImpl.BindRowCallback,
CommandQueue.Callbacks {
-
- private final LockscreenGestureLogger mLockscreenGestureLogger =
- Dependency.get(LockscreenGestureLogger.class);
-
private static final String TAG = "StatusBarNotificationPresenter";
private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
private final KeyguardStateController mKeyguardStateController;
- private final NotificationViewHierarchyManager mViewHierarchyManager =
- Dependency.get(NotificationViewHierarchyManager.class);
- private final NotificationLockscreenUserManager mLockscreenUserManager =
- Dependency.get(NotificationLockscreenUserManager.class);
- private final SysuiStatusBarStateController mStatusBarStateController =
- (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class);
- private final NotificationEntryManager mEntryManager =
- Dependency.get(NotificationEntryManager.class);
- private final NotificationMediaManager mMediaManager =
- Dependency.get(NotificationMediaManager.class);
- private final VisualStabilityManager mVisualStabilityManager =
- Dependency.get(VisualStabilityManager.class);
- private final NotificationGutsManager mGutsManager =
- Dependency.get(NotificationGutsManager.class);
+ private final NotificationViewHierarchyManager mViewHierarchyManager;
+ private final NotificationLockscreenUserManager mLockscreenUserManager;
+ private final SysuiStatusBarStateController mStatusBarStateController;
+ private final NotifShadeEventSource mNotifShadeEventSource;
+ private final NotificationEntryManager mEntryManager;
+ private final NotificationMediaManager mMediaManager;
+ private final NotificationGutsManager mGutsManager;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final LockscreenGestureLogger mLockscreenGestureLogger;
private final NotificationPanelViewController mNotificationPanel;
private final HeadsUpManagerPhone mHeadsUpManager;
@@ -113,6 +99,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
private final DozeScrimController mDozeScrimController;
private final ScrimController mScrimController;
private final KeyguardIndicationController mKeyguardIndicationController;
+ private final FeatureFlags mFeatureFlags;
private final StatusBar mStatusBar;
private final ShadeController mShadeController;
private final LockscreenShadeTransitionController mShadeTransitionController;
@@ -140,22 +127,44 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
DynamicPrivacyController dynamicPrivacyController,
KeyguardStateController keyguardStateController,
KeyguardIndicationController keyguardIndicationController,
+ FeatureFlags featureFlags,
StatusBar statusBar,
ShadeController shadeController,
LockscreenShadeTransitionController shadeTransitionController,
CommandQueue commandQueue,
+ NotificationViewHierarchyManager notificationViewHierarchyManager,
+ NotificationLockscreenUserManager lockscreenUserManager,
+ SysuiStatusBarStateController sysuiStatusBarStateController,
+ NotifShadeEventSource notifShadeEventSource,
+ NotificationEntryManager notificationEntryManager,
+ NotificationMediaManager notificationMediaManager,
+ NotificationGutsManager notificationGutsManager,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ LockscreenGestureLogger lockscreenGestureLogger,
InitController initController,
- NotificationInterruptStateProvider notificationInterruptStateProvider) {
+ NotificationInterruptStateProvider notificationInterruptStateProvider,
+ NotificationRemoteInputManager remoteInputManager,
+ ConfigurationController configurationController) {
mKeyguardStateController = keyguardStateController;
mNotificationPanel = panel;
mHeadsUpManager = headsUp;
mDynamicPrivacyController = dynamicPrivacyController;
mKeyguardIndicationController = keyguardIndicationController;
+ mFeatureFlags = featureFlags;
// TODO: use KeyguardStateController#isOccluded to remove this dependency
mStatusBar = statusBar;
mShadeController = shadeController;
mShadeTransitionController = shadeTransitionController;
mCommandQueue = commandQueue;
+ mViewHierarchyManager = notificationViewHierarchyManager;
+ mLockscreenUserManager = lockscreenUserManager;
+ mStatusBarStateController = sysuiStatusBarStateController;
+ mNotifShadeEventSource = notifShadeEventSource;
+ mEntryManager = notificationEntryManager;
+ mMediaManager = notificationMediaManager;
+ mGutsManager = notificationGutsManager;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mLockscreenGestureLogger = lockscreenGestureLogger;
mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView());
mNotificationShadeWindowController = notificationShadeWindowController;
mAboveShelfObserver.setListener(statusBarWindow.findViewById(
@@ -176,39 +185,23 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
Slog.e(TAG, "Failed to register VR mode state listener: " + e);
}
}
- NotificationRemoteInputManager remoteInputManager =
- Dependency.get(NotificationRemoteInputManager.class);
remoteInputManager.setUpWithCallback(
Dependency.get(NotificationRemoteInputManager.Callback.class),
mNotificationPanel.createRemoteInputDelegate());
- remoteInputManager.getController().addCallback(
- Dependency.get(NotificationShadeWindowController.class));
initController.addPostInitTask(() -> {
- NotificationEntryListener notificationEntryListener = new NotificationEntryListener() {
- @Override
- public void onEntryRemoved(
- @Nullable NotificationEntry entry,
- NotificationVisibility visibility,
- boolean removedByUser,
- int reason) {
- StatusBarNotificationPresenter.this.onNotificationRemoved(
- entry.getKey(), entry.getSbn(), reason);
- if (removedByUser) {
- maybeEndAmbientPulse();
- }
- }
- };
-
mKeyguardIndicationController.init();
mViewHierarchyManager.setUpWithPresenter(this,
stackScrollerController.getNotificationListContainer());
- mEntryManager.setUpWithPresenter(this);
- mEntryManager.addNotificationEntryListener(notificationEntryListener);
- mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager);
- mEntryManager.addNotificationLifetimeExtender(mGutsManager);
- mEntryManager.addNotificationLifetimeExtenders(
- remoteInputManager.getLifetimeExtenders());
+ mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
+ mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse);
+ if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ mEntryManager.setUpWithPresenter(this);
+ mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager);
+ mEntryManager.addNotificationLifetimeExtender(mGutsManager);
+ mEntryManager.addNotificationLifetimeExtenders(
+ remoteInputManager.getLifetimeExtenders());
+ }
notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
mLockscreenUserManager.setUpWithPresenter(this);
mMediaManager.setUpWithPresenter(this);
@@ -222,14 +215,27 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
onUserSwitched(mLockscreenUserManager.getCurrentUserId());
});
- Dependency.get(ConfigurationController.class).addCallback(this);
+ configurationController.addCallback(this);
+ }
+
+ /** Called when the shade has been emptied to attempt to close the shade */
+ private void maybeClosePanelForShadeEmptied() {
+ if (CLOSE_PANEL_WHEN_EMPTIED
+ && !mNotificationPanel.isTracking()
+ && !mNotificationPanel.isQsExpanded()
+ && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
+ && !isCollapsing()) {
+ mStatusBarStateController.setState(StatusBarState.KEYGUARD);
+ }
}
@Override
public void onDensityOrFontScaleChanged() {
+ // TODO(b/145659174): Remove legacy pipeline code
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return;
MessagingMessage.dropCache();
MessagingGroup.dropCache();
- if (!Dependency.get(KeyguardUpdateMonitor.class).isSwitchingUser()) {
+ if (!mKeyguardUpdateMonitor.isSwitchingUser()) {
updateNotificationsOnDensityOrFontScaleChanged();
} else {
mReinflateNotificationsOnUserSwitched = true;
@@ -238,19 +244,23 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
@Override
public void onUiModeChanged() {
- if (!Dependency.get(KeyguardUpdateMonitor.class).isSwitchingUser()) {
- updateNotificationOnUiModeChanged();
+ // TODO(b/145659174): Remove legacy pipeline code
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return;
+ if (!mKeyguardUpdateMonitor.isSwitchingUser()) {
+ updateNotificationsOnUiModeChanged();
} else {
mDispatchUiModeChangeOnUserSwitched = true;
}
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
onDensityOrFontScaleChanged();
}
- private void updateNotificationOnUiModeChanged() {
+ private void updateNotificationsOnUiModeChanged() {
+ // TODO(b/145659174): Remove legacy pipeline code
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return;
List<NotificationEntry> userNotifications =
mEntryManager.getActiveNotificationsForCurrentUser();
for (int i = 0; i < userNotifications.size(); i++) {
@@ -263,6 +273,8 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
}
private void updateNotificationsOnDensityOrFontScaleChanged() {
+ // TODO(b/145659174): Remove legacy pipeline code
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) return;
List<NotificationEntry> userNotifications =
mEntryManager.getActiveNotificationsForCurrentUser();
for (int i = 0; i < userNotifications.size(); i++) {
@@ -275,6 +287,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
}
}
+
@Override
public boolean isCollapsing() {
return mNotificationPanel.isCollapsing()
@@ -301,27 +314,10 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
mShadeController.addPostCollapseAction(() -> updateNotificationViews(reason));
return;
}
-
mViewHierarchyManager.updateNotificationViews();
-
mNotificationPanel.updateNotificationViews(reason);
}
- private void onNotificationRemoved(String key, StatusBarNotification old, int reason) {
- if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
-
- if (old != null && CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
- && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()
- && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
- && !isCollapsing()) {
- mStatusBarStateController.setState(StatusBarState.KEYGUARD);
- }
- }
-
- public boolean hasActiveNotifications() {
- return mEntryManager.hasActiveNotifications();
- }
-
@Override
public void onUserSwitched(int newUserId) {
// Begin old BaseStatusBar.userSwitched
@@ -334,7 +330,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
mReinflateNotificationsOnUserSwitched = false;
}
if (mDispatchUiModeChangeOnUserSwitched) {
- updateNotificationOnUiModeChanged();
+ updateNotificationsOnUiModeChanged();
mDispatchUiModeChangeOnUserSwitched = false;
}
updateNotificationViews("user switched");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index fe52281652a9..fbd9ef7e3707 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -25,12 +25,12 @@ import android.util.Log;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
-import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
+import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
+import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
+import com.android.systemui.statusbar.connectivity.NetworkControllerImpl;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index d3d90639546a..b742394b18a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -29,6 +29,7 @@ import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
import android.view.WindowInsets;
+import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.ScreenDecorations;
@@ -83,7 +84,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable {
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
initResources();
}
});
@@ -172,8 +173,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable {
Resources resources = mContext.getResources();
mDisplayCutoutTouchableRegionSize = resources.getDimensionPixelSize(
com.android.internal.R.dimen.display_cutout_touchable_region_size);
- mStatusBarHeight =
- resources.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
+ mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
index f33ff2732cda..ac43b679da0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
@@ -16,5 +16,6 @@
package com.android.systemui.statusbar.phone;
public interface StatusBarWindowCallback {
- void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing);
+ void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing,
+ boolean isDozing);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index 9a25a7078859..9d2dbc12c97d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
@@ -34,13 +36,14 @@ import android.os.RemoteException;
import android.util.Log;
import android.view.Gravity;
import android.view.IWindowManager;
+import android.view.Surface;
import android.view.ViewGroup;
import android.view.WindowManager;
+import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import javax.inject.Inject;
@@ -55,14 +58,13 @@ public class StatusBarWindowController {
private final Context mContext;
private final WindowManager mWindowManager;
private final IWindowManager mIWindowManager;
- private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
private final StatusBarContentInsetsProvider mContentInsetsProvider;
private final Resources mResources;
private int mBarHeight = -1;
private final State mCurrentState = new State();
- private ViewGroup mStatusBarView;
- private ViewGroup mLaunchAnimationContainer;
+ private final ViewGroup mStatusBarView;
+ private final ViewGroup mLaunchAnimationContainer;
private WindowManager.LayoutParams mLp;
private final WindowManager.LayoutParams mLpChanged;
@@ -71,23 +73,21 @@ public class StatusBarWindowController {
Context context,
WindowManager windowManager,
IWindowManager iWindowManager,
- SuperStatusBarViewFactory superStatusBarViewFactory,
+ StatusBarWindowView statusBarWindowView,
StatusBarContentInsetsProvider contentInsetsProvider,
@Main Resources resources) {
mContext = context;
mWindowManager = windowManager;
mIWindowManager = iWindowManager;
mContentInsetsProvider = contentInsetsProvider;
- mSuperStatusBarViewFactory = superStatusBarViewFactory;
- mStatusBarView = mSuperStatusBarViewFactory.getStatusBarWindowView();
+ mStatusBarView = statusBarWindowView;
mLaunchAnimationContainer = mStatusBarView.findViewById(
R.id.status_bar_launch_animation_container);
mLpChanged = new WindowManager.LayoutParams();
mResources = resources;
if (mBarHeight < 0) {
- mBarHeight = mResources.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
+ mBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
}
}
@@ -96,12 +96,11 @@ public class StatusBarWindowController {
}
/**
- * Rereads the status_bar_height from configuration and reapplys the current state if the height
+ * Rereads the status bar height and reapplys the current state if the height
* is different.
*/
public void refreshStatusBarHeight() {
- int heightFromConfig = mResources.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
+ int heightFromConfig = SystemBarUtils.getStatusBarHeight(mContext);
if (mBarHeight != heightFromConfig) {
mBarHeight = heightFromConfig;
@@ -118,21 +117,7 @@ public class StatusBarWindowController {
// Now that the status bar window encompasses the sliding panel and its
// translucent backdrop, the entire thing is made TRANSLUCENT and is
// hardware-accelerated.
- mLp = new WindowManager.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- mBarHeight,
- WindowManager.LayoutParams.TYPE_STATUS_BAR,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
- | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
- PixelFormat.TRANSLUCENT);
- mLp.privateFlags |= PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
- mLp.token = new Binder();
- mLp.gravity = Gravity.TOP;
- mLp.setFitInsetsTypes(0 /* types */);
- mLp.setTitle("StatusBar");
- mLp.packageName = mContext.getPackageName();
- mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mLp = getBarLayoutParams(mContext.getDisplay().getRotation());
mWindowManager.addView(mStatusBarView, mLp);
mLpChanged.copyFrom(mLp);
@@ -141,6 +126,54 @@ public class StatusBarWindowController {
calculateStatusBarLocationsForAllRotations();
}
+ private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
+ WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation);
+ lp.paramsForRotation = new WindowManager.LayoutParams[4];
+ for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+ lp.paramsForRotation[rot] = getBarLayoutParamsForRotation(rot);
+ }
+ return lp;
+ }
+
+ private WindowManager.LayoutParams getBarLayoutParamsForRotation(int rotation) {
+ int height = mBarHeight;
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ switch (rotation) {
+ case ROTATION_UNDEFINED:
+ case Surface.ROTATION_0:
+ case Surface.ROTATION_180:
+ height = SystemBarUtils.getStatusBarHeightForRotation(
+ mContext, Surface.ROTATION_0);
+ break;
+ case Surface.ROTATION_90:
+ height = SystemBarUtils.getStatusBarHeightForRotation(
+ mContext, Surface.ROTATION_90);
+ break;
+ case Surface.ROTATION_270:
+ height = SystemBarUtils.getStatusBarHeightForRotation(
+ mContext, Surface.ROTATION_270);
+ break;
+ }
+ }
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ height,
+ WindowManager.LayoutParams.TYPE_STATUS_BAR,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+ | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
+ lp.token = new Binder();
+ lp.gravity = Gravity.TOP;
+ lp.setFitInsetsTypes(0 /* types */);
+ lp.setTitle("StatusBar");
+ lp.packageName = mContext.getPackageName();
+ lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ return lp;
+
+ }
+
private void calculateStatusBarLocationsForAllRotations() {
Rect[] bounds = new Rect[4];
bounds[0] = mContentInsetsProvider
@@ -166,6 +199,21 @@ public class StatusBarWindowController {
}
/**
+ * Sets whether an ongoing process requires the status bar to be forced visible.
+ *
+ * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing
+ * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to
+ * false but this method is set to true, then the status bar **will** be visible.
+ *
+ * TODO(b/195839150): We should likely merge this method and
+ * {@link this#setForceStatusBarVisible} together and use some sort of ranking system instead.
+ */
+ public void setOngoingProcessRequiresStatusBarVisible(boolean visible) {
+ mCurrentState.mOngoingProcessRequiresStatusBarVisible = visible;
+ apply(mCurrentState);
+ }
+
+ /**
* Return the container in which we should run launch animations started from the status bar and
* expanding into the opening window.
*
@@ -205,10 +253,14 @@ public class StatusBarWindowController {
private static class State {
boolean mForceStatusBarVisible;
boolean mIsLaunchAnimationRunning;
+ boolean mOngoingProcessRequiresStatusBarVisible;
}
private void applyForceStatusBarVisibleFlag(State state) {
- if (state.mForceStatusBarVisible || state.mIsLaunchAnimationRunning) {
+ if (state.mForceStatusBarVisible
+ || state.mIsLaunchAnimationRunning
+ // Don't force-show the status bar if the user has already dismissed it.
+ || state.mOngoingProcessRequiresStatusBarVisible) {
mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
} else {
mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 1e98c75f2616..18aa6893e7bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -22,7 +22,11 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.SystemProperties;
import android.os.UserHandle;
+import android.util.TypedValue;
+import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets.Type;
import android.view.WindowManager;
@@ -30,19 +34,28 @@ import android.view.WindowManager.LayoutParams;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.animation.DialogListener;
+import com.android.systemui.animation.ListenableDialog;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
/**
* Base class for dialogs that should appear over panels and keyguard.
* The SystemUIDialog registers a listener for the screen off / close system dialogs broadcast,
* and dismisses itself when it receives the broadcast.
*/
-public class SystemUIDialog extends AlertDialog {
+public class SystemUIDialog extends AlertDialog implements ListenableDialog {
+ // TODO(b/203389579): Remove this once the dialog width on large screens has been agreed on.
+ private static final String FLAG_TABLET_DIALOG_WIDTH =
+ "persist.systemui.flag_tablet_dialog_width";
private final Context mContext;
private final DismissReceiver mDismissReceiver;
+ private final Set<DialogListener> mDialogListeners = new LinkedHashSet<>();
public SystemUIDialog(Context context) {
this(context, R.style.Theme_SystemUI_Dialog);
@@ -61,6 +74,41 @@ public class SystemUIDialog extends AlertDialog {
}
@Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Set the dialog window size.
+ getWindow().setLayout(getDialogWidth(), ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+
+ private int getDialogWidth() {
+ boolean isOnTablet =
+ mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
+ if (!isOnTablet) {
+ return ViewGroup.LayoutParams.MATCH_PARENT;
+ }
+
+ int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0);
+ if (flagValue == -1) {
+ // The width of bottom sheets (624dp).
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624,
+ mContext.getResources().getDisplayMetrics()));
+ } else if (flagValue == -2) {
+ // The suggested small width for all dialogs (348dp)
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348,
+ mContext.getResources().getDisplayMetrics()));
+ } else if (flagValue > 0) {
+ // Any given width.
+ return Math.round(
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue,
+ mContext.getResources().getDisplayMetrics()));
+ } else {
+ // By default we use the same width as the notification shade in portrait mode (504dp).
+ return mContext.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
+ }
+ }
+
+ @Override
protected void onStart() {
super.onStart();
mDismissReceiver.register();
@@ -72,6 +120,43 @@ public class SystemUIDialog extends AlertDialog {
mDismissReceiver.unregister();
}
+ @Override
+ public void addListener(DialogListener listener) {
+ mDialogListeners.add(listener);
+ }
+
+ @Override
+ public void removeListener(DialogListener listener) {
+ mDialogListeners.remove(listener);
+ }
+
+ @Override
+ public void dismiss() {
+ super.dismiss();
+
+ for (DialogListener listener : new LinkedHashSet<>(mDialogListeners)) {
+ listener.onDismiss();
+ }
+ }
+
+ @Override
+ public void hide() {
+ super.hide();
+
+ for (DialogListener listener : new LinkedHashSet<>(mDialogListeners)) {
+ listener.onHide();
+ }
+ }
+
+ @Override
+ public void show() {
+ super.show();
+
+ for (DialogListener listener : new LinkedHashSet<>(mDialogListeners)) {
+ listener.onShow();
+ }
+ }
+
public void setShowForAllUsers(boolean show) {
setShowForAllUsers(this, show);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIHostDialogProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIHostDialogProvider.kt
new file mode 100644
index 000000000000..6a49a6da0d62
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIHostDialogProvider.kt
@@ -0,0 +1,36 @@
+package com.android.systemui.statusbar.phone
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import com.android.systemui.animation.HostDialogProvider
+
+/** An implementation of [HostDialogProvider] to be used when animating SysUI dialogs. */
+class SystemUIHostDialogProvider : HostDialogProvider {
+ override fun createHostDialog(
+ context: Context,
+ theme: Int,
+ onCreateCallback: () -> Unit,
+ dismissOverride: (() -> Unit) -> Unit
+ ): Dialog {
+ return SystemUIHostDialog(context, theme, onCreateCallback, dismissOverride)
+ }
+
+ private class SystemUIHostDialog(
+ context: Context,
+ theme: Int,
+ private val onCreateCallback: () -> Unit,
+ private val dismissOverride: (() -> Unit) -> Unit
+ ) : SystemUIDialog(context, theme) {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ onCreateCallback()
+ }
+
+ override fun dismiss() {
+ dismissOverride {
+ super.dismiss()
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
index 0c5502bac8fc..26ba31c6f526 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
@@ -43,11 +43,6 @@ public class TapAgainViewController extends ViewController<TapAgainView> {
@VisibleForTesting
final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
@Override
- public void onOverlayChanged() {
- mView.updateColor();
- }
-
- @Override
public void onUiModeChanged() {
mView.updateColor();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index e3f4b03dc4f2..cddde6487ba2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -4,10 +4,10 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
-import android.content.res.Configuration
import android.database.ContentObserver
import android.os.Handler
import android.provider.Settings
+import android.view.Surface
import android.view.View
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
@@ -161,7 +161,6 @@ class UnlockedScreenOffAnimationController @Inject constructor(
// Done going to sleep, reset this flag.
decidedToAnimateGoingToSleep = null
-
// We need to unset the listener. These are persistent for future animators
keyguardView.animate().setListener(null)
}
@@ -231,6 +230,12 @@ class UnlockedScreenOffAnimationController @Inject constructor(
return false
}
+ // If animations are disabled system-wide, don't play this one either.
+ if (Settings.Global.getString(
+ context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE) == "0") {
+ return false
+ }
+
// We only play the unlocked screen off animation if we are... unlocked.
if (statusBarStateControllerImpl.state != StatusBarState.SHADE) {
return false
@@ -244,10 +249,11 @@ class UnlockedScreenOffAnimationController @Inject constructor(
return false
}
- // If we're not allowed to rotate the keyguard, then only do the screen off animation if
- // we're in portrait. Otherwise, AOD will animate in sideways, which looks weird.
+ // If we're not allowed to rotate the keyguard, it can only be displayed in zero-degree
+ // portrait. If we're in another orientation, disable the screen off animation so we don't
+ // animate in the keyguard AOD UI sideways or upside down.
if (!keyguardStateController.isKeyguardScreenRotationAllowed &&
- context.resources.configuration.orientation != Configuration.ORIENTATION_PORTRAIT) {
+ context.display.rotation != Surface.ROTATION_0) {
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
index fb25ae37ea74..418f5884ef62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
@@ -20,9 +20,15 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.android.keyguard.LockIconViewController;
import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.statusbar.NotificationShelfController;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
+import com.android.systemui.statusbar.phone.SplitShadeHeaderController;
+import com.android.systemui.statusbar.phone.StatusBarCommandQueueCallbacks;
+import com.android.systemui.statusbar.phone.StatusBarDemoMode;
+import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
import java.lang.annotation.Documented;
@@ -30,7 +36,6 @@ import java.lang.annotation.Retention;
import javax.inject.Scope;
-import dagger.BindsInstance;
import dagger.Subcomponent;
/**
@@ -42,11 +47,9 @@ public interface StatusBarComponent {
/**
* Builder for {@link StatusBarComponent}.
*/
- @Subcomponent.Builder
- interface Builder {
- @BindsInstance Builder statusBarWindowView(
- NotificationShadeWindowView notificationShadeWindowView);
- StatusBarComponent build();
+ @Subcomponent.Factory
+ interface Factory {
+ StatusBarComponent create();
}
/**
@@ -58,6 +61,21 @@ public interface StatusBarComponent {
@interface StatusBarScope {}
/**
+ * Creates a {@link NotificationShadeWindowView}/
+ * @return
+ */
+ @StatusBarScope
+ NotificationShadeWindowView getNotificationShadeWindowView();
+
+ /** */
+ @StatusBarScope
+ NotificationShelfController getNotificationShelfController();
+
+ /** */
+ @StatusBarScope
+ NotificationStackScrollLayoutController getNotificationStackScrollLayoutController();
+
+ /**
* Creates a NotificationShadeWindowViewController.
*/
@StatusBarScope
@@ -86,4 +104,28 @@ public interface StatusBarComponent {
*/
@StatusBarScope
AuthRippleController getAuthRippleController();
+
+ /**
+ * Creates a StatusBarDemoMode.
+ */
+ @StatusBarScope
+ StatusBarDemoMode getStatusBarDemoMode();
+
+ /**
+ * Creates a StatusBarHeadsUpChangeListener.
+ */
+ @StatusBarScope
+ StatusBarHeadsUpChangeListener getStatusBarHeadsUpChangeListener();
+
+ /**
+ * Creates a StatusBarCommandQueueCallbacks.
+ */
+ @StatusBarScope
+ StatusBarCommandQueueCallbacks getStatusBarCommandQueueCallbacks();
+
+ /**
+ * Creates a SplitShadeHeaderController.
+ */
+ @StatusBarScope
+ SplitShadeHeaderController getSplitShadeHeaderController();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index b6e8bd8bf7c1..959c67334108 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -18,26 +18,28 @@ package com.android.systemui.statusbar.phone.dagger;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
+import android.app.WallpaperManager;
import android.content.Context;
import android.os.Handler;
import android.os.PowerManager;
import android.util.DisplayMetrics;
-import androidx.annotation.Nullable;
-
import com.android.internal.logging.MetricsLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.InitController;
-import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
+import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.demomode.DemoModeController;
-import com.android.systemui.keyguard.DismissCallbackRegistry;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
@@ -46,10 +48,9 @@ import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.recents.ScreenPinningRequest;
-import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -58,15 +59,16 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
+import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.charging.WiredChargingRippleController;
+import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
@@ -74,27 +76,30 @@ import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
+import com.android.systemui.statusbar.phone.CollapsedStatusBarFragmentLogger;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.DozeScrimController;
import com.android.systemui.statusbar.phone.DozeServiceHost;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
-import com.android.systemui.statusbar.phone.KeyguardLiftController;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LightsOutNotifController;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.LockscreenWallpaper;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
+import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
+import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy;
import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
+import com.android.systemui.statusbar.phone.StatusBarWindowView;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -102,10 +107,16 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
+import com.android.systemui.unfold.UnfoldTransitionWallpaperController;
+import com.android.systemui.unfold.config.UnfoldTransitionConfig;
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
+import com.android.systemui.util.WallpaperController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.MessageRouter;
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubbles;
@@ -116,7 +127,6 @@ import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Named;
-import javax.inject.Provider;
import dagger.Lazy;
import dagger.Module;
@@ -138,7 +148,6 @@ public interface StatusBarPhoneModule {
LightBarController lightBarController,
AutoHideController autoHideController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- StatusBarSignalPolicy signalPolicy,
PulseExpansionHandler pulseExpansionHandler,
NotificationWakeUpCoordinator notificationWakeUpCoordinator,
KeyguardBypassController keyguardBypassController,
@@ -149,7 +158,8 @@ public interface StatusBarPhoneModule {
FalsingManager falsingManager,
FalsingCollector falsingCollector,
BroadcastDispatcher broadcastDispatcher,
- RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
+ NotifShadeEventSource notifShadeEventSource,
+ NotificationEntryManager notificationEntryManager,
NotificationGutsManager notificationGutsManager,
NotificationLogger notificationLogger,
NotificationInterruptStateProvider notificationInterruptStateProvider,
@@ -168,20 +178,18 @@ public interface StatusBarPhoneModule {
ScreenLifecycle screenLifecycle,
WakefulnessLifecycle wakefulnessLifecycle,
SysuiStatusBarStateController statusBarStateController,
- VibratorHelper vibratorHelper,
Optional<BubblesManager> bubblesManagerOptional,
Optional<Bubbles> bubblesOptional,
VisualStabilityManager visualStabilityManager,
DeviceProvisionedController deviceProvisionedController,
NavigationBarController navigationBarController,
- AccessibilityFloatingMenuController accessibilityFloatingMenuController,
Lazy<AssistManager> assistManagerLazy,
ConfigurationController configurationController,
NotificationShadeWindowController notificationShadeWindowController,
DozeParameters dozeParameters,
ScrimController scrimController,
- @Nullable KeyguardLiftController keyguardLiftController,
Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
+ LockscreenGestureLogger lockscreenGestureLogger,
Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
DozeServiceHost dozeServiceHost,
PowerManager powerManager,
@@ -189,14 +197,15 @@ public interface StatusBarPhoneModule {
DozeScrimController dozeScrimController,
VolumeComponent volumeComponent,
CommandQueue commandQueue,
- Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
+ CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
+ StatusBarComponent.Factory statusBarComponentFactory,
PluginManager pluginManager,
Optional<LegacySplitScreen> splitScreenOptional,
LightsOutNotifController lightsOutNotifController,
StatusBarNotificationActivityStarter.Builder
statusBarNotificationActivityStarterBuilder,
ShadeController shadeController,
- SuperStatusBarViewFactory superStatusBarViewFactory,
+ StatusBarWindowView statusBarWindowView,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
ViewMediatorCallback viewMediatorCallback,
InitController initController,
@@ -205,15 +214,21 @@ public interface StatusBarPhoneModule {
KeyguardDismissUtil keyguardDismissUtil,
ExtensionController extensionController,
UserInfoControllerImpl userInfoControllerImpl,
+ OperatorNameViewController.Factory operatorNameViewControllerFactory,
+ PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory,
PhoneStatusBarPolicy phoneStatusBarPolicy,
KeyguardIndicationController keyguardIndicationController,
DemoModeController demoModeController,
Lazy<NotificationShadeDepthController> notificationShadeDepthController,
- DismissCallbackRegistry dismissCallbackRegistry,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
NotificationIconAreaController notificationIconAreaController,
- BrightnessSlider.Factory brightnessSliderFactory,
- WiredChargingRippleController chargingRippleAnimationController,
+ BrightnessSliderController.Factory brightnessSliderFactory,
+ UnfoldTransitionConfig unfoldTransitionConfig,
+ Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation,
+ Lazy<NaturalRotationUnfoldProgressProvider> naturalRotationUnfoldProgressProvider,
+ Lazy<UnfoldTransitionWallpaperController> unfoldTransitionWallpaperController,
+ Lazy<StatusBarMoveFromCenterAnimationController> statusBarMoveFromCenterAnimation,
+ WallpaperController wallpaperController,
OngoingCallController ongoingCallController,
SystemStatusAnimationScheduler animationScheduler,
StatusBarLocationPublisher locationPublisher,
@@ -221,15 +236,22 @@ public interface StatusBarPhoneModule {
LockscreenShadeTransitionController transitionController,
FeatureFlags featureFlags,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+ @Main Handler mainHandler,
+ @Main DelayableExecutor delayableExecutor,
+ @Main MessageRouter messageRouter,
+ WallpaperManager wallpaperManager,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
- Optional<StartingSurface> startingSurfaceOptional) {
+ Optional<StartingSurface> startingSurfaceOptional,
+ TunerService tunerService,
+ DumpManager dumpManager,
+ ActivityLaunchAnimator activityLaunchAnimator,
+ DialogLaunchAnimator dialogLaunchAnimator) {
return new StatusBar(
context,
notificationsController,
lightBarController,
autoHideController,
keyguardUpdateMonitor,
- signalPolicy,
pulseExpansionHandler,
notificationWakeUpCoordinator,
keyguardBypassController,
@@ -240,7 +262,8 @@ public interface StatusBarPhoneModule {
falsingManager,
falsingCollector,
broadcastDispatcher,
- remoteInputQuickSettingsDisabler,
+ notifShadeEventSource,
+ notificationEntryManager,
notificationGutsManager,
notificationLogger,
notificationInterruptStateProvider,
@@ -259,20 +282,18 @@ public interface StatusBarPhoneModule {
screenLifecycle,
wakefulnessLifecycle,
statusBarStateController,
- vibratorHelper,
bubblesManagerOptional,
bubblesOptional,
visualStabilityManager,
deviceProvisionedController,
navigationBarController,
- accessibilityFloatingMenuController,
assistManagerLazy,
configurationController,
notificationShadeWindowController,
dozeParameters,
scrimController,
- keyguardLiftController,
lockscreenWallpaperLazy,
+ lockscreenGestureLogger,
biometricUnlockControllerLazy,
dozeServiceHost,
powerManager,
@@ -280,13 +301,14 @@ public interface StatusBarPhoneModule {
dozeScrimController,
volumeComponent,
commandQueue,
- statusBarComponentBuilder,
+ collapsedStatusBarFragmentLogger,
+ statusBarComponentFactory,
pluginManager,
splitScreenOptional,
lightsOutNotifController,
statusBarNotificationActivityStarterBuilder,
shadeController,
- superStatusBarViewFactory,
+ statusBarWindowView,
statusBarKeyguardViewManager,
viewMediatorCallback,
initController,
@@ -295,15 +317,20 @@ public interface StatusBarPhoneModule {
keyguardDismissUtil,
extensionController,
userInfoControllerImpl,
+ operatorNameViewControllerFactory,
+ phoneStatusBarViewControllerFactory,
phoneStatusBarPolicy,
keyguardIndicationController,
- dismissCallbackRegistry,
demoModeController,
notificationShadeDepthController,
statusBarTouchableRegionManager,
notificationIconAreaController,
brightnessSliderFactory,
- chargingRippleAnimationController,
+ unfoldTransitionConfig,
+ unfoldLightRevealOverlayAnimation,
+ unfoldTransitionWallpaperController,
+ naturalRotationUnfoldProgressProvider,
+ wallpaperController,
ongoingCallController,
animationScheduler,
locationPublisher,
@@ -311,7 +338,15 @@ public interface StatusBarPhoneModule {
transitionController,
featureFlags,
keyguardUnlockAnimationController,
+ mainHandler,
+ delayableExecutor,
+ messageRouter,
+ wallpaperManager,
unlockedScreenOffAnimationController,
- startingSurfaceOptional);
+ startingSurfaceOptional,
+ tunerService,
+ dumpManager,
+ activityLaunchAnimator,
+ dialogLaunchAnimator);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 27d71edd5e8a..9de0c46ff078 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -17,19 +17,86 @@
package com.android.systemui.statusbar.phone.dagger;
import android.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
import com.android.keyguard.LockIconView;
import com.android.systemui.R;
+import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.biometrics.AuthRippleView;
+import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationShelfController;
+import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.phone.NotificationPanelView;
import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
+import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
import com.android.systemui.statusbar.phone.TapAgainView;
+import javax.inject.Named;
+
import dagger.Module;
import dagger.Provides;
@Module
public abstract class StatusBarViewModule {
+
+ public static final String SPLIT_SHADE_HEADER = "split_shade_header";
+
+ /** */
+ @Provides
+ @StatusBarComponent.StatusBarScope
+ public static NotificationShadeWindowView providesNotificationShadeWindowView(
+ LayoutInflater layoutInflater) {
+ NotificationShadeWindowView notificationShadeWindowView = (NotificationShadeWindowView)
+ layoutInflater.inflate(R.layout.super_notification_shade, /* root= */ null);
+ if (notificationShadeWindowView == null) {
+ throw new IllegalStateException(
+ "R.layout.super_notification_shade could not be properly inflated");
+ }
+
+ return notificationShadeWindowView;
+ }
+
+ /** */
+ @Provides
+ @StatusBarComponent.StatusBarScope
+ public static NotificationStackScrollLayout providesNotificationStackScrollLayout(
+ NotificationShadeWindowView notificationShadeWindowView) {
+ return notificationShadeWindowView.findViewById(R.id.notification_stack_scroller);
+ }
+
+ /** */
+ @Provides
+ @StatusBarComponent.StatusBarScope
+ public static NotificationShelf providesNotificationShelf(LayoutInflater layoutInflater,
+ NotificationStackScrollLayout notificationStackScrollLayout) {
+ NotificationShelf view = (NotificationShelf) layoutInflater.inflate(
+ R.layout.status_bar_notification_shelf, notificationStackScrollLayout, false);
+
+ if (view == null) {
+ throw new IllegalStateException(
+ "R.layout.status_bar_notification_shelf could not be properly inflated");
+ }
+ return view;
+ }
+
+ /** */
+ @Provides
+ @StatusBarComponent.StatusBarScope
+ public static NotificationShelfController providesStatusBarWindowView(
+ NotificationShelfComponent.Builder notificationShelfComponentBuilder,
+ NotificationShelf notificationShelf) {
+ NotificationShelfComponent component = notificationShelfComponentBuilder
+ .notificationShelf(notificationShelf)
+ .build();
+ NotificationShelfController notificationShelfController =
+ component.getNotificationShelfController();
+ notificationShelfController.init();
+
+ return notificationShelfController;
+ }
+
/** */
@Provides
@StatusBarComponent.StatusBarScope
@@ -57,8 +124,32 @@ public abstract class StatusBarViewModule {
/** */
@Provides
+ @Named(SPLIT_SHADE_HEADER)
+ @StatusBarComponent.StatusBarScope
+ public static View getSlitShadeStatusBarView(
+ NotificationShadeWindowView notificationShadeWindowView) {
+ return notificationShadeWindowView.findViewById(R.id.split_shade_status_bar);
+ }
+
+ /** */
+ @Provides
+ @StatusBarComponent.StatusBarScope
+ static BatteryMeterView getBatteryMeterView(@Named(SPLIT_SHADE_HEADER) View view) {
+ return view.findViewById(R.id.batteryRemainingIcon);
+ }
+
+ /** */
+ @Provides
@StatusBarComponent.StatusBarScope
public static TapAgainView getTapAgainView(NotificationPanelView npv) {
return npv.getTapAgainView();
}
+
+ /** */
+ @Provides
+ @StatusBarComponent.StatusBarScope
+ public static NotificationsQuickSettingsContainer getNotificationsQuickSettingsContainer(
+ NotificationShadeWindowView notificationShadeWindowView) {
+ return notificationShadeWindowView.findViewById(R.id.notification_container_parent);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 80a0a9824589..31cc823c54ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -24,20 +24,27 @@ import android.app.Notification.CallStyle.CALL_TYPE_ONGOING
import android.content.Intent
import android.util.Log
import android.view.View
-import android.widget.Chronometer
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.phone.StatusBarWindowController
import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.util.time.SystemClock
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -52,13 +59,18 @@ class OngoingCallController @Inject constructor(
private val activityStarter: ActivityStarter,
@Main private val mainExecutor: Executor,
private val iActivityManager: IActivityManager,
- private val logger: OngoingCallLogger
-) : CallbackController<OngoingCallListener> {
-
+ private val logger: OngoingCallLogger,
+ private val dumpManager: DumpManager,
+ private val statusBarWindowController: Optional<StatusBarWindowController>,
+ private val swipeStatusBarAwayGestureHandler: Optional<SwipeStatusBarAwayGestureHandler>,
+ private val statusBarStateController: StatusBarStateController,
+) : CallbackController<OngoingCallListener>, Dumpable {
+
+ private var isFullscreen: Boolean = false
/** Non-null if there's an active call notification. */
private var callNotificationInfo: CallNotificationInfo? = null
/** True if the application managing the call is visible to the user. */
- private var isCallAppVisible: Boolean = true
+ private var isCallAppVisible: Boolean = false
private var chipView: View? = null
private var uidObserver: IUidObserver.Stub? = null
@@ -74,7 +86,7 @@ class OngoingCallController @Inject constructor(
//
// TODO(b/183229367): Remove this function override when b/178406514 is fixed.
override fun onEntryAdded(entry: NotificationEntry) {
- onEntryUpdated(entry)
+ onEntryUpdated(entry, true)
}
override fun onEntryUpdated(entry: NotificationEntry) {
@@ -89,7 +101,8 @@ class OngoingCallController @Inject constructor(
entry.sbn.notification.contentIntent?.intent,
entry.sbn.uid,
entry.sbn.notification.extras.getInt(
- Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING
+ Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING,
+ statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
)
if (newOngoingCallInfo == callNotificationInfo) {
return
@@ -104,16 +117,7 @@ class OngoingCallController @Inject constructor(
}
}
- // Fix for b/199600334
- override fun onEntryCleanUp(entry: NotificationEntry) {
- removeChipIfNeeded(entry)
- }
-
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
- removeChipIfNeeded(entry)
- }
-
- private fun removeChipIfNeeded(entry: NotificationEntry) {
if (entry.sbn.key == callNotificationInfo?.key) {
removeChip()
}
@@ -121,8 +125,10 @@ class OngoingCallController @Inject constructor(
}
fun init() {
+ dumpManager.registerDumpable(this)
if (featureFlags.isOngoingCallStatusBarChipEnabled) {
notifCollection.addCollectionListener(notifListener)
+ statusBarStateController.addCallback(statusBarStateListener)
}
}
@@ -139,7 +145,6 @@ class OngoingCallController @Inject constructor(
}
}
-
/**
* Called when the chip's visibility may have changed.
*
@@ -177,10 +182,8 @@ class OngoingCallController @Inject constructor(
val currentChipView = chipView
val timeView = currentChipView?.getTimeView()
- val backgroundView =
- currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
- if (currentChipView != null && timeView != null && backgroundView != null) {
+ if (currentChipView != null && timeView != null) {
if (currentCallNotificationInfo.hasValidStartTime()) {
timeView.setShouldHideText(false)
timeView.base = currentCallNotificationInfo.callStartTime -
@@ -191,22 +194,15 @@ class OngoingCallController @Inject constructor(
timeView.setShouldHideText(true)
timeView.stop()
}
+ updateChipClickListener()
- currentCallNotificationInfo.intent?.let { intent ->
- currentChipView.setOnClickListener {
- logger.logChipClicked()
- activityStarter.postStartActivityDismissingKeyguard(
- intent,
- 0,
- ActivityLaunchAnimator.Controller.fromView(
- backgroundView,
- InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
- )
+ setUpUidObserver(currentCallNotificationInfo)
+ if (!currentCallNotificationInfo.statusBarSwipedAway) {
+ statusBarWindowController.ifPresent {
+ it.setOngoingProcessRequiresStatusBarVisible(true)
}
}
-
- setUpUidObserver(currentCallNotificationInfo)
-
+ updateGestureListening()
mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
} else {
// If we failed to update the chip, don't store the call info. Then [hasOngoingCall]
@@ -220,6 +216,30 @@ class OngoingCallController @Inject constructor(
}
}
+ private fun updateChipClickListener() {
+ if (callNotificationInfo == null) { return }
+ if (isFullscreen && !featureFlags.isOngoingCallInImmersiveChipTapEnabled) {
+ chipView?.setOnClickListener(null)
+ } else {
+ val currentChipView = chipView
+ val backgroundView =
+ currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
+ val intent = callNotificationInfo?.intent
+ if (currentChipView != null && backgroundView != null && intent != null) {
+ currentChipView.setOnClickListener {
+ logger.logChipClicked()
+ activityStarter.postStartActivityDismissingKeyguard(
+ intent,
+ 0,
+ ActivityLaunchAnimator.Controller.fromView(
+ backgroundView,
+ InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
+ )
+ }
+ }
+ }
+ }
+
/**
* Sets up an [IUidObserver] to monitor the status of the application managing the ongoing call.
*/
@@ -233,7 +253,11 @@ class OngoingCallController @Inject constructor(
uidObserver = object : IUidObserver.Stub() {
override fun onUidStateChanged(
- uid: Int, procState: Int, procStateSeq: Long, capability: Int) {
+ uid: Int,
+ procState: Int,
+ procStateSeq: Long,
+ capability: Int
+ ) {
if (uid == currentCallNotificationInfo.uid) {
val oldIsCallAppVisible = isCallAppVisible
isCallAppVisible = isProcessVisibleToUser(procState)
@@ -266,9 +290,23 @@ class OngoingCallController @Inject constructor(
return procState <= ActivityManager.PROCESS_STATE_TOP
}
+ private fun updateGestureListening() {
+ if (callNotificationInfo == null
+ || callNotificationInfo?.statusBarSwipedAway == true
+ || !isFullscreen) {
+ swipeStatusBarAwayGestureHandler.ifPresent { it.removeOnGestureDetectedCallback(TAG) }
+ } else {
+ swipeStatusBarAwayGestureHandler.ifPresent {
+ it.addOnGestureDetectedCallback(TAG, this::onSwipeAwayGestureDetected)
+ }
+ }
+ }
+
private fun removeChip() {
callNotificationInfo = null
tearDownChipView()
+ statusBarWindowController.ifPresent { it.setOngoingProcessRequiresStatusBarVisible(false) }
+ swipeStatusBarAwayGestureHandler.ifPresent { it.removeOnGestureDetectedCallback(TAG) }
mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
if (uidObserver != null) {
iActivityManager.unregisterUidObserver(uidObserver)
@@ -283,13 +321,42 @@ class OngoingCallController @Inject constructor(
return this.findViewById(R.id.ongoing_call_chip_time)
}
+ /**
+ * If there's an active ongoing call, then we will force the status bar to always show, even if
+ * the user is in immersive mode. However, we also want to give users the ability to swipe away
+ * the status bar if they need to access the area under the status bar.
+ *
+ * This method updates the status bar window appropriately when the swipe away gesture is
+ * detected.
+ */
+ private fun onSwipeAwayGestureDetected() {
+ if (DEBUG) { Log.d(TAG, "Swipe away gesture detected") }
+ callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true)
+ statusBarWindowController.ifPresent {
+ it.setOngoingProcessRequiresStatusBarVisible(false)
+ }
+ swipeStatusBarAwayGestureHandler.ifPresent {
+ it.removeOnGestureDetectedCallback(TAG)
+ }
+ }
+
+ private val statusBarStateListener = object : StatusBarStateController.StateListener {
+ override fun onFullscreenStateChanged(isFullscreen: Boolean) {
+ this@OngoingCallController.isFullscreen = isFullscreen
+ updateChipClickListener()
+ updateGestureListening()
+ }
+ }
+
private data class CallNotificationInfo(
val key: String,
val callStartTime: Long,
val intent: Intent?,
val uid: Int,
/** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */
- val isOngoing: Boolean
+ val isOngoing: Boolean,
+ /** True if the user has swiped away the status bar while in this phone call. */
+ val statusBarSwipedAway: Boolean
) {
/**
* Returns true if the notification information has a valid call start time.
@@ -297,6 +364,11 @@ class OngoingCallController @Inject constructor(
*/
fun hasValidStartTime(): Boolean = callStartTime > 0
}
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+ pw.println("Active call notification: $callNotificationInfo")
+ pw.println("Call app visible: $isCallAppVisible")
+ }
}
private fun isCallNotification(entry: NotificationEntry): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
index d38284a26a07..479ca8f1ff96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java
@@ -14,7 +14,6 @@
package com.android.systemui.statusbar.policy;
-import android.content.Context;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
@@ -35,8 +34,8 @@ public class AccessibilityManagerWrapper implements
private final AccessibilityManager mAccessibilityManager;
@Inject
- public AccessibilityManagerWrapper(Context context) {
- mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+ public AccessibilityManagerWrapper(AccessibilityManager accessibilityManager) {
+ mAccessibilityManager = accessibilityManager;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 1e5251196379..5bd20ff2d090 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -26,7 +26,7 @@ import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.android.systemui.R;
-import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.settings.brightness.ToggleSlider;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
@@ -46,8 +46,8 @@ public class BrightnessMirrorController
private final NotificationPanelViewController mNotificationPanel;
private final NotificationShadeDepthController mDepthController;
private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>();
- private final BrightnessSlider.Factory mToggleSliderFactory;
- private BrightnessSlider mToggleSliderController;
+ private final BrightnessSliderController.Factory mToggleSliderFactory;
+ private BrightnessSliderController mToggleSliderController;
private final int[] mInt2Cache = new int[2];
private FrameLayout mBrightnessMirror;
private int mBrightnessMirrorBackgroundPadding;
@@ -56,7 +56,7 @@ public class BrightnessMirrorController
public BrightnessMirrorController(NotificationShadeWindowView statusBarWindow,
NotificationPanelViewController notificationPanelViewController,
NotificationShadeDepthController notificationShadeDepthController,
- BrightnessSlider.Factory factory,
+ BrightnessSliderController.Factory factory,
@NonNull Consumer<Boolean> visibilityCallback) {
mStatusBarWindow = statusBarWindow;
mToggleSliderFactory = factory;
@@ -135,9 +135,10 @@ public class BrightnessMirrorController
reinflate();
}
- private BrightnessSlider setMirrorLayout() {
+ private BrightnessSliderController setMirrorLayout() {
Context context = mBrightnessMirror.getContext();
- BrightnessSlider controller = mToggleSliderFactory.create(context, mBrightnessMirror);
+ BrightnessSliderController controller = mToggleSliderFactory.create(context,
+ mBrightnessMirror);
controller.init();
mBrightnessMirror.addView(controller.getRootView(), ViewGroup.LayoutParams.MATCH_PARENT,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index 3a05ec78a8b0..6b80a9dab7cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -37,7 +37,7 @@ public interface ConfigurationController extends CallbackController<Configuratio
default void onConfigChanged(Configuration newConfig) {}
default void onDensityOrFontScaleChanged() {}
default void onSmallestScreenWidthChanged() {}
- default void onOverlayChanged() {}
+ default void onMaxBoundsChanged() {}
default void onUiModeChanged() {}
default void onThemeChanged() {}
default void onLocaleListChanged() {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java
new file mode 100644
index 000000000000..bbba19d61b5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java
@@ -0,0 +1,79 @@
+/*
+ * 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.systemui.statusbar.policy;
+
+import static com.android.systemui.statusbar.policy.DevicePostureController.Callback;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Listener for device posture changes. This can be used to query the current posture, or register
+ * for events when it changes.
+ */
+public interface DevicePostureController extends CallbackController<Callback> {
+ @IntDef(prefix = {"DEVICE_POSTURE_"}, value = {
+ DEVICE_POSTURE_UNKNOWN,
+ DEVICE_POSTURE_CLOSED,
+ DEVICE_POSTURE_HALF_OPENED,
+ DEVICE_POSTURE_OPENED,
+ DEVICE_POSTURE_FLIPPED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface DevicePostureInt {}
+
+ // NOTE: These constants **must** match those defined for Jetpack Sidecar. This is because we
+ // use the Device State -> Jetpack Posture map in DevicePostureControllerImpl to translate
+ // between the two.
+ int DEVICE_POSTURE_UNKNOWN = 0;
+ int DEVICE_POSTURE_CLOSED = 1;
+ int DEVICE_POSTURE_HALF_OPENED = 2;
+ int DEVICE_POSTURE_OPENED = 3;
+ int DEVICE_POSTURE_FLIPPED = 4;
+ int SUPPORTED_POSTURES_SIZE = DEVICE_POSTURE_FLIPPED + 1;
+
+ /** Return the current device posture. */
+ @DevicePostureInt int getDevicePosture();
+
+ /**
+ * String representation of DevicePostureInt.
+ */
+ static String devicePostureToString(@DevicePostureInt int posture) {
+ switch (posture) {
+ case DEVICE_POSTURE_CLOSED:
+ return "DEVICE_POSTURE_CLOSED";
+ case DEVICE_POSTURE_HALF_OPENED:
+ return "DEVICE_POSTURE_HALF_OPENED";
+ case DEVICE_POSTURE_OPENED:
+ return "DEVICE_POSTURE_OPENED";
+ case DEVICE_POSTURE_FLIPPED:
+ return "DEVICE_POSTURE_FLIPPED";
+ case DEVICE_POSTURE_UNKNOWN:
+ return "DEVICE_POSTURE_UNKNOWN";
+ default:
+ return "UNSUPPORTED POSTURE posture=" + posture;
+ }
+ }
+
+ /** Callback to be notified about device posture changes. */
+ interface Callback {
+ /** Called when the posture changes. */
+ void onPostureChanged(@DevicePostureInt int posture);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java
new file mode 100644
index 000000000000..8471e0aa1640
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java
@@ -0,0 +1,94 @@
+/*
+ * 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.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.util.SparseIntArray;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/** Implementation of {@link DevicePostureController} using the DeviceStateManager. */
+@SysUISingleton
+public class DevicePostureControllerImpl implements DevicePostureController {
+ private final List<Callback> mListeners = new ArrayList<>();
+ private int mCurrentDevicePosture = DEVICE_POSTURE_UNKNOWN;
+
+ private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
+
+ @Inject
+ public DevicePostureControllerImpl(
+ Context context, DeviceStateManager deviceStateManager, @Main Executor executor) {
+ // Most of this is borrowed from WindowManager/Jetpack/DeviceStateManagerPostureProducer.
+ // Using the sidecar/extension libraries directly brings in a new dependency that it'd be
+ // good to avoid (along with the fact that sidecar is deprecated, and extensions isn't fully
+ // ready yet), and we'd have to make our own layer over the sidecar library anyway to easily
+ // allow the implementation to change, so it was easier to just interface with
+ // DeviceStateManager directly.
+ String[] deviceStatePosturePairs = context.getResources()
+ .getStringArray(R.array.config_device_state_postures);
+ for (String deviceStatePosturePair : deviceStatePosturePairs) {
+ String[] deviceStatePostureMapping = deviceStatePosturePair.split(":");
+ if (deviceStatePostureMapping.length != 2) {
+ continue;
+ }
+
+ int deviceState;
+ int posture;
+ try {
+ deviceState = Integer.parseInt(deviceStatePostureMapping[0]);
+ posture = Integer.parseInt(deviceStatePostureMapping[1]);
+ } catch (NumberFormatException e) {
+ continue;
+ }
+
+ mDeviceStateToPostureMap.put(deviceState, posture);
+ }
+
+ deviceStateManager.registerCallback(executor, state -> {
+ mCurrentDevicePosture =
+ mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN);
+
+ mListeners.forEach(l -> l.onPostureChanged(mCurrentDevicePosture));
+ });
+ }
+
+ @Override
+ public void addCallback(@NonNull Callback listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeCallback(@NonNull Callback listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public int getDevicePosture() {
+ return mCurrentDevicePosture;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
index 7b4c35a8d25a..3944c8c77f49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
@@ -14,23 +14,60 @@
package com.android.systemui.statusbar.policy;
+import android.provider.Settings;
+
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+/**
+ * Controller to cache in process the state of the device provisioning.
+ * <p>
+ * This controller keeps track of the values of device provisioning and user setup complete
+ */
public interface DeviceProvisionedController extends CallbackController<DeviceProvisionedListener> {
+ /**
+ * @return whether the device is provisioned
+ * @see Settings.Global#DEVICE_PROVISIONED
+ */
boolean isDeviceProvisioned();
- boolean isUserSetup(int currentUser);
+
+ /**
+ * @deprecated use {@link com.android.systemui.settings.UserTracker}
+ */
+ @Deprecated
int getCurrentUser();
- default boolean isCurrentUserSetup() {
- return isUserSetup(getCurrentUser());
- }
+ /**
+ * @param user the user to query
+ * @return whether that user has completed the user setup
+ * @see Settings.Secure#USER_SETUP_COMPLETE
+ */
+ boolean isUserSetup(int user);
+ /**
+ * @see DeviceProvisionedController#isUserSetup
+ */
+ boolean isCurrentUserSetup();
+
+ /**
+ * Interface to provide calls when the values tracked change
+ */
interface DeviceProvisionedListener {
+ /**
+ * Call when the device changes from not provisioned to provisioned
+ */
default void onDeviceProvisionedChanged() { }
+
+ /**
+ * Call on user switched
+ */
default void onUserSwitched() {
onUserSetupChanged();
}
+
+ /**
+ * Call when some user changes from not provisioned to provisioned
+ */
default void onUserSetupChanged() { }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
deleted file mode 100644
index 485b1b109eb4..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.app.ActivityManager;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.provider.Settings.Global;
-import android.provider.Settings.Secure;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.settings.CurrentUserTracker;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.util.ArrayList;
-
-import javax.inject.Inject;
-
-/**
- */
-@SysUISingleton
-public class DeviceProvisionedControllerImpl extends CurrentUserTracker implements
- DeviceProvisionedController {
-
- protected static final String TAG = DeviceProvisionedControllerImpl.class.getSimpleName();
- protected final ArrayList<DeviceProvisionedListener> mListeners = new ArrayList<>();
- private final GlobalSettings mGlobalSettings;
- private final SecureSettings mSecureSettings;
- private final Uri mDeviceProvisionedUri;
- private final Uri mUserSetupUri;
- protected final ContentObserver mSettingsObserver;
-
- /**
- */
- @Inject
- public DeviceProvisionedControllerImpl(@Main Handler mainHandler,
- BroadcastDispatcher broadcastDispatcher, GlobalSettings globalSettings,
- SecureSettings secureSettings) {
- super(broadcastDispatcher);
- mGlobalSettings = globalSettings;
- mSecureSettings = secureSettings;
- mDeviceProvisionedUri = mGlobalSettings.getUriFor(Global.DEVICE_PROVISIONED);
- mUserSetupUri = mSecureSettings.getUriFor(Secure.USER_SETUP_COMPLETE);
- mSettingsObserver = new ContentObserver(mainHandler) {
- @Override
- public void onChange(boolean selfChange, Uri uri, int flags) {
- Log.d(TAG, "Setting change: " + uri);
- if (mUserSetupUri.equals(uri)) {
- notifySetupChanged();
- } else {
- notifyProvisionedChanged();
- }
- }
- };
- }
-
- @Override
- public boolean isDeviceProvisioned() {
- return mGlobalSettings.getInt(Global.DEVICE_PROVISIONED, 0) != 0;
- }
-
- @Override
- public boolean isUserSetup(int currentUser) {
- return mSecureSettings.getIntForUser(Secure.USER_SETUP_COMPLETE, 0, currentUser) != 0;
- }
-
- @Override
- public int getCurrentUser() {
- return ActivityManager.getCurrentUser();
- }
-
- @Override
- public void addCallback(@NonNull DeviceProvisionedListener listener) {
- mListeners.add(listener);
- if (mListeners.size() == 1) {
- startListening(getCurrentUser());
- }
- listener.onUserSetupChanged();
- listener.onDeviceProvisionedChanged();
- }
-
- @Override
- public void removeCallback(@NonNull DeviceProvisionedListener listener) {
- mListeners.remove(listener);
- if (mListeners.size() == 0) {
- stopListening();
- }
- }
-
- protected void startListening(int user) {
- mGlobalSettings.registerContentObserverForUser(mDeviceProvisionedUri, true,
- mSettingsObserver, 0);
- mSecureSettings.registerContentObserverForUser(mUserSetupUri, true,
- mSettingsObserver, user);
- startTracking();
- }
-
- protected void stopListening() {
- stopTracking();
- mGlobalSettings.unregisterContentObserver(mSettingsObserver);
- }
-
- @Override
- public void onUserSwitched(int newUserId) {
- mGlobalSettings.unregisterContentObserver(mSettingsObserver);
- mGlobalSettings.registerContentObserverForUser(mDeviceProvisionedUri, true,
- mSettingsObserver, 0);
- mSecureSettings.registerContentObserverForUser(mUserSetupUri, true,
- mSettingsObserver, newUserId);
- notifyUserChanged();
- }
-
- private void notifyUserChanged() {
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onUserSwitched();
- }
- }
-
- private void notifySetupChanged() {
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onUserSetupChanged();
- }
- }
-
- private void notifyProvisionedChanged() {
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onDeviceProvisionedChanged();
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
new file mode 100644
index 000000000000..acc12141796e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
@@ -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.systemui.statusbar.policy
+
+import android.content.Context
+import android.content.pm.UserInfo
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.UserHandle
+import android.provider.Settings
+import android.util.ArraySet
+import android.util.SparseBooleanArray
+import androidx.annotation.GuardedBy
+import androidx.annotation.WorkerThread
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SecureSettings
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.inject.Inject
+
+@SysUISingleton
+open class DeviceProvisionedControllerImpl @Inject constructor(
+ private val secureSettings: SecureSettings,
+ private val globalSettings: GlobalSettings,
+ private val userTracker: UserTracker,
+ private val dumpManager: DumpManager,
+ @Background private val backgroundHandler: Handler,
+ @Main private val mainExecutor: Executor
+) : DeviceProvisionedController,
+ DeviceProvisionedController.DeviceProvisionedListener,
+ Dumpable {
+
+ companion object {
+ private const val ALL_USERS = -1
+ private const val NO_USERS = -2
+ protected const val TAG = "DeviceProvisionedControllerImpl"
+ }
+
+ private val deviceProvisionedUri = globalSettings.getUriFor(Settings.Global.DEVICE_PROVISIONED)
+ private val userSetupUri = secureSettings.getUriFor(Settings.Secure.USER_SETUP_COMPLETE)
+
+ private val deviceProvisioned = AtomicBoolean(false)
+ @GuardedBy("lock")
+ private val userSetupComplete = SparseBooleanArray()
+ @GuardedBy("lock")
+ private val listeners = ArraySet<DeviceProvisionedController.DeviceProvisionedListener>()
+
+ private val lock = Any()
+
+ private val backgroundExecutor = HandlerExecutor(backgroundHandler)
+
+ private val initted = AtomicBoolean(false)
+
+ private val _currentUser: Int
+ get() = userTracker.userId
+
+ override fun getCurrentUser(): Int {
+ return _currentUser
+ }
+
+ private val observer = object : ContentObserver(backgroundHandler) {
+ override fun onChange(
+ selfChange: Boolean,
+ uris: MutableCollection<Uri>,
+ flags: Int,
+ userId: Int
+ ) {
+ val updateDeviceProvisioned = deviceProvisionedUri in uris
+ val updateUser = if (userSetupUri in uris) userId else NO_USERS
+ updateValues(updateDeviceProvisioned, updateUser)
+ if (updateDeviceProvisioned) {
+ onDeviceProvisionedChanged()
+ }
+ if (updateUser != NO_USERS) {
+ onUserSetupChanged()
+ }
+ }
+ }
+
+ private val userChangedCallback = object : UserTracker.Callback {
+ @WorkerThread
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ updateValues(updateDeviceProvisioned = false, updateUser = newUser)
+ onUserSwitched()
+ }
+
+ override fun onProfilesChanged(profiles: List<UserInfo>) {}
+ }
+
+ init {
+ userSetupComplete.put(currentUser, false)
+ }
+
+ /**
+ * Call to initialize values and register observers
+ */
+ open fun init() {
+ if (!initted.compareAndSet(false, true)) {
+ return
+ }
+ dumpManager.registerDumpable(this)
+ updateValues()
+ userTracker.addCallback(userChangedCallback, backgroundExecutor)
+ globalSettings.registerContentObserver(deviceProvisionedUri, observer)
+ secureSettings.registerContentObserverForUser(userSetupUri, observer, UserHandle.USER_ALL)
+ }
+
+ @WorkerThread
+ private fun updateValues(updateDeviceProvisioned: Boolean = true, updateUser: Int = ALL_USERS) {
+ if (updateDeviceProvisioned) {
+ deviceProvisioned
+ .set(globalSettings.getInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0)
+ }
+ synchronized(lock) {
+ if (updateUser == ALL_USERS) {
+ val N = userSetupComplete.size()
+ for (i in 0 until N) {
+ val user = userSetupComplete.keyAt(i)
+ val value = secureSettings
+ .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, user) != 0
+ userSetupComplete.put(user, value)
+ }
+ } else if (updateUser != NO_USERS) {
+ val value = secureSettings
+ .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, updateUser) != 0
+ userSetupComplete.put(updateUser, value)
+ }
+ }
+ }
+
+ /**
+ * Adds a listener.
+ *
+ * The listener will not be called when this happens.
+ */
+ override fun addCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) {
+ synchronized(lock) {
+ listeners.add(listener)
+ }
+ }
+
+ override fun removeCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) {
+ synchronized(lock) {
+ listeners.remove(listener)
+ }
+ }
+
+ override fun isDeviceProvisioned(): Boolean {
+ return deviceProvisioned.get()
+ }
+
+ override fun isUserSetup(user: Int): Boolean {
+ val index = synchronized(lock) {
+ userSetupComplete.indexOfKey(user)
+ }
+ return if (index < 0) {
+ val value = secureSettings
+ .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, user) != 0
+ synchronized(lock) {
+ userSetupComplete.put(user, value)
+ }
+ value
+ } else {
+ synchronized(lock) {
+ userSetupComplete.get(user, false)
+ }
+ }
+ }
+
+ override fun isCurrentUserSetup(): Boolean {
+ return isUserSetup(currentUser)
+ }
+
+ override fun onDeviceProvisionedChanged() {
+ dispatchChange(
+ DeviceProvisionedController.DeviceProvisionedListener::onDeviceProvisionedChanged
+ )
+ }
+
+ override fun onUserSetupChanged() {
+ dispatchChange(DeviceProvisionedController.DeviceProvisionedListener::onUserSetupChanged)
+ }
+
+ override fun onUserSwitched() {
+ dispatchChange(DeviceProvisionedController.DeviceProvisionedListener::onUserSwitched)
+ }
+
+ protected fun dispatchChange(
+ callback: DeviceProvisionedController.DeviceProvisionedListener.() -> Unit
+ ) {
+ val listenersCopy = synchronized(lock) {
+ ArrayList(listeners)
+ }
+ mainExecutor.execute {
+ listenersCopy.forEach(callback)
+ }
+ }
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+ pw.println("Device provisioned: ${deviceProvisioned.get()}")
+ synchronized(lock) {
+ pw.println("User setup complete: $userSetupComplete")
+ pw.println("Listeners: $listeners")
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
new file mode 100644
index 000000000000..41cacf5142fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -0,0 +1,239 @@
+/*
+ * 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.systemui.statusbar.policy;
+
+
+import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED;
+import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED;
+import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED;
+
+import static com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule.DEVICE_STATE_ROTATION_LOCK_DEFAULTS;
+
+import android.annotation.Nullable;
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.wrapper.RotationPolicyWrapper;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Handles reading and writing of rotation lock settings per device state, as well as setting
+ * the rotation lock when device state changes.
+ **/
+@SysUISingleton
+public final class DeviceStateRotationLockSettingController implements Listenable,
+ RotationLockController.RotationLockControllerCallback {
+
+ private static final String TAG = "DSRotateLockSettingCon";
+
+ private static final String SEPARATOR_REGEX = ":";
+
+ private final SecureSettings mSecureSettings;
+ private final RotationPolicyWrapper mRotationPolicyWrapper;
+ private final DeviceStateManager mDeviceStateManager;
+ private final Executor mMainExecutor;
+ private final String[] mDeviceStateRotationLockDefaults;
+
+ private SparseIntArray mDeviceStateRotationLockSettings;
+ // TODO(b/183001527): Add API to query current device state and initialize this.
+ private int mDeviceState = -1;
+ @Nullable
+ private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
+
+
+ @Inject
+ public DeviceStateRotationLockSettingController(
+ SecureSettings secureSettings,
+ RotationPolicyWrapper rotationPolicyWrapper,
+ DeviceStateManager deviceStateManager,
+ @Main Executor executor,
+ @Named(DEVICE_STATE_ROTATION_LOCK_DEFAULTS) String[] deviceStateRotationLockDefaults
+ ) {
+ mSecureSettings = secureSettings;
+ mRotationPolicyWrapper = rotationPolicyWrapper;
+ mDeviceStateManager = deviceStateManager;
+ mMainExecutor = executor;
+ mDeviceStateRotationLockDefaults = deviceStateRotationLockDefaults;
+ }
+
+ /**
+ * Loads the settings from storage.
+ */
+ public void initialize() {
+ String serializedSetting =
+ mSecureSettings.getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ UserHandle.USER_CURRENT);
+ if (TextUtils.isEmpty(serializedSetting)) {
+ // No settings saved, we should load the defaults and persist them.
+ fallbackOnDefaults();
+ return;
+ }
+ String[] values = serializedSetting.split(SEPARATOR_REGEX);
+ if (values.length % 2 != 0) {
+ // Each entry should be a key/value pair, so this is corrupt.
+ Log.wtf(TAG, "Can't deserialize saved settings, falling back on defaults");
+ fallbackOnDefaults();
+ return;
+ }
+ mDeviceStateRotationLockSettings = new SparseIntArray(values.length / 2);
+ int key;
+ int value;
+
+ for (int i = 0; i < values.length - 1; ) {
+ try {
+ key = Integer.parseInt(values[i++]);
+ value = Integer.parseInt(values[i++]);
+ mDeviceStateRotationLockSettings.put(key, value);
+ } catch (NumberFormatException e) {
+ Log.wtf(TAG, "Error deserializing one of the saved settings", e);
+ fallbackOnDefaults();
+ return;
+ }
+ }
+ }
+
+ private void fallbackOnDefaults() {
+ loadDefaults();
+ persistSettings();
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ if (listening) {
+ // Note that this is called once with the initial state of the device, even if there
+ // is no user action.
+ mDeviceStateCallback = this::updateDeviceState;
+ mDeviceStateManager.registerCallback(mMainExecutor, mDeviceStateCallback);
+ } else {
+ if (mDeviceStateCallback != null) {
+ mDeviceStateManager.unregisterCallback(mDeviceStateCallback);
+ }
+ }
+ }
+
+ @Override
+ public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) {
+ if (mDeviceState == -1) {
+ Log.wtf(TAG, "Device state was not initialized.");
+ return;
+ }
+
+ if (rotationLocked == isRotationLockedForCurrentState()) {
+ Log.v(TAG, "Rotation lock same as the current setting, no need to update.");
+ return;
+ }
+
+ saveNewRotationLockSetting(rotationLocked);
+ }
+
+ private void saveNewRotationLockSetting(boolean isRotationLocked) {
+ Log.v(TAG, "saveNewRotationLockSetting [state=" + mDeviceState + "] [isRotationLocked="
+ + isRotationLocked + "]");
+
+ mDeviceStateRotationLockSettings.put(mDeviceState,
+ isRotationLocked
+ ? DEVICE_STATE_ROTATION_LOCK_LOCKED
+ : DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
+ persistSettings();
+ }
+
+ private boolean isRotationLockedForCurrentState() {
+ return mDeviceStateRotationLockSettings.get(mDeviceState,
+ DEVICE_STATE_ROTATION_LOCK_IGNORED) == DEVICE_STATE_ROTATION_LOCK_LOCKED;
+ }
+
+ private void updateDeviceState(int state) {
+ Log.v(TAG, "updateDeviceState [state=" + state + "]");
+ if (mDeviceState == state) {
+ return;
+ }
+
+ int rotationLockSetting =
+ mDeviceStateRotationLockSettings.get(state, DEVICE_STATE_ROTATION_LOCK_IGNORED);
+ if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
+ // We won't handle this device state. The same rotation lock setting as before should
+ // apply and any changes to the rotation lock setting will be written for the previous
+ // valid device state.
+ Log.v(TAG, "Ignoring new device state: " + state);
+ return;
+ }
+
+ // Accept the new state
+ mDeviceState = state;
+
+ // Update the rotation lock setting if needed for this new device state
+ boolean newRotationLockSetting = rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_LOCKED;
+ if (newRotationLockSetting != mRotationPolicyWrapper.isRotationLocked()) {
+ mRotationPolicyWrapper.setRotationLock(newRotationLockSetting);
+ }
+ }
+
+ private void persistSettings() {
+ if (mDeviceStateRotationLockSettings.size() == 0) {
+ mSecureSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ /* value= */"", UserHandle.USER_CURRENT);
+ return;
+ }
+
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(mDeviceStateRotationLockSettings.keyAt(0))
+ .append(SEPARATOR_REGEX)
+ .append(mDeviceStateRotationLockSettings.valueAt(0));
+
+ for (int i = 1; i < mDeviceStateRotationLockSettings.size(); i++) {
+ stringBuilder
+ .append(SEPARATOR_REGEX)
+ .append(mDeviceStateRotationLockSettings.keyAt(i))
+ .append(SEPARATOR_REGEX)
+ .append(mDeviceStateRotationLockSettings.valueAt(i));
+ }
+ mSecureSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ stringBuilder.toString(), UserHandle.USER_CURRENT);
+ }
+
+ private void loadDefaults() {
+ if (mDeviceStateRotationLockDefaults.length == 0) {
+ Log.w(TAG, "Empty default settings");
+ mDeviceStateRotationLockSettings = new SparseIntArray(/* initialCapacity= */0);
+ return;
+ }
+ mDeviceStateRotationLockSettings =
+ new SparseIntArray(mDeviceStateRotationLockDefaults.length);
+ for (String serializedDefault : mDeviceStateRotationLockDefaults) {
+ String[] entry = serializedDefault.split(SEPARATOR_REGEX);
+ try {
+ int key = Integer.parseInt(entry[0]);
+ int value = Integer.parseInt(entry[1]);
+ mDeviceStateRotationLockSettings.put(key, value);
+ } catch (NumberFormatException e) {
+ Log.wtf(TAG, "Error deserializing default settings", e);
+ }
+ }
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
index 5011d96d57f8..4c6c7e0f6c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
@@ -56,7 +56,8 @@ public class ExtensionControllerImpl implements ExtensionController {
/**
*/
@Inject
- public ExtensionControllerImpl(Context context,
+ public ExtensionControllerImpl(
+ Context context,
LeakDetector leakDetector,
PluginManager pluginManager,
TunerService tunerService,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
index d7c2b9664011..ad47e2bc44a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -33,6 +33,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -72,10 +73,11 @@ public class FlashlightControllerImpl implements FlashlightController {
private boolean mTorchAvailable;
@Inject
- public FlashlightControllerImpl(Context context) {
+ public FlashlightControllerImpl(Context context, DumpManager dumpManager) {
mContext = context;
mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
tryInitCamera();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index a8097c4d74b0..e0b0dd36ccd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -38,11 +38,10 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.util.ListenerSet;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
/**
* A manager which handles heads up notifications which is a special mode where
@@ -52,7 +51,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
private static final String TAG = "HeadsUpManager";
private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
- protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
+ protected final ListenerSet<OnHeadsUpChangedListener> mListeners = new ListenerSet<>();
protected final Context mContext;
@@ -118,7 +117,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
* Adds an OnHeadUpChangedListener to observe events.
*/
public void addListener(@NonNull OnHeadsUpChangedListener listener) {
- mListeners.add(listener);
+ mListeners.addIfAbsent(listener);
}
/**
@@ -158,7 +157,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
NotificationPeekEvent.NOTIFICATION_PEEK, entry.getSbn().getUid(),
entry.getSbn().getPackageName(), entry.getSbn().getInstanceId());
}
- for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
+ for (OnHeadsUpChangedListener listener : mListeners) {
if (isPinned) {
listener.onHeadsUpPinned(entry);
} else {
@@ -178,7 +177,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
entry.setHeadsUp(true);
setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(entry));
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */);
- for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
+ for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, true);
}
}
@@ -189,7 +188,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
entry.setHeadsUp(false);
setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
- for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
+ for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, false);
}
}
@@ -207,7 +206,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
if (mHasPinnedNotification) {
MetricsLogger.count(mContext, "note_peek", 1);
}
- for (OnHeadsUpChangedListener listener : new ArrayList<>(mListeners)) {
+ for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpPinnedModeChanged(hasPinnedNotification);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index 987812b0581c..f364e49bb757 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -36,6 +36,7 @@ import com.android.internal.util.ConcurrentUtils;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -91,14 +92,18 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof
* Controller used to retrieve information related to a hotspot.
*/
@Inject
- public HotspotControllerImpl(Context context, @Main Handler mainHandler,
- @Background Handler backgroundHandler) {
+ public HotspotControllerImpl(
+ Context context,
+ @Main Handler mainHandler,
+ @Background Handler backgroundHandler,
+ DumpManager dumpManager) {
mContext = context;
mTetheringManager = context.getSystemService(TetheringManager.class);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mMainHandler = mainHandler;
mTetheringManager.registerTetheringEventCallback(
new HandlerExecutor(backgroundHandler), mTetheringCallback);
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index d838a05135e7..b4aba380d971 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -35,10 +35,12 @@ import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
import com.android.settingslib.drawable.CircleFramedDrawable;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.tiles.UserDetailView;
+import com.android.systemui.qs.user.UserSwitchDialogController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -76,6 +78,8 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout>
private final ConfigurationController mConfigurationController;
private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
private final KeyguardUserDetailAdapter mUserDetailAdapter;
+ private final FeatureFlags mFeatureFlags;
+ private final UserSwitchDialogController mUserSwitchDialogController;
private NotificationPanelViewController mNotificationPanelViewController;
private UserAvatarView mUserAvatarView;
UserSwitcherController.UserRecord mCurrentUser;
@@ -124,7 +128,9 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout>
SysuiStatusBarStateController statusBarStateController,
DozeParameters dozeParameters,
Provider<UserDetailView.Adapter> userDetailViewAdapterProvider,
- UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
+ UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+ FeatureFlags featureFlags,
+ UserSwitchDialogController userSwitchDialogController) {
super(view);
if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
mContext = context;
@@ -139,6 +145,8 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout>
keyguardStateController, dozeParameters,
unlockedScreenOffAnimationController, /* animateYPos= */ false);
mUserDetailAdapter = new KeyguardUserDetailAdapter(context, userDetailViewAdapterProvider);
+ mFeatureFlags = featureFlags;
+ mUserSwitchDialogController = userSwitchDialogController;
}
@Override
@@ -162,7 +170,11 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout>
}
// Tapping anywhere in the view will open QS user panel
- openQsUserPanel();
+ if (mFeatureFlags.useNewUserSwitcher()) {
+ mUserSwitchDialogController.showDialog(mView);
+ } else {
+ openQsUserPanel();
+ }
});
mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index 705761854532..7bf1601b2fd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -247,6 +247,12 @@ public interface KeyguardStateController extends CallbackController<Callback> {
default void onKeyguardDismissAmountChanged() {}
/**
+ * Triggered when face auth becomes available or unavailable. Value should be queried with
+ * {@link KeyguardStateController#isFaceAuthEnabled()}.
+ */
+ default void onFaceAuthEnabledChanged() {}
+
+ /**
* Triggered when the notification panel is starting or has finished
* fading away on transition to an app.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index f787ecf37372..2dfcc98b5037 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -34,6 +34,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
import java.io.FileDescriptor;
@@ -100,15 +101,20 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum
/**
*/
@Inject
- public KeyguardStateControllerImpl(Context context,
- KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils,
- SmartspaceTransitionController smartspaceTransitionController) {
+ public KeyguardStateControllerImpl(
+ Context context,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ LockPatternUtils lockPatternUtils,
+ SmartspaceTransitionController smartspaceTransitionController,
+ DumpManager dumpManager) {
mContext = context;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mSmartspaceTransitionController = smartspaceTransitionController;
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
+
update(true /* updateAlways */);
if (Build.IS_DEBUGGABLE && DEBUG_AUTH_WITH_ADB) {
// Watch for interesting updates
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 5d7d4809dd57..aa8d95fdb625 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -108,36 +108,36 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private final SendButtonTextWatcher mTextWatcher;
private final TextView.OnEditorActionListener mEditorActionHandler;
- private final UiEventLogger mUiEventLogger;
- private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
- private final List<OnFocusChangeListener> mEditTextFocusChangeListeners = new ArrayList<>();
- private final List<OnSendRemoteInputListener> mOnSendListeners = new ArrayList<>();
+ private final ArrayList<OnSendRemoteInputListener> mOnSendListeners = new ArrayList<>();
+ private final ArrayList<Consumer<Boolean>> mOnVisibilityChangedListeners = new ArrayList<>();
+ private final ArrayList<OnFocusChangeListener> mEditTextFocusChangeListeners =
+ new ArrayList<>();
+
private RemoteEditText mEditText;
private ImageButton mSendButton;
private GradientDrawable mContentBackground;
private ProgressBar mProgressBar;
- private PendingIntent mPendingIntent;
- private RemoteInput[] mRemoteInputs;
- private RemoteInput mRemoteInput;
- private RemoteInputController mController;
-
- private NotificationEntry mEntry;
-
- private boolean mRemoved;
-
+ private ImageView mDelete;
+ private ImageView mDeleteBg;
+ // TODO(b/193539698): remove reveal param fields, turn them into parameters where needed
private int mRevealCx;
private int mRevealCy;
private int mRevealR;
-
private boolean mColorized;
private int mTint;
-
private boolean mResetting;
- private NotificationViewWrapper mWrapper;
- private Consumer<Boolean> mOnVisibilityChangedListener;
+
+ // TODO(b/193539698): move these to a Controller
+ private RemoteInputController mController;
+ private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
+ private final UiEventLogger mUiEventLogger;
+ private NotificationEntry mEntry;
+ private PendingIntent mPendingIntent;
+ private RemoteInput mRemoteInput;
+ private RemoteInput[] mRemoteInputs;
private NotificationRemoteInputManager.BouncerChecker mBouncerChecker;
- private ImageView mDelete;
- private ImageView mDeleteBg;
+ private boolean mRemoved;
+ private NotificationViewWrapper mWrapper;
/**
* Enum for logged notification remote input UiEvents.
@@ -382,7 +382,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private void sendRemoteInput(Intent intent) {
if (mBouncerChecker != null && mBouncerChecker.showBouncerIfNecessary()) {
mEditText.hideIme();
- for (OnSendRemoteInputListener listener : mOnSendListeners) {
+ for (OnSendRemoteInputListener listener : new ArrayList<>(mOnSendListeners)) {
listener.onSendRequestBounced();
}
return;
@@ -399,7 +399,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mController.remoteInputSent(mEntry);
mEntry.setHasSentReply();
- for (OnSendRemoteInputListener listener : mOnSendListeners) {
+ for (OnSendRemoteInputListener listener : new ArrayList<>(mOnSendListeners)) {
listener.onSendRemoteInput();
}
@@ -760,15 +760,32 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mWrapper = wrapper;
}
- public void setOnVisibilityChangedListener(Consumer<Boolean> visibilityChangedListener) {
- mOnVisibilityChangedListener = visibilityChangedListener;
+ /**
+ * Register a listener to be notified when this view's visibility changes.
+ *
+ * Specifically, the passed {@link Consumer} will receive {@code true} when
+ * {@link #getVisibility()} would return {@link View#VISIBLE}, and {@code false} it would return
+ * any other value.
+ */
+ public void addOnVisibilityChangedListener(Consumer<Boolean> listener) {
+ mOnVisibilityChangedListeners.add(listener);
+ }
+
+ /**
+ * Unregister a listener previously registered via
+ * {@link #addOnVisibilityChangedListener(Consumer)}.
+ */
+ public void removeOnVisibilityChangedListener(Consumer<Boolean> listener) {
+ mOnVisibilityChangedListeners.remove(listener);
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
- if (changedView == this && mOnVisibilityChangedListener != null) {
- mOnVisibilityChangedListener.accept(visibility == VISIBLE);
+ if (changedView == this) {
+ for (Consumer<Boolean> listener : new ArrayList<>(mOnVisibilityChangedListeners)) {
+ listener.accept(visibility == VISIBLE);
+ }
// Hide soft-keyboard when the input view became invisible
// (i.e. The notification shade collapsed by pressing the home key)
if (visibility != VISIBLE && !mEditText.isVisibleToUser()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
index 1158324567ff..f258fb19ff7d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
@@ -23,7 +23,6 @@ public interface RotationLockController extends Listenable,
int getRotationLockOrientation();
boolean isRotationLockAffordanceVisible();
boolean isRotationLocked();
- boolean isCameraRotationEnabled();
void setRotationLocked(boolean locked);
void setRotationLockedAtAngle(boolean locked, int rotation);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index c185928998c4..67f5364e3d3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -16,40 +16,54 @@
package com.android.systemui.statusbar.policy;
-import android.content.Context;
+import static com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule.DEVICE_STATE_ROTATION_LOCK_DEFAULTS;
+
import android.os.UserHandle;
-import android.provider.Settings.Secure;
import androidx.annotation.NonNull;
-import com.android.internal.view.RotationPolicy;
+import com.android.internal.view.RotationPolicy.RotationPolicyListener;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.wrapper.RotationPolicyWrapper;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
+import javax.inject.Named;
/** Platform implementation of the rotation lock controller. **/
@SysUISingleton
public final class RotationLockControllerImpl implements RotationLockController {
- private final Context mContext;
- private final SecureSettings mSecureSettings;
private final CopyOnWriteArrayList<RotationLockControllerCallback> mCallbacks =
- new CopyOnWriteArrayList<RotationLockControllerCallback>();
+ new CopyOnWriteArrayList<>();
+
+ private final RotationPolicyListener mRotationPolicyListener =
+ new RotationPolicyListener() {
+ @Override
+ public void onChange() {
+ notifyChanged();
+ }
+ };
- private final RotationPolicy.RotationPolicyListener mRotationPolicyListener =
- new RotationPolicy.RotationPolicyListener() {
- @Override
- public void onChange() {
- notifyChanged();
- }
- };
+ private final RotationPolicyWrapper mRotationPolicy;
+ private final DeviceStateRotationLockSettingController
+ mDeviceStateRotationLockSettingController;
+ private final boolean mIsPerDeviceStateRotationLockEnabled;
@Inject
- public RotationLockControllerImpl(Context context, SecureSettings secureSettings) {
- mContext = context;
- mSecureSettings = secureSettings;
+ public RotationLockControllerImpl(
+ RotationPolicyWrapper rotationPolicyWrapper,
+ DeviceStateRotationLockSettingController deviceStateRotationLockSettingController,
+ @Named(DEVICE_STATE_ROTATION_LOCK_DEFAULTS) String[] deviceStateRotationLockDefaults
+ ) {
+ mRotationPolicy = rotationPolicyWrapper;
+ mDeviceStateRotationLockSettingController = deviceStateRotationLockSettingController;
+ mIsPerDeviceStateRotationLockEnabled = deviceStateRotationLockDefaults.length > 0;
+ if (mIsPerDeviceStateRotationLockEnabled) {
+ deviceStateRotationLockSettingController.initialize();
+ mCallbacks.add(mDeviceStateRotationLockSettingController);
+ }
+
setListening(true);
}
@@ -65,37 +79,35 @@ public final class RotationLockControllerImpl implements RotationLockController
}
public int getRotationLockOrientation() {
- return RotationPolicy.getRotationLockOrientation(mContext);
+ return mRotationPolicy.getRotationLockOrientation();
}
public boolean isRotationLocked() {
- return RotationPolicy.isRotationLocked(mContext);
- }
-
- public boolean isCameraRotationEnabled() {
- return mSecureSettings.getIntForUser(Secure.CAMERA_AUTOROTATE, 0, UserHandle.USER_CURRENT)
- == 1;
+ return mRotationPolicy.isRotationLocked();
}
public void setRotationLocked(boolean locked) {
- RotationPolicy.setRotationLock(mContext, locked);
+ mRotationPolicy.setRotationLock(locked);
}
- public void setRotationLockedAtAngle(boolean locked, int rotation) {
- RotationPolicy.setRotationLockAtAngle(mContext, locked, rotation);
+ public void setRotationLockedAtAngle(boolean locked, int rotation){
+ mRotationPolicy.setRotationLockAtAngle(locked, rotation);
}
public boolean isRotationLockAffordanceVisible() {
- return RotationPolicy.isRotationLockToggleVisible(mContext);
+ return mRotationPolicy.isRotationLockToggleVisible();
}
@Override
public void setListening(boolean listening) {
if (listening) {
- RotationPolicy.registerRotationPolicyListener(mContext, mRotationPolicyListener,
+ mRotationPolicy.registerRotationPolicyListener(mRotationPolicyListener,
UserHandle.USER_ALL);
} else {
- RotationPolicy.unregisterRotationPolicyListener(mContext, mRotationPolicyListener);
+ mRotationPolicy.unregisterRotationPolicyListener(mRotationPolicyListener);
+ }
+ if (mIsPerDeviceStateRotationLockEnabled) {
+ mDeviceStateRotationLockSettingController.setListening(listening);
}
}
@@ -106,7 +118,7 @@ public final class RotationLockControllerImpl implements RotationLockController
}
private void notifyChanged(RotationLockControllerCallback callback) {
- callback.onRotationLockStateChanged(RotationPolicy.isRotationLocked(mContext),
- RotationPolicy.isRotationLockToggleVisible(mContext));
+ callback.onRotationLockStateChanged(mRotationPolicy.isRotationLocked(),
+ mRotationPolicy.isRotationLockToggleVisible());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 3e661df802a6..7c13173a7ec4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -55,6 +55,7 @@ import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.settings.CurrentUserTracker;
import org.xmlpull.v1.XmlPullParserException;
@@ -109,7 +110,8 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi
Context context,
@Background Handler bgHandler,
BroadcastDispatcher broadcastDispatcher,
- @Background Executor bgExecutor
+ @Background Executor bgExecutor,
+ DumpManager dumpManager
) {
super(broadcastDispatcher);
mContext = context;
@@ -122,6 +124,8 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mBgExecutor = bgExecutor;
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
+
IntentFilter filter = new IntentFilter();
filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
filter.addAction(Intent.ACTION_USER_UNLOCKED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java
index 6f659c1f2af5..2b8d3a2f913b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java
@@ -29,7 +29,8 @@ import java.util.List;
* Controls sensor privacy state and notification.
*/
@SysUISingleton
-public class SensorPrivacyControllerImpl implements SensorPrivacyController,
+public class SensorPrivacyControllerImpl implements
+ SensorPrivacyController,
SensorPrivacyManager.OnAllSensorPrivacyChangedListener {
private SensorPrivacyManager mSensorPrivacyManager;
private final List<OnSensorPrivacyChangedListener> mListeners = new ArrayList<>(1);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 251ecc626387..b630689567ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -25,6 +25,7 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Dialog;
+import android.app.IActivityManager;
import android.app.IActivityTaskManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -55,8 +56,10 @@ import android.view.WindowManagerGlobal;
import android.widget.BaseAdapter;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.LatencyTracker;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.systemui.Dumpable;
import com.android.systemui.GuestResumeSessionReceiver;
@@ -68,12 +71,14 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.qs.tiles.UserDetailView;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.user.CreateUserActivity;
@@ -106,6 +111,7 @@ public class UserSwitcherController implements Dumpable {
private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000;
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+ private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000l;
protected final Context mContext;
protected final UserTracker mUserTracker;
@@ -122,6 +128,8 @@ public class UserSwitcherController implements Dumpable {
private final BroadcastDispatcher mBroadcastDispatcher;
private final TelephonyListenerManager mTelephonyListenerManager;
private final IActivityTaskManager mActivityTaskManager;
+ private final InteractionJankMonitor mInteractionJankMonitor;
+ private final LatencyTracker mLatencyTracker;
private ArrayList<UserRecord> mUsers = new ArrayList<>();
@VisibleForTesting
@@ -140,15 +148,18 @@ public class UserSwitcherController implements Dumpable {
private Intent mSecondaryUserServiceIntent;
private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
private final UiEventLogger mUiEventLogger;
+ private final IActivityManager mActivityManager;
public final DetailAdapter mUserDetailAdapter;
private final Executor mBgExecutor;
private final boolean mGuestUserAutoCreated;
private final AtomicBoolean mGuestIsResetting;
private final AtomicBoolean mGuestCreationScheduled;
private FalsingManager mFalsingManager;
+ private NotificationShadeWindowView mRootView;
@Inject
public UserSwitcherController(Context context,
+ IActivityManager activityManager,
UserManager userManager,
UserTracker userTracker,
KeyguardStateController keyguardStateController,
@@ -163,14 +174,20 @@ public class UserSwitcherController implements Dumpable {
IActivityTaskManager activityTaskManager,
UserDetailAdapter userDetailAdapter,
SecureSettings secureSettings,
- @Background Executor bgExecutor) {
+ @Background Executor bgExecutor,
+ InteractionJankMonitor interactionJankMonitor,
+ LatencyTracker latencyTracker,
+ DumpManager dumpManager) {
mContext = context;
+ mActivityManager = activityManager;
mUserTracker = userTracker;
mBroadcastDispatcher = broadcastDispatcher;
mTelephonyListenerManager = telephonyListenerManager;
mActivityTaskManager = activityTaskManager;
mUiEventLogger = uiEventLogger;
mFalsingManager = falsingManager;
+ mInteractionJankMonitor = interactionJankMonitor;
+ mLatencyTracker = latencyTracker;
mGuestResumeSessionReceiver = new GuestResumeSessionReceiver(
this, mUserTracker, mUiEventLogger, secureSettings);
mUserDetailAdapter = userDetailAdapter;
@@ -231,6 +248,8 @@ public class UserSwitcherController implements Dumpable {
keyguardStateController.addCallback(mCallback);
listenForCallState();
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
+
refreshUsers(UserHandle.USER_NULL);
}
@@ -481,8 +500,12 @@ public class UserSwitcherController implements Dumpable {
protected void switchToUserId(int id) {
try {
+ mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
+ .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mRootView)
+ .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
+ mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH);
pauseRefreshUsers();
- ActivityManager.getService().switchUser(id);
+ mActivityManager.switchUser(id);
} catch (RemoteException e) {
Log.e(TAG, "Couldn't switch user.", e);
}
@@ -789,6 +812,10 @@ public class UserSwitcherController implements Dumpable {
return guest.id;
}
+ public void init(NotificationShadeWindowView notificationShadeWindowView) {
+ mRootView = notificationShadeWindowView;
+ }
+
public static abstract class BaseUserAdapter extends BaseAdapter {
final UserSwitcherController mController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 897a3b863e73..5acce7f80f6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -44,6 +44,7 @@ import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.qs.GlobalSetting;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.util.Utils;
@@ -80,8 +81,11 @@ public class ZenModeControllerImpl extends CurrentUserTracker
private NotificationManager.Policy mConsolidatedNotificationPolicy;
@Inject
- public ZenModeControllerImpl(Context context, @Main Handler handler,
- BroadcastDispatcher broadcastDispatcher) {
+ public ZenModeControllerImpl(
+ Context context,
+ @Main Handler handler,
+ BroadcastDispatcher broadcastDispatcher,
+ DumpManager dumpManager) {
super(broadcastDispatcher);
mContext = context;
mModeSetting = new GlobalSetting(mContext, handler, Global.ZEN_MODE) {
@@ -108,6 +112,8 @@ public class ZenModeControllerImpl extends CurrentUserTracker
mSetupObserver.register();
mUserManager = context.getSystemService(UserManager.class);
startTracking();
+
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 9fb0453a2888..92908790770a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -16,18 +16,24 @@
package com.android.systemui.statusbar.policy.dagger;
+import android.content.res.Resources;
import android.os.UserManager;
+import com.android.internal.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.policy.AccessPointControllerImpl;
+import com.android.systemui.statusbar.connectivity.AccessPointControllerImpl;
+import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.NetworkControllerImpl;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastControllerImpl;
import com.android.systemui.statusbar.policy.DeviceControlsController;
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl;
+import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.statusbar.policy.DevicePostureControllerImpl;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
import com.android.systemui.statusbar.policy.FlashlightController;
@@ -38,8 +44,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.KeyguardStateControllerImpl;
import com.android.systemui.statusbar.policy.LocationController;
import com.android.systemui.statusbar.policy.LocationControllerImpl;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
import com.android.systemui.statusbar.policy.RotationLockController;
@@ -55,6 +59,8 @@ import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
import java.util.concurrent.Executor;
+import javax.inject.Named;
+
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
@@ -63,6 +69,9 @@ import dagger.Provides;
/** Dagger Module for code in the statusbar.policy package. */
@Module
public interface StatusBarPolicyModule {
+
+ String DEVICE_STATE_ROTATION_LOCK_DEFAULTS = "DEVICE_STATE_ROTATION_LOCK_DEFAULTS";
+
/** */
@Binds
BluetoothController provideBluetoothController(BluetoothControllerImpl controllerImpl);
@@ -130,6 +139,11 @@ public interface StatusBarPolicyModule {
AccessPointControllerImpl accessPointControllerImpl);
/** */
+ @Binds
+ DevicePostureController provideDevicePostureController(
+ DevicePostureControllerImpl devicePostureControllerImpl);
+
+ /** */
@SysUISingleton
@Provides
static AccessPointControllerImpl provideAccessPointControllerImpl(
@@ -147,4 +161,14 @@ public interface StatusBarPolicyModule {
controller.init();
return controller;
}
+
+ /**
+ * Default values for per-device state rotation lock settings.
+ */
+ @Provides
+ @Named(DEVICE_STATE_ROTATION_LOCK_DEFAULTS)
+ static String[] providesDeviceStateRotationLockDefaults(@Main Resources resources) {
+ return resources.getStringArray(
+ R.array.config_perDeviceStateRotationLockDefaults);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 81999b534046..cd5865f58c85 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -37,6 +37,7 @@ import android.content.IntentFilter;
import android.content.om.FabricatedOverlay;
import android.content.om.OverlayIdentifier;
import android.content.pm.UserInfo;
+import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.Color;
import android.net.Uri;
@@ -47,9 +48,11 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.TypedValue;
import androidx.annotation.NonNull;
+import com.android.internal.graphics.ColorUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.SystemUI;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -57,9 +60,10 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.monet.ColorScheme;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.util.settings.SecureSettings;
@@ -71,6 +75,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -107,6 +112,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable {
private DeviceProvisionedController mDeviceProvisionedController;
private WallpaperColors mCurrentColors;
private WallpaperManager mWallpaperManager;
+ private ColorScheme mColorScheme;
// If fabricated overlays were already created for the current theme.
private boolean mNeedsOverlayCreation;
// Dominant color extracted from wallpaper, NOT the color used on the overlay
@@ -403,25 +409,47 @@ public class ThemeOverlayController extends SystemUI implements Dumpable {
* Return the main theme color from a given {@link WallpaperColors} instance.
*/
protected int getNeutralColor(@NonNull WallpaperColors wallpaperColors) {
- return wallpaperColors.getPrimaryColor().toArgb();
+ return ColorScheme.getSeedColor(wallpaperColors);
}
protected int getAccentColor(@NonNull WallpaperColors wallpaperColors) {
- Color accentCandidate = wallpaperColors.getSecondaryColor();
- if (accentCandidate == null) {
- accentCandidate = wallpaperColors.getTertiaryColor();
- }
- if (accentCandidate == null) {
- accentCandidate = wallpaperColors.getPrimaryColor();
- }
- return accentCandidate.toArgb();
+ return ColorScheme.getSeedColor(wallpaperColors);
}
/**
* Given a color candidate, return an overlay definition.
*/
protected @Nullable FabricatedOverlay getOverlay(int color, int type) {
- return null;
+ boolean nightMode = (mContext.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+
+ mColorScheme = new ColorScheme(color, nightMode);
+ List<Integer> colorShades = type == ACCENT
+ ? mColorScheme.getAllAccentColors() : mColorScheme.getAllNeutralColors();
+ String name = type == ACCENT ? "accent" : "neutral";
+ int paletteSize = mColorScheme.getAccent1().size();
+ FabricatedOverlay.Builder overlay =
+ new FabricatedOverlay.Builder("com.android.systemui", name, "android");
+ for (int i = 0; i < colorShades.size(); i++) {
+ int luminosity = i % paletteSize;
+ int paletteIndex = i / paletteSize + 1;
+ String resourceName;
+ switch (luminosity) {
+ case 0:
+ resourceName = "android:color/system_" + name + paletteIndex + "_10";
+ break;
+ case 1:
+ resourceName = "android:color/system_" + name + paletteIndex + "_50";
+ break;
+ default:
+ int l = luminosity - 1;
+ resourceName = "android:color/system_" + name + paletteIndex + "_" + l + "00";
+ }
+ overlay.setResourceValue(resourceName, TypedValue.TYPE_INT_COLOR_ARGB8,
+ ColorUtils.setAlphaComponent(colorShades.get(i), 0xFF));
+ }
+
+ return overlay.build();
}
private void updateThemeOverlays() {
@@ -540,6 +568,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable {
pw.println("mSecondaryOverlay=" + mSecondaryOverlay);
pw.println("mNeutralOverlay=" + mNeutralOverlay);
pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
+ pw.println("mColorScheme=" + mColorScheme);
pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation);
pw.println("mAcceptColorEvents=" + mAcceptColorEvents);
pw.println("mDeferredThemeEvaluation=" + mDeferredThemeEvaluation);
diff --git a/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java b/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java
index 8a8f92b32bfe..98b2ccada4e6 100644
--- a/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java
+++ b/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java
@@ -48,8 +48,13 @@ import javax.inject.Inject;
* Controller for coordinating winscope proto tracing.
*/
@SysUISingleton
-public class ProtoTracer implements Dumpable, ProtoTraceParams<MessageNano, SystemUiTraceFileProto,
- SystemUiTraceEntryProto, SystemUiTraceProto> {
+public class ProtoTracer implements
+ Dumpable,
+ ProtoTraceParams<
+ MessageNano,
+ SystemUiTraceFileProto,
+ SystemUiTraceEntryProto,
+ SystemUiTraceProto> {
private static final String TAG = "ProtoTracer";
private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
@@ -62,7 +67,7 @@ public class ProtoTracer implements Dumpable, ProtoTraceParams<MessageNano, Syst
public ProtoTracer(Context context, DumpManager dumpManager) {
mContext = context;
mProtoTracer = new FrameProtoTracer<>(this);
- dumpManager.registerDumpable(getClass().getName(), this);
+ dumpManager.registerDumpable(this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
index 0a29e04ce20f..fe183fc9e872 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
@@ -38,8 +38,8 @@ import com.android.internal.util.ArrayUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.PluginEnablerImpl;
+import com.android.systemui.shared.plugins.PluginActionManager;
import com.android.systemui.shared.plugins.PluginEnabler;
-import com.android.systemui.shared.plugins.PluginInstanceManager;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginPrefs;
@@ -102,12 +102,12 @@ public class PluginFragment extends PreferenceFragment {
}
List<PackageInfo> apps = pm.getPackagesHoldingPermissions(new String[]{
- PluginInstanceManager.PLUGIN_PERMISSION},
+ PluginActionManager.PLUGIN_PERMISSION},
PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES);
apps.forEach(app -> {
if (!plugins.containsKey(app.packageName)) return;
- if (ArrayUtils.contains(manager.getWhitelistedPlugins(), app.packageName)) {
- // Don't manage whitelisted plugins, they are part of the OS.
+ if (ArrayUtils.contains(manager.getPrivilegedPlugins(), app.packageName)) {
+ // Don't manage privileged plugins, they are part of the OS.
return;
}
SwitchPreference pref = new PluginPreference(prefContext, app, mPluginEnabler);
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 35251002fb7b..923aff1e6b3d 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -70,6 +70,7 @@ import com.android.systemui.statusbar.policy.IndividualSensorPrivacyControllerIm
import com.android.systemui.statusbar.policy.SensorPrivacyController;
import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler;
+import com.android.systemui.volume.dagger.VolumeModule;
import javax.inject.Named;
@@ -83,7 +84,8 @@ import dagger.Provides;
*/
@Module(includes = {
PowerModule.class,
- QSModule.class
+ QSModule.class,
+ VolumeModule.class,
},
subcomponents = {
})
@@ -178,9 +180,13 @@ public abstract class TvSystemUIModule {
return new Recents(context, recentsImplementation, commandQueue);
}
- @Binds
- abstract DeviceProvisionedController bindDeviceProvisionedController(
- DeviceProvisionedControllerImpl deviceProvisionedController);
+ @SysUISingleton
+ @Provides
+ static DeviceProvisionedController providesDeviceProvisionedController(
+ DeviceProvisionedControllerImpl deviceProvisionedController) {
+ deviceProvisionedController.init();
+ return deviceProvisionedController;
+ }
@Binds
abstract KeyguardViewController bindKeyguardViewController(
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/ShellUnfoldProgressProvider.kt b/packages/SystemUI/src/com/android/systemui/unfold/ShellUnfoldProgressProvider.kt
new file mode 100644
index 000000000000..4a884359d315
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/ShellUnfoldProgressProvider.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.systemui.unfold
+
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener
+import java.util.concurrent.Executor
+
+class ShellUnfoldProgressProvider(
+ private val unfoldProgressProvider: UnfoldTransitionProgressProvider
+) : ShellUnfoldProgressProvider {
+
+ override fun addListener(executor: Executor, listener: UnfoldListener) {
+ unfoldProgressProvider.addCallback(object : TransitionProgressListener {
+ override fun onTransitionStarted() {
+ executor.execute {
+ listener.onStateChangeStarted()
+ }
+ }
+
+ override fun onTransitionProgress(progress: Float) {
+ executor.execute {
+ listener.onStateChangeProgress(progress)
+ }
+ }
+
+ override fun onTransitionFinished() {
+ executor.execute {
+ listener.onStateChangeFinished()
+ }
+ }
+ })
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
new file mode 100644
index 000000000000..f0760d4e2187
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -0,0 +1,270 @@
+/*
+ * 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.systemui.unfold
+
+import android.content.Context
+import android.graphics.PixelFormat
+import android.hardware.devicestate.DeviceStateManager
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.hardware.display.DisplayManager
+import android.os.Handler
+import android.os.Trace
+import android.view.Choreographer
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.Surface
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.SurfaceSession
+import android.view.WindowManager
+import android.view.WindowlessWindowManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.LinearLightRevealEffect
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.wm.shell.displayareahelper.DisplayAreaHelper
+import java.util.Optional
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+import javax.inject.Inject
+
+@SysUISingleton
+class UnfoldLightRevealOverlayAnimation @Inject constructor(
+ private val context: Context,
+ private val deviceStateManager: DeviceStateManager,
+ private val displayManager: DisplayManager,
+ private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+ private val displayAreaHelper: Optional<DisplayAreaHelper>,
+ @Main private val executor: Executor,
+ @Main private val handler: Handler,
+ @UiBackground private val backgroundExecutor: Executor
+) {
+
+ private val transitionListener = TransitionListener()
+ private val displayListener = DisplayChangeListener()
+
+ private lateinit var wwm: WindowlessWindowManager
+ private lateinit var unfoldedDisplayInfo: DisplayInfo
+ private lateinit var overlayContainer: SurfaceControl
+
+ private var root: SurfaceControlViewHost? = null
+ private var scrimView: LightRevealScrim? = null
+ private var isFolded: Boolean = false
+ private var isUnfoldHandled: Boolean = true
+
+ private var currentRotation: Int = context.display!!.rotation
+
+ fun init() {
+ deviceStateManager.registerCallback(executor, FoldListener())
+ unfoldTransitionProgressProvider.addCallback(transitionListener)
+
+ val containerBuilder = SurfaceControl.Builder(SurfaceSession())
+ .setContainerLayer()
+ .setName("unfold-overlay-container")
+
+ displayAreaHelper.get().attachToRootDisplayArea(Display.DEFAULT_DISPLAY,
+ containerBuilder) { builder ->
+ executor.execute {
+ overlayContainer = builder.build()
+
+ SurfaceControl.Transaction()
+ .setLayer(overlayContainer, Integer.MAX_VALUE)
+ .show(overlayContainer)
+ .apply()
+
+ wwm = WindowlessWindowManager(context.resources.configuration,
+ overlayContainer, null)
+ }
+ }
+
+ displayManager.registerDisplayListener(displayListener, handler,
+ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
+
+ // Get unfolded display size immediately as 'current display info' might be
+ // not up-to-date during unfolding
+ unfoldedDisplayInfo = getUnfoldedDisplayInfo()
+ }
+
+ /**
+ * Called when screen starts turning on, the contents of the screen might not be visible yet.
+ * This method reports back that the overlay is ready in [onOverlayReady] callback.
+ *
+ * @param onOverlayReady callback when the overlay is drawn and visible on the screen
+ * @see [com.android.systemui.keyguard.KeyguardViewMediator]
+ */
+ fun onScreenTurningOn(onOverlayReady: Runnable) {
+ Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn")
+ try {
+ // Add the view only if we are unfolding and this is the first screen on
+ if (!isFolded && !isUnfoldHandled) {
+ addView(onOverlayReady)
+ isUnfoldHandled = true
+ } else {
+ // No unfold transition, immediately report that overlay is ready
+ ensureOverlayRemoved()
+ onOverlayReady.run()
+ }
+ } finally {
+ Trace.endSection()
+ }
+ }
+
+ private fun addView(onOverlayReady: Runnable? = null) {
+ if (!::wwm.isInitialized) {
+ // Surface overlay is not created yet on the first SysUI launch
+ onOverlayReady?.run()
+ return
+ }
+
+ ensureOverlayRemoved()
+
+ val newRoot = SurfaceControlViewHost(context, context.display!!, wwm, false)
+ val newView = LightRevealScrim(context, null)
+ .apply {
+ revealEffect = createLightRevealEffect()
+ isScrimOpaqueChangedListener = Consumer {}
+ revealAmount = 0f
+ }
+
+ val params = getLayoutParams()
+ newRoot.setView(newView, params)
+
+ onOverlayReady?.let { callback ->
+ Trace.beginAsyncSection(
+ "UnfoldLightRevealOverlayAnimation#relayout", 0)
+
+ newRoot.relayout(params) { transaction ->
+ val vsyncId = Choreographer.getSfInstance().vsyncId
+
+ backgroundExecutor.execute {
+ // Apply the transaction that contains the first frame of the overlay
+ // synchronously and apply another empty transaction with
+ // 'vsyncId + 1' to make sure that it is actually displayed on
+ // the screen. The second transaction is necessary to remove the screen blocker
+ // (turn on the brightness) only when the content is actually visible as it
+ // might be presented only in the next frame.
+ // See b/197538198
+ transaction.setFrameTimelineVsync(vsyncId)
+ .apply(/* sync */true)
+
+ transaction
+ .setFrameTimelineVsync(vsyncId + 1)
+ .apply(/* sync */ true)
+
+ Trace.endAsyncSection(
+ "UnfoldLightRevealOverlayAnimation#relayout", 0)
+ callback.run()
+ }
+ }
+ }
+
+ scrimView = newView
+ root = newRoot
+ }
+
+ private fun getLayoutParams(): WindowManager.LayoutParams {
+ val params: WindowManager.LayoutParams = WindowManager.LayoutParams()
+
+ val rotation = context.display!!.rotation
+ val isNatural = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
+
+ params.height = if (isNatural)
+ unfoldedDisplayInfo.naturalHeight else unfoldedDisplayInfo.naturalWidth
+ params.width = if (isNatural)
+ unfoldedDisplayInfo.naturalWidth else unfoldedDisplayInfo.naturalHeight
+
+ params.format = PixelFormat.TRANSLUCENT
+ params.type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
+ params.title = "Unfold Light Reveal Animation"
+ params.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ params.fitInsetsTypes = 0
+ params.flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
+ params.setTrustedOverlay()
+
+ val packageName: String = context.opPackageName
+ params.packageName = packageName
+
+ return params
+ }
+
+ private fun createLightRevealEffect(): LightRevealEffect {
+ val isVerticalFold = currentRotation == Surface.ROTATION_0 ||
+ currentRotation == Surface.ROTATION_180
+ return LinearLightRevealEffect(isVertical = isVerticalFold)
+ }
+
+ private fun ensureOverlayRemoved() {
+ root?.release()
+ root = null
+ scrimView = null
+ }
+
+ private fun getUnfoldedDisplayInfo(): DisplayInfo =
+ displayManager.displays
+ .asSequence()
+ .map { DisplayInfo().apply { it.getDisplayInfo(this) } }
+ .filter { it.type == Display.TYPE_INTERNAL }
+ .maxByOrNull { it.naturalWidth }!!
+
+ private inner class TransitionListener : TransitionProgressListener {
+
+ override fun onTransitionProgress(progress: Float) {
+ scrimView?.revealAmount = progress
+ }
+
+ override fun onTransitionFinished() {
+ ensureOverlayRemoved()
+ }
+
+ override fun onTransitionStarted() {
+ // Add view for folding case (when unfolding the view is added earlier)
+ if (scrimView == null) {
+ addView()
+ }
+ }
+ }
+
+ private inner class DisplayChangeListener : DisplayManager.DisplayListener {
+
+ override fun onDisplayChanged(displayId: Int) {
+ val newRotation: Int = context.display!!.rotation
+ if (currentRotation != newRotation) {
+ currentRotation = newRotation
+ scrimView?.revealEffect = createLightRevealEffect()
+ root?.relayout(getLayoutParams())
+ }
+ }
+
+ override fun onDisplayAdded(displayId: Int) {
+ }
+
+ override fun onDisplayRemoved(displayId: Int) {
+ }
+ }
+
+ private inner class FoldListener : FoldStateListener(context, Consumer { isFolded ->
+ if (isFolded) {
+ ensureOverlayRemoved()
+ isUnfoldHandled = false
+ }
+ this.isFolded = isFolded
+ })
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
new file mode 100644
index 000000000000..7e4ec67e6e18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.systemui.unfold
+
+import android.content.Context
+import android.hardware.SensorManager
+import android.hardware.devicestate.DeviceStateManager
+import android.os.Handler
+import android.view.IWindowManager
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.LifecycleScreenStatusProvider
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
+import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import java.util.Optional
+import java.util.concurrent.Executor
+import javax.inject.Named
+import javax.inject.Singleton
+
+@Module
+class UnfoldTransitionModule {
+
+ @Provides
+ @Singleton
+ fun provideUnfoldTransitionProgressProvider(
+ context: Context,
+ config: UnfoldTransitionConfig,
+ screenStatusProvider: LifecycleScreenStatusProvider,
+ deviceStateManager: DeviceStateManager,
+ sensorManager: SensorManager,
+ @Main executor: Executor,
+ @Main handler: Handler
+ ): UnfoldTransitionProgressProvider =
+ createUnfoldTransitionProgressProvider(
+ context,
+ config,
+ screenStatusProvider,
+ deviceStateManager,
+ sensorManager,
+ handler,
+ executor
+ )
+
+ @Provides
+ @Singleton
+ fun provideUnfoldTransitionConfig(context: Context): UnfoldTransitionConfig =
+ createConfig(context)
+
+ @Provides
+ @Singleton
+ fun provideNaturalRotationProgressProvider(
+ context: Context,
+ windowManager: IWindowManager,
+ unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider
+ ): NaturalRotationUnfoldProgressProvider =
+ NaturalRotationUnfoldProgressProvider(
+ context,
+ windowManager,
+ unfoldTransitionProgressProvider
+ )
+
+ @Provides
+ @Named(UNFOLD_STATUS_BAR)
+ @Singleton
+ fun provideStatusBarScopedTransitionProvider(
+ source: NaturalRotationUnfoldProgressProvider
+ ): ScopedUnfoldTransitionProgressProvider =
+ ScopedUnfoldTransitionProgressProvider(source)
+
+ @Provides
+ @Singleton
+ fun provideShellProgressProvider(
+ config: UnfoldTransitionConfig,
+ provider: Lazy<UnfoldTransitionProgressProvider>
+ ): Optional<ShellUnfoldProgressProvider> =
+ if (config.isEnabled) {
+ Optional.ofNullable(ShellUnfoldProgressProvider(provider.get()))
+ } else {
+ Optional.empty()
+ }
+}
+
+const val UNFOLD_STATUS_BAR = "unfold_status_bar" \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt
new file mode 100644
index 000000000000..4f45aafce416
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.systemui.unfold
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.util.WallpaperController
+import javax.inject.Inject
+
+@SysUISingleton
+class UnfoldTransitionWallpaperController @Inject constructor(
+ private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+ private val wallpaperController: WallpaperController
+) {
+
+ fun init() {
+ unfoldTransitionProgressProvider.addCallback(TransitionListener())
+ }
+
+ private inner class TransitionListener : TransitionProgressListener {
+ override fun onTransitionProgress(progress: Float) {
+ // Fully zoomed in when fully unfolded
+ wallpaperController.setUnfoldTransitionZoom(1 - progress)
+ }
+
+ override fun onTransitionFinished() {
+ // Resets wallpaper zoom-out to 0f when fully folded
+ // When fully unfolded it is set to 0f by onTransitionProgress
+ wallpaperController.setUnfoldTransitionZoom(0f)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/AlphaTintDrawableWrapper.java b/packages/SystemUI/src/com/android/systemui/util/AlphaTintDrawableWrapper.java
index a22793b05070..516efc06386a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/AlphaTintDrawableWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/util/AlphaTintDrawableWrapper.java
@@ -109,6 +109,12 @@ public class AlphaTintDrawableWrapper extends InsetDrawable {
}
}
+ @Override
+ public void setTintList(ColorStateList tint) {
+ super.setTintList(tint);
+ mTint = tint;
+ }
+
private void applyTint() {
if (getDrawable() != null && mTint != null) {
getDrawable().mutate().setTintList(mTint);
diff --git a/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt b/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt
new file mode 100644
index 000000000000..9f33c271881d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.systemui.util
+
+import android.util.IndentingPrintWriter
+import android.view.View
+import java.io.PrintWriter
+import java.util.function.Consumer
+
+/**
+ * Run some code that will print to an [IndentingPrintWriter] that wraps the given [PrintWriter].
+ *
+ * If the given [PrintWriter] is an [IndentingPrintWriter], the block will be passed that same
+ * instance with [IndentingPrintWriter.increaseIndent] having been called, and calling
+ * [IndentingPrintWriter.decreaseIndent] after completion of the block, so the passed [PrintWriter]
+ * should not be used before the block completes.
+ */
+inline fun PrintWriter.withIndenting(block: (IndentingPrintWriter) -> Unit) {
+ if (this is IndentingPrintWriter) {
+ this.withIncreasedIndent { block(this) }
+ } else {
+ block(IndentingPrintWriter(this))
+ }
+}
+
+/**
+ * Run some code that will print to an [IndentingPrintWriter] that wraps the given [PrintWriter].
+ *
+ * If the given [PrintWriter] is an [IndentingPrintWriter], the block will be passed that same
+ * instance with [IndentingPrintWriter.increaseIndent] having been called, and calling
+ * [IndentingPrintWriter.decreaseIndent] after completion of the block, so the passed [PrintWriter]
+ * should not be used before the block completes.
+ */
+fun PrintWriter.withIndenting(consumer: Consumer<IndentingPrintWriter>) {
+ if (this is IndentingPrintWriter) {
+ this.withIncreasedIndent { consumer.accept(this) }
+ } else {
+ consumer.accept(IndentingPrintWriter(this))
+ }
+}
+
+/**
+ * Run some code inside a block, with [IndentingPrintWriter.increaseIndent] having been called on
+ * the given argument, and calling [IndentingPrintWriter.decreaseIndent] after completion.
+ */
+inline fun IndentingPrintWriter.withIncreasedIndent(block: () -> Unit) {
+ this.increaseIndent()
+ try {
+ block()
+ } finally {
+ this.decreaseIndent()
+ }
+}
+
+/** Return a readable string for the visibility */
+fun visibilityString(@View.Visibility visibility: Int): String = when (visibility) {
+ View.GONE -> "gone"
+ View.VISIBLE -> "visible"
+ View.INVISIBLE -> "invisible"
+ else -> "unknown:$visibility"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
deleted file mode 100644
index cdc5d8795b91..000000000000
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util;
-
-import android.content.Context;
-import android.util.ArrayMap;
-import android.util.AttributeSet;
-import android.view.InflateException;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import dagger.BindsInstance;
-import dagger.Subcomponent;
-
-/**
- * Manages inflation that requires dagger injection.
- * See docs/dagger.md for details.
- */
-@SysUISingleton
-public class InjectionInflationController {
-
- public static final String VIEW_CONTEXT = "view_context";
- private final ArrayMap<String, Method> mInjectionMap = new ArrayMap<>();
- private final LayoutInflater.Factory2 mFactory = new InjectionFactory();
- private final ViewInstanceCreator.Factory mViewInstanceCreatorFactory;
-
- @Inject
- public InjectionInflationController(ViewInstanceCreator.Factory viewInstanceCreatorFactory) {
- mViewInstanceCreatorFactory = viewInstanceCreatorFactory;
- initInjectionMap();
- }
-
- /**
- * Wraps a {@link LayoutInflater} to support creating dagger injected views.
- * See docs/dagger.md for details.
- */
- public LayoutInflater injectable(LayoutInflater inflater) {
- LayoutInflater ret = inflater.cloneInContext(inflater.getContext());
- ret.setPrivateFactory(mFactory);
- return ret;
- }
-
- private void initInjectionMap() {
- for (Method method : ViewInstanceCreator.class.getDeclaredMethods()) {
- if (View.class.isAssignableFrom(method.getReturnType())
- && (method.getModifiers() & Modifier.PUBLIC) != 0) {
- mInjectionMap.put(method.getReturnType().getName(), method);
- }
- }
- }
-
- /**
- * Subcomponent that actually creates injected views.
- */
- @Subcomponent
- public interface ViewInstanceCreator {
-
- /** Factory for creating a ViewInstanceCreator. */
- @Subcomponent.Factory
- interface Factory {
- ViewInstanceCreator build(
- @BindsInstance @Named(VIEW_CONTEXT) Context context,
- @BindsInstance AttributeSet attributeSet);
- }
-
- /**
- * Creates the NotificationStackScrollLayout.
- */
- NotificationStackScrollLayout createNotificationStackScrollLayout();
- }
-
-
- private class InjectionFactory implements LayoutInflater.Factory2 {
-
- @Override
- public View onCreateView(String name, Context context, AttributeSet attrs) {
- Method creationMethod = mInjectionMap.get(name);
- if (creationMethod != null) {
- try {
- return (View) creationMethod.invoke(
- mViewInstanceCreatorFactory.build(context, attrs));
- } catch (IllegalAccessException e) {
- throw new InflateException("Could not inflate " + name, e);
- } catch (InvocationTargetException e) {
- throw new InflateException("Could not inflate " + name, e);
- }
- }
- return null;
- }
-
- @Override
- public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
- return onCreateView(name, context, attrs);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
new file mode 100644
index 000000000000..0f4193e94196
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.systemui.util
+
+import java.util.concurrent.CopyOnWriteArrayList
+
+/**
+ * A collection of listeners, observers, callbacks, etc.
+ *
+ * This container is optimized for infrequent mutation and frequent iteration, with thread safety
+ * and reentrant-safety guarantees as well.
+ */
+class ListenerSet<E> : Iterable<E> {
+ private val listeners: CopyOnWriteArrayList<E> = CopyOnWriteArrayList()
+
+ /**
+ * A thread-safe, reentrant-safe method to add a listener.
+ * Does nothing if the listener is already in the set.
+ */
+ fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(element)
+
+ /**
+ * A thread-safe, reentrant-safe method to remove a listener.
+ */
+ fun remove(element: E): Boolean = listeners.remove(element)
+
+ /**
+ * Returns an iterator over the listeners currently in the set. Note that to ensure
+ * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes
+ * made to the set after the iterator is constructed.
+ */
+ override fun iterator(): Iterator<E> = listeners.iterator()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index 0ba072e9e72f..f97f4d0edc24 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -31,7 +31,6 @@ import java.util.Arrays;
public class NotificationChannels extends SystemUI {
public static String ALERTS = "ALR";
- public static String SCREENSHOTS_LEGACY = "SCN";
public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP";
public static String GENERAL = "GEN";
public static String STORAGE = "DSK";
@@ -84,18 +83,11 @@ public class NotificationChannels extends SystemUI {
general,
storage,
createScreenshotChannel(
- context.getString(R.string.notification_channel_screenshot),
- nm.getNotificationChannel(SCREENSHOTS_LEGACY)),
+ context.getString(R.string.notification_channel_screenshot)),
batteryChannel,
hint
));
- // Delete older SS channel if present.
- // Screenshots promoted to heads-up in P, this cleans up the lower priority channel from O.
- // This line can be deleted in Q.
- nm.deleteNotificationChannel(SCREENSHOTS_LEGACY);
-
-
if (isTv(context)) {
// TV specific notification channel for TV PIP controls.
// Importance should be {@link NotificationManager#IMPORTANCE_MAX} to have the highest
@@ -113,7 +105,7 @@ public class NotificationChannels extends SystemUI {
* @return
*/
@VisibleForTesting static NotificationChannel createScreenshotChannel(
- String name, NotificationChannel legacySS) {
+ String name) {
NotificationChannel screenshotChannel = new NotificationChannel(SCREENSHOTS_HEADSUP,
name, NotificationManager.IMPORTANCE_HIGH); // pop on screen
@@ -121,24 +113,6 @@ public class NotificationChannels extends SystemUI {
new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build());
screenshotChannel.setBlockable(true);
- if (legacySS != null) {
- // Respect any user modified fields from the old channel.
- int userlock = legacySS.getUserLockedFields();
- if ((userlock & NotificationChannel.USER_LOCKED_IMPORTANCE) != 0) {
- screenshotChannel.setImportance(legacySS.getImportance());
- }
- if ((userlock & NotificationChannel.USER_LOCKED_SOUND) != 0) {
- screenshotChannel.setSound(legacySS.getSound(), legacySS.getAudioAttributes());
- }
- if ((userlock & NotificationChannel.USER_LOCKED_VIBRATION) != 0) {
- screenshotChannel.setVibrationPattern(legacySS.getVibrationPattern());
- }
- if ((userlock & NotificationChannel.USER_LOCKED_LIGHTS) != 0) {
- screenshotChannel.setLightColor(legacySS.getLightColor());
- }
- // skip show_badge, irrelevant for system channel
- }
-
return screenshotChannel;
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
index 3b64f9f953c3..1059d6c61287 100644
--- a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
@@ -69,6 +69,10 @@ class RoundedCornerProgressDrawable @JvmOverloads constructor(
return super.getChangingConfigurations() or ActivityInfo.CONFIG_DENSITY
}
+ override fun canApplyTheme(): Boolean {
+ return (drawable?.canApplyTheme() ?: false) || super.canApplyTheme()
+ }
+
private class RoundedCornerState(private val wrappedState: ConstantState) : ConstantState() {
override fun newDrawable(): Drawable {
return newDrawable(null, null)
@@ -82,5 +86,9 @@ class RoundedCornerProgressDrawable @JvmOverloads constructor(
override fun getChangingConfigurations(): Int {
return wrappedState.changingConfigurations
}
+
+ override fun canApplyTheme(): Boolean {
+ return true
+ }
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
new file mode 100644
index 000000000000..5b16ae999aa3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.systemui.util
+
+import android.os.Trace
+
+/**
+ * Run a block within a [Trace] section.
+ * Calls [Trace.beginSection] before and [Trace.endSection] after the passed block.
+ */
+inline fun <T> traceSection(tag: String, block: () -> T): T {
+ Trace.beginSection(tag)
+ try {
+ return block()
+ } finally {
+ Trace.endSection()
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index bf006672f4d9..407dc5e2787a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -24,12 +24,13 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.provider.Settings;
import android.view.ContextThemeWrapper;
+import android.view.DisplayCutout;
import android.view.View;
+import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.R;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
import java.util.List;
import java.util.function.Consumer;
@@ -161,13 +162,11 @@ public class Utils {
}
/**
- * Returns true if the device should use the split notification shade, based on feature flags,
- * orientation and screen width.
+ * Returns true if the device should use the split notification shade, based on orientation and
+ * screen width.
*/
- public static boolean shouldUseSplitNotificationShade(FeatureFlags featureFlags,
- Resources resources) {
- return featureFlags.isTwoColumnNotificationShadeEnabled()
- && resources.getBoolean(R.bool.config_use_split_notification_shade);
+ public static boolean shouldUseSplitNotificationShade(Resources resources) {
+ return resources.getBoolean(R.bool.config_use_split_notification_shade);
}
/**
@@ -189,4 +188,39 @@ public class Utils {
return color;
}
+ /**
+ * Gets the {@link R.dimen#split_shade_header_height}.
+ *
+ * Currently, it's the same as {@link com.android.internal.R.dimen#quick_qs_offset_height}.
+ */
+ public static int getSplitShadeStatusBarHeight(Context context) {
+ return SystemBarUtils.getQuickQsOffsetHeight(context);
+ }
+
+ /**
+ * Gets the {@link R.dimen#qs_header_system_icons_area_height}.
+ *
+ * It's the same as {@link com.android.internal.R.dimen#quick_qs_offset_height} except for
+ * sw600dp-land.
+ */
+ public static int getQsHeaderSystemIconsAreaHeight(Context context) {
+ final Resources res = context.getResources();
+ if (Utils.shouldUseSplitNotificationShade(res)) {
+ return res.getDimensionPixelSize(R.dimen.qs_header_system_icons_area_height);
+ } else {
+ return SystemBarUtils.getQuickQsOffsetHeight(context);
+ }
+ }
+
+ /**
+ * Gets the {@link R.dimen#status_bar_header_height_keyguard}.
+ */
+ public static int getStatusBarHeaderHeightKeyguard(Context context) {
+ final int statusBarHeight = SystemBarUtils.getStatusBarHeight(context);
+ final DisplayCutout cutout = context.getDisplay().getCutout();
+ final int waterfallInsetTop = cutout == null ? 0 : cutout.getWaterfallInsets().top;
+ final int statusBarHeaderHeightKeyguard = context.getResources()
+ .getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard);
+ return Math.max(statusBarHeight, statusBarHeaderHeightKeyguard + waterfallInsetTop);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
new file mode 100644
index 000000000000..db2aca873d0c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.systemui.util
+
+import android.app.WallpaperInfo
+import android.app.WallpaperManager
+import android.util.Log
+import android.view.View
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlin.math.max
+
+private const val TAG = "WallpaperController"
+
+@SysUISingleton
+class WallpaperController @Inject constructor(private val wallpaperManager: WallpaperManager) {
+
+ var rootView: View? = null
+
+ private var notificationShadeZoomOut: Float = 0f
+ private var unfoldTransitionZoomOut: Float = 0f
+
+ private var wallpaperInfo: WallpaperInfo? = null
+
+ fun onWallpaperInfoUpdated(wallpaperInfo: WallpaperInfo?) {
+ this.wallpaperInfo = wallpaperInfo
+ }
+
+ private val shouldUseDefaultUnfoldTransition: Boolean
+ get() = wallpaperInfo?.shouldUseDefaultUnfoldTransition()
+ ?: true
+
+ fun setNotificationShadeZoom(zoomOut: Float) {
+ notificationShadeZoomOut = zoomOut
+ updateZoom()
+ }
+
+ fun setUnfoldTransitionZoom(zoomOut: Float) {
+ if (shouldUseDefaultUnfoldTransition) {
+ unfoldTransitionZoomOut = zoomOut
+ updateZoom()
+ }
+ }
+
+ private fun updateZoom() {
+ setWallpaperZoom(max(notificationShadeZoomOut, unfoldTransitionZoomOut))
+ }
+
+ private fun setWallpaperZoom(zoomOut: Float) {
+ try {
+ rootView?.let { root ->
+ if (root.isAttachedToWindow && root.windowToken != null) {
+ wallpaperManager.setWallpaperZoomOut(root.windowToken, zoomOut)
+ } else {
+ Log.i(TAG, "Won't set zoom. Window not attached $root")
+ }
+ }
+ } catch (e: IllegalArgumentException) {
+ Log.w(TAG, "Can't set zoom. Window is gone: ${rootView?.windowToken}", e)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java
index 1c504961e715..107fe870a07a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java
@@ -22,8 +22,10 @@ import android.os.Looper;
import com.android.systemui.dagger.qualifiers.Main;
+import java.util.Optional;
import java.util.concurrent.Executor;
+import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Binds;
@@ -35,6 +37,7 @@ import dagger.Provides;
*/
@Module
public abstract class GlobalConcurrencyModule {
+ public static final String PRE_HANDLER = "pre_handler";
/**
* Binds {@link ThreadFactoryImpl} to {@link ThreadFactory}.
@@ -64,13 +67,32 @@ public abstract class GlobalConcurrencyModule {
* Provide a Main-Thread Executor.
*/
@Provides
+ @Singleton
@Main
public static Executor provideMainExecutor(Context context) {
return context.getMainExecutor();
}
+ /**
+ * Provide a Main-Thread DelayableExecutor.
+ */
+ @Provides
+ @Singleton
+ @Main
+ public static DelayableExecutor provideMainDelayableExecutor(@Main Looper looper) {
+ return new ExecutorImpl(looper);
+ }
+
+
/** */
@Binds
@Singleton
public abstract Execution provideExecution(ExecutionImpl execution);
+
+ /** */
+ @Provides
+ @Named(PRE_HANDLER)
+ public static Optional<Thread.UncaughtExceptionHandler> providesUncaughtExceptionHandler() {
+ return Optional.ofNullable(Thread.getUncaughtExceptionPreHandler());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/MessageRouter.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/MessageRouter.java
new file mode 100644
index 000000000000..542cf6559c64
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/MessageRouter.java
@@ -0,0 +1,198 @@
+/*
+ * 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.systemui.util.concurrency;
+
+/**
+ * Allows triggering methods based on a passed in id or message, generally on another thread.
+ *
+ * Messages sent on to this router must be processed in order. That is to say, if three
+ * messages are sent with no delay, they must be processed in the order they were sent. Moreover,
+ * if messages are sent with various delays, they must be processed in order of their delay.
+ *
+ * Messages can be passed by either a simple integer or an instance of a class. Unique integers are
+ * considered unique messages. Unique message classes (not instances) are considered unique
+ * messages. You can use message classes to pass extra data for processing to subscribers.
+ *
+ * <pre>
+ * // Three messages with three unique integer messages.
+ * // They can be subscribed to independently.
+ * router.sendMessage(0);
+ * router.sendMessage(1);
+ * router.sendMessage(2);
+ *
+ * // Three messages with two unique message classes.
+ * // The first and third messages will be delivered to the same subscribers.
+ * router.sendMessage(new Foo(0));
+ * router.sendMessage(new Bar(1));
+ * router.sendMessage(new Foo(2));
+ * </pre>
+ *
+ * The number of unique ids and message types used should be relatively constrained. Construct
+ * a custom message-class and put unique, per-message data inside of it.
+ */
+public interface MessageRouter {
+ /**
+ * Alerts any listeners subscribed to the passed in id.
+ *
+ * The number of unique ids used should be relatively constrained - used to identify the type
+ * of message being sent. If unique information needs to be passed with each call, use
+ * {@link #sendMessage(Object)}.
+ *
+ * @param id An identifier for the message
+ */
+ default void sendMessage(int id) {
+ sendMessageDelayed(id, 0);
+ }
+
+ /**
+ * Alerts any listeners subscribed to the passed in message.
+ *
+ * The number of message types used should be relatively constrained. If no unique information
+ * needs to be passed in, you can simply use {@link #sendMessage(int)}} which takes an integer
+ * instead of a unique class type.
+ *
+ * The class of the passed in object will be used to router the message.
+ *
+ * @param data A message containing extra data for processing.
+ */
+ default void sendMessage(Object data) {
+ sendMessageDelayed(data, 0);
+ }
+
+ /**
+ * Alerts any listeners subscribed to the passed in id in the future.
+ *
+ * The number of unique ids used should be relatively constrained - used to identify the type
+ * of message being sent. If unique information needs to be passed with each call, use
+ * {@link #sendMessageDelayed(Object, long)}.
+ *
+ * @param id An identifier for the message
+ * @param delayMs Number of milliseconds to wait before alerting.
+ */
+ void sendMessageDelayed(int id, long delayMs);
+
+
+ /**
+ * Alerts any listeners subscribed to the passed in message in the future.
+ *
+ * The number of message types used should be relatively constrained. If no unique information
+ * needs to be passed in, you can simply use {@link #sendMessageDelayed(int, long)} which takes
+ * an integer instead of a unique class type.
+ *
+ * @param data A message containing extra data for processing.
+ * @param delayMs Number of milliseconds to wait before alerting.
+ */
+ void sendMessageDelayed(Object data, long delayMs);
+
+ /**
+ * Cancel all unprocessed messages for a given id.
+ *
+ * If a message has multiple listeners and one of those listeners has been alerted, the other
+ * listeners that follow it may also be alerted. This is only guaranteed to cancel messages
+ * that are still queued.
+ *
+ * @param id The message id to cancel.
+ */
+ void cancelMessages(int id);
+
+ /**
+ * Cancel all unprocessed messages for a given message type.
+ *
+ * If a message has multiple listeners and one of those listeners has been alerted, the other
+ * listeners that follow it may also be alerted. This is only guaranteed to cancel messages
+ * that are still queued.
+ *
+ * @param messageType The class of the message to cancel
+ */
+ <T> void cancelMessages(Class<T> messageType);
+
+ /**
+ * Add a listener for a message that does not handle any extra data.
+ *
+ * See also {@link #subscribeTo(Class, DataMessageListener)}.
+ *
+ * @param id The message id to listener for.
+ * @param listener
+ */
+ void subscribeTo(int id, SimpleMessageListener listener);
+
+ /**
+ * Add a listener for a message of a specific type.
+ *
+ * See also {@link #subscribeTo(Class, DataMessageListener)}.
+ *
+ * @param messageType The class of message to listen for.
+ * @param listener
+ */
+ <T> void subscribeTo(Class<T> messageType, DataMessageListener<T> listener);
+
+ /**
+ * Remove a listener for a specific message.
+ *
+ * See also {@link #unsubscribeFrom(Class, DataMessageListener)}
+ *
+ * @param id The message id to stop listening for.
+ * @param listener The listener to remove.
+ */
+ void unsubscribeFrom(int id, SimpleMessageListener listener);
+
+ /**
+ * Remove a listener for a specific message.
+ *
+ * See also {@link #unsubscribeFrom(int, SimpleMessageListener)}.
+ *
+ * @param messageType The class of message to stop listening for.
+ * @param listener The listener to remove.
+ */
+ <T> void unsubscribeFrom(Class<T> messageType, DataMessageListener<T> listener);
+
+ /**
+ * Remove a listener for all messages that it is subscribed to.
+ *
+ * See also {@link #unsubscribeFrom(DataMessageListener)}.
+ *
+ * @param listener The listener to remove.
+ */
+ void unsubscribeFrom(SimpleMessageListener listener);
+
+ /**
+ * Remove a listener for all messages that it is subscribed to.
+ *
+ * See also {@link #unsubscribeFrom(SimpleMessageListener)}.
+ *
+ * @param listener The listener to remove.
+ */
+ <T> void unsubscribeFrom(DataMessageListener<T> listener);
+
+ /**
+ * A Listener interface for when no extra data is expected or desired.
+ */
+ interface SimpleMessageListener {
+ /** */
+ void onMessage(int id);
+ }
+
+ /**
+ * A Listener interface for when extra data is expected or desired.
+ *
+ * @param <T>
+ */
+ interface DataMessageListener<T> {
+ /** */
+ void onMessage(T data);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/MessageRouterImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/MessageRouterImpl.java
new file mode 100644
index 000000000000..7145c775fe39
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/MessageRouterImpl.java
@@ -0,0 +1,186 @@
+/*
+ * 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.systemui.util.concurrency;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of {@link MessageRouter}.
+ */
+public class MessageRouterImpl implements MessageRouter {
+
+ private final DelayableExecutor mDelayableExecutor;
+
+ private final Map<Integer, List<Runnable>> mIdMessageCancelers = new HashMap<>();
+ private final Map<Class<Object>, List<Runnable>> mDataMessageCancelers = new HashMap<>();
+ private final Map<Integer, List<SimpleMessageListener>> mSimpleMessageListenerMap =
+ new HashMap<>();
+ private final Map<Class<?>, List<DataMessageListener<Object>>> mDataMessageListenerMap =
+ new HashMap<>();
+
+ public MessageRouterImpl(DelayableExecutor delayableExecutor) {
+ mDelayableExecutor = delayableExecutor;
+ }
+
+ @Override
+ public void sendMessageDelayed(int id, long delayMs) {
+ addCanceler(id, mDelayableExecutor.executeDelayed(() -> onMessage(id), delayMs));
+ }
+
+ @Override
+ public void sendMessageDelayed(Object data, long delayMs) {
+ addCanceler((Class<Object>) data.getClass(), mDelayableExecutor.executeDelayed(
+ () -> onMessage(data), delayMs));
+ }
+
+ @Override
+ public void cancelMessages(int id) {
+ synchronized (mIdMessageCancelers) {
+ if (mIdMessageCancelers.containsKey(id)) {
+ for (Runnable canceler : mIdMessageCancelers.get(id)) {
+ canceler.run();
+ }
+ // Remove, don't clear, otherwise this could look like a memory leak as
+ // more and more unique message ids are passed in.
+ mIdMessageCancelers.remove(id);
+ }
+ }
+ }
+
+ @Override
+ public <T> void cancelMessages(Class<T> messageType) {
+ synchronized (mDataMessageCancelers) {
+ if (mDataMessageCancelers.containsKey(messageType)) {
+ for (Runnable canceler : mDataMessageCancelers.get(messageType)) {
+ canceler.run();
+ }
+ // Remove, don't clear, otherwise this could look like a memory leak as
+ // more and more unique message types are passed in.
+ mDataMessageCancelers.remove(messageType);
+ }
+ }
+ }
+
+ @Override
+ public void subscribeTo(int id, SimpleMessageListener listener) {
+ synchronized (mSimpleMessageListenerMap) {
+ mSimpleMessageListenerMap.putIfAbsent(id, new ArrayList<>());
+ mSimpleMessageListenerMap.get(id).add(listener);
+ }
+ }
+
+ @Override
+ public <T> void subscribeTo(Class<T> messageType, DataMessageListener<T> listener) {
+ synchronized (mDataMessageListenerMap) {
+ mDataMessageListenerMap.putIfAbsent(messageType, new ArrayList<>());
+ mDataMessageListenerMap.get(messageType).add((DataMessageListener<Object>) listener);
+ }
+ }
+
+ @Override
+ public void unsubscribeFrom(int id, SimpleMessageListener listener) {
+ synchronized (mSimpleMessageListenerMap) {
+ if (mSimpleMessageListenerMap.containsKey(id)) {
+ mSimpleMessageListenerMap.get(id).remove(listener);
+ }
+ }
+ }
+
+ @Override
+ public <T> void unsubscribeFrom(Class<T> messageType, DataMessageListener<T> listener) {
+ synchronized (mDataMessageListenerMap) {
+ if (mDataMessageListenerMap.containsKey(messageType)) {
+ mDataMessageListenerMap.get(messageType).remove(listener);
+ }
+ }
+ }
+
+ @Override
+ public void unsubscribeFrom(SimpleMessageListener listener) {
+ synchronized (mSimpleMessageListenerMap) {
+ for (Integer id : mSimpleMessageListenerMap.keySet()) {
+ mSimpleMessageListenerMap.get(id).remove(listener);
+ }
+ }
+ }
+
+ @Override
+ public <T> void unsubscribeFrom(DataMessageListener<T> listener) {
+ synchronized (mDataMessageListenerMap) {
+ for (Class<?> messageType : mDataMessageListenerMap.keySet()) {
+ mDataMessageListenerMap.get(messageType).remove(listener);
+ }
+ }
+ }
+
+ private void addCanceler(int id, Runnable canceler) {
+ synchronized (mIdMessageCancelers) {
+ mIdMessageCancelers.putIfAbsent(id, new ArrayList<>());
+ mIdMessageCancelers.get(id).add(canceler);
+ }
+ }
+
+ private void addCanceler(Class<Object> data, Runnable canceler) {
+ synchronized (mDataMessageCancelers) {
+ mDataMessageCancelers.putIfAbsent(data, new ArrayList<>());
+ mDataMessageCancelers.get(data).add(canceler);
+ }
+ }
+
+ private void onMessage(int id) {
+ synchronized (mSimpleMessageListenerMap) {
+ if (mSimpleMessageListenerMap.containsKey(id)) {
+ for (SimpleMessageListener listener : mSimpleMessageListenerMap.get(id)) {
+ listener.onMessage(id);
+ }
+ }
+ }
+
+ synchronized (mIdMessageCancelers) {
+ if (mIdMessageCancelers.containsKey(id) && !mIdMessageCancelers.get(id).isEmpty()) {
+ mIdMessageCancelers.get(id).remove(0);
+ if (mIdMessageCancelers.get(id).isEmpty()) {
+ mIdMessageCancelers.remove(id);
+ }
+ }
+ }
+ }
+
+ private void onMessage(Object data) {
+ synchronized (mDataMessageListenerMap) {
+ if (mDataMessageListenerMap.containsKey(data.getClass())) {
+ for (DataMessageListener<Object> listener : mDataMessageListenerMap.get(
+ data.getClass())) {
+ listener.onMessage(data);
+ }
+ }
+ }
+
+ synchronized (mDataMessageCancelers) {
+ if (mDataMessageCancelers.containsKey(data.getClass())
+ && !mDataMessageCancelers.get(data.getClass()).isEmpty()) {
+ mDataMessageCancelers.get(data.getClass()).remove(0);
+ if (mDataMessageCancelers.get(data.getClass()).isEmpty()) {
+ mDataMessageCancelers.remove(data.getClass());
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
index b9b20c73c5d5..e8a9bc702352 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
@@ -120,16 +120,6 @@ public abstract class SysUIConcurrencyModule {
}
/**
- * Provide a Main-Thread Executor.
- */
- @Provides
- @SysUISingleton
- @Main
- public static DelayableExecutor provideMainDelayableExecutor(@Main Looper looper) {
- return new ExecutorImpl(looper);
- }
-
- /**
* Provide a Background-Thread Executor by default.
*/
@Provides
@@ -170,4 +160,20 @@ public abstract class SysUIConcurrencyModule {
public static Executor provideUiBackgroundExecutor() {
return Executors.newSingleThreadExecutor();
}
+
+ /** */
+ @Provides
+ @Main
+ public static MessageRouter providesMainMessageRouter(
+ @Main DelayableExecutor executor) {
+ return new MessageRouterImpl(executor);
+ }
+
+ /** */
+ @Provides
+ @Background
+ public static MessageRouter providesBackgroundMessageRouter(
+ @Background DelayableExecutor executor) {
+ return new MessageRouterImpl(executor);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
index cdfa1457f4a5..981bf01164e3 100644
--- a/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
@@ -18,12 +18,15 @@ package com.android.systemui.util.dagger;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.RingerModeTrackerImpl;
+import com.android.systemui.util.wrapper.UtilWrapperModule;
import dagger.Binds;
import dagger.Module;
/** Dagger Module for code in the util package. */
-@Module
+@Module(includes = {
+ UtilWrapperModule.class
+ })
public interface UtilModule {
/** */
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index edea3055a783..431899460586 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -36,7 +36,6 @@ import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
-import android.os.Message;
import android.os.Process;
import android.os.SystemProperties;
import android.provider.Settings;
@@ -52,6 +51,7 @@ import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile;
@@ -60,6 +60,8 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSIconViewImpl;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.MessageRouter;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -110,18 +112,18 @@ public class GarbageMonitor implements Dumpable {
private static final int DO_GARBAGE_INSPECTION = 1000;
private static final int DO_HEAP_TRACK = 3000;
- private static final int GARBAGE_ALLOWANCE = 5;
+ static final int GARBAGE_ALLOWANCE = 5;
private static final String TAG = "GarbageMonitor";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private final Handler mHandler;
+ private final MessageRouter mMessageRouter;
private final TrackedGarbage mTrackedGarbage;
private final LeakReporter mLeakReporter;
private final Context mContext;
- private final ActivityManager mAm;
+ private final DelayableExecutor mDelayableExecutor;
private MemoryTile mQSTile;
- private DumpTruck mDumpTruck;
+ private final DumpTruck mDumpTruck;
private final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<>();
private final ArrayList<Long> mPids = new ArrayList<>();
@@ -133,19 +135,25 @@ public class GarbageMonitor implements Dumpable {
@Inject
public GarbageMonitor(
Context context,
- @Background Looper bgLooper,
+ @Background DelayableExecutor delayableExecutor,
+ @Background MessageRouter messageRouter,
LeakDetector leakDetector,
- LeakReporter leakReporter) {
+ LeakReporter leakReporter,
+ DumpManager dumpManager) {
mContext = context.getApplicationContext();
- mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- mHandler = new BackgroundHeapCheckHandler(bgLooper);
+ mDelayableExecutor = delayableExecutor;
+ mMessageRouter = messageRouter;
+ mMessageRouter.subscribeTo(DO_GARBAGE_INSPECTION, this::doGarbageInspection);
+ mMessageRouter.subscribeTo(DO_HEAP_TRACK, this::doHeapTrack);
mTrackedGarbage = leakDetector.getTrackedGarbage();
mLeakReporter = leakReporter;
mDumpTruck = new DumpTruck(mContext);
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
+
if (ENABLE_AM_HEAP_LIMIT) {
mHeapLimit = Settings.Global.getInt(context.getContentResolver(),
SETTINGS_KEY_AM_HEAP_LIMIT,
@@ -158,13 +166,13 @@ public class GarbageMonitor implements Dumpable {
return;
}
- mHandler.sendEmptyMessage(DO_GARBAGE_INSPECTION);
+ mMessageRouter.sendMessage(DO_GARBAGE_INSPECTION);
}
public void startHeapTracking() {
startTrackingProcess(
android.os.Process.myPid(), mContext.getPackageName(), System.currentTimeMillis());
- mHandler.sendEmptyMessage(DO_HEAP_TRACK);
+ mMessageRouter.sendMessage(DO_HEAP_TRACK);
}
private boolean gcAndCheckGarbage() {
@@ -586,33 +594,18 @@ public class GarbageMonitor implements Dumpable {
}
}
- private class BackgroundHeapCheckHandler extends Handler {
- BackgroundHeapCheckHandler(Looper onLooper) {
- super(onLooper);
- if (Looper.getMainLooper().equals(onLooper)) {
- throw new RuntimeException(
- "BackgroundHeapCheckHandler may not run on the ui thread");
- }
+ private void doGarbageInspection(int id) {
+ if (gcAndCheckGarbage()) {
+ mDelayableExecutor.executeDelayed(this::reinspectGarbageAfterGc, 100);
}
- @Override
- public void handleMessage(Message m) {
- switch (m.what) {
- case DO_GARBAGE_INSPECTION:
- if (gcAndCheckGarbage()) {
- postDelayed(GarbageMonitor.this::reinspectGarbageAfterGc, 100);
- }
-
- removeMessages(DO_GARBAGE_INSPECTION);
- sendEmptyMessageDelayed(DO_GARBAGE_INSPECTION, GARBAGE_INSPECTION_INTERVAL);
- break;
+ mMessageRouter.cancelMessages(DO_GARBAGE_INSPECTION);
+ mMessageRouter.sendMessageDelayed(DO_GARBAGE_INSPECTION, GARBAGE_INSPECTION_INTERVAL);
+ }
- case DO_HEAP_TRACK:
- update();
- removeMessages(DO_HEAP_TRACK);
- sendEmptyMessageDelayed(DO_HEAP_TRACK, HEAP_TRACK_INTERVAL);
- break;
- }
- }
+ private void doHeapTrack(int id) {
+ update();
+ mMessageRouter.cancelMessages(DO_HEAP_TRACK);
+ mMessageRouter.sendMessageDelayed(DO_HEAP_TRACK, HEAP_TRACK_INTERVAL);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/LeakDetector.java b/packages/SystemUI/src/com/android/systemui/util/leak/LeakDetector.java
index c50e8f8a3596..f215082584a8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/LeakDetector.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/LeakDetector.java
@@ -21,6 +21,7 @@ import android.os.Build;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.systemui.Dumpable;
+import com.android.systemui.dump.DumpManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -38,12 +39,16 @@ public class LeakDetector implements Dumpable {
private final TrackedObjects mTrackedObjects;
@VisibleForTesting
- public LeakDetector(TrackedCollections trackedCollections,
+ public LeakDetector(
+ TrackedCollections trackedCollections,
TrackedGarbage trackedGarbage,
- TrackedObjects trackedObjects) {
+ TrackedObjects trackedObjects,
+ DumpManager dumpManager) {
mTrackedCollections = trackedCollections;
mTrackedGarbage = trackedGarbage;
mTrackedObjects = trackedObjects;
+
+ dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
/**
@@ -130,13 +135,16 @@ public class LeakDetector implements Dumpable {
pw.println();
}
- public static LeakDetector create() {
+ public static LeakDetector create(DumpManager dumpManager) {
if (ENABLED) {
TrackedCollections collections = new TrackedCollections();
- return new LeakDetector(collections, new TrackedGarbage(collections),
- new TrackedObjects(collections));
+ return new LeakDetector(
+ collections,
+ new TrackedGarbage(collections),
+ new TrackedObjects(collections),
+ dumpManager);
} else {
- return new LeakDetector(null, null, null);
+ return new LeakDetector(null, null, null, dumpManager);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java
new file mode 100644
index 000000000000..40982bb18023
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java
@@ -0,0 +1,108 @@
+/*
+ * 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.systemui.util.sensors;
+
+import android.util.Log;
+
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.Execution;
+
+import javax.inject.Inject;
+
+/**
+ * Proximity sensor that changes proximity sensor usage based on the current posture.
+ * Posture -> prox sensor mapping can be found in SystemUI config overlays at:
+ * - proximity_sensor_posture_mapping
+ * - proximity_sensor_secondary_posture_mapping.
+ * where the array indices correspond to the following postures:
+ * [UNKNOWN, CLOSED, HALF_OPENED, OPENED]
+ */
+class PostureDependentProximitySensor extends ProximitySensorImpl {
+ private final ThresholdSensor[] mPostureToPrimaryProxSensorMap;
+ private final ThresholdSensor[] mPostureToSecondaryProxSensorMap;
+
+ @Inject
+ PostureDependentProximitySensor(
+ @PrimaryProxSensor ThresholdSensor[] postureToPrimaryProxSensorMap,
+ @SecondaryProxSensor ThresholdSensor[] postureToSecondaryProxSensorMap,
+ @Main DelayableExecutor delayableExecutor,
+ Execution execution,
+ DevicePostureController devicePostureController
+ ) {
+ super(
+ postureToPrimaryProxSensorMap[0],
+ postureToSecondaryProxSensorMap[0],
+ delayableExecutor,
+ execution
+ );
+ mPostureToPrimaryProxSensorMap = postureToPrimaryProxSensorMap;
+ mPostureToSecondaryProxSensorMap = postureToSecondaryProxSensorMap;
+ mDevicePosture = devicePostureController.getDevicePosture();
+ devicePostureController.addCallback(mDevicePostureCallback);
+
+ chooseSensors();
+ }
+ private void chooseSensors() {
+ if (mDevicePosture >= mPostureToPrimaryProxSensorMap.length
+ || mDevicePosture >= mPostureToSecondaryProxSensorMap.length) {
+ Log.e("PostureDependentProxSensor",
+ "unsupported devicePosture=" + mDevicePosture);
+ return;
+ }
+
+ ThresholdSensor newPrimaryProx = mPostureToPrimaryProxSensorMap[mDevicePosture];
+ ThresholdSensor newSecondaryProx = mPostureToSecondaryProxSensorMap[mDevicePosture];
+
+ if (newPrimaryProx != mPrimaryThresholdSensor
+ || newSecondaryProx != mSecondaryThresholdSensor) {
+ logDebug("Register new proximity sensors newPosture="
+ + DevicePostureController.devicePostureToString(mDevicePosture));
+ unregisterInternal();
+
+ if (mPrimaryThresholdSensor != null) {
+ mPrimaryThresholdSensor.unregister(mPrimaryEventListener);
+ }
+ if (mSecondaryThresholdSensor != null) {
+ mSecondaryThresholdSensor.unregister(mSecondaryEventListener);
+ }
+
+ mPrimaryThresholdSensor = newPrimaryProx;
+ mSecondaryThresholdSensor = newSecondaryProx;
+
+ mInitializedListeners = false;
+ registerInternal();
+ }
+ }
+
+ private final DevicePostureController.Callback mDevicePostureCallback =
+ posture -> {
+ if (mDevicePosture == posture) {
+ return;
+ }
+
+ mDevicePosture = posture;
+ chooseSensors();
+ };
+
+ @Override
+ public String toString() {
+ return String.format("{posture=%s, proximitySensor=%s}",
+ DevicePostureController.devicePostureToString(mDevicePosture), super.toString());
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximityCheck.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximityCheck.java
new file mode 100644
index 000000000000..a8a6341cc5ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximityCheck.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.systemui.util.sensors;
+
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+/**
+ * Convenience class allowing for briefly checking the proximity sensor.
+ */
+public class ProximityCheck implements Runnable {
+
+ private final ProximitySensor mSensor;
+ private final DelayableExecutor mDelayableExecutor;
+ private List<Consumer<Boolean>> mCallbacks = new ArrayList<>();
+ private final ThresholdSensor.Listener mListener;
+ private final AtomicBoolean mRegistered = new AtomicBoolean();
+
+ @Inject
+ public ProximityCheck(
+ ProximitySensor sensor,
+ @Main DelayableExecutor delayableExecutor) {
+ mSensor = sensor;
+ mSensor.setTag("prox_check");
+ mDelayableExecutor = delayableExecutor;
+ mListener = this::onProximityEvent;
+ }
+
+ /** Set a descriptive tag for the sensors registration. */
+ public void setTag(String tag) {
+ mSensor.setTag(tag);
+ }
+
+ @Override
+ public void run() {
+ unregister();
+ onProximityEvent(null);
+ }
+
+ /**
+ * Query the proximity sensor, timing out if no result.
+ */
+ public void check(long timeoutMs, Consumer<Boolean> callback) {
+ if (!mSensor.isLoaded()) {
+ callback.accept(null);
+ return;
+ }
+ mCallbacks.add(callback);
+ if (!mRegistered.getAndSet(true)) {
+ mSensor.register(mListener);
+ mDelayableExecutor.executeDelayed(this, timeoutMs);
+ }
+ }
+
+ private void unregister() {
+ mSensor.unregister(mListener);
+ mRegistered.set(false);
+ }
+
+ private void onProximityEvent(ThresholdSensorEvent proximityEvent) {
+ mCallbacks.forEach(
+ booleanConsumer ->
+ booleanConsumer.accept(
+ proximityEvent == null ? null : proximityEvent.getBelow()));
+ mCallbacks.clear();
+ unregister();
+ mRegistered.set(false);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index bd1103982017..d3f1c93195a1 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -16,149 +16,24 @@
package com.android.systemui.util.sensors;
-import android.hardware.SensorManager;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.concurrency.Execution;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-
/**
- * Wrapper around SensorManager customized for the Proximity sensor.
- *
- * The ProximitySensor supports the concept of a primary and a
- * secondary hardware sensor. The primary sensor is used for a first
- * pass check if the phone covered. When triggered, it then checks
- * the secondary sensor for confirmation (if there is one). It does
- * not send a proximity event until the secondary sensor confirms (or
- * rejects) the reading. The secondary sensor is, in fact, the source
- * of truth.
- *
- * This is necessary as sometimes keeping the secondary sensor on for
- * extends periods is undesirable. It may, however, result in increased
- * latency for proximity readings.
- *
- * Phones should configure this via a config.xml overlay. If no
- * proximity sensor is set (primary or secondary) we fall back to the
- * default Sensor.TYPE_PROXIMITY. If proximity_sensor_type is set in
- * config.xml, that will be used as the primary sensor. If
- * proximity_sensor_secondary_type is set, that will function as the
- * secondary sensor. If no secondary is set, only the primary will be
- * used.
+ * Wrapper class for a dual ProximitySensor which supports a secondary sensor gated upon the
+ * primary).
*/
-public class ProximitySensor implements ThresholdSensor {
- private static final String TAG = "ProxSensor";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final long SECONDARY_PING_INTERVAL_MS = 5000;
-
- private final ThresholdSensor mPrimaryThresholdSensor;
- private final ThresholdSensor mSecondaryThresholdSensor;
- private final DelayableExecutor mDelayableExecutor;
- private final Execution mExecution;
- private final List<ThresholdSensor.Listener> mListeners = new ArrayList<>();
- private String mTag = null;
- @VisibleForTesting protected boolean mPaused;
- private ThresholdSensorEvent mLastPrimaryEvent;
- @VisibleForTesting
- ThresholdSensorEvent mLastEvent;
- private boolean mRegistered;
- private final AtomicBoolean mAlerting = new AtomicBoolean();
- private Runnable mCancelSecondaryRunnable;
- private boolean mInitializedListeners = false;
- private boolean mSecondarySafe = false;
-
- private final ThresholdSensor.Listener mPrimaryEventListener = this::onPrimarySensorEvent;
-
- private final ThresholdSensor.Listener mSecondaryEventListener =
- new ThresholdSensor.Listener() {
- @Override
- public void onThresholdCrossed(ThresholdSensorEvent event) {
- // If we no longer have a "below" signal and the secondary sensor is not
- // considered "safe", then we need to turn it off.
- if (!mSecondarySafe
- && (mLastPrimaryEvent == null
- || !mLastPrimaryEvent.getBelow()
- || !event.getBelow())) {
- chooseSensor();
- if (mLastPrimaryEvent == null || !mLastPrimaryEvent.getBelow()) {
- // Only check the secondary as long as the primary thinks we're near.
- if (mCancelSecondaryRunnable != null) {
- mCancelSecondaryRunnable.run();
- mCancelSecondaryRunnable = null;
- }
- return;
- } else {
- // Check this sensor again in a moment.
- mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed(() -> {
- // This is safe because we know that mSecondaryThresholdSensor
- // is loaded, otherwise we wouldn't be here.
- mPrimaryThresholdSensor.pause();
- mSecondaryThresholdSensor.resume();
- },
- SECONDARY_PING_INTERVAL_MS);
- }
- }
- logDebug("Secondary sensor event: " + event.getBelow() + ".");
-
- if (!mPaused) {
- onSensorEvent(event);
- }
- }
- };
-
- @Inject
- public ProximitySensor(
- @PrimaryProxSensor ThresholdSensor primary,
- @SecondaryProxSensor ThresholdSensor secondary,
- @Main DelayableExecutor delayableExecutor,
- Execution execution) {
- mPrimaryThresholdSensor = primary;
- mSecondaryThresholdSensor = secondary;
- mDelayableExecutor = delayableExecutor;
- mExecution = execution;
- }
-
- @Override
- public void setTag(String tag) {
- mTag = tag;
- mPrimaryThresholdSensor.setTag(tag + ":primary");
- mSecondaryThresholdSensor.setTag(tag + ":secondary");
- }
-
- @Override
- public void setDelay(int delay) {
- mExecution.assertIsMainThread();
- mPrimaryThresholdSensor.setDelay(delay);
- mSecondaryThresholdSensor.setDelay(delay);
- }
-
+public interface ProximitySensor extends ThresholdSensor {
/**
- * Unregister with the {@link SensorManager} without unsetting listeners on this object.
+ * Returns true if we are registered with the SensorManager.
*/
- @Override
- public void pause() {
- mExecution.assertIsMainThread();
- mPaused = true;
- unregisterInternal();
- }
+ boolean isRegistered();
/**
- * Register with the {@link SensorManager}. No-op if no listeners are registered on this object.
+ * Whether the proximity sensor reports near. Can return null if no information has been
+ * received yet.
*/
- @Override
- public void resume() {
- mExecution.assertIsMainThread();
- mPaused = false;
- registerInternal();
- }
+ Boolean isNear();
+
+ /** Update all listeners with the last value this class received from the sensor. */
+ void alertListeners();
/**
* Sets that it is safe to leave the secondary sensor on indefinitely.
@@ -166,249 +41,5 @@ public class ProximitySensor implements ThresholdSensor {
* The secondary sensor will be turned on if there are any registered listeners, regardless
* of what is reported by the primary sensor.
*/
- public void setSecondarySafe(boolean safe) {
- mSecondarySafe = mSecondaryThresholdSensor.isLoaded() && safe;
- chooseSensor();
- }
-
- /**
- * Returns true if we are registered with the SensorManager.
- */
- public boolean isRegistered() {
- return mRegistered;
- }
-
- /**
- * Returns {@code false} if a Proximity sensor is not available.
- */
- @Override
- public boolean isLoaded() {
- return mPrimaryThresholdSensor.isLoaded();
- }
-
- /**
- * Add a listener.
- *
- * Registers itself with the {@link SensorManager} if this is the first listener
- * added. If the ProximitySensor is paused, it will be registered when resumed.
- */
- @Override
- public void register(ThresholdSensor.Listener listener) {
- mExecution.assertIsMainThread();
- if (!isLoaded()) {
- return;
- }
-
- if (mListeners.contains(listener)) {
- logDebug("ProxListener registered multiple times: " + listener);
- } else {
- mListeners.add(listener);
- }
- registerInternal();
- }
-
- protected void registerInternal() {
- mExecution.assertIsMainThread();
- if (mRegistered || mPaused || mListeners.isEmpty()) {
- return;
- }
- if (!mInitializedListeners) {
- mPrimaryThresholdSensor.pause();
- mSecondaryThresholdSensor.pause();
- mPrimaryThresholdSensor.register(mPrimaryEventListener);
- mSecondaryThresholdSensor.register(mSecondaryEventListener);
- mInitializedListeners = true;
- }
- logDebug("Registering sensor listener");
-
- mRegistered = true;
- chooseSensor();
- }
-
- private void chooseSensor() {
- mExecution.assertIsMainThread();
- if (!mRegistered || mPaused || mListeners.isEmpty()) {
- return;
- }
- if (mSecondarySafe) {
- mSecondaryThresholdSensor.resume();
- mPrimaryThresholdSensor.pause();
- } else {
- mPrimaryThresholdSensor.resume();
- mSecondaryThresholdSensor.pause();
- }
- }
-
- /**
- * Remove a listener.
- *
- * If all listeners are removed from an instance of this class,
- * it will unregister itself with the SensorManager.
- */
- @Override
- public void unregister(ThresholdSensor.Listener listener) {
- mExecution.assertIsMainThread();
- mListeners.remove(listener);
- if (mListeners.size() == 0) {
- unregisterInternal();
- }
- }
-
- protected void unregisterInternal() {
- mExecution.assertIsMainThread();
- if (!mRegistered) {
- return;
- }
- logDebug("unregistering sensor listener");
- mPrimaryThresholdSensor.pause();
- mSecondaryThresholdSensor.pause();
- if (mCancelSecondaryRunnable != null) {
- mCancelSecondaryRunnable.run();
- mCancelSecondaryRunnable = null;
- }
- mLastPrimaryEvent = null; // Forget what we know.
- mLastEvent = null;
- mRegistered = false;
- }
-
- public Boolean isNear() {
- return isLoaded() && mLastEvent != null ? mLastEvent.getBelow() : null;
- }
-
- /** Update all listeners with the last value this class received from the sensor. */
- public void alertListeners() {
- mExecution.assertIsMainThread();
- if (mAlerting.getAndSet(true)) {
- return;
- }
- if (mLastEvent != null) {
- ThresholdSensorEvent lastEvent = mLastEvent; // Listeners can null out mLastEvent.
- List<ThresholdSensor.Listener> listeners = new ArrayList<>(mListeners);
- listeners.forEach(proximitySensorListener ->
- proximitySensorListener.onThresholdCrossed(lastEvent));
- }
-
- mAlerting.set(false);
- }
-
- private void onPrimarySensorEvent(ThresholdSensorEvent event) {
- mExecution.assertIsMainThread();
- if (mLastPrimaryEvent != null && event.getBelow() == mLastPrimaryEvent.getBelow()) {
- return;
- }
-
- mLastPrimaryEvent = event;
-
- if (mSecondarySafe && mSecondaryThresholdSensor.isLoaded()) {
- logDebug("Primary sensor reported " + (event.getBelow() ? "near" : "far")
- + ". Checking secondary.");
- if (mCancelSecondaryRunnable == null) {
- mSecondaryThresholdSensor.resume();
- }
- return;
- }
-
-
- if (!mSecondaryThresholdSensor.isLoaded()) { // No secondary
- logDebug("Primary sensor event: " + event.getBelow() + ". No secondary.");
- onSensorEvent(event);
- } else if (event.getBelow()) { // Covered? Check secondary.
- logDebug("Primary sensor event: " + event.getBelow() + ". Checking secondary.");
- if (mCancelSecondaryRunnable != null) {
- mCancelSecondaryRunnable.run();
- }
- mSecondaryThresholdSensor.resume();
- } else { // Uncovered. Report immediately.
- onSensorEvent(event);
- }
- }
-
- private void onSensorEvent(ThresholdSensorEvent event) {
- mExecution.assertIsMainThread();
- if (mLastEvent != null && event.getBelow() == mLastEvent.getBelow()) {
- return;
- }
-
- if (!mSecondarySafe && !event.getBelow()) {
- chooseSensor();
- }
-
- mLastEvent = event;
- alertListeners();
- }
-
- @Override
- public String toString() {
- return String.format("{registered=%s, paused=%s, near=%s, primarySensor=%s, "
- + "secondarySensor=%s secondarySafe=%s}",
- isRegistered(), mPaused, isNear(), mPrimaryThresholdSensor,
- mSecondaryThresholdSensor, mSecondarySafe);
- }
-
- /**
- * Convenience class allowing for briefly checking the proximity sensor.
- */
- public static class ProximityCheck implements Runnable {
-
- private final ProximitySensor mSensor;
- private final DelayableExecutor mDelayableExecutor;
- private List<Consumer<Boolean>> mCallbacks = new ArrayList<>();
- private final ThresholdSensor.Listener mListener;
- private final AtomicBoolean mRegistered = new AtomicBoolean();
-
- @Inject
- public ProximityCheck(ProximitySensor sensor, @Main DelayableExecutor delayableExecutor) {
- mSensor = sensor;
- mSensor.setTag("prox_check");
- mDelayableExecutor = delayableExecutor;
- mListener = this::onProximityEvent;
- }
-
- /** Set a descriptive tag for the sensors registration. */
- public void setTag(String tag) {
- mSensor.setTag(tag);
- }
-
- @Override
- public void run() {
- unregister();
- onProximityEvent(null);
- }
-
- /**
- * Query the proximity sensor, timing out if no result.
- */
- public void check(long timeoutMs, Consumer<Boolean> callback) {
- if (!mSensor.isLoaded()) {
- callback.accept(null);
- return;
- }
- mCallbacks.add(callback);
- if (!mRegistered.getAndSet(true)) {
- mSensor.register(mListener);
- mDelayableExecutor.executeDelayed(this, timeoutMs);
- }
- }
-
- private void unregister() {
- mSensor.unregister(mListener);
- mRegistered.set(false);
- }
-
- private void onProximityEvent(ThresholdSensorEvent proximityEvent) {
- mCallbacks.forEach(
- booleanConsumer ->
- booleanConsumer.accept(
- proximityEvent == null ? null : proximityEvent.getBelow()));
- mCallbacks.clear();
- unregister();
- mRegistered.set(false);
- }
- }
-
- private void logDebug(String msg) {
- if (DEBUG) {
- Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg);
- }
- }
+ void setSecondarySafe(boolean safe);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java
new file mode 100644
index 000000000000..5568f64f5084
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.sensors;
+
+import android.hardware.SensorManager;
+import android.os.Build;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.Execution;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.inject.Inject;
+
+/**
+ * Wrapper around SensorManager customized for the Proximity sensor.
+ *
+ * The ProximitySensor supports the concept of a primary and a
+ * secondary hardware sensor. The primary sensor is used for a first
+ * pass check if the phone covered. When triggered, it then checks
+ * the secondary sensor for confirmation (if there is one). It does
+ * not send a proximity event until the secondary sensor confirms (or
+ * rejects) the reading. The secondary sensor is, in fact, the source
+ * of truth.
+ *
+ * This is necessary as sometimes keeping the secondary sensor on for
+ * extends periods is undesirable. It may, however, result in increased
+ * latency for proximity readings.
+ *
+ * Phones should configure this via a config.xml overlay. If no
+ * proximity sensor is set (primary or secondary) we fall back to the
+ * default Sensor.TYPE_PROXIMITY. If proximity_sensor_type is set in
+ * config.xml, that will be used as the primary sensor. If
+ * proximity_sensor_secondary_type is set, that will function as the
+ * secondary sensor. If no secondary is set, only the primary will be
+ * used.
+ */
+class ProximitySensorImpl implements ProximitySensor {
+ private static final String TAG = "ProxSensor";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Build.IS_DEBUGGABLE;
+ private static final long SECONDARY_PING_INTERVAL_MS = 5000;
+
+ ThresholdSensor mPrimaryThresholdSensor;
+ ThresholdSensor mSecondaryThresholdSensor;
+ private final DelayableExecutor mDelayableExecutor;
+ private final Execution mExecution;
+ private final List<ThresholdSensor.Listener> mListeners = new ArrayList<>();
+ private String mTag = null;
+ @VisibleForTesting protected boolean mPaused;
+ private ThresholdSensorEvent mLastPrimaryEvent;
+ @VisibleForTesting
+ ThresholdSensorEvent mLastEvent;
+ private boolean mRegistered;
+ private final AtomicBoolean mAlerting = new AtomicBoolean();
+ private Runnable mCancelSecondaryRunnable;
+ boolean mInitializedListeners = false;
+ private boolean mSecondarySafe = false; // safe to skip primary sensor check and use secondary
+ protected @DevicePostureController.DevicePostureInt int mDevicePosture;
+
+ final ThresholdSensor.Listener mPrimaryEventListener = this::onPrimarySensorEvent;
+
+ final ThresholdSensor.Listener mSecondaryEventListener =
+ new ThresholdSensor.Listener() {
+ @Override
+ public void onThresholdCrossed(ThresholdSensorEvent event) {
+ // If we no longer have a "below" signal and the secondary sensor is not
+ // considered "safe", then we need to turn it off.
+ if (!mSecondarySafe
+ && (mLastPrimaryEvent == null
+ || !mLastPrimaryEvent.getBelow()
+ || !event.getBelow())) {
+ chooseSensor();
+ if (mLastPrimaryEvent == null || !mLastPrimaryEvent.getBelow()) {
+ // Only check the secondary as long as the primary thinks we're near.
+ if (mCancelSecondaryRunnable != null) {
+ mCancelSecondaryRunnable.run();
+ mCancelSecondaryRunnable = null;
+ }
+ return;
+ } else {
+ // Check this sensor again in a moment.
+ mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed(() -> {
+ // This is safe because we know that mSecondaryThresholdSensor
+ // is loaded, otherwise we wouldn't be here.
+ mPrimaryThresholdSensor.pause();
+ mSecondaryThresholdSensor.resume();
+ },
+ SECONDARY_PING_INTERVAL_MS);
+ }
+ }
+ logDebug("Secondary sensor event: " + event.getBelow() + ".");
+
+ if (!mPaused) {
+ onSensorEvent(event);
+ }
+ }
+ };
+
+ @Inject
+ ProximitySensorImpl(
+ @PrimaryProxSensor ThresholdSensor primary,
+ @SecondaryProxSensor ThresholdSensor secondary,
+ @Main DelayableExecutor delayableExecutor,
+ Execution execution) {
+ mPrimaryThresholdSensor = primary;
+ mSecondaryThresholdSensor = secondary;
+ mDelayableExecutor = delayableExecutor;
+ mExecution = execution;
+ }
+
+ @Override
+ public void setTag(String tag) {
+ mTag = tag;
+ mPrimaryThresholdSensor.setTag(tag + ":primary");
+ mSecondaryThresholdSensor.setTag(tag + ":secondary");
+ }
+
+ @Override
+ public void setDelay(int delay) {
+ mExecution.assertIsMainThread();
+ mPrimaryThresholdSensor.setDelay(delay);
+ mSecondaryThresholdSensor.setDelay(delay);
+ }
+
+ /**
+ * Unregister with the {@link SensorManager} without unsetting listeners on this object.
+ */
+ @Override
+ public void pause() {
+ mExecution.assertIsMainThread();
+ mPaused = true;
+ unregisterInternal();
+ }
+
+ /**
+ * Register with the {@link SensorManager}. No-op if no listeners are registered on this object.
+ */
+ @Override
+ public void resume() {
+ mExecution.assertIsMainThread();
+ mPaused = false;
+ registerInternal();
+ }
+
+ @Override
+ public void setSecondarySafe(boolean safe) {
+ mSecondarySafe = mSecondaryThresholdSensor.isLoaded() && safe;
+ chooseSensor();
+ }
+
+ /**
+ * Returns true if we are registered with the SensorManager.
+ */
+ @Override
+ public boolean isRegistered() {
+ return mRegistered;
+ }
+
+ /**
+ * Returns {@code false} if a Proximity sensor is not available.
+ */
+ @Override
+ public boolean isLoaded() {
+ return mPrimaryThresholdSensor.isLoaded();
+ }
+
+ /**
+ * Add a listener.
+ *
+ * Registers itself with the {@link SensorManager} if this is the first listener
+ * added. If the ProximitySensor is paused, it will be registered when resumed.
+ */
+ @Override
+ public void register(ThresholdSensor.Listener listener) {
+ mExecution.assertIsMainThread();
+ if (!isLoaded()) {
+ return;
+ }
+
+ if (mListeners.contains(listener)) {
+ logDebug("ProxListener registered multiple times: " + listener);
+ } else {
+ mListeners.add(listener);
+ }
+ registerInternal();
+ }
+
+ protected void registerInternal() {
+ mExecution.assertIsMainThread();
+ if (mRegistered || mPaused || mListeners.isEmpty()) {
+ return;
+ }
+ if (!mInitializedListeners) {
+ mPrimaryThresholdSensor.pause();
+ mSecondaryThresholdSensor.pause();
+ mPrimaryThresholdSensor.register(mPrimaryEventListener);
+ mSecondaryThresholdSensor.register(mSecondaryEventListener);
+ mInitializedListeners = true;
+ }
+
+ mRegistered = true;
+ chooseSensor();
+ }
+
+ private void chooseSensor() {
+ mExecution.assertIsMainThread();
+ if (!mRegistered || mPaused || mListeners.isEmpty()) {
+ return;
+ }
+ if (mSecondarySafe) {
+ mSecondaryThresholdSensor.resume();
+ mPrimaryThresholdSensor.pause();
+ } else {
+ mPrimaryThresholdSensor.resume();
+ mSecondaryThresholdSensor.pause();
+ }
+ }
+
+ /**
+ * Remove a listener.
+ *
+ * If all listeners are removed from an instance of this class,
+ * it will unregister itself with the SensorManager.
+ */
+ @Override
+ public void unregister(ThresholdSensor.Listener listener) {
+ mExecution.assertIsMainThread();
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ unregisterInternal();
+ }
+ }
+
+ @Override
+ public String getName() {
+ return mPrimaryThresholdSensor.getName();
+ }
+
+ @Override
+ public String getType() {
+ return mPrimaryThresholdSensor.getType();
+ }
+
+ protected void unregisterInternal() {
+ mExecution.assertIsMainThread();
+ if (!mRegistered) {
+ return;
+ }
+ logDebug("unregistering sensor listener");
+ mPrimaryThresholdSensor.pause();
+ mSecondaryThresholdSensor.pause();
+ if (mCancelSecondaryRunnable != null) {
+ mCancelSecondaryRunnable.run();
+ mCancelSecondaryRunnable = null;
+ }
+ mLastPrimaryEvent = null; // Forget what we know.
+ mLastEvent = null;
+ mRegistered = false;
+ }
+
+ @Override
+ public Boolean isNear() {
+ return isLoaded() && mLastEvent != null ? mLastEvent.getBelow() : null;
+ }
+
+ @Override
+ public void alertListeners() {
+ mExecution.assertIsMainThread();
+ if (mAlerting.getAndSet(true)) {
+ return;
+ }
+ if (mLastEvent != null) {
+ ThresholdSensorEvent lastEvent = mLastEvent; // Listeners can null out mLastEvent.
+ List<ThresholdSensor.Listener> listeners = new ArrayList<>(mListeners);
+ listeners.forEach(proximitySensorListener ->
+ proximitySensorListener.onThresholdCrossed(lastEvent));
+ }
+
+ mAlerting.set(false);
+ }
+
+ private void onPrimarySensorEvent(ThresholdSensorEvent event) {
+ mExecution.assertIsMainThread();
+ if (mLastPrimaryEvent != null && event.getBelow() == mLastPrimaryEvent.getBelow()) {
+ return;
+ }
+
+ mLastPrimaryEvent = event;
+
+ if (mSecondarySafe && mSecondaryThresholdSensor.isLoaded()) {
+ logDebug("Primary sensor reported " + (event.getBelow() ? "near" : "far")
+ + ". Checking secondary.");
+ if (mCancelSecondaryRunnable == null) {
+ mSecondaryThresholdSensor.resume();
+ }
+ return;
+ }
+
+
+ if (!mSecondaryThresholdSensor.isLoaded()) { // No secondary
+ logDebug("Primary sensor event: " + event.getBelow() + ". No secondary.");
+ onSensorEvent(event);
+ } else if (event.getBelow()) { // Covered? Check secondary.
+ logDebug("Primary sensor event: " + event.getBelow() + ". Checking secondary.");
+ if (mCancelSecondaryRunnable != null) {
+ mCancelSecondaryRunnable.run();
+ }
+ mSecondaryThresholdSensor.resume();
+ } else { // Uncovered. Report immediately.
+ onSensorEvent(event);
+ }
+ }
+
+ private void onSensorEvent(ThresholdSensorEvent event) {
+ mExecution.assertIsMainThread();
+ if (mLastEvent != null && event.getBelow() == mLastEvent.getBelow()) {
+ return;
+ }
+
+ if (!mSecondarySafe && !event.getBelow()) {
+ chooseSensor();
+ }
+
+ mLastEvent = event;
+ alertListeners();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{registered=%s, paused=%s, near=%s, posture=%s, primarySensor=%s, "
+ + "secondarySensor=%s secondarySafe=%s}",
+ isRegistered(), mPaused, isNear(), mDevicePosture, mPrimaryThresholdSensor,
+ mSecondaryThresholdSensor, mSecondarySafe);
+ }
+
+ void logDebug(String msg) {
+ if (DEBUG) {
+ Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java
index 11e7df8bd85f..96980b85e410 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java
@@ -16,11 +16,24 @@
package com.android.systemui.util.sensors;
+import android.content.res.Resources;
import android.hardware.Sensor;
import android.hardware.SensorManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
@@ -31,8 +44,10 @@ import dagger.Provides;
public class SensorModule {
@Provides
@PrimaryProxSensor
- static ThresholdSensor providePrimaryProxSensor(SensorManager sensorManager,
- ThresholdSensorImpl.Builder thresholdSensorBuilder) {
+ static ThresholdSensor providePrimaryProximitySensor(
+ SensorManager sensorManager,
+ ThresholdSensorImpl.Builder thresholdSensorBuilder
+ ) {
try {
return thresholdSensorBuilder
.setSensorDelay(SensorManager.SENSOR_DELAY_NORMAL)
@@ -52,8 +67,9 @@ public class SensorModule {
@Provides
@SecondaryProxSensor
- static ThresholdSensor provideSecondaryProxSensor(
- ThresholdSensorImpl.Builder thresholdSensorBuilder) {
+ static ThresholdSensor provideSecondaryProximitySensor(
+ ThresholdSensorImpl.Builder thresholdSensorBuilder
+ ) {
try {
return thresholdSensorBuilder
.setSensorResourceId(R.string.proximity_sensor_secondary_type, true)
@@ -64,4 +80,151 @@ public class SensorModule {
return thresholdSensorBuilder.setSensor(null).setThresholdValue(0).build();
}
}
+
+ /**
+ * If postures are supported on the device, returns a posture dependent proximity sensor
+ * which switches proximity sensors based on the current posture.
+ *
+ * If postures are not supported the regular {@link ProximitySensorImpl} will be returned.
+ */
+ @Provides
+ static ProximitySensor provideProximitySensor(
+ @Main Resources resources,
+ Lazy<PostureDependentProximitySensor> postureDependentProximitySensorProvider,
+ Lazy<ProximitySensorImpl> proximitySensorProvider
+ ) {
+ if (hasPostureSupport(
+ resources.getStringArray(R.array.proximity_sensor_posture_mapping))) {
+ return postureDependentProximitySensorProvider.get();
+ } else {
+ return proximitySensorProvider.get();
+ }
+ }
+
+ @Provides
+ static ProximityCheck provideProximityCheck(
+ ProximitySensor proximitySensor,
+ @Main DelayableExecutor delayableExecutor
+ ) {
+ return new ProximityCheck(
+ proximitySensor,
+ delayableExecutor
+ );
+ }
+
+ @Provides
+ @PrimaryProxSensor
+ @NonNull
+ static ThresholdSensor[] providePostureToProximitySensorMapping(
+ ThresholdSensorImpl.BuilderFactory thresholdSensorImplBuilderFactory,
+ @Main Resources resources
+ ) {
+ return createPostureToSensorMapping(
+ thresholdSensorImplBuilderFactory,
+ resources.getStringArray(R.array.proximity_sensor_posture_mapping),
+ R.dimen.proximity_sensor_threshold,
+ R.dimen.proximity_sensor_threshold_latch
+ );
+ }
+
+ @Provides
+ @SecondaryProxSensor
+ @NonNull
+ static ThresholdSensor[] providePostureToSecondaryProximitySensorMapping(
+ ThresholdSensorImpl.BuilderFactory thresholdSensorImplBuilderFactory,
+ @Main Resources resources
+ ) {
+ return createPostureToSensorMapping(
+ thresholdSensorImplBuilderFactory,
+ resources.getStringArray(R.array.proximity_sensor_secondary_posture_mapping),
+ R.dimen.proximity_sensor_secondary_threshold,
+ R.dimen.proximity_sensor_secondary_threshold_latch
+ );
+ }
+
+ /**
+ * Builds sensors to use per posture.
+ *
+ * @param sensorTypes an array where the index represents
+ * {@link DevicePostureController.DevicePostureInt} and the value
+ * at the given index is the sensorType. Empty values represent
+ * no sensor desired.
+ * @param proximitySensorThresholdResourceId resource id for the threshold for all sensor
+ * postures. This currently only supports one value.
+ * This needs to be updated in the future if postures
+ * use different sensors with differing thresholds.
+ * @param proximitySensorThresholdLatchResourceId resource id for the latch for all sensor
+ * postures. This currently only supports one
+ * value. This needs to be updated in the future
+ * if postures use different sensors with
+ * differing latches.
+ * @return an array where the index represents the device posture
+ * {@link DevicePostureController.DevicePostureInt} and the value at the index is the sensor to
+ * use when the device is in that posture.
+ */
+ @NonNull
+ private static ThresholdSensor[] createPostureToSensorMapping(
+ ThresholdSensorImpl.BuilderFactory thresholdSensorImplBuilderFactory,
+ String[] sensorTypes,
+ int proximitySensorThresholdResourceId,
+ int proximitySensorThresholdLatchResourceId
+
+ ) {
+ ThresholdSensor noProxSensor = thresholdSensorImplBuilderFactory
+ .createBuilder()
+ .setSensor(null).setThresholdValue(0).build();
+
+
+ // length and index of sensorMap correspond to DevicePostureController.DevicePostureInt:
+ final ThresholdSensor[] sensorMap =
+ new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE];
+ Arrays.fill(sensorMap, noProxSensor);
+
+ if (!hasPostureSupport(sensorTypes)) {
+ Log.e("SensorModule", "config doesn't support postures,"
+ + " but attempting to retrieve proxSensorMapping");
+ return sensorMap;
+ }
+
+ // Map of sensorType => Sensor, so we reuse the same sensor if it's the same between
+ // postures
+ Map<String, ThresholdSensor> typeToSensorMap = new HashMap<>();
+ for (int i = 0; i < sensorTypes.length; i++) {
+ try {
+ final String sensorType = sensorTypes[i];
+ if (typeToSensorMap.containsKey(sensorType)) {
+ sensorMap[i] = typeToSensorMap.get(sensorType);
+ } else {
+ sensorMap[i] = thresholdSensorImplBuilderFactory
+ .createBuilder()
+ .setSensorType(sensorTypes[i], true)
+ .setThresholdResourceId(proximitySensorThresholdResourceId)
+ .setThresholdLatchResourceId(proximitySensorThresholdLatchResourceId)
+ .build();
+ typeToSensorMap.put(sensorType, sensorMap[i]);
+ }
+ } catch (IllegalStateException e) {
+ // do nothing, sensor at this posture is already set to noProxSensor
+ }
+ }
+
+ return sensorMap;
+ }
+
+ /**
+ * Returns true if there's at least one non-empty sensor type in the given array.
+ */
+ private static boolean hasPostureSupport(String[] postureToSensorTypeMapping) {
+ if (postureToSensorTypeMapping == null || postureToSensorTypeMapping.length == 0) {
+ return false;
+ }
+
+ for (String sensorType : postureToSensorTypeMapping) {
+ if (!TextUtils.isEmpty(sensorType)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensor.java
index 363a734a6ae5..d81a8d5991d1 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensor.java
@@ -16,8 +16,6 @@
package com.android.systemui.util.sensors;
-import java.util.Locale;
-
/**
* A wrapper class for sensors that have a boolean state - above/below.
*/
@@ -77,6 +75,16 @@ public interface ThresholdSensor {
void unregister(Listener listener);
/**
+ * Name of the sensor.
+ */
+ String getName();
+
+ /**
+ * Type of the sensor.
+ */
+ String getType();
+
+ /**
* Interface for listening to events on {@link ThresholdSensor}
*/
interface Listener {
@@ -85,34 +93,4 @@ public interface ThresholdSensor {
*/
void onThresholdCrossed(ThresholdSensorEvent event);
}
-
- /**
- * Returned when the below/above state of a {@link ThresholdSensor} changes.
- */
- class ThresholdSensorEvent {
- private final boolean mBelow;
- private final long mTimestampNs;
-
- public ThresholdSensorEvent(boolean below, long timestampNs) {
- mBelow = below;
- mTimestampNs = timestampNs;
- }
-
- public boolean getBelow() {
- return mBelow;
- }
-
- public long getTimestampNs() {
- return mTimestampNs;
- }
-
- public long getTimestampMs() {
- return mTimestampNs / 1000000;
- }
-
- @Override
- public String toString() {
- return String.format((Locale) null, "{near=%s, timestamp_ns=%d}", mBelow, mTimestampNs);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorEvent.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorEvent.java
new file mode 100644
index 000000000000..afce09fbe6d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorEvent.java
@@ -0,0 +1,49 @@
+/*
+ * 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.systemui.util.sensors;
+
+import java.util.Locale;
+
+/**
+ * Returned when the below/above state of a {@link ThresholdSensor} changes.
+ */
+public class ThresholdSensorEvent {
+ private final boolean mBelow;
+ private final long mTimestampNs;
+
+ public ThresholdSensorEvent(boolean below, long timestampNs) {
+ mBelow = below;
+ mTimestampNs = timestampNs;
+ }
+
+ public boolean getBelow() {
+ return mBelow;
+ }
+
+ public long getTimestampNs() {
+ return mTimestampNs;
+ }
+
+ public long getTimestampMs() {
+ return mTimestampNs / 1000000;
+ }
+
+ @Override
+ public String toString() {
+ return String.format((Locale) null, "{near=%s, timestamp_ns=%d}", mBelow, mTimestampNs);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
index d10cf9b180c3..a9086b140a3c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
@@ -21,6 +21,7 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -32,7 +33,10 @@ import java.util.List;
import javax.inject.Inject;
-class ThresholdSensorImpl implements ThresholdSensor {
+/**
+ * Sensor that will only trigger beyond some lower and upper threshold.
+ */
+public class ThresholdSensorImpl implements ThresholdSensor {
private static final String TAG = "ThresholdSensor";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -198,6 +202,15 @@ class ThresholdSensorImpl implements ThresholdSensor {
alertListenersInternal(belowThreshold, timestampNs);
}
+ @Override
+ public String getName() {
+ return mSensor != null ? mSensor.getName() : null;
+ }
+
+ @Override
+ public String getType() {
+ return mSensor != null ? mSensor.getStringType() : null;
+ }
@Override
public String toString() {
@@ -211,7 +224,12 @@ class ThresholdSensorImpl implements ThresholdSensor {
}
}
- static class Builder {
+ /**
+ * Use to build a ThresholdSensor. Should only be used once per sensor built, since
+ * parameters are not reset after calls to build(). For ease of retrievingnew Builders, use
+ * {@link BuilderFactory}.
+ */
+ public static class Builder {
private final Resources mResources;
private final AsyncSensorManager mSensorManager;
private final Execution mExecution;
@@ -318,7 +336,7 @@ class ThresholdSensorImpl implements ThresholdSensor {
@VisibleForTesting
Sensor findSensorByType(String sensorType, boolean requireWakeUp) {
- if (sensorType.isEmpty()) {
+ if (TextUtils.isEmpty(sensorType)) {
return null;
}
@@ -336,4 +354,29 @@ class ThresholdSensorImpl implements ThresholdSensor {
return sensor;
}
}
+
+ /**
+ * Factory that creates a new ThresholdSensorImpl.Builder. In general, Builders should not be
+ * reused after creating a ThresholdSensor or else there may be default threshold and sensor
+ * values set from the previous built sensor.
+ */
+ public static class BuilderFactory {
+ private final Resources mResources;
+ private final AsyncSensorManager mSensorManager;
+ private final Execution mExecution;
+
+ @Inject
+ BuilderFactory(
+ @Main Resources resources,
+ AsyncSensorManager sensorManager,
+ Execution execution) {
+ mResources = resources;
+ mSensorManager = sensorManager;
+ mExecution = execution;
+ }
+
+ ThresholdSensorImpl.Builder createBuilder() {
+ return new Builder(mResources, mSensorManager, mExecution);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
new file mode 100644
index 000000000000..2a0cc7ddacf5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.systemui.util.wrapper
+
+import android.content.Context
+import com.android.internal.view.RotationPolicy
+import com.android.internal.view.RotationPolicy.RotationPolicyListener
+import javax.inject.Inject
+
+/**
+ * Testable wrapper interface around RotationPolicy {link com.android.internal.view.RotationPolicy}
+ */
+interface RotationPolicyWrapper {
+ fun setRotationLock(enabled: Boolean)
+ fun setRotationLockAtAngle(enabled: Boolean, rotation: Int)
+ fun getRotationLockOrientation(): Int
+ fun isRotationLockToggleVisible(): Boolean
+ fun isRotationLocked(): Boolean
+ fun registerRotationPolicyListener(listener: RotationPolicyListener, userHandle: Int)
+ fun unregisterRotationPolicyListener(listener: RotationPolicyListener)
+}
+
+class RotationPolicyWrapperImpl @Inject constructor(private val context: Context) :
+ RotationPolicyWrapper {
+
+ override fun setRotationLock(enabled: Boolean) {
+ RotationPolicy.setRotationLock(context, enabled)
+ }
+
+ override fun setRotationLockAtAngle(enabled: Boolean, rotation: Int) {
+ RotationPolicy.setRotationLockAtAngle(context, enabled, rotation)
+ }
+
+ override fun getRotationLockOrientation(): Int =
+ RotationPolicy.getRotationLockOrientation(context)
+
+ override fun isRotationLockToggleVisible(): Boolean =
+ RotationPolicy.isRotationLockToggleVisible(context)
+
+ override fun isRotationLocked(): Boolean =
+ RotationPolicy.isRotationLocked(context)
+
+ override fun registerRotationPolicyListener(
+ listener: RotationPolicyListener,
+ userHandle: Int
+ ) {
+ RotationPolicy.registerRotationPolicyListener(context, listener, userHandle)
+ }
+
+ override fun unregisterRotationPolicyListener(listener: RotationPolicyListener) {
+ RotationPolicy.unregisterRotationPolicyListener(context, listener)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/wrapper/UtilWrapperModule.kt b/packages/SystemUI/src/com/android/systemui/util/wrapper/UtilWrapperModule.kt
new file mode 100644
index 000000000000..7e3aa277db33
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/wrapper/UtilWrapperModule.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.systemui.util.wrapper
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+
+@Module
+abstract class UtilWrapperModule {
+
+ @Binds
+ @SysUISingleton
+ abstract fun bindRotationPolicyWrapper(impl: RotationPolicyWrapperImpl): RotationPolicyWrapper
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index 56f1c092efd9..c083c14a3730 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -20,13 +20,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
-import android.media.AudioManager;
import android.media.VolumePolicy;
import android.os.Bundle;
import android.view.WindowManager.LayoutParams;
import com.android.settingslib.applications.InterestingConfigChanges;
-import com.android.systemui.Dependency;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
@@ -67,6 +65,7 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
| ActivityInfo.CONFIG_ASSETS_PATHS | ActivityInfo.CONFIG_UI_MODE);
private final KeyguardViewMediator mKeyguardViewMediator;
+ private final ActivityStarter mActivityStarter;
private VolumeDialog mDialog;
private VolumePolicy mVolumePolicy = new VolumePolicy(
DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT, // volumeDownToEnterSilent
@@ -79,18 +78,23 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
public VolumeDialogComponent(
Context context,
KeyguardViewMediator keyguardViewMediator,
+ ActivityStarter activityStarter,
VolumeDialogControllerImpl volumeDialogController,
- DemoModeController demoModeController) {
+ DemoModeController demoModeController,
+ PluginDependencyProvider pluginDependencyProvider,
+ ExtensionController extensionController,
+ TunerService tunerService,
+ VolumeDialog volumeDialog) {
mContext = context;
mKeyguardViewMediator = keyguardViewMediator;
+ mActivityStarter = activityStarter;
mController = volumeDialogController;
mController.setUserActivityListener(this);
// Allow plugins to reference the VolumeDialogController.
- Dependency.get(PluginDependencyProvider.class)
- .allowPluginDependency(VolumeDialogController.class);
- Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
+ pluginDependencyProvider.allowPluginDependency(VolumeDialogController.class);
+ extensionController.newExtension(VolumeDialog.class)
.withPlugin(VolumeDialog.class)
- .withDefault(this::createDefault)
+ .withDefault(() -> volumeDialog)
.withCallback(dialog -> {
if (mDialog != null) {
mDialog.destroy();
@@ -99,19 +103,11 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
mDialog.init(LayoutParams.TYPE_VOLUME_OVERLAY, mVolumeDialogCallback);
}).build();
applyConfiguration();
- Dependency.get(TunerService.class).addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
+ tunerService.addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
VOLUME_SILENT_DO_NOT_DISTURB);
demoModeController.addCallback(this);
}
- protected VolumeDialog createDefault() {
- VolumeDialogImpl impl = new VolumeDialogImpl(mContext);
- impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
- impl.setAutomute(true);
- impl.setSilentMode(false);
- return impl;
- }
-
@Override
public void onTuningChanged(String key, String newValue) {
boolean volumeDownToEnterSilent = mVolumePolicy.volumeDownToEnterSilent;
@@ -189,8 +185,7 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna
}
private void startSettings(Intent intent) {
- Dependency.get(ActivityStarter.class).startActivity(intent,
- true /* onlyProvisioned */, true /* dismissShade */);
+ mActivityStarter.startActivity(intent, true /* onlyProvisioned */, true /* dismissShade */);
}
private final VolumeDialogImpl.Callback mVolumeDialogCallback = new VolumeDialogImpl.Callback() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 60b92ef45291..58f74a0d2a02 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -104,8 +104,8 @@ import android.widget.Toast;
import androidx.annotation.Nullable;
import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
+import com.android.internal.view.RotationPolicy;
import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -234,6 +234,10 @@ public class VolumeDialogImpl implements VolumeDialog,
private final Object mSafetyWarningLock = new Object();
private final Accessibility mAccessibility = new Accessibility();
+ private final ConfigurationController mConfigurationController;
+ private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final ActivityStarter mActivityStarter;
+
private boolean mShowing;
private boolean mShowA11yStream;
@@ -255,14 +259,24 @@ public class VolumeDialogImpl implements VolumeDialog,
private Consumer<Boolean> mCrossWindowBlurEnabledListener;
private BackgroundBlurDrawable mDialogRowsViewBackground;
- public VolumeDialogImpl(Context context) {
+ public VolumeDialogImpl(
+ Context context,
+ VolumeDialogController volumeDialogController,
+ AccessibilityManagerWrapper accessibilityManagerWrapper,
+ DeviceProvisionedController deviceProvisionedController,
+ ConfigurationController configurationController,
+ MediaOutputDialogFactory mediaOutputDialogFactory,
+ ActivityStarter activityStarter) {
mContext =
new ContextThemeWrapper(context, R.style.volume_dialog_theme);
- mController = Dependency.get(VolumeDialogController.class);
+ mController = volumeDialogController;
mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
- mAccessibilityMgr = Dependency.get(AccessibilityManagerWrapper.class);
- mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
+ mAccessibilityMgr = accessibilityManagerWrapper;
+ mDeviceProvisionedController = deviceProvisionedController;
+ mConfigurationController = configurationController;
+ mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mActivityStarter = activityStarter;
mShowActiveStreamOnly = showActiveStreamOnly();
mHasSeenODICaptionsTooltip =
Prefs.getBoolean(context, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
@@ -305,14 +319,14 @@ public class VolumeDialogImpl implements VolumeDialog,
mController.addCallback(mControllerCallbackH, mHandler);
mController.getState();
- Dependency.get(ConfigurationController.class).addCallback(this);
+ mConfigurationController.addCallback(this);
}
@Override
public void destroy() {
mController.removeCallback(mControllerCallbackH);
mHandler.removeCallbacksAndMessages(null);
- Dependency.get(ConfigurationController.class).removeCallback(this);
+ mConfigurationController.removeCallback(this);
}
@Override
@@ -400,7 +414,9 @@ public class VolumeDialogImpl implements VolumeDialog,
mDialog.setCanceledOnTouchOutside(true);
mDialog.setOnShowListener(dialog -> {
mDialogView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
- if (!isLandscape()) mDialogView.setTranslationX(mDialogView.getWidth() / 2.0f);
+ if (!shouldSlideInVolumeTray()) {
+ mDialogView.setTranslationX(mDialogView.getWidth() / 2.0f);
+ }
mDialogView.setAlpha(0);
mDialogView.animate()
.alpha(1)
@@ -587,6 +603,10 @@ public class VolumeDialogImpl implements VolumeDialog,
return (int) (alpha * 255);
}
+ private boolean shouldSlideInVolumeTray() {
+ return mContext.getDisplay().getRotation() != RotationPolicy.NATURAL_ROTATION;
+ }
+
private boolean isLandscape() {
return mContext.getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_LANDSCAPE;
@@ -1010,9 +1030,8 @@ public class VolumeDialogImpl implements VolumeDialog,
Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
Intent intent = new Intent(Settings.Panel.ACTION_VOLUME);
dismissH(DISMISS_REASON_SETTINGS_CLICKED);
- Dependency.get(MediaOutputDialogFactory.class).dismiss();
- Dependency.get(ActivityStarter.class).startActivity(intent,
- true /* dismissShade */);
+ mMediaOutputDialogFactory.dismiss();
+ mActivityStarter.startActivity(intent, true /* dismissShade */);
});
}
}
@@ -1320,7 +1339,7 @@ public class VolumeDialogImpl implements VolumeDialog,
hideRingerDrawer();
}, 50));
- if (!isLandscape()) animator.translationX(mDialogView.getWidth() / 2.0f);
+ if (!shouldSlideInVolumeTray()) animator.translationX(mDialogView.getWidth() / 2.0f);
animator.start();
checkODICaptionsTooltip(true);
mController.notifyVisible(false);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 1ef4c169b786..79aa643fc6c2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -16,11 +16,23 @@
package com.android.systemui.volume.dagger;
+import android.content.Context;
+import android.media.AudioManager;
+
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.VolumeDialog;
+import com.android.systemui.plugins.VolumeDialogController;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.volume.VolumeDialogComponent;
+import com.android.systemui.volume.VolumeDialogImpl;
import dagger.Binds;
import dagger.Module;
+import dagger.Provides;
/** Dagger Module for code in the volume package. */
@@ -29,4 +41,28 @@ public interface VolumeModule {
/** */
@Binds
VolumeComponent provideVolumeComponent(VolumeDialogComponent volumeDialogComponent);
+
+ /** */
+ @Provides
+ static VolumeDialog provideVolumeDialog(
+ Context context,
+ VolumeDialogController volumeDialogController,
+ AccessibilityManagerWrapper accessibilityManagerWrapper,
+ DeviceProvisionedController deviceProvisionedController,
+ ConfigurationController configurationController,
+ MediaOutputDialogFactory mediaOutputDialogFactory,
+ ActivityStarter activityStarter) {
+ VolumeDialogImpl impl = new VolumeDialogImpl(
+ context,
+ volumeDialogController,
+ accessibilityManagerWrapper,
+ deviceProvisionedController,
+ configurationController,
+ mediaOutputDialogFactory,
+ activityStarter);
+ impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
+ impl.setAutomute(true);
+ impl.setSilentMode(false);
+ return impl;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardCarousel.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardCarousel.java
index 1e1b459382d7..77fd2e830873 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardCarousel.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardCarousel.java
@@ -190,6 +190,15 @@ public class WalletCardCarousel extends RecyclerView {
}
/**
+ * Sets the adapter again in the RecyclerView, updating the ViewHolders children's layout.
+ * This is needed when changing the state of the device (eg fold/unfold) so the ViewHolders are
+ * recreated.
+ */
+ void resetAdapter() {
+ setAdapter(mWalletCardCarouselAdapter);
+ }
+
+ /**
* Returns true if the data set is changed.
*/
boolean setData(List<WalletCardViewInfo> data, int selectedIndex, boolean hasLockStateChanged) {
@@ -376,8 +385,8 @@ public class WalletCardCarousel extends RecyclerView {
CardView cardView = viewHolder.mCardView;
cardView.setRadius(mCornerRadiusPx);
ViewGroup.LayoutParams layoutParams = cardView.getLayoutParams();
- layoutParams.width = mCardWidthPx;
- layoutParams.height = mCardHeightPx;
+ layoutParams.width = getCardWidthPx();
+ layoutParams.height = getCardHeightPx();
view.setTag(viewHolder);
return viewHolder;
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
index 420f84abe0dd..9b2702ff7bf2 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
@@ -99,17 +99,13 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard
mCardCarousel.setExpectedViewWidth(getWidth());
}
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- updateViewForOrientation(newConfig.orientation);
- }
-
private void updateViewForOrientation(@Configuration.Orientation int orientation) {
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
renderViewPortrait();
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
renderViewLandscape();
}
+ mCardCarousel.resetAdapter(); // necessary to update cards width
ViewGroup.LayoutParams params = mCardCarouselContainer.getLayoutParams();
if (params instanceof MarginLayoutParams) {
((MarginLayoutParams) params).topMargin =
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index a29a638b91a8..291c64dd6daf 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -50,7 +50,6 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
-import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -61,11 +60,10 @@ import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.notification.NotificationChannelHelper;
@@ -80,7 +78,6 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Di
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -118,7 +115,6 @@ public class BubblesManager implements Dumpable {
private final NotifPipeline mNotifPipeline;
private final Executor mSysuiMainExecutor;
- private ScrimView mBubbleScrim;
private final Bubbles.SysuiProxy mSysuiProxy;
// TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
private final List<NotifCallback> mCallbacks = new ArrayList<>();
@@ -193,12 +189,6 @@ public class BubblesManager implements Dumpable {
ServiceManager.getService(Context.STATUS_BAR_SERVICE))
: statusBarService;
- mBubbleScrim = new ScrimView(mContext);
- mBubbleScrim.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
- mBubbles.setBubbleScrim(mBubbleScrim, (executor, looper) -> {
- mBubbleScrim.setExecutor(executor, looper);
- });
-
if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
setupNotifPipeline();
} else {
@@ -387,10 +377,24 @@ public class BubblesManager implements Dumpable {
sysuiMainExecutor.execute(() -> {
sysUiState.setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand)
.commitUpdate(mContext.getDisplayId());
+ if (!shouldExpand) {
+ sysUiState.setFlag(
+ QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
+ false).commitUpdate(mContext.getDisplayId());
+ }
});
}
@Override
+ public void onManageMenuExpandChanged(boolean menuExpanded) {
+ sysuiMainExecutor.execute(() -> {
+ sysUiState.setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
+ menuExpanded).commitUpdate(mContext.getDisplayId());
+ });
+ }
+
+
+ @Override
public void onUnbubbleConversation(String key) {
sysuiMainExecutor.execute(() -> {
final NotificationEntry entry =
@@ -603,15 +607,6 @@ public class BubblesManager implements Dumpable {
}
/**
- * Returns the scrim drawn behind the bubble stack. This is managed by {@link ScrimController}
- * since we want the scrim's appearance and behavior to be identical to that of the notification
- * shade scrim.
- */
- public ScrimView getScrimForBubble() {
- return mBubbleScrim;
- }
-
- /**
* We intercept notification entries (including group summaries) dismissed by the user when
* there is an active bubble associated with it. We do this so that developers can still
* cancel it (and hence the bubbles associated with it).
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
index 2216a915b0d9..3be1d3cab869 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -38,6 +38,7 @@ import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.tv.TvPipController;
import com.android.wm.shell.pip.tv.TvPipMenuController;
@@ -144,10 +145,17 @@ public abstract class TvPipModule {
@WMSingleton
@Provides
+ static PipTransitionState providePipTransitionState() {
+ return new PipTransitionState();
+ }
+
+ @WMSingleton
+ @Provides
static PipTaskOrganizer providePipTaskOrganizer(Context context,
TvPipMenuController tvPipMenuController,
SyncTransactionQueue syncTransactionQueue,
PipBoundsState pipBoundsState,
+ PipTransitionState pipTransitionState,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipAnimationController pipAnimationController,
PipTransitionController pipTransitionController,
@@ -157,7 +165,7 @@ public abstract class TvPipModule {
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context,
- syncTransactionQueue, pipBoundsState, pipBoundsAlgorithm,
+ syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm,
tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper,
pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger,
shellTaskOrganizer, mainExecutor);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
index 39a8bd94bf37..dbdc460b7d83 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
@@ -25,6 +25,7 @@ import com.android.systemui.dagger.WMSingleton;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
@@ -58,10 +59,10 @@ public class TvWMShellModule {
@WMSingleton
@Provides
static DisplayImeController provideDisplayImeController(IWindowManager wmService,
- DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor,
- TransactionPool transactionPool) {
- return new DisplayImeController(wmService, displayController, mainExecutor,
- transactionPool);
+ DisplayController displayController, DisplayInsetsController displayInsetsController,
+ @ShellMainThread ShellExecutor mainExecutor, TransactionPool transactionPool) {
+ return new DisplayImeController(wmService, displayController, displayInsetsController,
+ mainExecutor, transactionPool);
}
//
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index bc956dc85702..74611cce6f87 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -20,6 +20,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
@@ -64,6 +65,7 @@ import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.protolog.ShellProtoLogImpl;
+import com.android.wm.shell.splitscreen.SplitScreen;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -100,11 +102,13 @@ public final class WMShell extends SystemUI
| SYSUI_STATE_BOUNCER_SHOWING
| SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
| SYSUI_STATE_BUBBLES_EXPANDED
+ | SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
// Shell interfaces
private final Optional<Pip> mPipOptional;
- private final Optional<LegacySplitScreen> mSplitScreenOptional;
+ private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+ private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<OneHanded> mOneHandedOptional;
private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
private final Optional<ShellCommandHandler> mShellCommandHandler;
@@ -120,6 +124,7 @@ public final class WMShell extends SystemUI
private final Executor mSysUiMainExecutor;
private boolean mIsSysUiStateValid;
+ private KeyguardUpdateMonitorCallback mLegacySplitScreenKeyguardCallback;
private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
private KeyguardUpdateMonitorCallback mPipKeyguardCallback;
private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback;
@@ -128,7 +133,8 @@ public final class WMShell extends SystemUI
@Inject
public WMShell(Context context,
Optional<Pip> pipOptional,
- Optional<LegacySplitScreen> splitScreenOptional,
+ Optional<LegacySplitScreen> legacySplitScreenOptional,
+ Optional<SplitScreen> splitScreenOptional,
Optional<OneHanded> oneHandedOptional,
Optional<HideDisplayCutout> hideDisplayCutoutOptional,
Optional<ShellCommandHandler> shellCommandHandler,
@@ -149,6 +155,7 @@ public final class WMShell extends SystemUI
mScreenLifecycle = screenLifecycle;
mSysUiState = sysUiState;
mPipOptional = pipOptional;
+ mLegacySplitScreenOptional = legacySplitScreenOptional;
mSplitScreenOptional = splitScreenOptional;
mOneHandedOptional = oneHandedOptional;
mHideDisplayCutoutOptional = hideDisplayCutoutOptional;
@@ -165,6 +172,7 @@ public final class WMShell extends SystemUI
mProtoTracer.add(this);
mCommandQueue.addCallback(this);
mPipOptional.ifPresent(this::initPip);
+ mLegacySplitScreenOptional.ifPresent(this::initLegacySplitScreen);
mSplitScreenOptional.ifPresent(this::initSplitScreen);
mOneHandedOptional.ifPresent(this::initOneHanded);
mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout);
@@ -206,7 +214,7 @@ public final class WMShell extends SystemUI
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
pip.onOverlayChanged();
}
});
@@ -218,8 +226,8 @@ public final class WMShell extends SystemUI
}
@VisibleForTesting
- void initSplitScreen(LegacySplitScreen legacySplitScreen) {
- mSplitScreenKeyguardCallback = new KeyguardUpdateMonitorCallback() {
+ void initLegacySplitScreen(LegacySplitScreen legacySplitScreen) {
+ mLegacySplitScreenKeyguardCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onKeyguardVisibilityChanged(boolean showing) {
// Hide the divider when keyguard is showing. Even though keyguard/statusbar is
@@ -229,6 +237,22 @@ public final class WMShell extends SystemUI
legacySplitScreen.onKeyguardVisibilityChanged(showing);
}
};
+ mKeyguardUpdateMonitor.registerCallback(mLegacySplitScreenKeyguardCallback);
+ }
+
+ @VisibleForTesting
+ void initSplitScreen(SplitScreen splitScreen) {
+ mSplitScreenKeyguardCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ splitScreen.onKeyguardVisibilityChanged(showing);
+ }
+
+ @Override
+ public void onKeyguardOccludedChanged(boolean occluded) {
+ splitScreen.onKeyguardOccludedChanged(occluded);
+ }
+ };
mKeyguardUpdateMonitor.registerCallback(mSplitScreenKeyguardCallback);
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index 6ef74505a681..0a33930d404c 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -26,9 +26,10 @@ import android.view.WindowManager;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.launcher3.icons.IconProvider;
import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.WMSingleton;
-import com.android.wm.shell.FullscreenTaskListener;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.ShellCommandHandlerImpl;
@@ -44,6 +45,7 @@ import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
@@ -54,7 +56,12 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
+import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.freeform.FreeformTaskListener;
+import com.android.wm.shell.fullscreen.FullscreenTaskListener;
+import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
@@ -70,6 +77,7 @@ import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.sizecompatui.SizeCompatUIController;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.splitscreen.StageTaskUnfoldController;
import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.startingsurface.StartingWindowController;
import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
@@ -77,10 +85,15 @@ import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelperController;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
+import com.android.wm.shell.unfold.UnfoldBackgroundController;
import java.util.Optional;
+import javax.inject.Provider;
+
import dagger.BindsOptionalOf;
+import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
@@ -109,6 +122,14 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
+ static DisplayInsetsController provideDisplayInsetsController( IWindowManager wmService,
+ DisplayController displayController,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new DisplayInsetsController(wmService, displayController, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
static DisplayLayout provideDisplayLayout() {
return new DisplayLayout();
}
@@ -116,8 +137,8 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static DragAndDropController provideDragAndDropController(Context context,
- DisplayController displayController) {
- return new DragAndDropController(context, displayController);
+ DisplayController displayController, UiEventLogger uiEventLogger) {
+ return new DragAndDropController(context, displayController, uiEventLogger);
}
@WMSingleton
@@ -149,6 +170,12 @@ public abstract class WMShellBaseModule {
return new SystemWindows(displayController, wmService);
}
+ @WMSingleton
+ @Provides
+ static IconProvider provideIconProvider(Context context) {
+ return new IconProvider(context);
+ }
+
// We currently dedupe multiple messages, so we use the shell main handler directly
@WMSingleton
@Provides
@@ -194,11 +221,12 @@ public abstract class WMShellBaseModule {
ShellTaskOrganizer organizer,
DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor,
- @ShellMainThread Handler mainHandler) {
+ @ShellMainThread Handler mainHandler,
+ SyncTransactionQueue syncQueue) {
return Optional.of(BubbleController.create(context, null /* synchronizer */,
floatingContentCoordinator, statusBarService, windowManager,
windowManagerShellWrapper, launcherApps, taskStackListener,
- uiEventLogger, organizer, displayController, mainExecutor, mainHandler));
+ uiEventLogger, organizer, displayController, mainExecutor, mainHandler, syncQueue));
}
//
@@ -207,11 +235,70 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
- return new FullscreenTaskListener(syncQueue);
+ static FullscreenTaskListener provideFullscreenTaskListener(
+ SyncTransactionQueue syncQueue, Optional<FullscreenUnfoldController> controller) {
+ return new FullscreenTaskListener(syncQueue, controller);
+ }
+
+ //
+ // Unfold transition
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController(
+ Context context,
+ Optional<ShellUnfoldProgressProvider> progressProvider,
+ Lazy<UnfoldBackgroundController> unfoldBackgroundController,
+ DisplayInsetsController displayInsetsController,
+ @ShellMainThread ShellExecutor mainExecutor
+ ) {
+ return progressProvider.map(shellUnfoldTransitionProgressProvider ->
+ new FullscreenUnfoldController(context, mainExecutor,
+ unfoldBackgroundController.get(), shellUnfoldTransitionProgressProvider,
+ displayInsetsController));
+ }
+
+ @Provides
+ static Optional<StageTaskUnfoldController> provideStageTaskUnfoldController(
+ Optional<ShellUnfoldProgressProvider> progressProvider,
+ Context context,
+ TransactionPool transactionPool,
+ Lazy<UnfoldBackgroundController> unfoldBackgroundController,
+ DisplayInsetsController displayInsetsController,
+ @ShellMainThread ShellExecutor mainExecutor
+ ) {
+ return progressProvider.map(shellUnfoldTransitionProgressProvider ->
+ new StageTaskUnfoldController(
+ context,
+ transactionPool,
+ shellUnfoldTransitionProgressProvider,
+ displayInsetsController,
+ unfoldBackgroundController.get(),
+ mainExecutor
+ ));
+ }
+
+ @WMSingleton
+ @Provides
+ static UnfoldBackgroundController provideUnfoldBackgroundController(
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ Context context
+ ) {
+ return new UnfoldBackgroundController(
+ context,
+ rootTaskDisplayAreaOrganizer
+ );
}
//
+ // Freeform (optional feature)
+ //
+
+ @BindsOptionalOf
+ abstract Optional<FreeformTaskListener> optionalFreeformTaskListener();
+
+ //
// Hide display cutout
//
@@ -265,13 +352,21 @@ public abstract class WMShellBaseModule {
return taskSurfaceController.map((controller) -> controller.asTaskSurfaceHelper());
}
- @WMSingleton
@Provides
static Optional<TaskSurfaceHelperController> provideTaskSurfaceHelperController(
ShellTaskOrganizer taskOrganizer, @ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(new TaskSurfaceHelperController(taskOrganizer, mainExecutor));
}
+ @WMSingleton
+ @Provides
+ static Optional<DisplayAreaHelper> provideDisplayAreaHelper(
+ @ShellMainThread ShellExecutor mainExecutor,
+ RootDisplayAreaOrganizer rootDisplayAreaOrganizer) {
+ return Optional.ofNullable(new DisplayAreaHelperController(mainExecutor,
+ rootDisplayAreaOrganizer));
+ }
+
//
// Pip (optional feature)
//
@@ -327,9 +422,11 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
- Context context, @ShellMainThread ShellExecutor mainExecutor,
+ DisplayController displayController, Context context,
+ @ShellMainThread ShellExecutor mainExecutor,
@ShellAnimationThread ShellExecutor animExecutor) {
- return new Transitions(organizer, pool, context, mainExecutor, animExecutor);
+ return new Transitions(organizer, pool, displayController, context, mainExecutor,
+ animExecutor);
}
//
@@ -345,6 +442,13 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
+ static RootDisplayAreaOrganizer provideRootDisplayAreaOrganizer(
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new RootDisplayAreaOrganizer(mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
static Optional<SplitScreen> provideSplitScreen(
Optional<SplitScreenController> splitScreenController) {
return splitScreenController.map((controller) -> controller.asSplitScreen());
@@ -357,12 +461,15 @@ public abstract class WMShellBaseModule {
SyncTransactionQueue syncQueue, Context context,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@ShellMainThread ShellExecutor mainExecutor,
- DisplayImeController displayImeController, Transitions transitions,
- TransactionPool transactionPool) {
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController, Transitions transitions,
+ TransactionPool transactionPool, IconProvider iconProvider,
+ Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) {
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
return Optional.of(new SplitScreenController(shellTaskOrganizer, syncQueue, context,
- rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController, transitions,
- transactionPool));
+ rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController,
+ displayInsetsController, transitions, transactionPool, iconProvider,
+ stageTaskUnfoldControllerProvider));
} else {
return Optional.empty();
}
@@ -404,9 +511,10 @@ public abstract class WMShellBaseModule {
@Provides
static StartingWindowController provideStartingWindowController(Context context,
@ShellSplashscreenThread ShellExecutor splashScreenExecutor,
- StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, TransactionPool pool) {
+ StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
+ TransactionPool pool) {
return new StartingWindowController(context, splashScreenExecutor,
- startingWindowTypeAlgorithm, pool);
+ startingWindowTypeAlgorithm, iconProvider, pool);
}
//
@@ -424,8 +532,9 @@ public abstract class WMShellBaseModule {
@Provides
static TaskViewFactoryController provideTaskViewFactoryController(
ShellTaskOrganizer shellTaskOrganizer,
- @ShellMainThread ShellExecutor mainExecutor) {
- return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor);
+ @ShellMainThread ShellExecutor mainExecutor,
+ SyncTransactionQueue syncQueue) {
+ return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor, syncQueue);
}
//
@@ -440,7 +549,9 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static ShellInitImpl provideShellInitImpl(DisplayImeController displayImeController,
+ static ShellInitImpl provideShellInitImpl(DisplayController displayController,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
Optional<BubbleController> bubblesOptional,
@@ -449,10 +560,14 @@ public abstract class WMShellBaseModule {
Optional<AppPairsController> appPairsOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
+ Optional<FullscreenUnfoldController> appUnfoldTransitionController,
+ Optional<Optional<FreeformTaskListener>> freeformTaskListener,
Transitions transitions,
StartingWindowController startingWindow,
@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellInitImpl(displayImeController,
+ return new ShellInitImpl(displayController,
+ displayImeController,
+ displayInsetsController,
dragAndDropController,
shellTaskOrganizer,
bubblesOptional,
@@ -461,6 +576,8 @@ public abstract class WMShellBaseModule {
appPairsOptional,
pipTouchHandlerOptional,
fullscreenTaskListener,
+ appUnfoldTransitionController,
+ freeformTaskListener,
transitions,
startingWindow,
mainExecutor);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 36fd9bee80ab..a7c5ad2e3716 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -28,6 +28,7 @@ import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -36,6 +37,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -48,6 +50,7 @@ import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransition;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.pip.phone.PipAppOpsListener;
@@ -81,10 +84,23 @@ public class WMShellModule {
@WMSingleton
@Provides
static DisplayImeController provideDisplayImeController(IWindowManager wmService,
- DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor,
+ DisplayController displayController, DisplayInsetsController displayInsetsController,
+ @ShellMainThread ShellExecutor mainExecutor,
TransactionPool transactionPool) {
- return new DisplayImeController(wmService, displayController, mainExecutor,
- transactionPool);
+ return new DisplayImeController(wmService, displayController, displayInsetsController,
+ mainExecutor, transactionPool);
+ }
+
+ //
+ // Freeform
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional<FreeformTaskListener> provideFreeformTaskListener(
+ Context context,
+ SyncTransactionQueue syncQueue) {
+ return Optional.ofNullable(FreeformTaskListener.create(context, syncQueue));
}
//
@@ -110,9 +126,10 @@ public class WMShellModule {
static AppPairsController provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue, DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor,
- DisplayImeController displayImeController) {
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController) {
return new AppPairsController(shellTaskOrganizer, syncQueue, displayController,
- mainExecutor, displayImeController);
+ mainExecutor, displayImeController, displayInsetsController);
}
//
@@ -184,8 +201,15 @@ public class WMShellModule {
@WMSingleton
@Provides
+ static PipTransitionState providePipTransitionState() {
+ return new PipTransitionState();
+ }
+
+ @WMSingleton
+ @Provides
static PipTaskOrganizer providePipTaskOrganizer(Context context,
SyncTransactionQueue syncTransactionQueue,
+ PipTransitionState pipTransitionState,
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
PhonePipMenuController menuPhoneController,
@@ -197,7 +221,7 @@ public class WMShellModule {
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context,
- syncTransactionQueue, pipBoundsState, pipBoundsAlgorithm,
+ syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm,
menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper,
pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger,
shellTaskOrganizer, mainExecutor);
@@ -215,8 +239,9 @@ public class WMShellModule {
static PipTransitionController providePipTransitionController(Context context,
Transitions transitions, ShellTaskOrganizer shellTaskOrganizer,
PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipBoundsState pipBoundsState, PhonePipMenuController pipMenuController) {
- return new PipTransition(context, pipBoundsState, pipMenuController,
+ PipBoundsState pipBoundsState, PipTransitionState pipTransitionState,
+ PhonePipMenuController pipMenuController) {
+ return new PipTransition(context, pipBoundsState, pipTransitionState, pipMenuController,
pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 8077deaf5fda..5e0f427800fc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -19,7 +19,6 @@ package com.android.keyguard;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -30,6 +29,7 @@ import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.view.View;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import androidx.test.filters.SmallTest;
@@ -108,7 +108,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
private final View mFakeSmartspaceView = new View(mContext);
private KeyguardClockSwitchController mController;
- private View mStatusArea;
+ private View mSliceView;
@Before
public void setup() {
@@ -149,8 +149,10 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
- mStatusArea = new View(getContext());
- when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea);
+ mSliceView = new View(getContext());
+ when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
+ when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(
+ new LinearLayout(getContext()));
}
@Test
@@ -192,7 +194,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
verifyAttachment(times(1));
listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
-
+ verify(mView).onViewDetached();
verify(mColorExtractor).removeOnColorsChangedListener(
any(ColorExtractor.OnColorsChangedListener.class));
}
@@ -215,7 +217,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mController.init();
- assertEquals(View.GONE, mStatusArea.getVisibility());
+ assertEquals(View.GONE, mSliceView.getVisibility());
}
@Test
@@ -223,22 +225,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
when(mSmartspaceController.isEnabled()).thenReturn(false);
mController.init();
- assertEquals(View.VISIBLE, mStatusArea.getVisibility());
- }
-
- @Test
- public void testDetachRemovesSmartspaceView() {
- when(mSmartspaceController.isEnabled()).thenReturn(true);
- when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
- mController.init();
- verify(mView).addView(eq(mFakeSmartspaceView), anyInt(), any());
-
- ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
- ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
- verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
-
- listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
- verify(mView).removeView(mFakeSmartspaceView);
+ assertEquals(View.VISIBLE, mSliceView.getVisibility());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 10ed1d75a533..e4336fe07dbb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -19,6 +19,9 @@ package com.android.keyguard;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
+import static com.android.keyguard.KeyguardClockSwitch.LARGE;
+import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
@@ -94,6 +97,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
mLargeClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view_large);
mLargeClockView = mKeyguardClockSwitch.findViewById(R.id.animatable_clock_view_large);
mBigClock = new TextClock(getContext());
+ mKeyguardClockSwitch.mChildrenAreLaidOut = true;
MockitoAnnotations.initMocks(this);
}
@@ -247,4 +251,36 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
verify(plugin).setStyle(style);
}
+
+ @Test
+ public void switchingToBigClock_makesSmallClockDisappear() {
+ mKeyguardClockSwitch.switchToClock(LARGE);
+
+ mKeyguardClockSwitch.mClockInAnim.end();
+ mKeyguardClockSwitch.mClockOutAnim.end();
+
+ assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
+ assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
+ assertThat(mClockFrame.getAlpha()).isEqualTo(0);
+ }
+
+ @Test
+ public void switchingToSmallClock_makesBigClockDisappear() {
+ mKeyguardClockSwitch.switchToClock(SMALL);
+
+ mKeyguardClockSwitch.mClockInAnim.end();
+ mKeyguardClockSwitch.mClockOutAnim.end();
+
+ assertThat(mClockFrame.getAlpha()).isEqualTo(1);
+ assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE);
+ // only big clock is removed at switch
+ assertThat(mLargeClockFrame.getParent()).isNull();
+ assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
+ }
+
+ @Test
+ public void switchingToBigClock_returnsTrueOnlyWhenItWasNotVisibleBefore() {
+ assertThat(mKeyguardClockSwitch.switchToClock(LARGE)).isTrue();
+ assertThat(mKeyguardClockSwitch.switchToClock(LARGE)).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index bb71bed84f43..8e1e42a9c9a2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.statusbar.policy.DevicePostureController
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -62,6 +63,8 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
private lateinit var mKeyguardMessageAreaController: KeyguardMessageAreaController
@Mock
private lateinit var mLockPatternView: LockPatternView
+ @Mock
+ private lateinit var mPostureController: DevicePostureController
private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
@@ -78,7 +81,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
mKeyguardPatternViewController = KeyguardPatternViewController(mKeyguardPatternView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mLatencyTracker, mFalsingCollector, mEmergencyButtonController,
- mKeyguardMessageAreaControllerFactory)
+ mKeyguardMessageAreaControllerFactory, mPostureController)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index ca857c5dd05b..64bdc2e9dc5d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -19,11 +19,13 @@ package com.android.keyguard;
import static android.view.WindowInsets.Type.ime;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -33,6 +35,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.MotionEvent;
import android.view.WindowInsetsController;
import androidx.test.filters.SmallTest;
@@ -43,6 +46,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -58,6 +62,7 @@ import org.mockito.junit.MockitoRule;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper()
public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
+ private static final int VIEW_WIDTH = 1600;
@Rule
public MockitoRule mRule = MockitoJUnit.rule();
@@ -100,6 +105,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
private EmergencyButtonController mEmergencyButtonController;
@Mock
private Resources mResources;
+ @Mock
+ private FalsingCollector mFalsingCollector;
private Configuration mConfiguration;
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
@@ -112,7 +119,9 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
mConfiguration.setToDefaults(); // Defaults to ORIENTATION_UNDEFINED.
when(mResources.getConfiguration()).thenReturn(mConfiguration);
+ when(mView.getContext()).thenReturn(mContext);
when(mView.getResources()).thenReturn(mResources);
+ when(mView.getWidth()).thenReturn(VIEW_WIDTH);
when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
.thenReturn(mAdminSecondaryLockScreenController);
when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
@@ -131,7 +140,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
mKeyguardStateController, mKeyguardSecurityViewFlipperController,
- mConfigurationController)
+ mConfigurationController, mFalsingCollector)
.create(mSecurityCallback);
}
@@ -169,18 +178,156 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
public void onResourcesUpdate_callsThroughOnRotationChange() {
// Rotation is the same, shouldn't cause an update
mKeyguardSecurityContainerController.updateResources();
- verify(mView, times(0)).updateLayoutForSecurityMode(any());
+ verify(mView, times(0)).setOneHandedMode(anyBoolean());
// Update rotation. Should trigger update
mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
mKeyguardSecurityContainerController.updateResources();
- verify(mView, times(1)).updateLayoutForSecurityMode(any());
+ verify(mView, times(1)).setOneHandedMode(anyBoolean());
}
@Test
- public void updateKeyguardPosition_callsThroughToView() {
+ public void updateKeyguardPosition_callsThroughToViewInOneHandedMode() {
+ when(mView.isOneHandedMode()).thenReturn(true);
+ mKeyguardSecurityContainerController.updateKeyguardPosition(VIEW_WIDTH / 3f);
+ verify(mView).setOneHandedModeLeftAligned(true, false);
+
+ mKeyguardSecurityContainerController.updateKeyguardPosition((VIEW_WIDTH / 3f) * 2);
+ verify(mView).setOneHandedModeLeftAligned(false, false);
+ }
+
+ @Test
+ public void updateKeyguardPosition_ignoredInTwoHandedMode() {
+ when(mView.isOneHandedMode()).thenReturn(false);
mKeyguardSecurityContainerController.updateKeyguardPosition(1.0f);
- verify(mView).updateKeyguardPosition(1.0f);
+ verify(mView, never()).setOneHandedModeLeftAligned(anyBoolean(), anyBoolean());
+ }
+
+ private void touchDownLeftSide() {
+ mKeyguardSecurityContainerController.mGlobalTouchListener.onTouchEvent(
+ MotionEvent.obtain(
+ /* downTime= */0,
+ /* eventTime= */0,
+ MotionEvent.ACTION_DOWN,
+ /* x= */VIEW_WIDTH / 3f,
+ /* y= */0,
+ /* metaState= */0));
+ }
+
+ private void touchDownRightSide() {
+ mKeyguardSecurityContainerController.mGlobalTouchListener.onTouchEvent(
+ MotionEvent.obtain(
+ /* downTime= */0,
+ /* eventTime= */0,
+ MotionEvent.ACTION_DOWN,
+ /* x= */(VIEW_WIDTH / 3f) * 2,
+ /* y= */0,
+ /* metaState= */0));
+ }
+
+ @Test
+ public void onInterceptTap_inhibitsFalsingInOneHandedMode() {
+ when(mView.isOneHandedMode()).thenReturn(true);
+ when(mView.isOneHandedModeLeftAligned()).thenReturn(true);
+
+ touchDownLeftSide();
+ verify(mFalsingCollector, never()).avoidGesture();
+
+ // Now on the right.
+ touchDownRightSide();
+ verify(mFalsingCollector).avoidGesture();
+
+ // Move and re-test
+ reset(mFalsingCollector);
+ when(mView.isOneHandedModeLeftAligned()).thenReturn(false);
+
+ // On the right...
+ touchDownRightSide();
+ verify(mFalsingCollector, never()).avoidGesture();
+
+ touchDownLeftSide();
+ verify(mFalsingCollector).avoidGesture();
+ }
+
+ @Test
+ public void showSecurityScreen_oneHandedMode_bothFlagsDisabled_noOneHandedMode() {
+ setUpKeyguardFlags(
+ /* deviceConfigCanUseOneHandedKeyguard= */false,
+ /* sysuiResourceCanUseOneHandedKeyguard= */false);
+
+ when(mKeyguardSecurityViewFlipperController.getSecurityView(
+ eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class)))
+ .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
+
+ mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
+ verify(mView).setOneHandedMode(false);
+ }
+
+ @Test
+ public void showSecurityScreen_oneHandedMode_deviceFlagDisabled_noOneHandedMode() {
+ setUpKeyguardFlags(
+ /* deviceConfigCanUseOneHandedKeyguard= */false,
+ /* sysuiResourceCanUseOneHandedKeyguard= */true);
+
+ when(mKeyguardSecurityViewFlipperController.getSecurityView(
+ eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class)))
+ .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
+
+ mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
+ verify(mView).setOneHandedMode(false);
+ }
+
+ @Test
+ public void showSecurityScreen_oneHandedMode_sysUiFlagDisabled_noOneHandedMode() {
+ setUpKeyguardFlags(
+ /* deviceConfigCanUseOneHandedKeyguard= */true,
+ /* sysuiResourceCanUseOneHandedKeyguard= */false);
+
+ when(mKeyguardSecurityViewFlipperController.getSecurityView(
+ eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class)))
+ .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
+
+ mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
+ verify(mView).setOneHandedMode(false);
+ }
+
+ @Test
+ public void showSecurityScreen_oneHandedMode_bothFlagsEnabled_oneHandedMode() {
+ setUpKeyguardFlags(
+ /* deviceConfigCanUseOneHandedKeyguard= */true,
+ /* sysuiResourceCanUseOneHandedKeyguard= */true);
+
+ when(mKeyguardSecurityViewFlipperController.getSecurityView(
+ eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class)))
+ .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
+
+ mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
+ verify(mView).setOneHandedMode(true);
+ }
+
+ @Test
+ public void showSecurityScreen_twoHandedMode_bothFlagsEnabled_noOneHandedMode() {
+ setUpKeyguardFlags(
+ /* deviceConfigCanUseOneHandedKeyguard= */true,
+ /* sysuiResourceCanUseOneHandedKeyguard= */true);
+
+ when(mKeyguardSecurityViewFlipperController.getSecurityView(
+ eq(SecurityMode.Password), any(KeyguardSecurityCallback.class)))
+ .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
+
+ mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
+ verify(mView).setOneHandedMode(false);
+ }
+
+ private void setUpKeyguardFlags(
+ boolean deviceConfigCanUseOneHandedKeyguard,
+ boolean sysuiResourceCanUseOneHandedKeyguard) {
+ when(mResources.getBoolean(
+ com.android.internal.R.bool.config_enableDynamicKeyguardPositioning))
+ .thenReturn(deviceConfigCanUseOneHandedKeyguard);
+ when(mResources.getBoolean(
+ R.bool.can_use_one_handed_bouncer))
+ .thenReturn(sysuiResourceCanUseOneHandedKeyguard);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index f5916e748f04..2efd3697f633 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -28,7 +28,6 @@ import static org.mockito.Mockito.when;
import android.graphics.Insets;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.testing.TestableResources;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -37,8 +36,6 @@ import android.widget.FrameLayout;
import androidx.test.filters.SmallTest;
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
@@ -57,11 +54,6 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
private static final int FAKE_MEASURE_SPEC =
View.MeasureSpec.makeMeasureSpec(SCREEN_WIDTH, View.MeasureSpec.EXACTLY);
- private static final SecurityMode ONE_HANDED_SECURITY_MODE = SecurityMode.PIN;
- private static final SecurityMode TWO_HANDED_SECURITY_MODE = SecurityMode.Password;
-
-
-
@Rule
public MockitoRule mRule = MockitoJUnit.rule();
@@ -90,45 +82,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
}
@Test
- public void onMeasure_usesFullWidthWithoutOneHandedMode() {
- setUpKeyguard(
- /* deviceConfigCanUseOneHandedKeyguard= */false,
- /* sysuiResourceCanUseOneHandedKeyguard= */ false,
- ONE_HANDED_SECURITY_MODE);
-
- mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
-
- verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
- }
-
- @Test
- public void onMeasure_usesFullWidthWithDeviceFlagDisabled() {
- setUpKeyguard(
- /* deviceConfigCanUseOneHandedKeyguard= */false,
- /* sysuiResourceCanUseOneHandedKeyguard= */ true,
- ONE_HANDED_SECURITY_MODE);
-
- mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
- verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
- }
-
- @Test
- public void onMeasure_usesFullWidthWithSysUIFlagDisabled() {
- setUpKeyguard(
- /* deviceConfigCanUseOneHandedKeyguard= */true,
- /* sysuiResourceCanUseOneHandedKeyguard= */ false,
- ONE_HANDED_SECURITY_MODE);
-
- mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
- verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
- }
-
- @Test
- public void onMeasure_usesHalfWidthWithFlagsEnabled() {
- setUpKeyguard(
- /* deviceConfigCanUseOneHandedKeyguard= */true,
- /* sysuiResourceCanUseOneHandedKeyguard= */ true,
- ONE_HANDED_SECURITY_MODE);
+ public void onMeasure_usesHalfWidthWithOneHandedModeEnabled() {
+ mKeyguardSecurityContainer.setOneHandedMode(/* oneHandedMode= */true);
int halfWidthMeasureSpec =
View.MeasureSpec.makeMeasureSpec(SCREEN_WIDTH / 2, View.MeasureSpec.EXACTLY);
@@ -138,11 +93,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
}
@Test
- public void onMeasure_usesFullWidthForFullScreenIme() {
- setUpKeyguard(
- /* deviceConfigCanUseOneHandedKeyguard= */true,
- /* sysuiResourceCanUseOneHandedKeyguard= */ true,
- TWO_HANDED_SECURITY_MODE);
+ public void onMeasure_usesFullWidthWithOneHandedModeDisabled() {
+ mKeyguardSecurityContainer.setOneHandedMode(/* oneHandedMode= */false);
mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
@@ -153,10 +105,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
int imeInsetAmount = 100;
int systemBarInsetAmount = 10;
- setUpKeyguard(
- /* deviceConfigCanUseOneHandedKeyguard= */false,
- /* sysuiResourceCanUseOneHandedKeyguard= */ false,
- ONE_HANDED_SECURITY_MODE);
+ mKeyguardSecurityContainer.setOneHandedMode(/* oneHandedMode= */false);
Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -180,10 +129,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
int imeInsetAmount = 0;
int systemBarInsetAmount = 10;
- setUpKeyguard(
- /* deviceConfigCanUseOneHandedKeyguard= */false,
- /* sysuiResourceCanUseOneHandedKeyguard= */ false,
- ONE_HANDED_SECURITY_MODE);
+ mKeyguardSecurityContainer.setOneHandedMode(/* oneHandedMode= */false);
Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -201,56 +147,42 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, expectedHeightMeasureSpec);
}
- private void setupForUpdateKeyguardPosition(SecurityMode securityMode) {
- setUpKeyguard(
- /* deviceConfigCanUseOneHandedKeyguard= */true,
- /* sysuiResourceCanUseOneHandedKeyguard= */ true,
- securityMode);
+ private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
+ mKeyguardSecurityContainer.setOneHandedMode(oneHandedMode);
+ mKeyguardSecurityContainer.setOneHandedModeLeftAligned(true, false);
mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
mKeyguardSecurityContainer.layout(0, 0, SCREEN_WIDTH, SCREEN_WIDTH);
- // Start off left-aligned. This should happen anyway, but just do this to ensure
- // definitely move to the left.
- mKeyguardSecurityContainer.updateKeyguardPosition(0.0f);
-
// Clear any interactions with the mock so we know the interactions definitely come from the
// below testing.
reset(mSecurityViewFlipper);
}
@Test
- public void updateKeyguardPosition_movesKeyguard() {
- setupForUpdateKeyguardPosition(ONE_HANDED_SECURITY_MODE);
+ public void setIsLeftAligned_movesKeyguard() {
+ setupForUpdateKeyguardPosition(/* oneHandedMode= */ true);
- mKeyguardSecurityContainer.updateKeyguardPosition((SCREEN_WIDTH / 4f) * 3f);
- verify(mSecurityViewFlipper).setTranslationX(SCREEN_WIDTH / 2.0f);
+ mKeyguardSecurityContainer.setOneHandedModeLeftAligned(
+ /* leftAligned= */false, /* animate= */false);
+ verify(mSecurityViewFlipper).setTranslationX(
+ mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
- mKeyguardSecurityContainer.updateKeyguardPosition(0.0f);
+ mKeyguardSecurityContainer.setOneHandedModeLeftAligned(
+ /* leftAligned= */true, /* animate= */false);
verify(mSecurityViewFlipper).setTranslationX(0.0f);
}
@Test
- public void updateKeyguardPosition_doesntMoveTwoHandedKeyguard() {
- setupForUpdateKeyguardPosition(TWO_HANDED_SECURITY_MODE);
+ public void setIsLeftAligned_doesntMoveTwoHandedKeyguard() {
+ setupForUpdateKeyguardPosition(/* oneHandedMode= */ false);
- mKeyguardSecurityContainer.updateKeyguardPosition((SCREEN_WIDTH / 4f) * 3f);
+ mKeyguardSecurityContainer.setOneHandedModeLeftAligned(
+ /* leftAligned= */false, /* animate= */false);
verify(mSecurityViewFlipper, never()).setTranslationX(anyInt());
- mKeyguardSecurityContainer.updateKeyguardPosition(0.0f);
+ mKeyguardSecurityContainer.setOneHandedModeLeftAligned(
+ /* leftAligned= */true, /* animate= */false);
verify(mSecurityViewFlipper, never()).setTranslationX(anyInt());
}
-
- private void setUpKeyguard(
- boolean deviceConfigCanUseOneHandedKeyguard,
- boolean sysuiResourceCanUseOneHandedKeyguard,
- SecurityMode securityMode) {
- TestableResources testableResources = mContext.getOrCreateTestableResources();
- testableResources.addOverride(
- com.android.internal.R.bool.config_enableDynamicKeyguardPositioning,
- deviceConfigCanUseOneHandedKeyguard);
- testableResources.addOverride(R.bool.can_use_one_handed_bouncer,
- sysuiResourceCanUseOneHandedKeyguard);
- mKeyguardSecurityContainer.updateLayoutForSecurityMode(securityMode);
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index 1ab08c27088a..77302ce30f09 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -54,7 +54,7 @@ public class KeyguardSliceViewTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
mKeyguardSliceView = (KeyguardSliceView) layoutInflater
- .inflate(R.layout.keyguard_status_area, null);
+ .inflate(R.layout.keyguard_slice_view, null);
mSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);
SliceProvider.setSpecs(new HashSet<>(Collections.singletonList(SliceSpecs.LIST)));
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index ec4dfba87af0..e53b450a895e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -64,7 +64,6 @@ import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.Vibrator;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -78,7 +77,9 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
@@ -86,8 +87,8 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
@@ -171,7 +172,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Mock
private FeatureFlags mFeatureFlags;
@Mock
- private Vibrator mVibrator;
+ private InteractionJankMonitor mInteractionJankMonitor;
+ @Mock
+ private LatencyTracker mLatencyTracker;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
// Direct executor
@@ -239,8 +242,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
- when(mFeatureFlags.isKeyguardLayoutEnabled()).thenReturn(false);
-
mMockitoSession = ExtendedMockito.mockitoSession()
.spyStatic(SubscriptionManager.class).startMocking();
ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
@@ -737,6 +738,17 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
+ public void testMultiUserJankMonitor_whenUserSwitches() throws Exception {
+ final IRemoteCallback reply = new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) {} // do nothing
+ };
+ mKeyguardUpdateMonitor.handleUserSwitchComplete(10 /* user */);
+ verify(mInteractionJankMonitor).end(InteractionJankMonitor.CUJ_USER_SWITCH);
+ verify(mLatencyTracker).onActionEnd(LatencyTracker.ACTION_USER_SWITCH);
+ }
+
+ @Test
public void testGetUserCanSkipBouncer_whenTrust() {
int user = KeyguardUpdateMonitor.getCurrentUser();
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, user, 0 /* flags */);
@@ -1051,7 +1063,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mRingerModeTracker, mBackgroundExecutor,
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager, mFeatureFlags,
- mVibrator);
+ mInteractionJankMonitor, mLatencyTracker);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
index 35fe1ba8f805..ff4412e97c04 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
@@ -17,7 +17,6 @@ package com.android.keyguard.clock;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -40,7 +39,6 @@ import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.settings.CurrentUserObservable;
import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.util.InjectionInflationController;
import org.junit.After;
import org.junit.Before;
@@ -69,7 +67,6 @@ public final class ClockManagerTest extends SysuiTestCase {
private ContentObserver mContentObserver;
private DockManagerFake mFakeDockManager;
private MutableLiveData<Integer> mCurrentUser;
- @Mock InjectionInflationController mMockInjectionInflationController;
@Mock PluginManager mMockPluginManager;
@Mock SysuiColorExtractor mMockColorExtractor;
@Mock ContentResolver mMockContentResolver;
@@ -83,7 +80,6 @@ public final class ClockManagerTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
LayoutInflater inflater = LayoutInflater.from(getContext());
- when(mMockInjectionInflationController.injectable(any())).thenReturn(inflater);
mFakeDockManager = new DockManagerFake();
@@ -91,7 +87,7 @@ public final class ClockManagerTest extends SysuiTestCase {
mCurrentUser.setValue(MAIN_USER_ID);
when(mMockCurrentUserObserable.getCurrentUser()).thenReturn(mCurrentUser);
- mClockManager = new ClockManager(getContext(), mMockInjectionInflationController,
+ mClockManager = new ClockManager(getContext(), inflater,
mMockPluginManager, mMockColorExtractor, mMockContentResolver,
mMockCurrentUserObserable, mMockSettingsWrapper, mFakeDockManager);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/SmallClockPositionTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/clock/SmallClockPositionTest.kt
index 456f32b4bd40..3a27e356ed84 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/SmallClockPositionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/SmallClockPositionTest.kt
@@ -43,7 +43,7 @@ class SmallClockPositionTest : SysuiTestCase() {
@Test
fun loadResources() {
// Cover constructor taking Resources object.
- position = SmallClockPosition(context.resources)
+ position = SmallClockPosition(context)
position.setDarkAmount(1f)
assertThat(position.preferredY).isGreaterThan(0)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index ed5cbe20aa11..c6df1c15e0b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -17,6 +17,7 @@ package com.android.systemui;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM;
import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
+import static android.view.DisplayCutout.BOUNDS_POSITION_LENGTH;
import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
@@ -27,11 +28,13 @@ import static com.google.common.truth.Truth.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -43,6 +46,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.Configuration;
+import android.content.res.TypedArray;
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
@@ -52,6 +56,7 @@ import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import android.util.RotationUtils;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.View;
@@ -60,7 +65,6 @@ import android.view.WindowMetrics;
import androidx.test.filters.SmallTest;
-import com.android.systemui.R.dimen;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.events.PrivacyDotViewController;
@@ -100,6 +104,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
private UserTracker mUserTracker;
@Mock
private PrivacyDotViewController mDotViewController;
+ @Mock
+ private TypedArray mMockTypedArray;
@Before
public void setup() {
@@ -121,6 +127,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
.getDisplay(DEFAULT_DISPLAY);
when(mDisplayManager.getDisplay(anyInt())).thenReturn(display);
mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
+ when(mMockTypedArray.length()).thenReturn(0);
mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController,
@@ -146,41 +153,155 @@ public class ScreenDecorationsTest extends SysuiTestCase {
reset(mTunerService);
}
+
+ private void verifyRoundedCornerViewsVisibility(
+ @DisplayCutout.BoundsPosition final int overlayId,
+ @View.Visibility final int visibility) {
+ final View overlay = mScreenDecorations.mOverlays[overlayId];
+ final View left = overlay.findViewById(R.id.left);
+ final View right = overlay.findViewById(R.id.right);
+ assertNotNull(left);
+ assertNotNull(right);
+ assertThat(left.getVisibility()).isEqualTo(visibility);
+ assertThat(right.getVisibility()).isEqualTo(visibility);
+ }
+
+ private void verifyTopDotViewsNullable(final boolean isAssertNull) {
+ if (isAssertNull) {
+ assertNull(mScreenDecorations.mTopLeftDot);
+ assertNull(mScreenDecorations.mTopRightDot);
+ } else {
+ assertNotNull(mScreenDecorations.mTopLeftDot);
+ assertNotNull(mScreenDecorations.mTopRightDot);
+ }
+ }
+
+ private void verifyBottomDotViewsNullable(final boolean isAssertNull) {
+ if (isAssertNull) {
+ assertNull(mScreenDecorations.mBottomLeftDot);
+ assertNull(mScreenDecorations.mBottomRightDot);
+ } else {
+ assertNotNull(mScreenDecorations.mBottomLeftDot);
+ assertNotNull(mScreenDecorations.mBottomRightDot);
+ }
+ }
+
+ private void verifyDotViewsNullable(final boolean isAssertNull) {
+ verifyTopDotViewsNullable(isAssertNull);
+ verifyBottomDotViewsNullable(isAssertNull);
+ }
+
+ private void verifyTopDotViewsVisibility(@View.Visibility final int visibility) {
+ verifyTopDotViewsNullable(false);
+ assertThat(mScreenDecorations.mTopLeftDot.getVisibility()).isEqualTo(visibility);
+ assertThat(mScreenDecorations.mTopRightDot.getVisibility()).isEqualTo(visibility);
+ }
+
+ private void verifyBottomDotViewsVisibility(@View.Visibility final int visibility) {
+ verifyBottomDotViewsNullable(false);
+ assertThat(mScreenDecorations.mBottomLeftDot.getVisibility()).isEqualTo(visibility);
+ assertThat(mScreenDecorations.mBottomRightDot.getVisibility()).isEqualTo(visibility);
+ }
+
+ private void verifyDotViewsVisibility(@View.Visibility final int visibility) {
+ verifyTopDotViewsVisibility(visibility);
+ verifyBottomDotViewsVisibility(visibility);
+ }
+
+ private void verifyOverlaysExistAndAdded(final boolean left, final boolean top,
+ final boolean right, final boolean bottom) {
+ if (left || top || right || bottom) {
+ assertNotNull(mScreenDecorations.mOverlays);
+ } else {
+ verify(mWindowManager, never()).addView(any(), any());
+ return;
+ }
+
+ if (left) {
+ assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
+ verify(mWindowManager, times(1))
+ .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]), any());
+ } else {
+ assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
+ }
+
+ if (top) {
+ assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]);
+ verify(mWindowManager, times(1))
+ .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]), any());
+ } else {
+ assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]);
+ }
+
+ if (right) {
+ assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
+ verify(mWindowManager, times(1))
+ .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]), any());
+ } else {
+ assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
+ }
+
+ if (bottom) {
+ assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
+ verify(mWindowManager, times(1))
+ .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]), any());
+ } else {
+ assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
+ }
+ }
+
@Test
- public void testNoRounding_NoCutout() {
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, 0);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_top, 0);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
- mContext.getOrCreateTestableResources()
- .addOverride(dimen.rounded_corner_content_padding, 0);
- mContext.getOrCreateTestableResources()
- .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
+ public void testNoRounding_NoCutout_NoPrivacyDot() {
+ setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ false /* fillCutout */, false /* privacyDot */);
// no cutout
doReturn(null).when(mScreenDecorations).getCutout();
mScreenDecorations.start();
// No views added.
- verify(mWindowManager, never()).addView(any(), any());
+ verifyOverlaysExistAndAdded(false, false, false, false);
// No Tuners tuned.
verify(mTunerService, never()).addTunable(any(), any());
+ // No dot controller init
+ verify(mDotViewController, never()).initialize(any(), any(), any(), any());
}
@Test
- public void testRounding_NoCutout() {
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, 20);
- mContext.getOrCreateTestableResources()
- .addOverride(dimen.rounded_corner_content_padding, 20);
- mContext.getOrCreateTestableResources()
- .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
+ public void testNoRounding_NoCutout_PrivacyDot() {
+ setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ false /* fillCutout */, true /* privacyDot */);
+
+ // no cutout
+ doReturn(null).when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+
+ // Top and bottom windows are created for privacy dot.
+ // Left and right window should be null.
+ verifyOverlaysExistAndAdded(false, true, false, true);
+
+ // Rounded corner views shall not exist
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE);
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE);
+
+ // Privacy dots shall exist but invisible
+ verifyDotViewsVisibility(View.INVISIBLE);
+
+ // One tunable.
+ verify(mTunerService, times(1)).addTunable(any(), any());
+ // Dot controller init
+ verify(mDotViewController, times(1)).initialize(
+ isA(View.class), isA(View.class), isA(View.class), isA(View.class));
+ }
+
+ @Test
+ public void testRounding_NoCutout_NoPrivacyDot() {
+ setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 20 /* roundedPadding */, false /* multipleRadius */,
+ false /* fillCutout */, false /* privacyDot */);
// no cutout
doReturn(null).when(mScreenDecorations).getCutout();
@@ -188,34 +309,57 @@ public class ScreenDecorationsTest extends SysuiTestCase {
mScreenDecorations.start();
// Top and bottom windows are created for rounded corners.
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]), any());
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]), any());
+ // Left and right window should be null.
+ verifyOverlaysExistAndAdded(false, true, false, true);
+
+ // Rounded corner views shall exist
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+
+ // Privacy dots shall not exist
+ verifyDotViewsNullable(true);
+
+ // One tunable.
+ verify(mTunerService, times(1)).addTunable(any(), any());
+ // No dot controller init
+ verify(mDotViewController, never()).initialize(any(), any(), any(), any());
+ }
+
+ @Test
+ public void testRounding_NoCutout_PrivacyDot() {
+ setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 20 /* roundedPadding */, false /* multipleRadius */,
+ false /* fillCutout */, true /* privacyDot */);
+
+ // no cutout
+ doReturn(null).when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+ // Top and bottom windows are created for rounded corners.
// Left and right window should be null.
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
+ verifyOverlaysExistAndAdded(false, true, false, true);
+
+ // Rounded corner views shall exist
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+
+ // Privacy dots shall exist but invisible
+ verifyDotViewsVisibility(View.INVISIBLE);
// One tunable.
verify(mTunerService, times(1)).addTunable(any(), any());
+ // Dot controller init
+ verify(mDotViewController, times(1)).initialize(
+ isA(View.class), isA(View.class), isA(View.class), isA(View.class));
}
@Test
public void testRoundingRadius_NoCutout() {
- final int testRadius = 1;
final Point testRadiusPoint = new Point(1, 1);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, testRadius);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_top, testRadius);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_bottom, testRadius);
- mContext.getOrCreateTestableResources()
- .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
-
+ setupResources(1 /* radius */, 1 /* radiusTop */, 1 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ false /* fillCutout */, true /* privacyDot */);
// no cutout
doReturn(null).when(mScreenDecorations).getCutout();
@@ -230,16 +374,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
public void testRoundingTopBottomRadius_OnTopBottomOverlay() {
final int testTopRadius = 1;
final int testBottomRadius = 5;
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, testTopRadius);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_top, testTopRadius);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_bottom, testBottomRadius);
- mContext.getOrCreateTestableResources()
- .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
+ setupResources(testTopRadius, testTopRadius, testBottomRadius, 0 /* roundedPadding */,
+ false /* multipleRadius */, false /* fillCutout */, true /* privacyDot */);
// no cutout
doReturn(null).when(mScreenDecorations).getCutout();
@@ -267,75 +403,104 @@ public class ScreenDecorationsTest extends SysuiTestCase {
public void testRoundingTopBottomRadius_OnLeftRightOverlay() {
final int testTopRadius = 1;
final int testBottomRadius = 5;
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, testTopRadius);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_top, testTopRadius);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_bottom, testBottomRadius);
- mContext.getOrCreateTestableResources()
- .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
+ setupResources(testTopRadius, testTopRadius, testBottomRadius, 0 /* roundedPadding */,
+ false /* multipleRadius */, false /* fillCutout */, true /* privacyDot */);
// left cutout
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- new Rect(0, 200, 1, 210),
- ZERO_RECT,
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.start();
+ final Point topRadius = new Point(testTopRadius, testTopRadius);
+ final Point bottomRadius = new Point(testBottomRadius, testBottomRadius);
View leftRoundedCorner =
mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].findViewById(R.id.left);
+ boolean isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_LEFT, R.id.left);
+ verify(mScreenDecorations, atLeastOnce())
+ .setSize(leftRoundedCorner, isTop ? topRadius : bottomRadius);
+
View rightRoundedCorner =
mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].findViewById(R.id.right);
+ isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_LEFT, R.id.right);
verify(mScreenDecorations, atLeastOnce())
- .setSize(leftRoundedCorner, new Point(testTopRadius, testTopRadius));
- verify(mScreenDecorations, atLeastOnce())
- .setSize(rightRoundedCorner, new Point(testBottomRadius, testBottomRadius));
+ .setSize(rightRoundedCorner, isTop ? topRadius : bottomRadius);
+
leftRoundedCorner =
mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].findViewById(R.id.left);
+ isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_RIGHT, R.id.left);
+ verify(mScreenDecorations, atLeastOnce())
+ .setSize(leftRoundedCorner, isTop ? topRadius : bottomRadius);
+
rightRoundedCorner =
mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].findViewById(R.id.right);
+ isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_RIGHT, R.id.right);
verify(mScreenDecorations, atLeastOnce())
- .setSize(leftRoundedCorner, new Point(testTopRadius, testTopRadius));
- verify(mScreenDecorations, atLeastOnce())
- .setSize(rightRoundedCorner, new Point(testBottomRadius, testBottomRadius));
+ .setSize(rightRoundedCorner, isTop ? topRadius : bottomRadius);
}
@Test
- public void testRoundingMultipleRadius_NoCutout() {
+ public void testRoundingMultipleRadius_NoCutout_NoPrivacyDot() {
final VectorDrawable d = (VectorDrawable) mContext.getDrawable(R.drawable.rounded);
final Point multipleRadiusSize = new Point(d.getIntrinsicWidth(), d.getIntrinsicHeight());
-
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, 9999);
- mContext.getOrCreateTestableResources()
- .addOverride(dimen.rounded_corner_content_padding, 9999);
- mContext.getOrCreateTestableResources()
- .addOverride(R.bool.config_roundedCornerMultipleRadius, true);
+ setupResources(9999 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 9999 /* roundedPadding */, true /* multipleRadius */,
+ false /* fillCutout */, false /* privacyDot */);
// no cutout
doReturn(null).when(mScreenDecorations).getCutout();
mScreenDecorations.start();
// Top and bottom windows are created for rounded corners.
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]), any());
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]), any());
+ // Left and right window should be null.
+ verifyOverlaysExistAndAdded(false, true, false, true);
+
+ // Rounded corner views shall exist
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+ // Privacy dots shall not exist
+ verifyDotViewsNullable(true);
+
+ // One tunable.
+ verify(mTunerService, times(1)).addTunable(any(), any());
+ // No dot controller init
+ verify(mDotViewController, never()).initialize(any(), any(), any(), any());
+
+ // Size of corner view should exactly match max(width, height) of R.drawable.rounded
+ assertThat(mScreenDecorations.mRoundedDefault).isEqualTo(multipleRadiusSize);
+ assertThat(mScreenDecorations.mRoundedDefaultTop).isEqualTo(multipleRadiusSize);
+ assertThat(mScreenDecorations.mRoundedDefaultBottom).isEqualTo(multipleRadiusSize);
+ }
+
+ @Test
+ public void testRoundingMultipleRadius_NoCutout_PrivacyDot() {
+ final VectorDrawable d = (VectorDrawable) mContext.getDrawable(R.drawable.rounded);
+ final Point multipleRadiusSize = new Point(d.getIntrinsicWidth(), d.getIntrinsicHeight());
+ setupResources(9999 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 9999 /* roundedPadding */, true /* multipleRadius */,
+ false /* fillCutout */, true /* privacyDot */);
+
+ // no cutout
+ doReturn(null).when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+ // Top and bottom windows are created for rounded corners.
// Left and right window should be null.
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
+ verifyOverlaysExistAndAdded(false, true, false, true);
+
+ // Rounded corner views shall exist
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+
+ // Privacy dots shall exist but invisible
+ verifyDotViewsVisibility(View.INVISIBLE);
// One tunable.
verify(mTunerService, times(1)).addTunable(any(), any());
+ // Dot controller init
+ verify(mDotViewController, times(1)).initialize(
+ isA(View.class), isA(View.class), isA(View.class), isA(View.class));
// Size of corner view should exactly match max(width, height) of R.drawable.rounded
assertThat(mScreenDecorations.mRoundedDefault).isEqualTo(multipleRadiusSize);
@@ -344,263 +509,341 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
@Test
- public void testNoRounding_CutoutShortEdge() {
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, true);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, 0);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_top, 0);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
- mContext.getOrCreateTestableResources()
- .addOverride(dimen.rounded_corner_content_padding, 0);
- mContext.getOrCreateTestableResources()
- .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
+ public void testNoRounding_CutoutShortEdge_NoPrivacyDot() {
+ setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, false /* privacyDot */);
// top cutout
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- ZERO_RECT,
- new Rect(9, 0, 10, 1),
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.start();
// Top window is created for top cutout.
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]), any());
- // Bottom window should be null.
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
- // Left window should be null.
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
- // Right window should be null.
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
+ // Bottom, left, or right window should be null.
+ verifyOverlaysExistAndAdded(false, true, false, false);
+
+ // Privacy dots shall not exist because of no privacy
+ verifyDotViewsNullable(true);
+
+ // No dot controller init
+ verify(mDotViewController, never()).initialize(any(), any(), any(), any());
}
@Test
- public void testNoRounding_CutoutLongEdge() {
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, true);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, 0);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_top, 0);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
- mContext.getOrCreateTestableResources()
- .addOverride(dimen.rounded_corner_content_padding, 0);
- mContext.getOrCreateTestableResources()
- .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
+ public void testNoRounding_CutoutShortEdge_PrivacyDot() {
+ setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, true /* privacyDot */);
+
+ // top cutout
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+ // Top window is created for top cutout.
+ // Bottom window is created for privacy dot.
+ // Left or right window should be null.
+ verifyOverlaysExistAndAdded(false, true, false, true);
+
+ // Top rounded corner views shall exist because of cutout
+ // but be gone because of no rounded corner
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE);
+ // Bottom rounded corner views shall exist because of privacy dot
+ // but be gone because of no rounded corner
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE);
+
+ // Privacy dots shall exist but invisible
+ verifyDotViewsVisibility(View.INVISIBLE);
+
+ // Dot controller init
+ verify(mDotViewController, times(1)).initialize(
+ isA(View.class), isA(View.class), isA(View.class), isA(View.class));
+ }
+
+ @Test
+ public void testNoRounding_CutoutLongEdge_NoPrivacyDot() {
+ setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, false /* privacyDot */);
// left cutout
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- new Rect(0, 200, 1, 210),
- ZERO_RECT,
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.start();
// Left window is created for left cutout.
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]), any());
- // Bottom window should be null.
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
- // Top window should be null.
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]);
- // Right window should be null.
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
+ // Bottom, top, or right window should be null.
+ verifyOverlaysExistAndAdded(true, false, false, false);
+
+ // Left rounded corner views shall exist because of cutout
+ // but be gone because of no rounded corner
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_LEFT, View.GONE);
+
+ // Top privacy dots shall not exist because of no privacy
+ verifyDotViewsNullable(true);
+
+ // No dot controller init
+ verify(mDotViewController, never()).initialize(any(), any(), any(), any());
}
@Test
- public void testRounding_CutoutShortEdge() {
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, true);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, 20);
- mContext.getOrCreateTestableResources()
- .addOverride(dimen.rounded_corner_content_padding, 20);
- mContext.getOrCreateTestableResources()
- .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
+ public void testNoRounding_CutoutLongEdge_PrivacyDot() {
+ setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, true /* privacyDot */);
+
+ // left cutout
+ final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+ // Left window is created for left cutout.
+ // Right window is created for privacy.
+ // Bottom, or top window should be null.
+ verifyOverlaysExistAndAdded(true, false, true, false);
+
+ // Privacy dots shall exist but invisible
+ verifyDotViewsVisibility(View.INVISIBLE);
+
+ // Dot controller init
+ verify(mDotViewController, times(1)).initialize(
+ isA(View.class), isA(View.class), isA(View.class), isA(View.class));
+ }
+
+ @Test
+ public void testRounding_CutoutShortEdge_NoPrivacyDot() {
+ setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 20 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, false /* privacyDot */);
// top cutout
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- ZERO_RECT,
- new Rect(9, 0, 10, 1),
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.start();
- // Top window is created for rouned corner and top cutout.
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]), any());
- // Bottom window is created for rouned corner.
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]), any());
- // Left window should be null.
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
- // Right window should be null.
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
+ // Top window is created for rounded corner and top cutout.
+ // Bottom window is created for rounded corner.
+ // Left, or right window should be null.
+ verifyOverlaysExistAndAdded(false, true, false, true);
+
+ // Rounded corner views shall exist
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+
+ // Top privacy dots shall not exist because of no privacy dot
+ verifyDotViewsNullable(true);
+
+ // No dot controller init
+ verify(mDotViewController, never()).initialize(any(), any(), any(), any());
}
@Test
- public void testRounding_CutoutLongEdge() {
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, true);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, 20);
- mContext.getOrCreateTestableResources()
- .addOverride(dimen.rounded_corner_content_padding, 20);
- mContext.getOrCreateTestableResources()
- .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
+ public void testRounding_CutoutShortEdge_PrivacyDot() {
+ setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 20 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, true /* privacyDot */);
+
+ // top cutout
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+ // Top window is created for rounded corner and top cutout.
+ // Bottom window is created for rounded corner.
+ // Left, or right window should be null.
+ verifyOverlaysExistAndAdded(false, true, false, true);
+
+ // Rounded corner views shall exist
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+
+ // Top privacy dots shall exist but invisible
+ verifyDotViewsVisibility(View.INVISIBLE);
+
+ // Dot controller init
+ verify(mDotViewController, times(1)).initialize(
+ isA(View.class), isA(View.class), isA(View.class), isA(View.class));
+ }
+
+ @Test
+ public void testRounding_CutoutLongEdge_NoPrivacyDot() {
+ setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 20 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, false /* privacyDot */);
// left cutout
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- new Rect(0, 200, 1, 210),
- ZERO_RECT,
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.start();
- // Left window is created for rouned corner and left cutout.
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]), any());
- // Right window is created for rouned corner.
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]), any());
- // Top window should be null.
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]);
- // Bottom window should be null.
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
+ // Left window is created for rounded corner and left cutout.
+ // Right window is created for rounded corner.
+ // Top, or bottom window should be null.
+ verifyOverlaysExistAndAdded(true, false, true, false);
}
@Test
- public void testRounding_CutoutShortAndLongEdge() {
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, true);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, 20);
- mContext.getOrCreateTestableResources()
- .addOverride(dimen.rounded_corner_content_padding, 20);
- mContext.getOrCreateTestableResources()
- .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
+ public void testRounding_CutoutLongEdge_PrivacyDot() {
+ setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 20 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, true /* privacyDot */);
+
+ // left cutout
+ final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+ // Left window is created for rounded corner, left cutout, and privacy.
+ // Right window is created for rounded corner and privacy dot.
+ // Top, or bottom window should be null.
+ verifyOverlaysExistAndAdded(true, false, true, false);
+ }
+
+ @Test
+ public void testRounding_CutoutShortAndLongEdge_NoPrivacyDot() {
+ setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 20 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, false /* privacyDot */);
// top and left cutout
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- new Rect(0, 200, 1, 210),
- new Rect(9, 0, 10, 1),
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] bounds = {new Rect(0, 50, 1, 60), new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(1, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.start();
- // Top window is created for rouned corner and top cutout.
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]), any());
- // Bottom window is created for rouned corner.
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]), any());
+ // Top window is created for rounded corner and top cutout.
+ // Bottom window is created for rounded corner.
// Left window is created for left cutout.
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]), any());
// Right window should be null.
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
+ verifyOverlaysExistAndAdded(true, true, false, true);
}
@Test
- public void testNoRounding_SwitchFrom_ShortEdgeCutout_To_LongCutout() {
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, true);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, 0);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_top, 0);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
- mContext.getOrCreateTestableResources()
- .addOverride(dimen.rounded_corner_content_padding, 0);
- mContext.getOrCreateTestableResources()
- .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
+ public void testRounding_CutoutShortAndLongEdge_PrivacyDot() {
+ setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 20 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, true /* privacyDot */);
+
+ // top and left cutout
+ final Rect[] bounds = {new Rect(0, 50, 1, 60), new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(1, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+ // Top window is created for rounded corner and top cutout.
+ // Bottom window is created for rounded corner.
+ // Left window is created for left cutout.
+ // Right window should be null.
+ verifyOverlaysExistAndAdded(true, true, false, true);
+ }
+
+ @Test
+ public void testNoRounding_SwitchFrom_ShortEdgeCutout_To_LongCutout_NoPrivacyDot() {
+ setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, false /* privacyDot */);
// Set to short edge cutout(top).
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- ZERO_RECT,
- new Rect(9, 0, 10, 1),
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.start();
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]), any());
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
+ verifyOverlaysExistAndAdded(false, true, false, false);
// Switch to long edge cutout(left).
- // left cutout
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- new Rect(0, 200, 1, 210),
- ZERO_RECT,
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] newBounds = {new Rect(0, 50, 1, 60), null, null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), newBounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.onConfigurationChanged(new Configuration());
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]), any());
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]);
+ verifyOverlaysExistAndAdded(true, false, false, false);
}
@Test
- public void testDelayedCutout() {
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, 0);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_top, 0);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
- mContext.getOrCreateTestableResources()
- .addOverride(dimen.rounded_corner_content_padding, 0);
- mContext.getOrCreateTestableResources()
- .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
+ public void testNoRounding_SwitchFrom_ShortEdgeCutout_To_LongCutout_PrivacyDot() {
+ setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ true /* fillCutout */, true /* privacyDot */);
+
+ // Set to short edge cutout(top).
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+ verifyOverlaysExistAndAdded(false, true, false, true);
+
+ // Switch to long edge cutout(left).
+ final Rect[] newBounds = {new Rect(0, 50, 1, 60), null, null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), newBounds))
+ .when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.onConfigurationChanged(new Configuration());
+ verifyOverlaysExistAndAdded(true, false, true, false);
+ }
+
+ @Test
+ public void testDelayedCutout_NoPrivacyDot() {
+ setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ false /* fillCutout */, false /* privacyDot */);
// top cutout
- doReturn(new DisplayCutout(
- Insets.of(0, 10, 0, 0),
- ZERO_RECT,
- new Rect(9, 0, 10, 1),
- ZERO_RECT,
- ZERO_RECT,
- Insets.NONE)).when(mScreenDecorations).getCutout();
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
mScreenDecorations.start();
assertNull(mScreenDecorations.mOverlays);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, true);
+ when(mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout))
+ .thenReturn(true);
mScreenDecorations.onConfigurationChanged(new Configuration());
// Only top windows should be added.
- verify(mWindowManager, times(1))
- .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]), any());
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]);
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
- assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
+ verifyOverlaysExistAndAdded(false, true, false, false);
+ }
+
+ @Test
+ public void testDelayedCutout_PrivacyDot() {
+ setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ false /* fillCutout */, true /* privacyDot */);
+
+ // top cutout
+ final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null};
+ doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds))
+ .when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+ // Both top and bottom windows should be added because of privacy dot,
+ // but their visibility shall be gone because of no rounding.
+ verifyOverlaysExistAndAdded(false, true, false, true);
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE);
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE);
+
+ when(mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout))
+ .thenReturn(true);
+ mScreenDecorations.onConfigurationChanged(new Configuration());
+
+ assertNotNull(mScreenDecorations.mOverlays);
+ // Both top and bottom windows should be added because of privacy dot,
+ // but their visibility shall be gone because of no rounding.
+ verifyOverlaysExistAndAdded(false, true, false, true);
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE);
+ verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE);
}
@Test
@@ -612,34 +855,24 @@ public class ScreenDecorationsTest extends SysuiTestCase {
@Test
public void testUpdateRoundedCorners() {
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, 20);
- mContext.getOrCreateTestableResources()
- .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
+ setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ false /* fillCutout */, true /* privacyDot */);
mScreenDecorations.start();
assertEquals(mScreenDecorations.mRoundedDefault, new Point(20, 20));
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, 5);
+ when(mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.rounded_corner_radius)).thenReturn(5);
mScreenDecorations.onConfigurationChanged(null);
assertEquals(mScreenDecorations.mRoundedDefault, new Point(5, 5));
}
@Test
public void testOnlyRoundedCornerRadiusTop() {
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, 0);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_top, 10);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
- mContext.getOrCreateTestableResources()
- .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
+ setupResources(0 /* radius */, 10 /* radiusTop */, 0 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ false /* fillCutout */, true /* privacyDot */);
mScreenDecorations.start();
assertEquals(new Point(0, 0), mScreenDecorations.mRoundedDefault);
@@ -649,16 +882,9 @@ public class ScreenDecorationsTest extends SysuiTestCase {
@Test
public void testOnlyRoundedCornerRadiusBottom() {
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius, 0);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_top, 0);
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.dimen.rounded_corner_radius_bottom, 20);
- mContext.getOrCreateTestableResources()
- .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
+ setupResources(0 /* radius */, 0 /* radiusTop */, 20 /* radiusBottom */,
+ 0 /* roundedPadding */, false /* multipleRadius */,
+ false /* fillCutout */, true /* privacyDot */);
mScreenDecorations.start();
assertEquals(new Point(0, 0), mScreenDecorations.mRoundedDefault);
@@ -666,7 +892,6 @@ public class ScreenDecorationsTest extends SysuiTestCase {
assertEquals(new Point(20, 20), mScreenDecorations.mRoundedDefaultBottom);
}
-
@Test
public void testBoundingRectsToRegion() throws Exception {
Rect rect = new Rect(1, 2, 3, 4);
@@ -721,4 +946,61 @@ public class ScreenDecorationsTest extends SysuiTestCase {
verify(mTunerService, times(1)).removeTunable(any());
assertThat(mScreenDecorations.mIsRegistered, is(false));
}
+
+ private void setupResources(int radius, int radiusTop, int radiusBottom, int roundedPadding,
+ boolean multipleRadius, boolean fillCutout, boolean privacyDot) {
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.array.config_displayUniqueIdArray,
+ new String[]{});
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.array.config_roundedCornerRadiusArray,
+ mMockTypedArray);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.array.config_roundedCornerTopRadiusArray,
+ mMockTypedArray);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.array.config_roundedCornerBottomRadiusArray,
+ mMockTypedArray);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.array.config_roundedCornerDrawableArray,
+ mMockTypedArray);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.array.config_roundedCornerTopDrawableArray,
+ mMockTypedArray);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.array.config_roundedCornerBottomDrawableArray,
+ mMockTypedArray);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.array.config_roundedCornerMultipleRadiusArray,
+ mMockTypedArray);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.dimen.rounded_corner_radius, radius);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.dimen.rounded_corner_radius_top, radiusTop);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.dimen.rounded_corner_radius_bottom, radiusBottom);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.dimen.rounded_corner_content_padding, roundedPadding);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.bool.config_roundedCornerMultipleRadius, multipleRadius);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, fillCutout);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.bool.config_enablePrivacyDot, privacyDot);
+ }
+
+ private DisplayCutout getDisplayCutoutForRotation(Insets safeInsets, Rect[] cutoutBounds) {
+ final int rotation = mContext.getDisplay().getRotation();
+ final Insets insets = RotationUtils.rotateInsets(safeInsets, rotation);
+ final Rect[] sorted = new Rect[BOUNDS_POSITION_LENGTH];
+ for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
+ final int rotatedPos = ScreenDecorations.getBoundPositionFromRotation(i, rotation);
+ if (cutoutBounds[i] != null) {
+ RotationUtils.rotateBounds(cutoutBounds[i], new Rect(0, 0, 100, 200), rotation);
+ }
+ sorted[rotatedPos] = cutoutBounds[i];
+ }
+ return new DisplayCutout(insets, sorted[BOUNDS_POSITION_LEFT], sorted[BOUNDS_POSITION_TOP],
+ sorted[BOUNDS_POSITION_RIGHT], sorted[BOUNDS_POSITION_BOTTOM]);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
index ee52c7804b69..0751475c2fb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
@@ -56,11 +56,6 @@ public class TestableDependency extends Dependency {
return mParent.createDependency(key);
}
- @Override
- protected boolean autoRegisterModulesForDump() {
- return false;
- }
-
public <T> boolean hasInstantiatedDependency(Class<T> key) {
return mObjs.containsKey(key) || mInstantiatedObjects.contains(key);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index 5617f1b6316b..1561b2028748 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -507,6 +507,30 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
expectedY, mWindowManager.getLayoutParamsFromAttachedView().y);
}
+ @Test
+ public void onScreenSizeChanged_buttonIsShowingOnTheRightSide_expectedPosition() {
+ final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ final Rect oldDraggableBounds = new Rect(mMagnificationModeSwitch.mDraggableWindowBounds);
+ final float windowHeightFraction =
+ (float) (mWindowManager.getLayoutParamsFromAttachedView().y
+ - oldDraggableBounds.top) / oldDraggableBounds.height();
+
+ // The window bounds and the draggable bounds are changed due to the screen size change.
+ final Rect tmpRect = new Rect(windowBounds);
+ tmpRect.scale(2);
+ final Rect newWindowBounds = new Rect(tmpRect);
+ mWindowManager.setWindowBounds(newWindowBounds);
+ mMagnificationModeSwitch.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+
+ final int expectedX = mMagnificationModeSwitch.mDraggableWindowBounds.right;
+ final int expectedY = (int) (windowHeightFraction
+ * mMagnificationModeSwitch.mDraggableWindowBounds.height())
+ + mMagnificationModeSwitch.mDraggableWindowBounds.top;
+ assertEquals(expectedX, mWindowManager.getLayoutParamsFromAttachedView().x);
+ assertEquals(expectedY, mWindowManager.getLayoutParamsFromAttachedView().y);
+ }
+
private void assertModeUnchanged(int expectedMode) {
final int actualMode = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
index 9621bedd9210..8bb9d423fa92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
@@ -16,9 +16,6 @@
package com.android.systemui.accessibility;
-import static android.view.WindowInsets.Type.systemGestures;
-
-import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
import android.view.Display;
@@ -79,14 +76,11 @@ public class TestableWindowManager implements WindowManager {
@Override
public WindowMetrics getCurrentWindowMetrics() {
- final Insets systemGesturesInsets = Insets.of(0, 0, 0, 10);
- final WindowInsets insets = new WindowInsets.Builder()
- .setInsets(systemGestures(), systemGesturesInsets)
- .build();
+ final WindowMetrics realMetrics = mWindowManager.getCurrentWindowMetrics();
final WindowMetrics windowMetrics = new WindowMetrics(
- mWindowBounds == null ? mWindowManager.getCurrentWindowMetrics().getBounds()
+ mWindowBounds == null ? realMetrics.getBounds()
: mWindowBounds,
- mWindowInsets == null ? insets : mWindowInsets);
+ mWindowInsets == null ? realMetrics.getWindowInsets() : mWindowInsets);
return windowMetrics;
}
@@ -106,10 +100,20 @@ public class TestableWindowManager implements WindowManager {
return (WindowManager.LayoutParams) mView.getLayoutParams();
}
+ /**
+ * Sets the given window bounds to current window metrics.
+ *
+ * @param bounds the window bounds
+ */
public void setWindowBounds(Rect bounds) {
mWindowBounds = bounds;
}
+ /**
+ * Sets the given window insets to the current window metics.
+ *
+ * @param insets the window insets.
+ */
public void setWindowInsets(WindowInsets insets) {
mWindowInsets = insets;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index f62069d35d35..3291a7c24867 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.accessibility;
import static android.view.Choreographer.FrameCallback;
+import static android.view.WindowInsets.Type.systemGestures;
import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
@@ -45,6 +46,8 @@ import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
+import android.graphics.Insets;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Handler;
import android.os.SystemClock;
@@ -55,6 +58,7 @@ import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -167,6 +171,29 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
}
@Test
+ public void enableWindowMagnification_LargeScreen_windowSizeIsConstrained() {
+ final int screenSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnification_max_frame_size) * 10;
+ mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
+ //We need to initialize new one because the window size is determined when initialization.
+ final WindowMagnificationController controller = new WindowMagnificationController(mContext,
+ mHandler, mSfVsyncFrameProvider,
+ mMirrorWindowControl, mTransaction, mWindowMagnifierCallback, mSysUiState);
+
+ mInstrumentation.runOnMainSync(() -> {
+ controller.enableWindowMagnification(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+
+ final int halfScreenSize = screenSize / 2;
+ WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
+ // The frame size should be the half of smaller value of window height/width unless it
+ //exceed the max frame size.
+ assertTrue(params.width < halfScreenSize);
+ assertTrue(params.height < halfScreenSize);
+ }
+
+ @Test
public void deleteWindowMagnification_destroyControl() {
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
@@ -184,6 +211,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
public void deleteWindowMagnification_enableAtTheBottom_overlapFlagIsFalse() {
final WindowManager wm = mContext.getSystemService(WindowManager.class);
final Rect bounds = wm.getCurrentWindowMetrics().getBounds();
+ setSystemGestureInsets();
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
@@ -239,25 +267,38 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
});
}
@Test
- public void onOrientationChanged_enabled_updateDisplayRotationAndLayout() {
+ public void onOrientationChanged_enabled_updateDisplayRotationAndCenterStayAtSamePosition() {
final Display display = Mockito.spy(mContext.getDisplay());
- when(display.getRotation()).thenReturn(Surface.ROTATION_90);
+ final int currentRotation = display.getRotation();
+ final int newRotation = (currentRotation + 1) % 4;
+ when(display.getRotation()).thenReturn(newRotation);
when(mContext.getDisplay()).thenReturn(display);
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
Float.NaN);
});
+ final PointF expectedCenter = new PointF(mWindowMagnificationController.getCenterY(),
+ mWindowMagnificationController.getCenterX());
+ final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
+ // Rotate the window clockwise 90 degree.
+ windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom,
+ windowBounds.right);
+ mWindowManager.setWindowBounds(windowBounds);
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
});
- assertEquals(Surface.ROTATION_90, mWindowMagnificationController.mRotation);
- // The first invocation is called when the surface is created.
+ assertEquals(newRotation, mWindowMagnificationController.mRotation);
+ final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(),
+ mWindowMagnificationController.getCenterY());
+ assertEquals(expectedCenter, actualCenter);
verify(mWindowManager, times(2)).updateViewLayout(any(), any());
}
@@ -275,6 +316,54 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
}
@Test
+ public void onScreenSizeChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() {
+ // The default position is at the center of the screen.
+ final float expectedRatio = 0.5f;
+ final Rect testWindowBounds = new Rect(
+ mWindowManager.getCurrentWindowMetrics().getBounds());
+ testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+ testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+ mWindowManager.setWindowBounds(testWindowBounds);
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+ });
+
+ // The ratio of center to window size should be the same.
+ assertEquals(expectedRatio,
+ mWindowMagnificationController.getCenterX() / testWindowBounds.width(),
+ 0);
+ assertEquals(expectedRatio,
+ mWindowMagnificationController.getCenterY() / testWindowBounds.height(),
+ 0);
+ }
+ @Test
+ public void screenSizeIsChangedToLarge_enabled_windowSizeIsConstrained() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+ final int screenSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnification_max_frame_size) * 10;
+ mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+ });
+
+ final int halfScreenSize = screenSize / 2;
+ WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
+ // The frame size should be the half of smaller value of window height/width unless it
+ //exceed the max frame size.
+ assertTrue(params.width < halfScreenSize);
+ assertTrue(params.height < halfScreenSize);
+ }
+
+ @Test
public void onDensityChanged_enabled_updateDimensionsAndResetWindowMagnification() {
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
@@ -419,9 +508,9 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
}
@Test
- public void moveWindowMagnificationToTheBottom_enabled_overlapFlagIsTrue() {
- final WindowManager wm = mContext.getSystemService(WindowManager.class);
- final Rect bounds = wm.getCurrentWindowMetrics().getBounds();
+ public void moveWindowMagnificationToTheBottom_enabledWithGestureInset_overlapFlagIsTrue() {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ setSystemGestureInsets();
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
Float.NaN);
@@ -447,4 +536,11 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
private boolean hasMagnificationOverlapFlag() {
return (mSysUiState.getFlags() & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0;
}
+
+ private void setSystemGestureInsets() {
+ final WindowInsets testInsets = new WindowInsets.Builder()
+ .setInsets(systemGestures(), Insets.of(0, 0, 0, 10))
+ .build();
+ mWindowManager.setWindowInsets(testInsets);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
index 06e27b5dbf48..7e9f84c1ef8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
@@ -16,10 +16,12 @@
package com.android.systemui.accessibility.floatingmenu;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.View.OVER_SCROLL_ALWAYS;
import static android.view.View.OVER_SCROLL_NEVER;
+import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
-import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.systemBars;
import static com.google.common.truth.Truth.assertThat;
@@ -38,6 +40,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Insets;
+import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.testing.AndroidTestingRunner;
@@ -97,14 +100,15 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
private AccessibilityFloatingMenuView mMenuView;
private RecyclerView mListView = new RecyclerView(mContext);
- private int mScreenHeight;
private int mMenuWindowHeight;
private int mMenuHalfWidth;
private int mMenuHalfHeight;
- private int mScreenHalfWidth;
- private int mScreenHalfHeight;
+ private int mDisplayHalfWidth;
+ private int mDisplayHalfHeight;
private int mMaxWindowX;
private int mMaxWindowY;
+ private final int mDisplayWindowWidth = 1080;
+ private final int mDisplayWindowHeight = 2340;
@Before
public void initMenuView() {
@@ -112,7 +116,10 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
mWindowManager).getMaximumWindowMetrics();
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
-
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ when(mWindowMetrics.getBounds()).thenReturn(new Rect(0, 0, mDisplayWindowWidth,
+ mDisplayWindowHeight));
+ when(mWindowMetrics.getWindowInsets()).thenReturn(fakeDisplayInsets());
mMenuView = spy(
new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition, mListView));
}
@@ -128,15 +135,16 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_width_height);
final int menuWidth = padding * 2 + iconWidthHeight;
final int menuHeight = (padding + iconWidthHeight) * mTargets.size() + padding;
- final int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels;
- mScreenHeight = mContext.getResources().getDisplayMetrics().heightPixels;
mMenuHalfWidth = menuWidth / 2;
mMenuHalfHeight = menuHeight / 2;
- mScreenHalfWidth = screenWidth / 2;
- mScreenHalfHeight = mScreenHeight / 2;
- mMaxWindowX = screenWidth - margin - menuWidth;
+ mDisplayHalfWidth = mDisplayWindowWidth / 2;
+ mDisplayHalfHeight = mDisplayWindowHeight / 2;
+ int marginStartEnd =
+ mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT
+ ? margin : 0;
+ mMaxWindowX = mDisplayWindowWidth - marginStartEnd - menuWidth;
mMenuWindowHeight = menuHeight + margin * 2;
- mMaxWindowY = mScreenHeight - mMenuWindowHeight;
+ mMaxWindowY = mDisplayWindowHeight - mMenuWindowHeight;
}
@Test
@@ -275,15 +283,15 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
final MotionEvent moveEvent =
mMotionEventHelper.obtainMotionEvent(2, 3,
MotionEvent.ACTION_MOVE,
- /* screenCenterX */mScreenHalfWidth
- - /* offsetXToScreenLeftHalfRegion */ 10,
- /* screenCenterY */ mScreenHalfHeight);
+ /* displayCenterX */mDisplayHalfWidth
+ - /* offsetXToDisplayLeftHalfRegion */ 10,
+ /* displayCenterY */ mDisplayHalfHeight);
final MotionEvent upEvent =
mMotionEventHelper.obtainMotionEvent(4, 5,
MotionEvent.ACTION_UP,
- /* screenCenterX */ mScreenHalfWidth
- - /* offsetXToScreenLeftHalfRegion */ 10,
- /* screenCenterY */ mScreenHalfHeight);
+ /* displayCenterX */ mDisplayHalfWidth
+ - /* offsetXToDisplayLeftHalfRegion */ 10,
+ /* displayCenterY */ mDisplayHalfHeight);
listView.dispatchTouchEvent(downEvent);
listView.dispatchTouchEvent(moveEvent);
@@ -311,15 +319,15 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
final MotionEvent moveEvent =
mMotionEventHelper.obtainMotionEvent(2, 3,
MotionEvent.ACTION_MOVE,
- /* screenCenterX */mScreenHalfWidth
- + /* offsetXToScreenRightHalfRegion */ 10,
- /* screenCenterY */ mScreenHalfHeight);
+ /* displayCenterX */mDisplayHalfWidth
+ + /* offsetXToDisplayRightHalfRegion */ 10,
+ /* displayCenterY */ mDisplayHalfHeight);
final MotionEvent upEvent =
mMotionEventHelper.obtainMotionEvent(4, 5,
MotionEvent.ACTION_UP,
- /* screenCenterX */ mScreenHalfWidth
- + /* offsetXToScreenRightHalfRegion */ 10,
- /* screenCenterY */ mScreenHalfHeight);
+ /* displayCenterX */ mDisplayHalfWidth
+ + /* offsetXToDisplayRightHalfRegion */ 10,
+ /* displayCenterY */ mDisplayHalfHeight);
listView.dispatchTouchEvent(downEvent);
listView.dispatchTouchEvent(moveEvent);
@@ -328,12 +336,12 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
assertThat((float) menuView.mCurrentLayoutParams.x).isWithin(1.0f).of(mMaxWindowX);
assertThat((float) menuView.mCurrentLayoutParams.y).isWithin(1.0f).of(
- /* newWindowY = screenCenterY - offsetY */ mScreenHalfHeight - mMenuHalfHeight);
+ /* newWindowY = displayCenterY - offsetY */ mDisplayHalfHeight - mMenuHalfHeight);
}
@Test
- public void tapOnAndDragMenuToScreenSide_transformShapeHalfOval() {
+ public void tapOnAndDragMenuToDisplaySide_transformShapeHalfOval() {
final Position alignRightPosition = new Position(1.0f, 0.8f);
final RecyclerView listView = new RecyclerView(mContext);
final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
@@ -351,13 +359,13 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
mMotionEventHelper.obtainMotionEvent(2, 3,
MotionEvent.ACTION_MOVE,
/* downX */(currentWindowX + mMenuHalfWidth)
- + /* offsetXToScreenRightSide */ mMenuHalfWidth,
+ + /* offsetXToDisplayRightSide */ mMenuHalfWidth,
/* downY */ (currentWindowY + mMenuHalfHeight));
final MotionEvent upEvent =
mMotionEventHelper.obtainMotionEvent(4, 5,
MotionEvent.ACTION_UP,
/* downX */(currentWindowX + mMenuHalfWidth)
- + /* offsetXToScreenRightSide */ mMenuHalfWidth,
+ + /* offsetXToDisplayRightSide */ mMenuHalfWidth,
/* downY */ (currentWindowY + mMenuHalfHeight));
listView.dispatchTouchEvent(downEvent);
@@ -419,7 +427,7 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
}
@Test
- public void showMenuAndIme_withHigherIme_alignScreenTopEdge() {
+ public void showMenuAndIme_withHigherIme_alignDisplayTopEdge() {
final int offset = 99999;
setupBasicMenuView(mMenuView);
@@ -471,10 +479,21 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
private WindowInsets fakeImeInsetWith(AccessibilityFloatingMenuView menuView, int offset) {
// Ensure the keyboard has overlapped on the menu view.
final int fakeImeHeight =
- mScreenHeight - (menuView.mCurrentLayoutParams.y + mMenuWindowHeight) + offset;
+ mDisplayWindowHeight - (menuView.mCurrentLayoutParams.y + mMenuWindowHeight)
+ + offset;
+ return new WindowInsets.Builder()
+ .setVisible(ime(), true)
+ .setInsets(ime(), Insets.of(0, 0, 0, fakeImeHeight))
+ .build();
+ }
+
+ private WindowInsets fakeDisplayInsets() {
+ final int fakeStatusBarHeight = 75;
+ final int fakeNavigationBarHeight = 125;
return new WindowInsets.Builder()
- .setVisible(ime() | navigationBars(), true)
- .setInsets(ime() | navigationBars(), Insets.of(0, 0, 0, fakeImeHeight))
+ .setVisible(systemBars() | displayCutout(), true)
+ .setInsets(systemBars() | displayCutout(),
+ Insets.of(0, fakeStatusBarHeight, 0, fakeNavigationBarHeight))
.build();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipViewTest.java
index eb1f15b30d6f..3553a0adce27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipViewTest.java
@@ -23,12 +23,16 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
+import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
+import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.test.filters.SmallTest;
@@ -52,6 +56,9 @@ public class BaseTooltipViewTest extends SysuiTestCase {
@Mock
private WindowManager mWindowManager;
+ @Mock
+ private WindowMetrics mWindowMetrics;
+
private AccessibilityFloatingMenuView mMenuView;
private BaseTooltipView mToolTipView;
@@ -66,6 +73,9 @@ public class BaseTooltipViewTest extends SysuiTestCase {
doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
mWindowManager).getMaximumWindowMetrics();
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ when(mWindowMetrics.getBounds()).thenReturn(new Rect());
+ when(mWindowMetrics.getWindowInsets()).thenReturn(new WindowInsets.Builder().build());
mMenuView = new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition);
mToolTipView = new BaseTooltipView(mContext, mMenuView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java
index ca4e3e914a8c..9eba49daaf6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java
@@ -21,12 +21,16 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
+import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
+import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowMetrics;
import androidx.test.filters.SmallTest;
@@ -49,6 +53,9 @@ public class DockTooltipViewTest extends SysuiTestCase {
@Mock
private WindowManager mWindowManager;
+ @Mock
+ private WindowMetrics mWindowMetrics;
+
private AccessibilityFloatingMenuView mMenuView;
private DockTooltipView mDockTooltipView;
private final Position mPlaceholderPosition = new Position(0.0f, 0.0f);
@@ -62,6 +69,9 @@ public class DockTooltipViewTest extends SysuiTestCase {
doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
mWindowManager).getMaximumWindowMetrics();
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ when(mWindowMetrics.getBounds()).thenReturn(new Rect());
+ when(mWindowMetrics.getWindowInsets()).thenReturn(new WindowInsets.Builder().build());
mMenuView = spy(new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition));
mDockTooltipView = new DockTooltipView(mContext, mMenuView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompatTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompatTest.java
index dae436427319..ea104a724b8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompatTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompatTest.java
@@ -22,12 +22,15 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
@@ -57,6 +60,8 @@ public class ItemDelegateCompatTest extends SysuiTestCase {
@Mock
private WindowManager mWindowManager;
+ @Mock
+ private WindowMetrics mWindowMetrics;
private RecyclerView mListView;
private AccessibilityFloatingMenuView mMenuView;
private ItemDelegateCompat mItemDelegateCompat;
@@ -69,6 +74,9 @@ public class ItemDelegateCompatTest extends SysuiTestCase {
doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
mWindowManager).getMaximumWindowMetrics();
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+ when(mWindowMetrics.getBounds()).thenReturn(new Rect());
+ when(mWindowMetrics.getWindowInsets()).thenReturn(new WindowInsets.Builder().build());
mListView = new RecyclerView(mContext);
mMenuView =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index 14f112b8b071..d819fa2adc38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -46,6 +46,7 @@ import org.mockito.junit.MockitoJUnit
@RunWithLooper
class ActivityLaunchAnimatorTest : SysuiTestCase() {
private val launchContainer = LinearLayout(mContext)
+ private val launchAnimator = LaunchAnimator(mContext, isForTesting = true)
@Mock lateinit var callback: ActivityLaunchAnimator.Callback
@Spy private val controller = TestLaunchAnimatorController(launchContainer)
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
@@ -56,7 +57,8 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
@Before
fun setup() {
- activityLaunchAnimator = ActivityLaunchAnimator(callback, mContext)
+ activityLaunchAnimator = ActivityLaunchAnimator(launchAnimator)
+ activityLaunchAnimator.callback = callback
}
private fun startIntentWithAnimation(
@@ -120,7 +122,8 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
@Test
fun animatesIfActivityIsAlreadyOpenAndIsOnKeyguard() {
`when`(callback.isOnKeyguard()).thenReturn(true)
- val animator = ActivityLaunchAnimator(callback, context)
+ val animator = ActivityLaunchAnimator(launchAnimator)
+ animator.callback = callback
val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
var animationAdapter: RemoteAnimationAdapter? = null
@@ -196,7 +199,7 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
return RemoteAnimationTarget(
0, RemoteAnimationTarget.MODE_OPENING, SurfaceControl(), false, Rect(), Rect(), 0,
Point(), Rect(), bounds, WindowConfiguration(), false, SurfaceControl(), Rect(),
- taskInfo
+ taskInfo, false
)
}
}
@@ -208,7 +211,7 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
private class TestLaunchAnimatorController(
override var launchContainer: ViewGroup
) : ActivityLaunchAnimator.Controller {
- override fun createAnimatorState() = ActivityLaunchAnimator.State(
+ override fun createAnimatorState() = LaunchAnimator.State(
top = 100,
bottom = 200,
left = 300,
@@ -232,7 +235,7 @@ private class TestLaunchAnimatorController(
}
override fun onLaunchAnimationProgress(
- state: ActivityLaunchAnimator.State,
+ state: LaunchAnimator.State,
progress: Float,
linearProgress: Float
) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
new file mode 100644
index 000000000000..5bcf828afe9f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -0,0 +1,186 @@
+package com.android.systemui.animation
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.ViewUtils
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.widget.LinearLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class DialogLaunchAnimatorTest : SysuiTestCase() {
+ private val launchAnimator = LaunchAnimator(context, isForTesting = true)
+ private val hostDialogprovider = TestHostDialogProvider()
+ private val dialogLaunchAnimator =
+ DialogLaunchAnimator(context, launchAnimator, hostDialogprovider)
+
+ @Test
+ fun testShowDialogFromView() {
+ // Show the dialog. showFromView() must be called on the main thread with a dialog created
+ // on the main thread too.
+ val (dialog, hostDialog) = runOnMainThreadAndWaitForIdleSync {
+ val touchSurfaceRoot = LinearLayout(context)
+ val touchSurface = View(context)
+ touchSurfaceRoot.addView(touchSurface)
+
+ // We need to attach the root to the window manager otherwise the exit animation will
+ // be skipped
+ ViewUtils.attachView(touchSurfaceRoot)
+
+ val dialog = TestDialog(context)
+ val hostDialog =
+ dialogLaunchAnimator.showFromView(dialog, touchSurface) as TestHostDialog
+ dialog to hostDialog
+ }
+
+ // Only the host dialog is actually showing.
+ assertTrue(hostDialog.isShowing)
+ assertFalse(dialog.isShowing)
+
+ // The dialog onStart() method was called but not onStop().
+ assertTrue(dialog.onStartCalled)
+ assertFalse(dialog.onStopCalled)
+
+ // The dialog content has been stolen and is shown inside the host dialog.
+ val hostDialogContent = hostDialog.findViewById<ViewGroup>(android.R.id.content)
+ assertEquals(0, dialog.findViewById<ViewGroup>(android.R.id.content).childCount)
+ assertEquals(1, hostDialogContent.childCount)
+
+ val hostDialogRoot = hostDialogContent.getChildAt(0) as ViewGroup
+ assertEquals(1, hostDialogRoot.childCount)
+ assertEquals(dialog.contentView, hostDialogRoot.getChildAt(0))
+
+ // If we are dozing, the host dialog window also fades out.
+ runOnMainThreadAndWaitForIdleSync { dialogLaunchAnimator.onDozeAmountChanged(0.5f) }
+ assertTrue(hostDialog.window!!.decorView.alpha < 1f)
+
+ // Hiding/showing/dismissing the dialog should hide/show/dismiss the host dialog given that
+ // it's a ListenableDialog.
+ runOnMainThreadAndWaitForIdleSync { dialog.hide() }
+ assertFalse(hostDialog.isShowing)
+ assertFalse(dialog.isShowing)
+
+ runOnMainThreadAndWaitForIdleSync { dialog.show() }
+ assertTrue(hostDialog.isShowing)
+ assertFalse(dialog.isShowing)
+
+ assertFalse(dialog.onStopCalled)
+ runOnMainThreadAndWaitForIdleSync { dialog.dismiss() }
+ assertFalse(hostDialog.isShowing)
+ assertFalse(dialog.isShowing)
+ assertTrue(hostDialog.wasDismissed)
+ assertTrue(dialog.onStopCalled)
+ }
+
+ private fun <T : Any> runOnMainThreadAndWaitForIdleSync(f: () -> T): T {
+ lateinit var result: T
+ context.mainExecutor.execute {
+ result = f()
+ }
+ waitForIdleSync()
+ return result
+ }
+
+ private class TestHostDialogProvider : HostDialogProvider {
+ override fun createHostDialog(
+ context: Context,
+ theme: Int,
+ onCreateCallback: () -> Unit,
+ dismissOverride: (() -> Unit) -> Unit
+ ): Dialog = TestHostDialog(context, onCreateCallback, dismissOverride)
+ }
+
+ private class TestHostDialog(
+ context: Context,
+ private val onCreateCallback: () -> Unit,
+ private val dismissOverride: (() -> Unit) -> Unit
+ ) : Dialog(context) {
+ var wasDismissed = false
+
+ init {
+ // We need to set the window type for dialogs shown by SysUI, otherwise WM will throw.
+ window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ onCreateCallback()
+ }
+
+ override fun dismiss() {
+ dismissOverride {
+ super.dismiss()
+ wasDismissed = true
+ }
+ }
+ }
+
+ private class TestDialog(context: Context) : Dialog(context), ListenableDialog {
+ private val listeners = hashSetOf<DialogListener>()
+ val contentView = View(context)
+ var onStartCalled = false
+ var onStopCalled = false
+
+ init {
+ // We need to set the window type for dialogs shown by SysUI, otherwise WM will throw.
+ window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(contentView)
+ }
+
+ override fun onStart() {
+ super.onStart()
+ onStartCalled = true
+ }
+
+ override fun onStop() {
+ super.onStart()
+ onStopCalled = true
+ }
+
+ override fun addListener(listener: DialogListener) {
+ listeners.add(listener)
+ }
+
+ override fun removeListener(listener: DialogListener) {
+ listeners.remove(listener)
+ }
+
+ override fun dismiss() {
+ super.dismiss()
+ notifyListeners { onDismiss() }
+ }
+
+ override fun hide() {
+ super.hide()
+ notifyListeners { onHide() }
+ }
+
+ override fun show() {
+ super.show()
+ notifyListeners { onShow() }
+ }
+
+ private fun notifyListeners(notify: DialogListener.() -> Unit) {
+ for (listener in HashSet(listeners)) {
+ listener.notify()
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
index 8cba25dc1b92..58e0cb259bb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
@@ -32,7 +32,7 @@ class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() {
fun animatingOrphanViewDoesNotCrash() {
val ghostedView = LinearLayout(mContext)
val controller = GhostedViewLaunchAnimatorController(ghostedView)
- val state = ActivityLaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
+ val state = LaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
controller.onIntentStarted(willAnimate = true)
controller.onLaunchAnimationStart(isExpandingFullyAbove = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
new file mode 100644
index 000000000000..1d038a43ec8e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.systemui.battery;
+
+import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
+
+import static com.android.systemui.util.mockito.KotlinMockitoHelpersKt.eq;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.os.Handler;
+import android.provider.Settings;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.tuner.TunerService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class BatteryMeterViewControllerTest extends SysuiTestCase {
+ @Mock
+ private BatteryMeterView mBatteryMeterView;
+
+ @Mock
+ private ConfigurationController mConfigurationController;
+ @Mock
+ private TunerService mTunerService;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private Handler mHandler;
+ @Mock
+ private ContentResolver mContentResolver;
+ @Mock
+ private BatteryController mBatteryController;
+
+ private BatteryMeterViewController mController;
+
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mBatteryMeterView.getContext()).thenReturn(mContext);
+ when(mBatteryMeterView.getResources()).thenReturn(mContext.getResources());
+
+ mController = new BatteryMeterViewController(
+ mBatteryMeterView,
+ mConfigurationController,
+ mTunerService,
+ mBroadcastDispatcher,
+ mHandler,
+ mContentResolver,
+ mBatteryController
+ );
+ }
+
+ @Test
+ public void onViewAttached_callbacksRegistered() {
+ mController.onViewAttached();
+
+ verify(mConfigurationController).addCallback(any());
+ verify(mTunerService).addTunable(any(), any());
+ verify(mContentResolver).registerContentObserver(
+ eq(Settings.System.getUriFor(SHOW_BATTERY_PERCENT)), anyBoolean(), any(), anyInt()
+ );
+ verify(mContentResolver).registerContentObserver(
+ eq(Settings.Global.getUriFor(Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME)),
+ anyBoolean(),
+ any()
+ );
+ verify(mBatteryController).addCallback(any());
+ }
+
+ @Test
+ public void onViewDetached_callbacksUnregistered() {
+ // Set everything up first.
+ mController.onViewAttached();
+
+ mController.onViewDetached();
+
+ verify(mConfigurationController).removeCallback(any());
+ verify(mTunerService).removeTunable(any());
+ verify(mContentResolver).unregisterContentObserver(any());
+ verify(mBatteryController).removeCallback(any());
+ }
+
+ @Test
+ public void ignoreTunerUpdates_afterOnViewAttached_callbackUnregistered() {
+ // Start out receiving tuner updates
+ mController.onViewAttached();
+
+ mController.ignoreTunerUpdates();
+
+ verify(mTunerService).removeTunable(any());
+ }
+
+ @Test
+ public void ignoreTunerUpdates_beforeOnViewAttached_callbackNeverRegistered() {
+ mController.ignoreTunerUpdates();
+
+ mController.onViewAttached();
+
+ verify(mTunerService, never()).addTunable(any(), any());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
new file mode 100644
index 000000000000..b4ff2a5e1fbf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.systemui.battery
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatteryMeterView.BatteryEstimateFetcher
+import com.android.systemui.statusbar.policy.BatteryController.EstimateFetchCompletion
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class BatteryMeterViewTest : SysuiTestCase() {
+
+ private lateinit var mBatteryMeterView: BatteryMeterView
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mBatteryMeterView = BatteryMeterView(mContext, null)
+ }
+
+ @Test
+ fun updatePercentText_estimateModeAndNotCharging_estimateFetched() {
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ assertThat(mBatteryMeterView.batteryPercentViewText).isEqualTo(ESTIMATE)
+ }
+
+ @Test
+ fun updatePercentText_noBatteryEstimateFetcher_noCrash() {
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+
+ mBatteryMeterView.updatePercentText()
+ // No assert needed
+ }
+
+ private class Fetcher : BatteryEstimateFetcher {
+ override fun fetchBatteryTimeRemainingEstimate(
+ completion: EstimateFetchCompletion) {
+ completion.onBatteryRemainingEstimateRetrieved(ESTIMATE)
+ }
+ }
+
+ private companion object {
+ const val ESTIMATE = "2 hours 2 minutes"
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
index f91c02938845..619d48d1e306 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java
@@ -29,6 +29,7 @@ import static org.mockito.Mockito.verify;
import android.content.Context;
import android.hardware.biometrics.ComponentInfoInternal;
+import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.SensorProperties;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -47,6 +48,7 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -165,6 +167,7 @@ public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
}
@Test
+ @Ignore("flaky, b/189031816")
public void testModeUpdated_onSoftError_whenSwitchToFingerprint() {
mFaceToFpView.onDialogAnimatedIn();
mFaceToFpView.onAuthenticationFailed(TYPE_FACE, "no face");
@@ -181,6 +184,7 @@ public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
}
@Test
+ @Ignore("flaky, b/189031816")
public void testModeUpdated_onHardError_whenSwitchToFingerprint() {
mFaceToFpView.onDialogAnimatedIn();
mFaceToFpView.onError(TYPE_FACE, "oh no!");
@@ -254,9 +258,10 @@ public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
true /* resetLockoutRequiresHardwareAuthToken */,
- 540 /* sensorLocationX */,
- 1600 /* sensorLocationY */,
- 100 /* sensorRadius */);
+ List.of(new SensorLocationInternal("" /* displayId */,
+ 540 /* sensorLocationX */,
+ 1600 /* sensorLocationY */,
+ 100 /* sensorRadius */)));
}
public class TestableView extends AuthBiometricFaceToFingerprintView {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
index bd518ff9a1ea..f8e38e4994bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
@@ -45,6 +45,7 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -199,6 +200,7 @@ public class AuthBiometricViewTest extends SysuiTestCase {
}
@Test
+ @Ignore("flaky, b/189031816")
public void testError_sendsActionError() {
initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
final String testError = "testError";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index f2f0029708ed..2c4808a4b84f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -21,11 +21,12 @@ import android.hardware.biometrics.BiometricSourceType
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -34,6 +35,8 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.StatusBar
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.leak.RotationUtils
+import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -48,12 +51,15 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-
+import org.mockito.MockitoSession
+import org.mockito.quality.Strictness
import javax.inject.Provider
@SmallTest
@RunWith(AndroidTestingRunner::class)
class AuthRippleControllerTest : SysuiTestCase() {
+ private lateinit var staticMockSession: MockitoSession
+
private lateinit var controller: AuthRippleController
@Mock private lateinit var statusBar: StatusBar
@Mock private lateinit var rippleView: AuthRippleView
@@ -74,6 +80,12 @@ class AuthRippleControllerTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ staticMockSession = mockitoSession()
+ .mockStatic(RotationUtils::class.java)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
+
+ `when`(RotationUtils.getRotation(context)).thenReturn(RotationUtils.ROTATION_NONE)
`when`(udfpsControllerProvider.get()).thenReturn(udfpsController)
controller = AuthRippleController(
@@ -96,6 +108,11 @@ class AuthRippleControllerTest : SysuiTestCase() {
`when`(statusBar.lightRevealScrim).thenReturn(lightRevealScrim)
}
+ @After
+ fun tearDown() {
+ staticMockSession.finishMocking()
+ }
+
@Test
fun testFingerprintTrigger_Ripple() {
// GIVEN fp exists, keyguard is visible, user doesn't need strong auth
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
index 977b05ce150c..5fee7fbf8705 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -16,6 +16,9 @@
package com.android.systemui.biometrics
+import android.graphics.Rect
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
import android.hardware.biometrics.SensorProperties
import android.hardware.display.DisplayManager
import android.hardware.display.DisplayManagerGlobal
@@ -30,8 +33,12 @@ import android.view.Display
import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
import android.view.DisplayInfo
import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowInsets
import android.view.WindowManager
+import android.view.WindowMetrics
import androidx.test.filters.SmallTest
+import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.concurrency.FakeExecutor
@@ -42,9 +49,13 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
@@ -66,11 +77,13 @@ class SidefpsControllerTest : SysuiTestCase() {
@Mock
lateinit var windowManager: WindowManager
@Mock
- lateinit var sidefpsView: SidefpsView
+ lateinit var sidefpsView: View
@Mock
lateinit var displayManager: DisplayManager
@Mock
lateinit var handler: Handler
+ @Captor
+ lateinit var overlayCaptor: ArgumentCaptor<View>
private val executor = FakeExecutor(FakeSystemClock())
private lateinit var overlayController: ISidefpsController
@@ -79,6 +92,8 @@ class SidefpsControllerTest : SysuiTestCase() {
@Before
fun setup() {
`when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sidefpsView)
+ `when`(sidefpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
+ .thenReturn(mock(LottieAnimationView::class.java))
`when`(fingerprintManager.sensorPropertiesInternal).thenReturn(
listOf(
FingerprintSensorPropertiesInternal(
@@ -99,6 +114,9 @@ class SidefpsControllerTest : SysuiTestCase() {
DEFAULT_DISPLAY_ADJUSTMENTS
)
)
+ `when`(windowManager.maximumWindowMetrics).thenReturn(
+ WindowMetrics(Rect(0, 0, 800, 800), WindowInsets.CONSUMED)
+ )
sideFpsController = SidefpsController(
mContext, layoutInflater, fingerprintManager, windowManager, executor,
@@ -112,13 +130,61 @@ class SidefpsControllerTest : SysuiTestCase() {
@Test
fun testSubscribesToOrientationChangesWhenShowingOverlay() {
- overlayController.show()
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
executor.runAllReady()
verify(displayManager).registerDisplayListener(any(), eq(handler))
- overlayController.hide()
+ overlayController.hide(SENSOR_ID)
executor.runAllReady()
verify(displayManager).unregisterDisplayListener(any())
}
+
+ @Test
+ fun testShowsAndHides() {
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ verify(windowManager).addView(overlayCaptor.capture(), any())
+
+ reset(windowManager)
+ overlayController.hide(SENSOR_ID)
+ executor.runAllReady()
+
+ verify(windowManager, never()).addView(any(), any())
+ verify(windowManager).removeView(eq(overlayCaptor.value))
+ }
+
+ @Test
+ fun testShowsOnce() {
+ repeat(5) {
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+ }
+
+ verify(windowManager).addView(any(), any())
+ verify(windowManager, never()).removeView(any())
+ }
+
+ @Test
+ fun testHidesOnce() {
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ repeat(5) {
+ overlayController.hide(SENSOR_ID)
+ executor.runAllReady()
+ }
+
+ verify(windowManager).addView(any(), any())
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun testIgnoredForKeyguard() {
+ overlayController.show(SENSOR_ID, REASON_AUTH_KEYGUARD)
+ executor.runAllReady()
+
+ verify(windowManager, never()).addView(any(), any())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 04ebee8913c0..deabda35aeae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -32,6 +32,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.TypedArray;
+import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorProperties;
import android.hardware.display.DisplayManager;
@@ -218,7 +219,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
mWindowManager,
mStatusBarStateController,
mFgExecutor,
- mStatusBar,
+ Optional.of(mStatusBar),
mStatusBarKeyguardViewManager,
mDumpManager,
mKeyguardUpdateMonitor,
@@ -257,7 +258,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void dozeTimeTick() throws RemoteException {
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
mUdfpsController.dozeTimeTick();
verify(mUdfpsView).dozeTimeTick();
@@ -272,7 +273,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
// GIVEN that the overlay is showing
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
// WHEN ACTION_DOWN is received
@@ -295,7 +296,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
// GIVEN that the overlay is showing
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
// WHEN ACTION_DOWN is received
@@ -318,7 +319,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
// GIVEN that the overlay is showing
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
// WHEN ACTION_DOWN is received
@@ -341,7 +342,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
// GIVEN that the overlay is showing
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
// WHEN ACTION_DOWN is received
@@ -363,7 +364,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
// GIVEN that the overlay is showing
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
// WHEN ACTION_MOVE is received
@@ -385,7 +386,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
// GIVEN that the overlay is showing
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
// WHEN multiple touches are received
@@ -405,7 +406,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void showUdfpsOverlay_addsViewToWindow() throws RemoteException {
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
verify(mWindowManager).addView(eq(mUdfpsView), any());
}
@@ -413,7 +414,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void hideUdfpsOverlay_removesViewFromWindow() throws RemoteException {
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
mFgExecutor.runAllReady();
verify(mWindowManager).removeView(eq(mUdfpsView));
@@ -423,7 +424,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException {
// GIVEN overlay was showing and the udfps bouncer is showing
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
// WHEN the overlay is hidden
@@ -437,7 +438,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
verify(mDisplayManager).registerDisplayListener(any(), eq(mHandler));
@@ -456,7 +457,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
// GIVEN that the overlay is showing
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
// WHEN ACTION_DOWN is received
verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
@@ -480,7 +481,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
public void aodInterrupt() throws RemoteException {
// GIVEN that the overlay is showing and screen is on and fp is running
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
@@ -498,7 +499,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
public void cancelAodInterrupt() throws RemoteException {
// GIVEN AOD interrupt
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
@@ -514,7 +515,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
public void aodInterruptTimeout() throws RemoteException {
// GIVEN AOD interrupt
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
@@ -531,7 +532,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
public void aodInterruptScreenOff() throws RemoteException {
// GIVEN screen off
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOff();
mFgExecutor.runAllReady();
@@ -547,7 +548,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
public void aodInterrupt_fingerprintNotRunning() throws RemoteException {
// GIVEN showing overlay
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
+ mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
@@ -567,7 +569,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
// GIVEN that the overlay is showing
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
// WHEN ACTION_DOWN is received
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
index 88b4039fd2cd..27755edecba6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
@@ -19,6 +19,7 @@ package com.android.systemui.biometrics;
import static org.junit.Assert.assertEquals;
import android.hardware.biometrics.ComponentInfoInternal;
+import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.SensorProperties;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -61,8 +62,9 @@ public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase {
0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
- true /* resetLockoutRequiresHardwareAuthToken */, sensorLocationX, sensorLocationY,
- sensorRadius);
+ true /* resetLockoutRequiresHardwareAuthToken */,
+ List.of(new SensorLocationInternal("" /* displayId */,
+ sensorLocationX, sensorLocationY, sensorRadius)));
assertEquals(970,
UdfpsDialogMeasureAdapter.calculateBottomSpacerHeightForPortrait(
@@ -125,8 +127,9 @@ public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase {
0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
- true /* resetLockoutRequiresHardwareAuthToken */, sensorLocationX, sensorLocationY,
- sensorRadius);
+ true /* resetLockoutRequiresHardwareAuthToken */,
+ List.of(new SensorLocationInternal("" /* displayId */,
+ sensorLocationX, sensorLocationY, sensorRadius)));
assertEquals(1205,
UdfpsDialogMeasureAdapter.calculateHorizontalSpacerWidthForLandscape(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 4b35de15147b..2821f3d21606 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -57,9 +57,9 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
import java.util.List;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -121,7 +121,7 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
mController = new UdfpsKeyguardViewController(
mView,
mStatusBarStateController,
- mStatusBar,
+ Optional.of(mStatusBar),
mStatusBarKeyguardViewManager,
mKeyguardUpdateMonitor,
mDumpManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
index 41747f407546..15e5e1c7b8db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
@@ -35,6 +35,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.types.Tonal;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import org.junit.Before;
@@ -60,6 +61,8 @@ public class SysuiColorExtractorTests extends SysuiTestCase {
@Mock
private WallpaperManager mWallpaperManager;
+ @Mock
+ private DumpManager mDumpManager;
private ColorExtractor.GradientColors mColors;
private SysuiColorExtractor mColorExtractor;
@@ -69,13 +72,18 @@ public class SysuiColorExtractorTests extends SysuiTestCase {
mColors = new ColorExtractor.GradientColors();
mColors.setMainColor(Color.RED);
mColors.setSecondaryColor(Color.RED);
- mColorExtractor = new SysuiColorExtractor(getContext(),
+ mColorExtractor = new SysuiColorExtractor(
+ getContext(),
(inWallpaperColors, outGradientColorsNormal, outGradientColorsDark,
outGradientColorsExtraDark) -> {
outGradientColorsNormal.set(mColors);
outGradientColorsDark.set(mColors);
outGradientColorsExtraDark.set(mColors);
- }, mock(ConfigurationController.class), mWallpaperManager, true /* immediately */);
+ },
+ mock(ConfigurationController.class),
+ mWallpaperManager,
+ mDumpManager,
+ true /* immediately */);
}
@Test
@@ -111,8 +119,13 @@ public class SysuiColorExtractorTests extends SysuiTestCase {
public void onUiModeChanged_reloadsColors() {
Tonal tonal = mock(Tonal.class);
ConfigurationController configurationController = mock(ConfigurationController.class);
- SysuiColorExtractor sysuiColorExtractor = new SysuiColorExtractor(getContext(),
- tonal, configurationController, mWallpaperManager, true /* immediately */);
+ SysuiColorExtractor sysuiColorExtractor = new SysuiColorExtractor(
+ getContext(),
+ tonal,
+ configurationController,
+ mWallpaperManager,
+ mDumpManager,
+ true /* immediately */);
verify(configurationController).addCallback(eq(sysuiColorExtractor));
reset(tonal);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
index d6226aa53f67..364b5d9be2b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
@@ -40,7 +40,7 @@ public class DozeConfigurationUtil {
when(params.doubleTapReportsTouchCoordinates()).thenReturn(false);
when(params.getDisplayNeedsBlanking()).thenReturn(false);
when(params.getSelectivelyRegisterSensorsUsingProx()).thenReturn(false);
- when(params.singleTapUsesProx()).thenReturn(true);
+ when(params.singleTapUsesProx(anyInt())).thenReturn(true);
when(params.longPressUsesProx()).thenReturn(true);
when(params.getQuickPickupAodDuration()).thenReturn(500);
@@ -61,14 +61,14 @@ public class DozeConfigurationUtil {
when(config.getWakeLockScreenDebounce()).thenReturn(0L);
when(config.doubleTapSensorType()).thenReturn(null);
- when(config.tapSensorType()).thenReturn(null);
when(config.longPressSensorType()).thenReturn(null);
when(config.udfpsLongPressSensorType()).thenReturn(null);
when(config.quickPickupSensorType()).thenReturn(null);
when(config.tapGestureEnabled(anyInt())).thenReturn(true);
when(config.tapSensorAvailable()).thenReturn(true);
- when(config.tapSensorType()).thenReturn(FakeSensorManager.TAP_SENSOR_TYPE);
+ when(config.tapSensorTypeMapping()).thenReturn(
+ new String[]{FakeSensorManager.TAP_SENSOR_TYPE});
when(config.dozePickupSensorAvailable()).thenReturn(false);
when(config.wakeScreenGestureAvailable()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index b8d465a8c881..886f84e19e0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -51,7 +51,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -61,6 +61,7 @@ import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -77,6 +78,7 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
private DozeServiceFake mServiceFake;
private FakeSensorManager.FakeGenericSensor mSensor;
+ private FakeSensorManager.FakeGenericSensor mSensorInner;
private AsyncSensorManager mSensorManager;
private AlwaysOnDisplayPolicy mAlwaysOnDisplayPolicy;
@Mock
@@ -88,7 +90,9 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
@Mock
DockManager mDockManager;
@Mock
- private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ DevicePostureController mDevicePostureController;
+ @Mock
+ DozeLog mDozeLog;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor);
@@ -114,10 +118,19 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
mAlwaysOnDisplayPolicy.dimBrightness = DIM_BRIGHTNESS;
mAlwaysOnDisplayPolicy.dimmingScrimArray = SENSOR_TO_OPACITY;
mSensor = fakeSensorManager.getFakeLightSensor();
- mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
- Optional.of(mSensor.getSensor()), mDozeHost, null /* handler */,
- mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager,
- mUnlockedScreenOffAnimationController);
+ mSensorInner = fakeSensorManager.getFakeLightSensor2();
+ mScreen = new DozeScreenBrightness(
+ mContext,
+ mServiceFake,
+ mSensorManager,
+ new Optional[]{Optional.of(mSensor.getSensor())},
+ mDozeHost,
+ null /* handler */,
+ mAlwaysOnDisplayPolicy,
+ mWakefulnessLifecycle,
+ mDozeParameters,
+ mDevicePostureController,
+ mDozeLog);
}
@Test
@@ -155,7 +168,7 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
@Test
public void doze_doesNotUseLightSensor() {
- // GIVEN the device is docked and the display state changes to ON
+ // GIVEN the device is DOZE and the display state changes to ON
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
waitForSensorManager();
@@ -170,7 +183,7 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
@Test
public void aod_usesLightSensor() {
- // GIVEN the device is docked and the display state changes to ON
+ // GIVEN the device is DOZE_AOD and the display state changes to ON
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
waitForSensorManager();
@@ -213,10 +226,17 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
@Test
public void testPulsing_withoutLightSensor_setsAoDDimmingScrimTransparent() throws Exception {
- mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
- Optional.empty() /* sensor */, mDozeHost, null /* handler */,
- mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager,
- mUnlockedScreenOffAnimationController);
+ mScreen = new DozeScreenBrightness(
+ mContext,
+ mServiceFake,
+ mSensorManager,
+ new Optional[] {Optional.empty()} /* sensor */,
+ mDozeHost, null /* handler */,
+ mAlwaysOnDisplayPolicy,
+ mWakefulnessLifecycle,
+ mDozeParameters,
+ mDevicePostureController,
+ mDozeLog);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
reset(mDozeHost);
@@ -243,10 +263,17 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
@Test
public void testNullSensor() throws Exception {
- mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
- Optional.empty() /* sensor */, mDozeHost, null /* handler */,
- mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager,
- mUnlockedScreenOffAnimationController);
+ mScreen = new DozeScreenBrightness(
+ mContext,
+ mServiceFake,
+ mSensorManager,
+ new Optional[]{Optional.empty()} /* sensor */,
+ mDozeHost, null /* handler */,
+ mAlwaysOnDisplayPolicy,
+ mWakefulnessLifecycle,
+ mDozeParameters,
+ mDevicePostureController,
+ mDozeLog);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
@@ -255,6 +282,130 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
}
@Test
+ public void testSensorsSupportPostures_closed() throws Exception {
+ // GIVEN the device is CLOSED
+ when(mDevicePostureController.getDevicePosture()).thenReturn(
+ DevicePostureController.DEVICE_POSTURE_CLOSED);
+
+ // GIVEN closed and opened postures use different light sensors
+ mScreen = new DozeScreenBrightness(
+ mContext,
+ mServiceFake,
+ mSensorManager,
+ new Optional[]{
+ Optional.empty() /* unknown */,
+ Optional.of(mSensor.getSensor()) /* closed */,
+ Optional.of(mSensorInner.getSensor()) /* half-opened */,
+ Optional.of(mSensorInner.getSensor()) /* opened */,
+ Optional.empty() /* flipped */
+ },
+ mDozeHost, null /* handler */,
+ mAlwaysOnDisplayPolicy,
+ mWakefulnessLifecycle,
+ mDozeParameters,
+ mDevicePostureController,
+ mDozeLog);
+
+ // GIVEN the device is in AOD
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ waitForSensorManager();
+
+ // WHEN new different events are sent from the inner and outer sensors
+ mSensor.sendSensorEvent(3); // CLOSED sensor
+ mSensorInner.sendSensorEvent(4); // OPENED sensor
+
+ // THEN brightness is updated according to the sensor for CLOSED
+ assertEquals(3, mServiceFake.screenBrightness);
+ }
+
+ @Test
+ public void testSensorsSupportPostures_open() throws Exception {
+ // GIVEN the device is OPENED
+ when(mDevicePostureController.getDevicePosture()).thenReturn(
+ DevicePostureController.DEVICE_POSTURE_OPENED);
+
+ // GIVEN closed and opened postures use different light sensors
+ mScreen = new DozeScreenBrightness(
+ mContext,
+ mServiceFake,
+ mSensorManager,
+ new Optional[]{
+ Optional.empty() /* unknown */,
+ Optional.of(mSensor.getSensor()) /* closed */,
+ Optional.of(mSensorInner.getSensor()) /* half-opened */,
+ Optional.of(mSensorInner.getSensor()) /* opened */,
+ Optional.empty() /* flipped */
+ },
+ mDozeHost, null /* handler */,
+ mAlwaysOnDisplayPolicy,
+ mWakefulnessLifecycle,
+ mDozeParameters,
+ mDevicePostureController,
+ mDozeLog);
+
+ // GIVEN device is in AOD
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ waitForSensorManager();
+
+ // WHEN new different events are sent from the inner and outer sensors
+ mSensorInner.sendSensorEvent(4); // OPENED sensor
+ mSensor.sendSensorEvent(3); // CLOSED sensor
+
+ // THEN brightness is updated according to the sensor for OPENED
+ assertEquals(4, mServiceFake.screenBrightness);
+ }
+
+ @Test
+ public void testSensorsSupportPostures_swapPostures() throws Exception {
+ ArgumentCaptor<DevicePostureController.Callback> postureCallbackCaptor =
+ ArgumentCaptor.forClass(DevicePostureController.Callback.class);
+ reset(mDevicePostureController);
+
+ // GIVEN the device starts up AOD OPENED
+ when(mDevicePostureController.getDevicePosture()).thenReturn(
+ DevicePostureController.DEVICE_POSTURE_OPENED);
+
+ // GIVEN closed and opened postures use different light sensors
+ mScreen = new DozeScreenBrightness(
+ mContext,
+ mServiceFake,
+ mSensorManager,
+ new Optional[]{
+ Optional.empty() /* unknown */,
+ Optional.of(mSensor.getSensor()) /* closed */,
+ Optional.of(mSensorInner.getSensor()) /* half-opened */,
+ Optional.of(mSensorInner.getSensor()) /* opened */,
+ Optional.empty() /* flipped */
+ },
+ mDozeHost, null /* handler */,
+ mAlwaysOnDisplayPolicy,
+ mWakefulnessLifecycle,
+ mDozeParameters,
+ mDevicePostureController,
+ mDozeLog);
+ verify(mDevicePostureController).addCallback(postureCallbackCaptor.capture());
+
+ // GIVEN device is in AOD
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ waitForSensorManager();
+
+ // WHEN the posture changes to CLOSED
+ postureCallbackCaptor.getValue().onPostureChanged(
+ DevicePostureController.DEVICE_POSTURE_CLOSED);
+ waitForSensorManager();
+
+ // WHEN new different events are sent from the inner and outer sensors
+ mSensor.sendSensorEvent(3); // CLOSED sensor
+ mSensorInner.sendSensorEvent(4); // OPENED sensor
+
+ // THEN brightness is updated according to the sensor for CLOSED
+ assertEquals(3, mServiceFake.screenBrightness);
+ }
+
+ @Test
public void testNoBrightnessDeliveredAfterFinish() throws Exception {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
@@ -319,14 +470,13 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
- when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(true);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
// If we're dozing after a timeout, and playing the unlocked screen animation, we should
- // stay at or below dim brightness, because the screen dims just before timeout.
- assertTrue(mServiceFake.screenBrightness <= DIM_BRIGHTNESS);
+ // stay at dim brightness, because the screen dims just before timeout.
+ assertEquals(mServiceFake.screenBrightness, DIM_BRIGHTNESS);
}
@Test
@@ -334,7 +484,6 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON);
when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
- when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(true);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
@@ -349,7 +498,6 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false);
- when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(false);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
index 3e19cc436dca..150ab7700e4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -82,8 +82,6 @@ public class DozeScreenStateTest extends SysuiTestCase {
private UdfpsController mUdfpsController;
@Mock
private DozeLog mDozeLog;
- @Mock
- private DozeScreenBrightness mDozeScreenBrightness;
@Before
public void setUp() throws Exception {
@@ -98,8 +96,7 @@ public class DozeScreenStateTest extends SysuiTestCase {
mHandlerFake = new FakeHandler(Looper.getMainLooper());
mWakeLock = new WakeLockFake();
mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeHost, mDozeParameters,
- mWakeLock, mAuthController, mUdfpsControllerProvider, mDozeLog,
- mDozeScreenBrightness);
+ mWakeLock, mAuthController, mUdfpsControllerProvider, mDozeLog);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 5c4c27ccc4ca..f525fee27e20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -19,6 +19,7 @@ package com.android.systemui.doze;
import static com.android.systemui.doze.DozeLog.REASON_SENSOR_TAP;
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -47,6 +48,7 @@ import com.android.systemui.biometrics.AuthController;
import com.android.systemui.doze.DozeSensors.TriggerSensor;
import com.android.systemui.plugins.SensorManagerPlugin;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.settings.FakeSettings;
@@ -58,6 +60,11 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.Consumer;
@RunWith(AndroidTestingRunner.class)
@@ -84,6 +91,8 @@ public class DozeSensorsTest extends SysuiTestCase {
@Mock
private AuthController mAuthController;
@Mock
+ private DevicePostureController mDevicePostureController;
+ @Mock
private ProximitySensor mProximitySensor;
private FakeSettings mFakeSettings = new FakeSettings();
private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener;
@@ -95,6 +104,8 @@ public class DozeSensorsTest extends SysuiTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
+ when(mAmbientDisplayConfiguration.tapSensorTypeMapping())
+ .thenReturn(new String[]{"tapSEnsor"});
when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
doAnswer(invocation -> {
@@ -117,14 +128,14 @@ public class DozeSensorsTest extends SysuiTestCase {
mWakeLockScreenListener.onSensorChanged(mock(SensorManagerPlugin.SensorEvent.class));
mTestableLooper.processAllMessages();
- verify(mCallback).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN),
+ verify(mCallback).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH),
anyFloat(), anyFloat(), eq(null));
mDozeSensors.requestTemporaryDisable();
reset(mCallback);
mWakeLockScreenListener.onSensorChanged(mock(SensorManagerPlugin.SensorEvent.class));
mTestableLooper.processAllMessages();
- verify(mCallback, never()).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN),
+ verify(mCallback, never()).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH),
anyFloat(), anyFloat(), eq(null));
}
@@ -157,7 +168,7 @@ public class DozeSensorsTest extends SysuiTestCase {
// GIVEN we only should register sensors using prox when not in low-powered mode / off
// and the single tap sensor uses the proximity sensor
when(mDozeParameters.getSelectivelyRegisterSensorsUsingProx()).thenReturn(true);
- when(mDozeParameters.singleTapUsesProx()).thenReturn(true);
+ when(mDozeParameters.singleTapUsesProx(anyInt())).thenReturn(true);
TestableDozeSensors dozeSensors = new TestableDozeSensors();
// THEN on initialization, the tap sensor isn't requested
@@ -258,13 +269,120 @@ public class DozeSensorsTest extends SysuiTestCase {
assertTrue(triggerSensor.mRegistered);
}
- private class TestableDozeSensors extends DozeSensors {
+ @Test
+ public void testPostureStartStateClosed_registersCorrectSensor() throws Exception {
+ // GIVEN doze sensor that supports postures
+ Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+ Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ new Sensor[] {
+ null /* unknown */,
+ closedSensor,
+ null /* half-opened */,
+ openedSensor},
+ DevicePostureController.DEVICE_POSTURE_CLOSED);
+
+ // WHEN trigger sensor requests listening
+ triggerSensor.setListening(true);
+
+ // THEN the correct sensor is registered
+ verify(mSensorManager).requestTriggerSensor(eq(triggerSensor), eq(closedSensor));
+ verify(mSensorManager, never()).requestTriggerSensor(eq(triggerSensor), eq(openedSensor));
+ }
+
+ @Test
+ public void testPostureChange_registersCorrectSensor() throws Exception {
+ // GIVEN doze sensor that supports postures
+ Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+ Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ new Sensor[] {
+ null /* unknown */,
+ closedSensor,
+ null /* half-opened */,
+ openedSensor},
+ DevicePostureController.DEVICE_POSTURE_CLOSED);
+
+ // GIVEN sensor is listening
+ when(mSensorManager.requestTriggerSensor(any(), any())).thenReturn(true);
+ triggerSensor.setListening(true);
+ reset(mSensorManager);
+ assertTrue(triggerSensor.mRegistered);
+
+ // WHEN posture changes
+ boolean sensorChanged =
+ triggerSensor.setPosture(DevicePostureController.DEVICE_POSTURE_OPENED);
+
+ // THEN the correct sensor is registered
+ assertTrue(sensorChanged);
+ verify(mSensorManager).requestTriggerSensor(eq(triggerSensor), eq(openedSensor));
+ verify(mSensorManager, never()).requestTriggerSensor(eq(triggerSensor), eq(closedSensor));
+ }
+
+ @Test
+ public void testPostureChange_noSensorChange() throws Exception {
+ // GIVEN doze sensor that supports postures
+ Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+ Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ new Sensor[] {
+ null /* unknown */,
+ closedSensor,
+ openedSensor /* half-opened uses the same sensor as opened*/,
+ openedSensor},
+ DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
+
+ // GIVEN sensor is listening
+ when(mSensorManager.requestTriggerSensor(any(), any())).thenReturn(true);
+ triggerSensor.setListening(true);
+ reset(mSensorManager);
+
+ // WHEN posture changes
+ boolean sensorChanged =
+ triggerSensor.setPosture(DevicePostureController.DEVICE_POSTURE_OPENED);
+ // THEN no change in sensor
+ assertFalse(sensorChanged);
+ verify(mSensorManager, never()).requestTriggerSensor(eq(triggerSensor), any());
+ }
+
+ @Test
+ public void testFindSensor() throws Exception {
+ // GIVEN a prox sensor
+ List<Sensor> sensors = new ArrayList<>();
+ Sensor proxSensor =
+ createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY);
+ sensors.add(proxSensor);
+
+ when(mSensorManager.getSensorList(anyInt())).thenReturn(sensors);
+
+ // WHEN we try to find the prox sensor with the same type and name
+ // THEN we find the added sensor
+ assertEquals(
+ proxSensor,
+ DozeSensors.findSensor(
+ mSensorManager,
+ Sensor.STRING_TYPE_PROXIMITY,
+ proxSensor.getName()));
+
+ // WHEN we try to find a prox sensor with a different name
+ // THEN no sensor is found
+ assertEquals(
+ null,
+ DozeSensors.findSensor(
+ mSensorManager,
+ Sensor.STRING_TYPE_PROXIMITY,
+ "some other name"));
+ }
+
+
+ private class TestableDozeSensors extends DozeSensors {
TestableDozeSensors() {
super(getContext(), mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
- mProximitySensor, mFakeSettings, mAuthController);
- for (TriggerSensor sensor : mSensors) {
+ mProximitySensor, mFakeSettings, mAuthController,
+ mDevicePostureController);
+ for (TriggerSensor sensor : mTriggerSensors) {
if (sensor instanceof PluginSensor
&& ((PluginSensor) sensor).mPluginSensor.getType()
== TYPE_WAKE_LOCK_SCREEN) {
@@ -273,7 +391,7 @@ public class DozeSensorsTest extends SysuiTestCase {
mSensorTap = sensor;
}
}
- mSensors = new TriggerSensor[] {mTriggerSensor, mSensorTap};
+ mTriggerSensors = new TriggerSensor[] {mTriggerSensor, mSensorTap};
}
public TriggerSensor createDozeSensor(Sensor sensor, boolean settingEnabled,
@@ -284,8 +402,44 @@ public class DozeSensorsTest extends SysuiTestCase {
/* configured */ true,
/* pulseReason*/ 0,
/* reportsTouchCoordinate*/ false,
- requiresTouchScreen,
- mDozeLog);
+ /* requiresTouchscreen */ false,
+ /* ignoresSetting */ false,
+ requiresTouchScreen);
}
+
+ /**
+ * create a doze sensor that supports postures and is enabled
+ */
+ public TriggerSensor createDozeSensor(Sensor[] sensors, int posture) {
+ return new TriggerSensor(/* sensor */ sensors,
+ /* setting name */ "test_setting",
+ /* settingDefault */ true,
+ /* configured */ true,
+ /* pulseReason*/ 0,
+ /* reportsTouchCoordinate*/ false,
+ /* requiresTouchscreen */ false,
+ /* ignoresSetting */ true,
+ /* requiresProx */false,
+ posture);
+ }
+ }
+
+ public static void setSensorType(Sensor sensor, int type, String strType) throws Exception {
+ Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE);
+ setter.setAccessible(true);
+ setter.invoke(sensor, type);
+ if (strType != null) {
+ Field f = sensor.getClass().getDeclaredField("mStringType");
+ f.setAccessible(true);
+ f.set(sensor, strType);
+ }
+ }
+
+ public static Sensor createSensor(int type, String strType) throws Exception {
+ Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+ constr.setAccessible(true);
+ Sensor sensor = constr.newInstance();
+ setSensorType(sensor, type, strType);
+ return sensor;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 9577c7a2d6fa..35dca7ef5fce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -45,6 +45,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeTriggers.DozingUpdateUiEvent;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
@@ -52,7 +53,8 @@ import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.FakeProximitySensor;
import com.android.systemui.util.sensors.FakeSensorManager;
import com.android.systemui.util.sensors.FakeThresholdSensor;
-import com.android.systemui.util.sensors.ProximitySensor;
+import com.android.systemui.util.sensors.ProximityCheck;
+import com.android.systemui.util.sensors.ThresholdSensorEvent;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.wakelock.WakeLock;
@@ -79,13 +81,15 @@ public class DozeTriggersTest extends SysuiTestCase {
@Mock
private DockManager mDockManager;
@Mock
- private ProximitySensor.ProximityCheck mProximityCheck;
+ private ProximityCheck mProximityCheck;
@Mock
private AuthController mAuthController;
@Mock
private UiEventLogger mUiEventLogger;
@Mock
private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private DevicePostureController mDevicePostureController;
private DozeTriggers mTriggers;
private FakeSensorManager mSensors;
@@ -117,7 +121,8 @@ public class DozeTriggersTest extends SysuiTestCase {
mTriggers = new DozeTriggers(mContext, mHost, config, dozeParameters,
asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(),
- mAuthController, mExecutor, mUiEventLogger, mKeyguardStateController);
+ mAuthController, mExecutor, mUiEventLogger, mKeyguardStateController,
+ mDevicePostureController);
mTriggers.setDozeMachine(mMachine);
waitForSensorManager();
}
@@ -132,14 +137,14 @@ public class DozeTriggersTest extends SysuiTestCase {
mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
clearInvocations(mMachine);
- mProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(true, 1));
+ mProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 1));
captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
mProximitySensor.alertListeners();
verify(mMachine, never()).requestState(any());
verify(mMachine, never()).requestPulse(anyInt());
- mProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(false, 2));
+ mProximitySensor.setLastEvent(new ThresholdSensorEvent(false, 2));
mProximitySensor.alertListeners();
waitForSensorManager();
captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
@@ -199,7 +204,7 @@ public class DozeTriggersTest extends SysuiTestCase {
public void testProximitySensorNotAvailablel() {
mProximitySensor.setSensorAvailable(false);
mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_LONG_PRESS, 100, 100, null);
- mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, 100, 100,
+ mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH, 100, 100,
new float[]{1});
mTriggers.onSensor(DozeLog.REASON_SENSOR_TAP, 100, 100, null);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
new file mode 100644
index 000000000000..8243be8448c0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.systemui.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.content.Context;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
+ * overriding, and should never return any value other than the one provided as the default.
+ */
+@SmallTest
+public class FeatureFlagManagerTest extends SysuiTestCase {
+ FeatureFlagManager mFeatureFlagManager;
+
+ @Mock private SystemPropertiesHelper mProps;
+ @Mock private Context mContext;
+ @Mock private DumpManager mDumpManager;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mFeatureFlagManager = new FeatureFlagManager(mProps, mContext, mDumpManager);
+ }
+
+ @After
+ public void onFinished() {
+ // SystemPropertiesHelper and Context are provided for constructor consistency with the
+ // debug version of the FeatureFlagManager, but should never be used.
+ verifyZeroInteractions(mProps, mContext);
+ // The dump manager should be registered with even for the release version, but that's it.
+ verify(mDumpManager).registerDumpable(anyString(), any());
+ verifyNoMoreInteractions(mDumpManager);
+ }
+
+ @Test
+ public void testIsEnabled() {
+ mFeatureFlagManager.setEnabled(1, true);
+ // Again, nothing changes.
+ assertThat(mFeatureFlagManager.isEnabled(1, false)).isFalse();
+ }
+
+ @Test
+ public void testDump() {
+ // Even if a flag is set before
+ mFeatureFlagManager.setEnabled(1, true);
+
+ // WHEN the flags have been accessed
+ assertFalse(mFeatureFlagManager.isEnabled(1, false));
+ assertTrue(mFeatureFlagManager.isEnabled(2, true));
+
+ // Even if a flag is set after
+ mFeatureFlagManager.setEnabled(2, false);
+
+ // THEN the dump contains the flags and the default values
+ String dump = dumpToString();
+ assertThat(dump).contains(" sysui_flag_1: false\n");
+ assertThat(dump).contains(" sysui_flag_2: true\n");
+ }
+
+ private String dumpToString() {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ mFeatureFlagManager.dump(mock(FileDescriptor.class), pw, new String[0]);
+ pw.flush();
+ String dump = sw.toString();
+ return dump;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
index 223714cfda30..fc6f3fd1d9c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
@@ -32,6 +32,8 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.util.wrapper.BuildInfo;
import org.junit.Before;
@@ -43,7 +45,9 @@ import org.mockito.MockitoAnnotations;
public class FeatureFlagReaderTest extends SysuiTestCase {
@Mock private Resources mResources;
@Mock private BuildInfo mBuildInfo;
+ @Mock private DumpManager mDumpManager;
@Mock private SystemPropertiesHelper mSystemPropertiesHelper;
+ @Mock private FlagReader mFlagReader;
private FeatureFlagReader mReader;
@@ -63,7 +67,8 @@ public class FeatureFlagReaderTest extends SysuiTestCase {
private void initialize(boolean isDebuggable, boolean isOverrideable) {
when(mBuildInfo.isDebuggable()).thenReturn(isDebuggable);
when(mResources.getBoolean(R.bool.are_flags_overrideable)).thenReturn(isOverrideable);
- mReader = new FeatureFlagReader(mResources, mBuildInfo, mSystemPropertiesHelper);
+ mReader = new FeatureFlagReader(
+ mResources, mBuildInfo, mDumpManager, mSystemPropertiesHelper, mFlagReader);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
new file mode 100644
index 000000000000..a850f70ae318
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.systemui.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class FeatureFlagsTest extends SysuiTestCase {
+
+ @Mock FeatureFlagReader mFeatureFlagReader;
+
+ private FeatureFlags mFeatureFlags;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mFeatureFlags = new FeatureFlags(mFeatureFlagReader, getContext());
+ }
+
+ @Test
+ public void testAddListener() {
+ Flag<?> flag = new BooleanFlag(1);
+ mFeatureFlags.addFlag(flag);
+
+ // Assert and capture that a plugin listener was added.
+ ArgumentCaptor<FlagReader.Listener> pluginListenerCaptor =
+ ArgumentCaptor.forClass(FlagReader.Listener.class);
+ verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture());
+ FlagReader.Listener pluginListener = pluginListenerCaptor.getValue();
+
+ // Signal a change. No listeners, so no real effect.
+ pluginListener.onFlagChanged(flag.getId());
+
+ // Add a listener for the flag
+ final Flag<?>[] changedFlag = {null};
+ FeatureFlags.Listener listener = f -> changedFlag[0] = f;
+ mFeatureFlags.addFlagListener(flag, listener);
+
+ // No changes seen yet.
+ assertThat(changedFlag[0]).isNull();
+
+ // Signal a change.
+ pluginListener.onFlagChanged(flag.getId());
+
+ // Assert that the change was for the correct flag.
+ assertThat(changedFlag[0]).isEqualTo(flag);
+ }
+
+ @Test
+ public void testRemoveListener() {
+ Flag<?> flag = new BooleanFlag(1);
+ mFeatureFlags.addFlag(flag);
+
+ // Assert and capture that a plugin listener was added.
+ ArgumentCaptor<FlagReader.Listener> pluginListenerCaptor =
+ ArgumentCaptor.forClass(FlagReader.Listener.class);
+ verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture());
+ FlagReader.Listener pluginListener = pluginListenerCaptor.getValue();
+
+ // Add a listener for the flag
+ final Flag<?>[] changedFlag = {null};
+ FeatureFlags.Listener listener = f -> changedFlag[0] = f;
+ mFeatureFlags.addFlagListener(flag, listener);
+
+ // Signal a change.
+ pluginListener.onFlagChanged(flag.getId());
+
+ // Assert that the change was for the correct flag.
+ assertThat(changedFlag[0]).isEqualTo(flag);
+
+ changedFlag[0] = null;
+
+ // Now remove the listener.
+ mFeatureFlags.removeFlagListener(flag, listener);
+ // Signal a change.
+ pluginListener.onFlagChanged(flag.getId());
+ // Assert that the change was not triggered
+ assertThat(changedFlag[0]).isNull();
+
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java
new file mode 100644
index 000000000000..25c302885e07
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.systemui.flags;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@SmallTest
+public class FlagsTest extends SysuiTestCase {
+
+ @Test
+ public void testDuplicateFlagIdCheckWorks() {
+ List<Pair<String, Flag<?>>> flags = collectFlags(DuplicateFlagContainer.class);
+ Map<Integer, List<String>> duplicates = groupDuplicateFlags(flags);
+
+ assertWithMessage(generateAssertionMessage(duplicates))
+ .that(duplicates.size()).isEqualTo(2);
+ }
+
+ @Test
+ public void testNoDuplicateFlagIds() {
+ List<Pair<String, Flag<?>>> flags = collectFlags(Flags.class);
+ Map<Integer, List<String>> duplicates = groupDuplicateFlags(flags);
+
+ assertWithMessage(generateAssertionMessage(duplicates))
+ .that(duplicates.size()).isEqualTo(0);
+ }
+
+ private String generateAssertionMessage(Map<Integer, List<String>> duplicates) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("Duplicate flag keys found: {");
+ for (int id : duplicates.keySet()) {
+ stringBuilder
+ .append(" ")
+ .append(id)
+ .append(": [")
+ .append(String.join(", ", duplicates.get(id)))
+ .append("]");
+ }
+ stringBuilder.append(" }");
+
+ return stringBuilder.toString();
+ }
+
+ private List<Pair<String, Flag<?>>> collectFlags(Class<?> clz) {
+ List<Pair<String, Flag<?>>> flags = new ArrayList<>();
+
+ Field[] fields = clz.getFields();
+
+ for (Field field : fields) {
+ Class<?> t = field.getType();
+ if (Flag.class.isAssignableFrom(t)) {
+ try {
+ flags.add(Pair.create(field.getName(), (Flag<?>) field.get(null)));
+ } catch (IllegalAccessException e) {
+ // no-op
+ }
+ }
+ }
+
+ return flags;
+ }
+
+ private Map<Integer, List<String>> groupDuplicateFlags(List<Pair<String, Flag<?>>> flags) {
+ Map<Integer, List<String>> grouping = new HashMap<>();
+
+ for (Pair<String, Flag<?>> flag : flags) {
+ grouping.putIfAbsent(flag.second.getId(), new ArrayList<>());
+ grouping.get(flag.second.getId()).add(flag.first);
+ }
+
+ Map<Integer, List<String>> result = new HashMap<>();
+ for (Integer id : grouping.keySet()) {
+ if (grouping.get(id).size() > 1) {
+ result.put(id, grouping.get(id));
+ }
+ }
+
+ return result;
+ }
+
+ private static class DuplicateFlagContainer {
+ public static final BooleanFlag A_FLAG = new BooleanFlag(0);
+ public static final BooleanFlag B_FLAG = new BooleanFlag(0);
+ public static final StringFlag C_FLAG = new StringFlag(0);
+
+ public static final BooleanFlag D_FLAG = new BooleanFlag(1);
+
+ public static final DoubleFlag E_FLAG = new DoubleFlag(3);
+ public static final DoubleFlag F_FLAG = new DoubleFlag(3);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 509ef82ee426..3b1c5f32a772 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -76,6 +76,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.Executor;
@SmallTest
@@ -106,7 +107,6 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
@Mock private IWindowManager mWindowManager;
@Mock private Executor mBackgroundExecutor;
@Mock private UiEventLogger mUiEventLogger;
- @Mock private GlobalActionsInfoProvider mInfoProvider;
@Mock private RingerModeTracker mRingerModeTracker;
@Mock private RingerModeLiveData mRingerModeLiveData;
@Mock private SysUiState mSysUiState;
@@ -154,12 +154,11 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
mWindowManager,
mBackgroundExecutor,
mUiEventLogger,
- mInfoProvider,
mRingerModeTracker,
mSysUiState,
mHandler,
mPackageManager,
- mStatusBar,
+ Optional.of(mStatusBar),
mKeyguardUpdateMonitor
);
mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
@@ -174,14 +173,14 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
public void testShouldLogShow() {
mGlobalActionsDialogLite.onShow(null);
mTestableLooper.processAllMessages();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_OPEN);
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_POWER_MENU_OPEN);
}
@Test
public void testShouldLogDismiss() {
mGlobalActionsDialogLite.onDismiss(mGlobalActionsDialogLite.mDialog);
mTestableLooper.processAllMessages();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_CLOSE);
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_POWER_MENU_CLOSE);
}
@Test
@@ -191,16 +190,16 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
String[] actions = {
- GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
};
doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog();
dialog.onBackPressed();
mTestableLooper.processAllMessages();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_BACK);
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_BACK);
}
@Test
@@ -210,17 +209,17 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
String[] actions = {
- GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
};
doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog();
GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener);
gestureListener.onSingleTapConfirmed(null);
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
}
@Test
@@ -231,10 +230,10 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
doReturn(true).when(mStatusBar).isKeyguardShowing();
String[] actions = {
- GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
};
doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog();
@@ -243,7 +242,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
MotionEvent start = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0);
gestureListener.onFling(start, end, 0, 1000);
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
verify(mStatusBar).animateExpandSettingsPanel(null);
}
@@ -255,10 +254,10 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
doReturn(false).when(mStatusBar).isKeyguardShowing();
String[] actions = {
- GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
};
doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog();
@@ -267,40 +266,40 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
MotionEvent start = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0);
gestureListener.onFling(start, end, 0, 1000);
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
verify(mStatusBar).animateExpandNotificationsPanel();
}
@Test
public void testShouldLogBugreportPress() throws InterruptedException {
- GlobalActionsDialog.BugReportAction bugReportAction =
+ GlobalActionsDialogLite.BugReportAction bugReportAction =
mGlobalActionsDialogLite.makeBugReportActionForTesting();
bugReportAction.onPress();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_BUGREPORT_PRESS);
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_BUGREPORT_PRESS);
}
@Test
public void testShouldLogBugreportLongPress() {
- GlobalActionsDialog.BugReportAction bugReportAction =
+ GlobalActionsDialogLite.BugReportAction bugReportAction =
mGlobalActionsDialogLite.makeBugReportActionForTesting();
bugReportAction.onLongPress();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS);
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS);
}
@Test
public void testShouldLogEmergencyDialerPress() {
- GlobalActionsDialog.EmergencyDialerAction emergencyDialerAction =
+ GlobalActionsDialogLite.EmergencyDialerAction emergencyDialerAction =
mGlobalActionsDialogLite.makeEmergencyDialerActionForTesting();
emergencyDialerAction.onPress();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS);
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS);
}
@Test
public void testShouldLogScreenshotPress() {
- GlobalActionsDialog.ScreenshotAction screenshotAction =
+ GlobalActionsDialogLite.ScreenshotAction screenshotAction =
mGlobalActionsDialogLite.makeScreenshotActionForTesting();
screenshotAction.onPress();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_SCREENSHOT_PRESS);
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_SCREENSHOT_PRESS);
}
@Test
@@ -309,7 +308,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
com.android.internal.R.integer.config_navBarInteractionMode,
WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON);
- GlobalActionsDialog.ScreenshotAction screenshotAction =
+ GlobalActionsDialogLite.ScreenshotAction screenshotAction =
mGlobalActionsDialogLite.makeScreenshotActionForTesting();
assertThat(screenshotAction.shouldShow()).isTrue();
}
@@ -320,12 +319,12 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
com.android.internal.R.integer.config_navBarInteractionMode,
WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON);
- GlobalActionsDialog.ScreenshotAction screenshotAction =
+ GlobalActionsDialogLite.ScreenshotAction screenshotAction =
mGlobalActionsDialogLite.makeScreenshotActionForTesting();
assertThat(screenshotAction.shouldShow()).isFalse();
}
- private void verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent event) {
+ private void verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent event) {
mTestableLooper.processAllMessages();
verify(mUiEventLogger, times(1))
.log(event);
@@ -346,19 +345,19 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
String[] actions = {
- GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
};
doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
mGlobalActionsDialogLite.createActionItems();
assertItemsOfType(mGlobalActionsDialogLite.mItems,
- GlobalActionsDialog.EmergencyAction.class,
- GlobalActionsDialog.LockDownAction.class,
- GlobalActionsDialog.ShutDownAction.class,
- GlobalActionsDialog.RestartAction.class);
+ GlobalActionsDialogLite.EmergencyAction.class,
+ GlobalActionsDialogLite.LockDownAction.class,
+ GlobalActionsDialogLite.ShutDownAction.class,
+ GlobalActionsDialogLite.RestartAction.class);
assertThat(mGlobalActionsDialogLite.mOverflowItems).isEmpty();
assertThat(mGlobalActionsDialogLite.mPowerItems).isEmpty();
}
@@ -370,19 +369,19 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
// make sure lockdown action will NOT be shown
doReturn(false).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
String[] actions = {
- GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
// lockdown action not allowed
- GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
};
doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
mGlobalActionsDialogLite.createActionItems();
assertItemsOfType(mGlobalActionsDialogLite.mItems,
- GlobalActionsDialog.EmergencyAction.class,
- GlobalActionsDialog.ShutDownAction.class,
- GlobalActionsDialog.RestartAction.class);
+ GlobalActionsDialogLite.EmergencyAction.class,
+ GlobalActionsDialogLite.ShutDownAction.class,
+ GlobalActionsDialogLite.RestartAction.class);
assertThat(mGlobalActionsDialogLite.mOverflowItems).isEmpty();
assertThat(mGlobalActionsDialogLite.mPowerItems).isEmpty();
}
@@ -392,7 +391,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
GlobalActionsDialogLite.LockDownAction lockDownAction =
mGlobalActionsDialogLite.new LockDownAction();
lockDownAction.onPress();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_LOCKDOWN_PRESS);
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_LOCKDOWN_PRESS);
}
@Test
@@ -400,7 +399,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
GlobalActionsDialogLite.ShutDownAction shutDownAction =
mGlobalActionsDialogLite.new ShutDownAction();
shutDownAction.onPress();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_SHUTDOWN_PRESS);
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_SHUTDOWN_PRESS);
}
@Test
@@ -408,7 +407,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
GlobalActionsDialogLite.ShutDownAction shutDownAction =
mGlobalActionsDialogLite.new ShutDownAction();
shutDownAction.onLongPress();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_SHUTDOWN_LONG_PRESS);
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_SHUTDOWN_LONG_PRESS);
}
@Test
@@ -416,7 +415,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
GlobalActionsDialogLite.RestartAction restartAction =
mGlobalActionsDialogLite.new RestartAction();
restartAction.onPress();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_REBOOT_PRESS);
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_REBOOT_PRESS);
}
@Test
@@ -424,7 +423,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
GlobalActionsDialogLite.RestartAction restartAction =
mGlobalActionsDialogLite.new RestartAction();
restartAction.onLongPress();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_REBOOT_LONG_PRESS);
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_REBOOT_LONG_PRESS);
}
@Test
@@ -436,10 +435,10 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
doReturn(false).when(mStatusBar).isKeyguardShowing();
String[] actions = {
- GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
};
doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
deleted file mode 100644
index 338bb3037331..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ /dev/null
@@ -1,575 +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.systemui.globalactions;
-
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.IActivityManager;
-import android.app.admin.DevicePolicyManager;
-import android.app.trust.TrustManager;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.media.AudioManager;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserManager;
-import android.service.dreams.IDreamManager;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.IWindowManager;
-import android.view.View;
-import android.view.WindowManagerPolicyConstants;
-import android.widget.FrameLayout;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.GlobalActions;
-import com.android.systemui.plugins.GlobalActionsPanelPlugin;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.util.RingerModeLiveData;
-import com.android.systemui.util.RingerModeTracker;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.regex.Pattern;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class GlobalActionsDialogTest extends SysuiTestCase {
- private static final long UI_TIMEOUT_MILLIS = 5000; // 5 sec
- private static final Pattern CANCEL_BUTTON =
- Pattern.compile("cancel", Pattern.CASE_INSENSITIVE);
-
- private GlobalActionsDialog mGlobalActionsDialog;
-
- @Mock private GlobalActions.GlobalActionsManager mWindowManagerFuncs;
- @Mock private AudioManager mAudioManager;
- @Mock private IDreamManager mDreamManager;
- @Mock private DevicePolicyManager mDevicePolicyManager;
- @Mock private LockPatternUtils mLockPatternUtils;
- @Mock private BroadcastDispatcher mBroadcastDispatcher;
- @Mock private TelephonyListenerManager mTelephonyListenerManager;
- @Mock private GlobalSettings mGlobalSettings;
- @Mock private Resources mResources;
- @Mock private ConfigurationController mConfigurationController;
- @Mock private ActivityStarter mActivityStarter;
- @Mock private KeyguardStateController mKeyguardStateController;
- @Mock private UserManager mUserManager;
- @Mock private TrustManager mTrustManager;
- @Mock private IActivityManager mActivityManager;
- @Mock private MetricsLogger mMetricsLogger;
- @Mock private SysuiColorExtractor mColorExtractor;
- @Mock private IStatusBarService mStatusBarService;
- @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
- @Mock private IWindowManager mWindowManager;
- @Mock private Executor mBackgroundExecutor;
- @Mock private UiEventLogger mUiEventLogger;
- @Mock private RingerModeTracker mRingerModeTracker;
- @Mock private RingerModeLiveData mRingerModeLiveData;
- @Mock private SysUiState mSysUiState;
- @Mock GlobalActionsPanelPlugin mWalletPlugin;
- @Mock GlobalActionsPanelPlugin.PanelViewController mWalletController;
- @Mock private Handler mHandler;
- @Mock private UserTracker mUserTracker;
- @Mock private PackageManager mPackageManager;
- @Mock private SecureSettings mSecureSettings;
- @Mock private StatusBar mStatusBar;
- @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
- private TestableLooper mTestableLooper;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mTestableLooper = TestableLooper.get(this);
- allowTestableLooperAsMainThread();
-
- when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
- when(mResources.getConfiguration()).thenReturn(
- getContext().getResources().getConfiguration());
-
- mGlobalActionsDialog = new GlobalActionsDialog(mContext,
- mWindowManagerFuncs,
- mAudioManager,
- mDreamManager,
- mDevicePolicyManager,
- mLockPatternUtils,
- mBroadcastDispatcher,
- mTelephonyListenerManager,
- mGlobalSettings,
- mSecureSettings,
- null,
- mResources,
- mConfigurationController,
- mActivityStarter,
- mKeyguardStateController,
- mUserManager,
- mTrustManager,
- mActivityManager,
- null,
- mMetricsLogger,
- mColorExtractor,
- mStatusBarService,
- mNotificationShadeWindowController,
- mWindowManager,
- mBackgroundExecutor,
- mUiEventLogger,
- mRingerModeTracker,
- mSysUiState,
- mHandler,
- mPackageManager,
- mStatusBar,
- mKeyguardUpdateMonitor
- );
- mGlobalActionsDialog.setZeroDialogPressDelayForTesting();
-
- ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors();
- backdropColors.setMainColor(Color.BLACK);
- when(mColorExtractor.getNeutralColors()).thenReturn(backdropColors);
- when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
- }
-
- @Test
- public void testShouldLogShow() {
- mGlobalActionsDialog.onShow(null);
- mTestableLooper.processAllMessages();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_OPEN);
- }
-
- @Test
- public void testShouldLogDismiss() {
- mGlobalActionsDialog.onDismiss(mGlobalActionsDialog.mDialog);
- mTestableLooper.processAllMessages();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_CLOSE);
- }
-
- @Test
- public void testShouldLogBugreportPress() throws InterruptedException {
- GlobalActionsDialog.BugReportAction bugReportAction =
- mGlobalActionsDialog.makeBugReportActionForTesting();
- bugReportAction.onPress();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_BUGREPORT_PRESS);
- }
-
- @Test
- public void testShouldLogBugreportLongPress() {
- GlobalActionsDialog.BugReportAction bugReportAction =
- mGlobalActionsDialog.makeBugReportActionForTesting();
- bugReportAction.onLongPress();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS);
- }
-
- @Test
- public void testShouldLogEmergencyDialerPress() {
- GlobalActionsDialog.EmergencyDialerAction emergencyDialerAction =
- mGlobalActionsDialog.makeEmergencyDialerActionForTesting();
- emergencyDialerAction.onPress();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS);
- }
-
- @Test
- public void testShouldLogScreenshotPress() {
- GlobalActionsDialog.ScreenshotAction screenshotAction =
- mGlobalActionsDialog.makeScreenshotActionForTesting();
- screenshotAction.onPress();
- verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_SCREENSHOT_PRESS);
- }
-
- @Test
- public void testShouldShowScreenshot() {
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.integer.config_navBarInteractionMode,
- WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON);
-
- GlobalActionsDialog.ScreenshotAction screenshotAction =
- mGlobalActionsDialog.makeScreenshotActionForTesting();
- assertThat(screenshotAction.shouldShow()).isTrue();
- }
-
- @Test
- public void testShouldNotShowScreenshot() {
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.integer.config_navBarInteractionMode,
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON);
-
- GlobalActionsDialog.ScreenshotAction screenshotAction =
- mGlobalActionsDialog.makeScreenshotActionForTesting();
- assertThat(screenshotAction.shouldShow()).isFalse();
- }
-
- private void verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent event) {
- mTestableLooper.processAllMessages();
- verify(mUiEventLogger, times(1))
- .log(event);
- }
-
- @SafeVarargs
- private static <T> void assertItemsOfType(List<T> stuff, Class<? extends T>... classes) {
- assertThat(stuff).hasSize(classes.length);
- for (int i = 0; i < stuff.size(); i++) {
- assertThat(stuff.get(i)).isInstanceOf(classes[i]);
- }
- }
-
- @Test
- public void testCreateActionItems_maxThree_noOverflow() {
- mGlobalActionsDialog = spy(mGlobalActionsDialog);
- // allow 3 items to be shown
- doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems();
- // ensure items are not blocked by keyguard or device provisioning
- doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any());
- String[] actions = {
- GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
- };
- doReturn(actions).when(mGlobalActionsDialog).getDefaultActions();
- mGlobalActionsDialog.createActionItems();
-
- assertItemsOfType(mGlobalActionsDialog.mItems,
- GlobalActionsDialog.EmergencyAction.class,
- GlobalActionsDialog.ShutDownAction.class,
- GlobalActionsDialog.RestartAction.class);
- assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty();
- assertThat(mGlobalActionsDialog.mPowerItems).isEmpty();
- }
-
- @Test
- public void testCreateActionItems_maxThree_condensePower() {
- mGlobalActionsDialog = spy(mGlobalActionsDialog);
- // allow 3 items to be shown
- doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems();
- // ensure items are not blocked by keyguard or device provisioning
- doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any());
- // make sure lockdown action will be shown
- doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any());
- String[] actions = {
- GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
- };
- doReturn(actions).when(mGlobalActionsDialog).getDefaultActions();
- mGlobalActionsDialog.createActionItems();
-
- assertItemsOfType(mGlobalActionsDialog.mItems,
- GlobalActionsDialog.EmergencyAction.class,
- GlobalActionsDialog.LockDownAction.class,
- GlobalActionsDialog.PowerOptionsAction.class);
- assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty();
- assertItemsOfType(mGlobalActionsDialog.mPowerItems,
- GlobalActionsDialog.ShutDownAction.class,
- GlobalActionsDialog.RestartAction.class);
- }
-
- @Test
- public void testCreateActionItems_maxThree_condensePower_splitPower() {
- mGlobalActionsDialog = spy(mGlobalActionsDialog);
- // allow 3 items to be shown
- doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems();
- // make sure lockdown action will be shown
- doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any());
- // make sure bugreport also shown
- doReturn(true).when(mGlobalActionsDialog).shouldDisplayBugReport(any());
- // ensure items are not blocked by keyguard or device provisioning
- doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any());
- String[] actions = {
- GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_BUGREPORT,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
- };
- doReturn(actions).when(mGlobalActionsDialog).getDefaultActions();
- mGlobalActionsDialog.createActionItems();
-
- assertItemsOfType(mGlobalActionsDialog.mItems,
- GlobalActionsDialog.EmergencyAction.class,
- GlobalActionsDialog.LockDownAction.class,
- GlobalActionsDialog.PowerOptionsAction.class);
- assertItemsOfType(mGlobalActionsDialog.mOverflowItems,
- GlobalActionsDialog.BugReportAction.class);
- assertItemsOfType(mGlobalActionsDialog.mPowerItems,
- GlobalActionsDialog.ShutDownAction.class,
- GlobalActionsDialog.RestartAction.class);
- }
-
- @Test
- public void testCreateActionItems_maxFour_condensePower() {
- mGlobalActionsDialog = spy(mGlobalActionsDialog);
- // allow 3 items to be shown
- doReturn(4).when(mGlobalActionsDialog).getMaxShownPowerItems();
- // make sure lockdown action will be shown
- doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any());
- // ensure items are not blocked by keyguard or device provisioning
- doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any());
- String[] actions = {
- GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT
- };
- doReturn(actions).when(mGlobalActionsDialog).getDefaultActions();
- mGlobalActionsDialog.createActionItems();
-
- assertItemsOfType(mGlobalActionsDialog.mItems,
- GlobalActionsDialog.EmergencyAction.class,
- GlobalActionsDialog.LockDownAction.class,
- GlobalActionsDialog.PowerOptionsAction.class,
- GlobalActionsDialog.ScreenshotAction.class);
- assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty();
- assertItemsOfType(mGlobalActionsDialog.mPowerItems,
- GlobalActionsDialog.ShutDownAction.class,
- GlobalActionsDialog.RestartAction.class);
- }
-
- @Test
- public void testCreateActionItems_maxThree_doNotCondensePower() {
- mGlobalActionsDialog = spy(mGlobalActionsDialog);
- // allow 3 items to be shown
- doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems();
- // make sure lockdown action will be shown
- doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any());
- // make sure bugreport is also shown
- doReturn(true).when(mGlobalActionsDialog).shouldDisplayBugReport(any());
- // ensure items are not blocked by keyguard or device provisioning
- doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any());
- String[] actions = {
- GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_BUGREPORT,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
- };
- doReturn(actions).when(mGlobalActionsDialog).getDefaultActions();
- mGlobalActionsDialog.createActionItems();
-
- assertItemsOfType(mGlobalActionsDialog.mItems,
- GlobalActionsDialog.EmergencyAction.class,
- GlobalActionsDialog.ShutDownAction.class,
- GlobalActionsDialog.BugReportAction.class);
- assertItemsOfType(mGlobalActionsDialog.mOverflowItems,
- GlobalActionsDialog.LockDownAction.class);
- assertThat(mGlobalActionsDialog.mPowerItems).isEmpty();
- }
-
- @Test
- public void testCreateActionItems_maxAny() {
- mGlobalActionsDialog = spy(mGlobalActionsDialog);
- // allow any number of power menu items to be shown
- doReturn(Integer.MAX_VALUE).when(mGlobalActionsDialog).getMaxShownPowerItems();
- // ensure items are not blocked by keyguard or device provisioning
- doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any());
- // make sure lockdown action will be shown
- doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any());
- String[] actions = {
- GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
- };
- doReturn(actions).when(mGlobalActionsDialog).getDefaultActions();
- mGlobalActionsDialog.createActionItems();
-
- assertItemsOfType(mGlobalActionsDialog.mItems,
- GlobalActionsDialog.EmergencyAction.class,
- GlobalActionsDialog.ShutDownAction.class,
- GlobalActionsDialog.RestartAction.class,
- GlobalActionsDialog.LockDownAction.class);
- assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty();
- assertThat(mGlobalActionsDialog.mPowerItems).isEmpty();
- }
-
- @Test
- public void testCreateActionItems_maxThree_lockdownDisabled_doesNotShowLockdown() {
- mGlobalActionsDialog = spy(mGlobalActionsDialog);
- // allow only 3 items to be shown
- doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems();
- // make sure lockdown action will NOT be shown
- doReturn(false).when(mGlobalActionsDialog).shouldDisplayLockdown(any());
- String[] actions = {
- GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
- // lockdown action not allowed
- GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
- };
- doReturn(actions).when(mGlobalActionsDialog).getDefaultActions();
- mGlobalActionsDialog.createActionItems();
-
- assertItemsOfType(mGlobalActionsDialog.mItems,
- GlobalActionsDialog.EmergencyAction.class,
- GlobalActionsDialog.ShutDownAction.class,
- GlobalActionsDialog.RestartAction.class);
- assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty();
- assertThat(mGlobalActionsDialog.mPowerItems).isEmpty();
- }
-
- @Test
- public void testCreateActionItems_shouldShowAction_excludeBugReport() {
- mGlobalActionsDialog = spy(mGlobalActionsDialog);
- // allow only 3 items to be shown
- doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems();
- doReturn(true).when(mGlobalActionsDialog).shouldDisplayBugReport(any());
- // exclude bugreport in shouldShowAction to demonstrate how any button can be removed
- doAnswer(
- invocation -> !(invocation.getArgument(0)
- instanceof GlobalActionsDialog.BugReportAction))
- .when(mGlobalActionsDialog).shouldShowAction(any());
-
- String[] actions = {
- GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
- // bugreport action not allowed
- GlobalActionsDialog.GLOBAL_ACTION_KEY_BUGREPORT,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
- };
- doReturn(actions).when(mGlobalActionsDialog).getDefaultActions();
- mGlobalActionsDialog.createActionItems();
-
- assertItemsOfType(mGlobalActionsDialog.mItems,
- GlobalActionsDialog.EmergencyAction.class,
- GlobalActionsDialog.ShutDownAction.class,
- GlobalActionsDialog.RestartAction.class);
- assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty();
- assertThat(mGlobalActionsDialog.mPowerItems).isEmpty();
- }
-
- @Test
- public void testShouldShowLockScreenMessage() throws RemoteException {
- mGlobalActionsDialog = spy(mGlobalActionsDialog);
- mGlobalActionsDialog.mDialog = null;
- when(mKeyguardStateController.isUnlocked()).thenReturn(false);
- when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo());
- when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED);
- mGlobalActionsDialog.mShowLockScreenCards = false;
- setupDefaultActions();
- when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController);
- when(mWalletController.getPanelContent()).thenReturn(new FrameLayout(mContext));
-
- mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin);
-
- GlobalActionsDialog.ActionsDialog dialog =
- (GlobalActionsDialog.ActionsDialog) mGlobalActionsDialog.mDialog;
- assertThat(dialog).isNotNull();
- assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.VISIBLE);
-
- // Dismiss the dialog so that it does not pollute other tests
- mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin);
- }
-
- @Test
- public void testShouldNotShowLockScreenMessage_whenWalletShownOnLockScreen()
- throws RemoteException {
- mGlobalActionsDialog = spy(mGlobalActionsDialog);
- mGlobalActionsDialog.mDialog = null;
- when(mKeyguardStateController.isUnlocked()).thenReturn(false);
- when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo());
- when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED);
- mGlobalActionsDialog.mShowLockScreenCards = true;
- setupDefaultActions();
- when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController);
- when(mWalletController.getPanelContent()).thenReturn(new FrameLayout(mContext));
-
- mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin);
-
- GlobalActionsDialog.ActionsDialog dialog =
- (GlobalActionsDialog.ActionsDialog) mGlobalActionsDialog.mDialog;
- assertThat(dialog).isNotNull();
- assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.GONE);
-
- // Dismiss the dialog so that it does not pollute other tests
- mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin);
- }
-
- @Test
- public void testShouldNotShowLockScreenMessage_whenWalletBothDisabled()
- throws RemoteException {
- mGlobalActionsDialog = spy(mGlobalActionsDialog);
- mGlobalActionsDialog.mDialog = null;
- when(mKeyguardStateController.isUnlocked()).thenReturn(false);
-
- when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo());
- when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED);
- mGlobalActionsDialog.mShowLockScreenCards = true;
- setupDefaultActions();
- when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController);
- when(mWalletController.getPanelContent()).thenReturn(null);
-
- mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin);
-
- GlobalActionsDialog.ActionsDialog dialog =
- (GlobalActionsDialog.ActionsDialog) mGlobalActionsDialog.mDialog;
- assertThat(dialog).isNotNull();
- assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.GONE);
-
- // Dismiss the dialog so that it does not pollute other tests
- mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin);
- }
-
- private UserInfo newUserInfo() {
- return new UserInfo(0, null, null, UserInfo.FLAG_PRIMARY, null);
- }
-
- private void setupDefaultActions() {
- String[] actions = {
- GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
- GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
- };
- doReturn(actions).when(mGlobalActionsDialog).getDefaultActions();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsInfoProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsInfoProviderTest.kt
deleted file mode 100644
index 302a8d3f2efa..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsInfoProviderTest.kt
+++ /dev/null
@@ -1,134 +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.google.android.systemui.globalactions
-
-import android.content.Context
-import android.content.SharedPreferences
-import android.content.res.Configuration
-import android.content.res.Resources
-import android.service.quickaccesswallet.QuickAccessWalletClient
-import android.testing.AndroidTestingRunner
-import android.view.ViewGroup
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.controls.controller.ControlsController
-import com.android.systemui.globalactions.GlobalActionsInfoProvider
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.SysuiTestCase
-import junit.framework.Assert.assertFalse
-import junit.framework.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyObject
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
-
-private const val PREFERENCE = "global_actions_info_prefs"
-private const val KEY_VIEW_COUNT = "view_count"
-
-private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class GlobalActionsInfoProviderTest : SysuiTestCase() {
-
- @Mock private lateinit var walletClient: QuickAccessWalletClient
- @Mock private lateinit var controlsController: ControlsController
- @Mock private lateinit var activityStarter: ActivityStarter
- @Mock private lateinit var mockContext: Context
- @Mock private lateinit var mockResources: Resources
- @Mock private lateinit var sharedPrefs: SharedPreferences
- @Mock private lateinit var sharedPrefsEditor: SharedPreferences.Editor
-
- private lateinit var infoProvider: GlobalActionsInfoProvider
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- mockContext = spy(context)
- mockResources = spy(context.resources)
- whenever(mockContext.resources).thenReturn(mockResources)
- whenever(mockResources.getBoolean(R.bool.global_actions_show_change_info))
- .thenReturn(true)
- whenever(mockContext.getSharedPreferences(eq(PREFERENCE), anyInt()))
- .thenReturn(sharedPrefs)
- whenever(sharedPrefs.edit()).thenReturn(sharedPrefsEditor)
- whenever(sharedPrefsEditor.putInt(anyString(), anyInt())).thenReturn(sharedPrefsEditor)
- whenever(sharedPrefsEditor.putBoolean(anyString(), anyBoolean()))
- .thenReturn(sharedPrefsEditor)
-
- infoProvider = GlobalActionsInfoProvider(
- mockContext,
- walletClient,
- controlsController,
- activityStarter
- )
- }
-
- @Test
- fun testIsEligible_noCards() {
- whenever(sharedPrefs.contains(eq(KEY_VIEW_COUNT))).thenReturn(false)
- whenever(walletClient.isWalletFeatureAvailable).thenReturn(false)
-
- assertFalse(infoProvider.shouldShowMessage())
- }
-
- @Test
- fun testIsEligible_hasCards() {
- whenever(sharedPrefs.contains(eq(KEY_VIEW_COUNT))).thenReturn(false)
- whenever(walletClient.isWalletFeatureAvailable).thenReturn(true)
-
- assertTrue(infoProvider.shouldShowMessage())
- }
-
- @Test
- fun testNotEligible_shouldNotShow() {
- whenever(mockResources.getBoolean(R.bool.global_actions_show_change_info))
- .thenReturn(false)
-
- assertFalse(infoProvider.shouldShowMessage())
- }
-
- @Test
- fun testTooManyButtons_doesNotAdd() {
- val configuration = Configuration()
- configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
- whenever(mockResources.configuration).thenReturn(configuration)
-
- val parent = mock(ViewGroup::class.java)
- infoProvider.addPanel(mockContext, parent, 5, { })
-
- verify(parent, never()).addView(anyObject(), anyInt())
- }
-
- @Test
- fun testLimitTimesShown() {
- whenever(sharedPrefs.getInt(eq(KEY_VIEW_COUNT), anyInt())).thenReturn(4)
-
- assertFalse(infoProvider.shouldShowMessage())
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessControllerTest.kt
deleted file mode 100644
index cb05a6b21b3a..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/FaceAuthScreenBrightnessControllerTest.kt
+++ /dev/null
@@ -1,137 +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.systemui.keyguard
-
-import android.animation.ValueAnimator
-import android.content.res.Resources
-import android.hardware.biometrics.BiometricSourceType
-import android.os.Handler
-import android.provider.Settings.System.SCREEN_BRIGHTNESS_FLOAT
-import android.testing.AndroidTestingRunner
-import android.util.TypedValue
-import android.view.View
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.Dumpable
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.settings.SystemSettings
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.anyString
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-const val INITIAL_BRIGHTNESS = 0.5f
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class FaceAuthScreenBrightnessControllerTest : SysuiTestCase() {
-
- @Mock
- lateinit var whiteOverlay: View
- @Mock
- lateinit var dumpManager: DumpManager
- @Mock
- lateinit var resources: Resources
- @Mock
- lateinit var mainHandler: Handler
- @Mock
- lateinit var globalSettings: GlobalSettings
- @Mock
- lateinit var systemSettings: SystemSettings
- @Mock
- lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock
- lateinit var notificationShadeWindowController: NotificationShadeWindowController
- @Mock
- lateinit var animator: ValueAnimator
- @Captor
- lateinit var keyguardUpdateCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
- lateinit var faceAuthScreenBrightnessController: FaceAuthScreenBrightnessController
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- faceAuthScreenBrightnessController = object : FaceAuthScreenBrightnessController(
- notificationShadeWindowController, keyguardUpdateMonitor, resources, globalSettings,
- systemSettings, mainHandler, dumpManager, true) {
- override fun createAnimator(start: Float, end: Float) = animator
- }
- `when`(systemSettings.getFloat(eq(SCREEN_BRIGHTNESS_FLOAT))).thenReturn(INITIAL_BRIGHTNESS)
- `when`(systemSettings.getFloat(eq(SCREEN_BRIGHTNESS_FLOAT), eq(1f)))
- .thenReturn(INITIAL_BRIGHTNESS)
- faceAuthScreenBrightnessController.attach(whiteOverlay)
- verify(keyguardUpdateMonitor).registerCallback(capture(keyguardUpdateCallback))
- }
-
- @Test
- fun init_registersDumpManager() {
- verify(dumpManager).registerDumpable(anyString(), any(Dumpable::class.java))
- }
-
- @Test
- fun init_registersKeyguardCallback() {
- verify(keyguardUpdateMonitor)
- .registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
- }
-
- @Test
- fun onBiometricRunningChanged_animatesBrightness() {
- clearInvocations(whiteOverlay)
- keyguardUpdateCallback.value
- .onBiometricRunningStateChanged(true, BiometricSourceType.FACE)
- verify(whiteOverlay).visibility = eq(View.VISIBLE)
- verify(animator).start()
- }
-
- @Test
- fun faceAuthWallpaper_whenFaceIsDisabledForUser() {
- faceAuthScreenBrightnessController.useFaceAuthWallpaper = true
- faceAuthScreenBrightnessController.faceAuthWallpaper
- verify(resources, never()).openRawResource(anyInt(), any(TypedValue::class.java))
- }
-
- @Test
- fun faceAuthWallpaper_whenFaceFlagIsDisabled() {
- faceAuthScreenBrightnessController.useFaceAuthWallpaper = true
- faceAuthScreenBrightnessController.faceAuthWallpaper
- verify(resources, never()).openRawResource(anyInt(), any(TypedValue::class.java))
- }
-
- @Test
- fun faceAuthWallpaper_whenFaceIsEnabledForUser() {
- faceAuthScreenBrightnessController.useFaceAuthWallpaper = true
- `when`(keyguardUpdateMonitor.isFaceAuthEnabledForUser(anyInt())).thenReturn(true)
- faceAuthScreenBrightnessController.faceAuthWallpaper
- verify(resources).openRawResource(anyInt(), any(TypedValue::class.java))
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 31d70f5c811f..1bb660e4cced 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -34,12 +34,14 @@ import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
+import android.os.RemoteException;
import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
+import com.android.internal.policy.IKeyguardDrawnCallback;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -55,6 +57,8 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
+import com.android.systemui.unfold.config.UnfoldTransitionConfig;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -63,6 +67,7 @@ import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -85,11 +90,14 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock NavigationModeController mNavigationModeController;
private @Mock KeyguardDisplayManager mKeyguardDisplayManager;
private @Mock DozeParameters mDozeParameters;
+ private @Mock UnfoldTransitionConfig mUnfoldTransitionConfig;
+ private @Mock UnfoldLightRevealOverlayAnimation mUnfoldAnimation;
private @Mock SysuiStatusBarStateController mStatusBarStateController;
private @Mock KeyguardStateController mKeyguardStateController;
private @Mock NotificationShadeDepthController mNotificationShadeDepthController;
private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ private @Mock IKeyguardDrawnCallback mKeyguardDrawnCallback;
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -120,6 +128,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mNavigationModeController,
mKeyguardDisplayManager,
mDozeParameters,
+ mUnfoldTransitionConfig,
+ () -> mUnfoldAnimation,
mStatusBarStateController,
mKeyguardStateController,
() -> mKeyguardUnlockAnimationController,
@@ -148,6 +158,33 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
}
@Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testUnfoldTransitionEnabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback()
+ throws RemoteException {
+ when(mUnfoldTransitionConfig.isEnabled()).thenReturn(true);
+
+ mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback);
+ TestableLooper.get(this).processAllMessages();
+ onUnfoldOverlayReady();
+
+ // Should be called when both unfold overlay and keyguard drawn ready
+ verify(mKeyguardDrawnCallback).onDrawn();
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback()
+ throws RemoteException {
+ when(mUnfoldTransitionConfig.isEnabled()).thenReturn(false);
+
+ mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback);
+ TestableLooper.get(this).processAllMessages();
+
+ // Should be called when only keyguard drawn
+ verify(mKeyguardDrawnCallback).onDrawn();
+ }
+
+ @Test
public void testIsAnimatingScreenOff() {
when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
@@ -187,4 +224,11 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
// then make sure it comes back
verify(mStatusBarKeyguardViewManager, atLeast(1)).show(null);
}
+
+ private void onUnfoldOverlayReady() {
+ ArgumentCaptor<Runnable> overlayReadyCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mUnfoldAnimation).onScreenTurningOn(overlayReadyCaptor.capture());
+ overlayReadyCaptor.getValue().run();
+ TestableLooper.get(this).processAllMessages();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index 9356c547db70..5e73dbcbc95d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -16,40 +16,54 @@
package com.android.systemui.keyguard;
+import static com.android.keyguard.LockIconView.ICON_LOCK;
+import static com.android.keyguard.LockIconView.ICON_UNLOCK;
+
import static junit.framework.Assert.assertEquals;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PointF;
+import android.graphics.drawable.AnimatedStateListDrawable;
+import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.DisplayMetrics;
import android.util.Pair;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardViewController;
import com.android.keyguard.LockIconView;
import com.android.keyguard.LockIconViewController;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import com.airbnb.lottie.LottieAnimationView;
@@ -68,7 +82,10 @@ import java.util.List;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class LockIconViewControllerTest extends SysuiTestCase {
+ private static final String UNLOCKED_LABEL = "unlocked";
+
private @Mock LockIconView mLockIconView;
+ private @Mock AnimatedStateListDrawable mIconDrawable;
private @Mock Context mContext;
private @Mock Resources mResources;
private @Mock DisplayMetrics mDisplayMetrics;
@@ -81,10 +98,11 @@ public class LockIconViewControllerTest extends SysuiTestCase {
private @Mock DumpManager mDumpManager;
private @Mock AccessibilityManager mAccessibilityManager;
private @Mock ConfigurationController mConfigurationController;
- private @Mock DelayableExecutor mDelayableExecutor;
private @Mock Vibrator mVibrator;
private @Mock AuthRippleController mAuthRippleController;
private @Mock LottieAnimationView mAodFp;
+ private @Mock LayoutInflater mLayoutInflater;
+ private FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
private LockIconViewController mLockIconViewController;
@@ -93,9 +111,22 @@ public class LockIconViewControllerTest extends SysuiTestCase {
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
private View.OnAttachStateChangeListener mAttachListener;
+ @Captor private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateCaptor =
+ ArgumentCaptor.forClass(KeyguardStateController.Callback.class);
+ private KeyguardStateController.Callback mKeyguardStateCallback;
+
+ @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor =
+ ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+ private StatusBarStateController.StateListener mStatusBarStateListener;
+
@Captor private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
private AuthController.Callback mAuthControllerCallback;
+ @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback>
+ mKeyguardUpdateMonitorCallbackCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
+
@Captor private ArgumentCaptor<PointF> mPointCaptor;
@Before
@@ -104,9 +135,16 @@ public class LockIconViewControllerTest extends SysuiTestCase {
when(mLockIconView.getResources()).thenReturn(mResources);
when(mLockIconView.getContext()).thenReturn(mContext);
+ when(mLockIconView.findViewById(R.layout.udfps_aod_lock_icon)).thenReturn(mAodFp);
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
- when(mLockIconView.findViewById(anyInt())).thenReturn(mAodFp);
+ when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL);
+ when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable);
+
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
mLockIconViewController = new LockIconViewController(
mLockIconView,
@@ -121,11 +159,42 @@ public class LockIconViewControllerTest extends SysuiTestCase {
mConfigurationController,
mDelayableExecutor,
mVibrator,
- mAuthRippleController
+ mAuthRippleController,
+ mResources,
+ mLayoutInflater
);
}
@Test
+ public void testIgnoreUdfpsWhenNotSupported() {
+ // GIVEN Udpfs sensor is NOT available
+ mLockIconViewController.init();
+ captureAttachListener();
+
+ // WHEN the view is attached
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN lottie animation should NOT be inflated
+ verify(mLayoutInflater, never()).inflate(eq(R.layout.udfps_aod_lock_icon), any());
+ }
+
+ @Test
+ public void testInflateUdfpsWhenSupported() {
+ // GIVEN Udpfs sensor is available
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
+
+ mLockIconViewController.init();
+ captureAttachListener();
+
+ // WHEN the view is attached
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN lottie animation should be inflated
+ verify(mLayoutInflater).inflate(eq(R.layout.udfps_aod_lock_icon), any());
+ }
+
+ @Test
public void testUpdateFingerprintLocationOnInit() {
// GIVEN fp sensor location is available pre-attached
Pair<Integer, PointF> udfps = setupUdfps();
@@ -155,6 +224,7 @@ public class LockIconViewControllerTest extends SysuiTestCase {
// WHEN all authenticators are registered
mAuthControllerCallback.onAllAuthenticatorsRegistered();
+ mDelayableExecutor.runAllReady();
// THEN lock icon view location is updated with the same coordinates as fpProps
verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(udfps.first));
@@ -191,6 +261,109 @@ public class LockIconViewControllerTest extends SysuiTestCase {
verify(mLockIconView).setUseBackground(false);
}
+ @Test
+ public void testUnlockIconShows_biometricUnlockedTrue() {
+ // GIVEN UDFPS sensor location is available
+ setupUdfps();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+ captureKeyguardUpdateMonitorCallback();
+
+ // GIVEN user has unlocked with a biometric auth (ie: face auth)
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+ reset(mLockIconView);
+
+ // WHEN face auth's biometric running state changes
+ mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
+ BiometricSourceType.FACE);
+
+ // THEN the unlock icon is shown
+ verify(mLockIconView).setContentDescription(UNLOCKED_LABEL);
+ }
+
+ @Test
+ public void testLockIconStartState() {
+ // GIVEN lock icon state
+ setupShowLockIcon();
+
+ // WHEN lock icon controller is initialized
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN the lock icon should show
+ verify(mLockIconView).updateIcon(ICON_LOCK, false);
+ }
+
+ @Test
+ public void testLockIcon_updateToUnlock() {
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+ captureKeyguardStateCallback();
+ reset(mLockIconView);
+
+ // WHEN the unlocked state changes to canDismissLockScreen=true
+ when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
+ mKeyguardStateCallback.onUnlockedChanged();
+
+ // THEN the unlock should show
+ verify(mLockIconView).updateIcon(ICON_UNLOCK, false);
+ }
+
+ @Test
+ public void testLockIcon_clearsIconOnAod_whenUdfpsNotEnrolled() {
+ // GIVEN udfps not enrolled
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false);
+
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+ captureStatusBarStateListener();
+ reset(mLockIconView);
+
+ // WHEN the dozing state changes
+ mStatusBarStateListener.onDozingChanged(true /* isDozing */);
+
+ // THEN the icon is cleared
+ verify(mLockIconView).clearIcon();
+ }
+
+ @Test
+ public void testLockIcon_updateToAodLock_whenUdfpsEnrolled() {
+ // GIVEN udfps enrolled
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
+
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+ captureStatusBarStateListener();
+ reset(mLockIconView);
+
+ // WHEN the dozing state changes
+ mStatusBarStateListener.onDozingChanged(true /* isDozing */);
+
+ // THEN the AOD lock icon should show
+ verify(mLockIconView).updateIcon(ICON_LOCK, true);
+ }
+
private Pair<Integer, PointF> setupUdfps() {
final PointF udfpsLocation = new PointF(50, 75);
final int radius = 33;
@@ -202,13 +375,23 @@ public class LockIconViewControllerTest extends SysuiTestCase {
/* component info */ new ArrayList<>(),
/* sensorType */ 3,
/* resetLockoutRequiresHwToken */ false,
- (int) udfpsLocation.x, (int) udfpsLocation.y, radius);
+ List.of(new SensorLocationInternal("" /* displayId */,
+ (int) udfpsLocation.x, (int) udfpsLocation.y, radius)));
when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation);
when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));
return new Pair(radius, udfpsLocation);
}
+ private void setupShowLockIcon() {
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
+ when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
+ }
+
private void captureAuthControllerCallback() {
verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture());
mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
@@ -218,4 +401,20 @@ public class LockIconViewControllerTest extends SysuiTestCase {
verify(mLockIconView).addOnAttachStateChangeListener(mAttachCaptor.capture());
mAttachListener = mAttachCaptor.getValue();
}
+
+ private void captureKeyguardStateCallback() {
+ verify(mKeyguardStateController).addCallback(mKeyguardStateCaptor.capture());
+ mKeyguardStateCallback = mKeyguardStateCaptor.getValue();
+ }
+
+ private void captureStatusBarStateListener() {
+ verify(mStatusBarStateController).addCallback(mStatusBarStateCaptor.capture());
+ mStatusBarStateListener = mStatusBarStateCaptor.getValue();
+ }
+
+ private void captureKeyguardUpdateMonitorCallback() {
+ verify(mKeyguardUpdateMonitor).registerCallback(
+ mKeyguardUpdateMonitorCallbackCaptor.capture());
+ mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java
index 06e597e1c87a..2d1b25806dcc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java
@@ -26,6 +26,7 @@ import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import org.junit.Before;
import org.junit.Test;
@@ -43,7 +44,7 @@ public class ScreenLifecycleTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
- mScreen = new ScreenLifecycle();
+ mScreen = new ScreenLifecycle(mock(DumpManager.class));
mScreenObserverMock = mock(ScreenLifecycle.Observer.class);
mScreen.addObserver(mScreenObserverMock);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
index 910b38105332..e453ff2dc7bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
@@ -29,6 +29,7 @@ import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import org.junit.Before;
import org.junit.Test;
@@ -49,7 +50,8 @@ public class WakefulnessLifecycleTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
mWallpaperManager = mock(IWallpaperManager.class);
- mWakefulness = new WakefulnessLifecycle(mContext, mWallpaperManager);
+ mWakefulness =
+ new WakefulnessLifecycle(mContext, mWallpaperManager, mock(DumpManager.class));
mWakefulnessObserver = mock(WakefulnessLifecycle.Observer.class);
mWakefulness.addObserver(mWakefulnessObserver);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
index 25ae67baa9be..8cc2776bc16b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
@@ -16,13 +16,12 @@
package com.android.systemui.media
+import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.FrameLayout
-import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -52,8 +51,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
private lateinit var statusBarStateController: SysuiStatusBarStateController
@Mock
private lateinit var configurationController: ConfigurationController
- @Mock
- private lateinit var featureFlags: FeatureFlags
+
@Mock
private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager
@JvmField @Rule
@@ -71,17 +69,17 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
whenever(notificationLockscreenUserManager.shouldShowLockscreenNotifications())
.thenReturn(true)
whenever(mediaHost.hostView).thenReturn(hostView)
-
+ hostView.layoutParams = FrameLayout.LayoutParams(100, 100)
keyguardMediaController = KeyguardMediaController(
mediaHost,
bypassController,
statusBarStateController,
notificationLockscreenUserManager,
- featureFlags,
context,
configurationController
)
keyguardMediaController.attachSinglePaneContainer(mediaHeaderView)
+ keyguardMediaController.useSplitShade = false
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
new file mode 100644
index 000000000000..175ec87fd943
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.systemui.media
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import javax.inject.Provider
+
+private val DATA = MediaData(
+ userId = -1,
+ initialized = false,
+ backgroundColor = 0,
+ app = null,
+ appIcon = null,
+ artist = null,
+ song = null,
+ artwork = null,
+ actions = emptyList(),
+ actionsToShowInCompact = emptyList(),
+ packageName = "INVALID",
+ token = null,
+ clickIntent = null,
+ device = null,
+ active = true,
+ resumeAction = null)
+
+private val SMARTSPACE_KEY = "smartspace"
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+class MediaCarouselControllerTest : SysuiTestCase() {
+
+ @Mock lateinit var mediaControlPanelFactory: Provider<MediaControlPanel>
+ @Mock lateinit var panel: MediaControlPanel
+ @Mock lateinit var visualStabilityManager: VisualStabilityManager
+ @Mock lateinit var mediaHostStatesManager: MediaHostStatesManager
+ @Mock lateinit var activityStarter: ActivityStarter
+ @Mock @Main private lateinit var executor: DelayableExecutor
+ @Mock lateinit var mediaDataManager: MediaDataManager
+ @Mock lateinit var configurationController: ConfigurationController
+ @Mock lateinit var falsingCollector: FalsingCollector
+ @Mock lateinit var falsingManager: FalsingManager
+ @Mock lateinit var dumpManager: DumpManager
+
+ private val clock = FakeSystemClock()
+ private lateinit var mediaCarouselController: MediaCarouselController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ mediaCarouselController = MediaCarouselController(
+ context,
+ mediaControlPanelFactory,
+ visualStabilityManager,
+ mediaHostStatesManager,
+ activityStarter,
+ clock,
+ executor,
+ mediaDataManager,
+ configurationController,
+ falsingCollector,
+ falsingManager,
+ dumpManager
+ )
+
+ MediaPlayerData.clear()
+ }
+
+ @Test
+ fun testPlayerOrdering() {
+ // Test values: key, data, last active time
+ val playingLocal = Triple("playing local",
+ DATA.copy(active = true, isPlaying = true, isLocalSession = true, resumption = false),
+ 4500L)
+
+ val playingRemote = Triple("playing remote",
+ DATA.copy(active = true, isPlaying = true, isLocalSession = false, resumption = false),
+ 5000L)
+
+ val pausedLocal = Triple("paused local",
+ DATA.copy(active = true, isPlaying = false, isLocalSession = true, resumption = false),
+ 1000L)
+
+ val pausedRemote = Triple("paused remote",
+ DATA.copy(active = true, isPlaying = false, isLocalSession = false, resumption = false),
+ 2000L)
+
+ val resume1 = Triple("resume 1",
+ DATA.copy(active = false, isPlaying = false, isLocalSession = true, resumption = true),
+ 500L)
+
+ val resume2 = Triple("resume 2",
+ DATA.copy(active = false, isPlaying = false, isLocalSession = true, resumption = true),
+ 1000L)
+
+ // Expected ordering for media players:
+ // Actively playing local sessions
+ // Actively playing remote sessions
+ // Paused sessions, by last active
+ // Resume controls, by last active
+
+ val expected = listOf(playingLocal, playingRemote, pausedRemote, pausedLocal, resume2,
+ resume1)
+
+ expected.forEach {
+ clock.setCurrentTimeMillis(it.third)
+ MediaPlayerData.addMediaPlayer(it.first, it.second.copy(notificationKey = it.first),
+ panel, clock)
+ }
+
+ for ((index, key) in MediaPlayerData.playerKeys().withIndex()) {
+ assertEquals(expected.get(index).first, key.data.notificationKey)
+ }
+ }
+
+ @Test
+ fun testOrderWithSmartspace_prioritized() {
+ testPlayerOrdering()
+
+ // If smartspace is prioritized
+ MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
+ true, clock)
+
+ // Then it should be shown immediately after any actively playing controls
+ assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
+ }
+
+ @Test
+ fun testOrderWithSmartspace_notPrioritized() {
+ testPlayerOrdering()
+
+ // If smartspace is not prioritized
+ MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
+ false, clock)
+
+ // Then it should be shown at the end of the carousel
+ val size = MediaPlayerData.playerKeys().size
+ assertTrue(MediaPlayerData.playerKeys().elementAt(size - 1).isSsMediaRec)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 42629f545559..bf5a6e4086f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -41,6 +41,7 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.FakeExecutor
@@ -95,6 +96,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Mock private lateinit var collapsedSet: ConstraintSet
@Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
@Mock private lateinit var mediaCarouselController: MediaCarouselController
+ @Mock private lateinit var falsingManager: FalsingManager
private lateinit var appIcon: ImageView
private lateinit var albumView: ImageView
private lateinit var titleText: TextView
@@ -131,8 +133,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
whenever(mediaViewController.collapsedLayout).thenReturn(collapsedSet)
player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
- seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil,
- mediaOutputDialogFactory, mediaCarouselController)
+ seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil,
+ mediaOutputDialogFactory, mediaCarouselController, falsingManager)
whenever(seekBarViewModel.progress).thenReturn(seekBarData)
// Mock out a view holder for the player to attach to.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index 28aed2085528..a435e7961b35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -86,6 +86,7 @@ class MediaDataFilterTest : SysuiTestCase() {
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ MediaPlayerData.clear()
mediaDataFilter = MediaDataFilter(context, broadcastDispatcher, mediaResumeListener,
lockscreenUserManager, executor, clock)
mediaDataFilter.mediaDataManager = mediaDataManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 47c5545ab587..2b2fc513d03c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -255,6 +255,7 @@ class MediaDataManagerTest : SysuiTestCase() {
.onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
eq(false))
assertThat(mediaDataCaptor.value.resumption).isTrue()
+ assertThat(mediaDataCaptor.value.isPlaying).isFalse()
}
@Test
@@ -405,6 +406,26 @@ class MediaDataManagerTest : SysuiTestCase() {
}
@Test
+ fun testOnSmartspaceMediaDataLoaded_hasNullIntent_callsListener() {
+ val recommendationExtras = Bundle().apply {
+ putString("package_name", PACKAGE_NAME)
+ putParcelable("dismiss_intent", null)
+ }
+ whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
+ whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
+ whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
+
+ smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
+
+ verify(listener).onSmartspaceMediaDataLoaded(
+ eq(KEY_MEDIA_SMARTSPACE),
+ eq(EMPTY_SMARTSPACE_MEDIA_DATA
+ .copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true,
+ isValid = false, dismissIntent = null)),
+ eq(false))
+ }
+
+ @Test
fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_notCallsListener() {
smartspaceMediaDataProvider.onTargetsAvailable(listOf())
verify(listener, never())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
index e9e965e92303..8dc9eff97ab9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
@@ -127,7 +127,7 @@ public class MediaPlayerDataTest : SysuiTestCase() {
val players = MediaPlayerData.players()
assertThat(players).hasSize(6)
assertThat(players).containsExactly(playerIsPlaying, playerIsPlayingAndRemote,
- playerIsStoppedAndLocal, playerCanResume, playerIsStoppedAndRemote,
+ playerIsStoppedAndRemote, playerIsStoppedAndLocal, playerCanResume,
playerUndetermined).inOrder()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
index 7d8728e4acab..e77802f8db32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
@@ -76,6 +76,7 @@ public class SeekBarObserverTest : SysuiTestCase() {
assertThat(seekBarView.getThumb().getAlpha()).isEqualTo(0)
assertThat(elapsedTimeView.getText()).isEqualTo("")
assertThat(totalTimeView.getText()).isEqualTo("")
+ assertThat(seekBarView.contentDescription).isEqualTo("")
assertThat(seekBarView.maxHeight).isEqualTo(disabledHeight)
}
@@ -102,6 +103,9 @@ public class SeekBarObserverTest : SysuiTestCase() {
assertThat(seekBarView.max).isEqualTo(120000)
assertThat(elapsedTimeView.getText()).isEqualTo("00:03")
assertThat(totalTimeView.getText()).isEqualTo("02:00")
+
+ val desc = context.getString(R.string.controls_media_seekbar_description, "00:03", "02:00")
+ assertThat(seekBarView.contentDescription).isEqualTo(desc)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 2c686618a361..52173c13ca6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -54,6 +54,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
// Mock
private MediaOutputController mMediaOutputController = mock(MediaOutputController.class);
+ private MediaOutputDialog mMediaOutputDialog = mock(MediaOutputDialog.class);
private MediaDevice mMediaDevice1 = mock(MediaDevice.class);
private MediaDevice mMediaDevice2 = mock(MediaDevice.class);
private Icon mIcon = mock(Icon.class);
@@ -65,7 +66,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Before
public void setUp() {
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController, mMediaOutputDialog);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -118,7 +119,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
@@ -180,7 +180,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(mMediaDevices);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.VISIBLE);
}
@@ -189,7 +188,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(new ArrayList<>());
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
}
@@ -197,7 +195,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
public void onBindViewHolder_bindNonActiveConnectedDevice_verifyView() {
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
@@ -214,14 +211,15 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaDevice2.isConnected()).thenReturn(false);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(
- mContext.getString(R.string.media_output_dialog_disconnected, TEST_DEVICE_NAME_2));
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTwoLineTitleText.getText().toString()).isEqualTo(
+ TEST_DEVICE_NAME_2);
+ assertThat(mViewHolder.mSubTitleText.getText().toString()).isEqualTo(
+ mContext.getString(R.string.media_output_dialog_disconnected));
}
@Test
@@ -230,7 +228,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
LocalMediaManager.MediaDeviceState.STATE_CONNECTING_FAILED);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 9bd07b88417d..053851ec385d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -39,6 +39,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.phone.ShadeController;
@@ -63,6 +64,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
private NotificationEntryManager mNotificationEntryManager =
mock(NotificationEntryManager.class);
private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
+ private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
private MediaOutputController mMediaOutputController;
@@ -75,7 +77,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
public void setUp() {
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager, mUiEventLogger);
+ mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext,
mMediaOutputController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index d1a617bcc0cb..f7e60caa2624 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -49,6 +49,7 @@ import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -91,6 +92,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
private NotificationEntryManager mNotificationEntryManager =
mock(NotificationEntryManager.class);
private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
+ private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
private Context mSpyContext;
private MediaOutputController mMediaOutputController;
@@ -113,7 +115,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager, mUiEventLogger);
+ mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
MediaDescription.Builder builder = new MediaDescription.Builder();
@@ -157,7 +159,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
public void start_withoutPackageName_verifyMediaControllerInit() {
mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager, mUiEventLogger);
+ mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
mMediaOutputController.start(mCb);
@@ -178,7 +180,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
public void stop_withoutPackageName_verifyMediaControllerDeinit() {
mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager, mUiEventLogger);
+ mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
mMediaOutputController.start(mCb);
@@ -449,7 +451,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
public void getNotificationLargeIcon_withoutPackageName_returnsNull() {
mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager, mUiEventLogger);
+ mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
assertThat(mMediaOutputController.getNotificationIcon()).isNull();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 86f6bdec43f0..8a3ea562269d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -36,6 +36,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.phone.ShadeController;
@@ -65,6 +66,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
private final NotificationEntryManager mNotificationEntryManager =
mock(NotificationEntryManager.class);
private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
+ private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
private MediaOutputDialog mMediaOutputDialog;
private MediaOutputController mMediaOutputController;
@@ -74,10 +76,11 @@ public class MediaOutputDialogTest extends SysuiTestCase {
public void setUp() {
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager, mUiEventLogger);
+ mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputDialog = new MediaOutputDialog(mContext, false,
mMediaOutputController, mUiEventLogger);
+ mMediaOutputDialog.show();
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice);
when(mMediaDevice.getFeatures()).thenReturn(mFeatures);
@@ -123,6 +126,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
public void onCreate_ShouldLogVisibility() {
MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false,
mMediaOutputController, mUiEventLogger);
+ testDialog.show();
testDialog.dismissDialog();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java
index 1f85112dfb74..ca5d570969c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java
@@ -99,7 +99,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase {
assertThat(mGroupViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mGroupViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
@@ -114,7 +113,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase {
assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
@@ -141,7 +139,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase {
assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
@@ -167,7 +164,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase {
assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
@@ -186,7 +182,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase {
assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
index c296ff5cf19a..e8cd6c88956d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
@@ -34,6 +34,7 @@ import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.phone.ShadeController;
@@ -64,6 +65,7 @@ public class MediaOutputGroupDialogTest extends SysuiTestCase {
private NotificationEntryManager mNotificationEntryManager =
mock(NotificationEntryManager.class);
private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
+ private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
private MediaOutputGroupDialog mMediaOutputGroupDialog;
private MediaOutputController mMediaOutputController;
@@ -73,10 +75,11 @@ public class MediaOutputGroupDialogTest extends SysuiTestCase {
public void setUp() {
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
- mNotificationEntryManager, mUiEventLogger);
+ mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false,
mMediaOutputController);
+ mMediaOutputGroupDialog.show();
when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mMediaDevices);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
new file mode 100644
index 000000000000..bc86ef98c6fe
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.systemui.monet;
+
+import android.app.WallpaperColors;
+import android.graphics.Color;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ColorSchemeTest extends SysuiTestCase {
+ @Test
+ public void testFilterTransparency() {
+ ColorScheme colorScheme = new ColorScheme(Color.TRANSPARENT, false /* darkTheme */);
+ Assert.assertEquals(colorScheme.getAllAccentColors(),
+ new ColorScheme(0xFF1b6ef3, false).getAllAccentColors());
+ }
+
+ @Test
+ public void testDontFilterOpaque() {
+ ColorScheme colorScheme = new ColorScheme(0xFFFF0000, false /* darkTheme */);
+ Assert.assertNotEquals(colorScheme.getAllAccentColors(),
+ new ColorScheme(0xFF1b6ef3, false).getAllAccentColors());
+ }
+
+ @Test
+ public void testUniqueColors() {
+ WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xffaec00a),
+ Color.valueOf(0xffaec00a), Color.valueOf(0xffaec00a));
+
+ List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors);
+ Assert.assertEquals(rankedSeedColors, List.of(0xffaec00a));
+ }
+
+
+ @Test
+ public void testFiltersInvalidColors() {
+ WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xff5e7ea2),
+ Color.valueOf(0xff5e7ea2), Color.valueOf(0xff000000));
+
+ List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors);
+ Assert.assertEquals(rankedSeedColors, List.of(0xff5e7ea2));
+ }
+
+ @Test
+ public void testInvalidColorBecomesGBlue() {
+ WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xff000000), null,
+ null);
+
+ List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors);
+ Assert.assertEquals(rankedSeedColors, List.of(0xFF1b6ef3));
+ }
+
+ @Test
+ public void testDontFilterRRGGBB() {
+ ColorScheme colorScheme = new ColorScheme(0xFF0000, false /* darkTheme */);
+ Assert.assertEquals(colorScheme.getAllAccentColors(),
+ new ColorScheme(0xFFFF0000, false).getAllAccentColors());
+ }
+
+ @Test
+ public void testNoPopulationSignal() {
+ WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xffaec00a),
+ Color.valueOf(0xffbe0000), Color.valueOf(0xffcc040f));
+
+ List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors);
+ Assert.assertEquals(rankedSeedColors, List.of(0xffaec00a, 0xffbe0000, 0xffcc040f));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index d2527c679a13..4fc329ffc7af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -35,41 +35,24 @@ import static org.mockito.Mockito.verify;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.SparseArray;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
-import com.android.systemui.accessibility.SystemActions;
-import com.android.systemui.assist.AssistManager;
-import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.pip.Pip;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-
-import java.util.Optional;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
/** atest NavigationBarControllerTest */
@RunWith(AndroidTestingRunner.class)
@@ -77,42 +60,33 @@ import java.util.Optional;
@SmallTest
public class NavigationBarControllerTest extends SysuiTestCase {
+ private static final int SECONDARY_DISPLAY = 1;
+
private NavigationBarController mNavigationBarController;
private NavigationBar mDefaultNavBar;
private NavigationBar mSecondaryNavBar;
- private static final int SECONDARY_DISPLAY = 1;
+ @Mock
+ private CommandQueue mCommandQueue;
+ @Mock
+ private NavigationBar.Factory mNavigationBarFactory;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
mNavigationBarController = spy(
new NavigationBarController(mContext,
- mock(WindowManager.class),
- () -> mock(AssistManager.class),
- mock(AccessibilityManager.class),
- mock(AccessibilityManagerWrapper.class),
- mock(DeviceProvisionedController.class),
- mock(MetricsLogger.class),
mock(OverviewProxyService.class),
mock(NavigationModeController.class),
- mock(AccessibilityButtonModeObserver.class),
- mock(StatusBarStateController.class),
mock(SysUiState.class),
- mock(BroadcastDispatcher.class),
- mock(CommandQueue.class),
- Optional.of(mock(Pip.class)),
- Optional.of(mock(LegacySplitScreen.class)),
- Optional.of(mock(Recents.class)),
- () -> mock(StatusBar.class),
- mock(ShadeController.class),
- mock(NotificationRemoteInputManager.class),
- mock(NotificationShadeDepthController.class),
- mock(SystemActions.class),
+ mCommandQueue,
Dependency.get(Dependency.MAIN_HANDLER),
- mock(UiEventLogger.class),
- mock(NavigationBarOverlayController.class),
mock(ConfigurationController.class),
- mock(UserTracker.class)));
+ mock(NavigationBarA11yHelper.class),
+ mock(TaskbarDelegate.class),
+ mNavigationBarFactory,
+ mock(DumpManager.class),
+ mock(AutoHideController.class)));
initializeNavigationBars();
}
@@ -138,6 +112,8 @@ public class NavigationBarControllerTest extends SysuiTestCase {
@Test
public void testCreateNavigationBarsIncludeDefaultTrue() {
+ // Tablets may be using taskbar and the logic is different
+ mNavigationBarController.mIsTablet = false;
doNothing().when(mNavigationBarController).createNavigationBar(any(), any(), any());
mNavigationBarController.createNavigationBars(true, null);
@@ -275,4 +251,9 @@ public class NavigationBarControllerTest extends SysuiTestCase {
verify(mSecondaryNavBar).disableAnimationsDuringHide(eq(500L));
}
+
+ @Test
+ public void test3ButtonTaskbarFlagDisabledNoRegister() {
+ verify(mCommandQueue, never()).addCallback(any(TaskbarDelegate.class));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
index eac68f6e76f4..85bc634c28b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
@@ -22,6 +22,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.view.Display;
import android.view.View;
import android.view.WindowInsetsController;
@@ -31,9 +32,8 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
-import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
-import com.android.systemui.navigationbar.RotationButton;
-import com.android.systemui.navigationbar.RotationButtonController;
+import com.android.systemui.shared.rotation.RotationButton;
+import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.statusbar.policy.RotationLockController;
import org.junit.Before;
@@ -42,11 +42,12 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
+import java.util.function.Supplier;
+
/** atest NavigationBarRotationContextTest */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class NavigationBarRotationContextTest extends SysuiTestCase {
- static final int RES_UNDEF = 0;
static final int DEFAULT_ROTATE = 0;
@Rule
@@ -54,6 +55,8 @@ public class NavigationBarRotationContextTest extends SysuiTestCase {
InstrumentationRegistry.getContext(), getLeakCheck());
private RotationButtonController mRotationButtonController;
private RotationButton mRotationButton;
+ private int mWindowRotation = DEFAULT_ROTATE;
+ private Supplier<Integer> mWindowRotationSupplier = () -> mWindowRotation;
@Before
public void setup() {
@@ -62,27 +65,43 @@ public class NavigationBarRotationContextTest extends SysuiTestCase {
final View view = new View(mContext);
mRotationButton = mock(RotationButton.class);
- mRotationButtonController = new RotationButtonController(mContext, 0, 0);
- mRotationButtonController.setRotationButton(mRotationButton, (visibility) -> {});
+ mRotationButtonController = new RotationButtonController(mContext,
+ /* lightIconColor */ 0,
+ /* darkIconColor */ 0,
+ /* iconCcwStart0 */ 0,
+ /* iconCcwStart90 */ 0,
+ /* iconCwStart0 */ 0,
+ /* iconCwStart90 */ 0,
+ mWindowRotationSupplier
+ );
+ mRotationButtonController.setRotationButton(mRotationButton,
+ new RotationButton.RotationButtonUpdatesCallback() {
+ @Override
+ public void onVisibilityChanged(boolean isVisible) {
+ }
+
+ @Override
+ public void onPositionChanged() {
+ }
+ });
// Due to a mockito issue, only spy the object after setting the initial state
mRotationButtonController = spy(mRotationButtonController);
- final KeyButtonDrawable kbd = mock(KeyButtonDrawable.class);
doReturn(view).when(mRotationButton).getCurrentView();
doReturn(true).when(mRotationButton).acceptRotationProposal();
}
@Test
public void testOnInvalidRotationProposal() {
- mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1,
- false /* isValid */);
+ mWindowRotation = DEFAULT_ROTATE + 1;
+ mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, false /* isValid */);
verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
false /* visible */);
}
@Test
public void testOnSameRotationProposal() {
- mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE,
- true /* isValid */);
+ mWindowRotation = DEFAULT_ROTATE;
+ mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, true /* isValid */);
verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
false /* visible */);
}
@@ -90,17 +109,17 @@ public class NavigationBarRotationContextTest extends SysuiTestCase {
@Test
public void testOnRotationProposalShowButtonShowNav() {
// No navigation bar should not call to set visibility state
- mRotationButtonController.onBehaviorChanged(
+ mRotationButtonController.onBehaviorChanged(Display.DEFAULT_DISPLAY,
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
mRotationButtonController.onNavigationBarWindowVisibilityChange(false /* showing */);
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
false /* visible */);
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
true /* visible */);
+ mWindowRotation = DEFAULT_ROTATE + 1;
// No navigation bar with rotation change should not call to set visibility state
- mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1,
- true /* isValid */);
+ mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, true /* isValid */);
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
false /* visible */);
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
@@ -120,10 +139,10 @@ public class NavigationBarRotationContextTest extends SysuiTestCase {
false /* visible */);
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
true /* visible */);
+ mWindowRotation = DEFAULT_ROTATE + 1;
// Navigation bar is visible and rotation requested
- mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, DEFAULT_ROTATE + 1,
- true /* isValid */);
+ mRotationButtonController.onRotationProposal(DEFAULT_ROTATE, true /* isValid */);
verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
true /* visible */);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 1968f7fd3457..223ffbd7bba5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -19,6 +19,7 @@ package com.android.systemui.navigationbar;
import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
+import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
import static android.inputmethodservice.InputMethodService.IME_VISIBLE;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
@@ -28,7 +29,6 @@ import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -45,26 +45,27 @@ import android.content.Context;
import android.content.IntentFilter;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
-import android.os.Looper;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
+import android.telecom.TelecomManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.MotionEvent;
+import android.view.View;
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
+import android.view.inputmethod.InputMethodManager;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
@@ -80,9 +81,10 @@ import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -109,7 +111,13 @@ public class NavigationBarTest extends SysuiTestCase {
private NavigationBar mExternalDisplayNavigationBar;
private SysuiTestableContext mSysuiTestableContextExternal;
+ @Mock
private OverviewProxyService mOverviewProxyService;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
+ @Mock
+ private NavigationModeController mNavigationModeController;
+ @Mock
private CommandQueue mCommandQueue;
private SysUiState mMockSysUiState;
@Mock
@@ -122,11 +130,27 @@ public class NavigationBarTest extends SysuiTestCase {
EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
@Mock
EdgeBackGestureHandler mEdgeBackGestureHandler;
+ @Mock
+ NavigationBarA11yHelper mNavigationBarA11yHelper;
+ @Mock
+ private LightBarController mLightBarController;
+ @Mock
+ private LightBarController.Factory mLightBarcontrollerFactory;
+ @Mock
+ private AutoHideController mAutoHideController;
+ @Mock
+ private AutoHideController.Factory mAutoHideControllerFactory;
+ @Mock
+ private WindowManager mWindowManager;
+ @Mock
+ private TelecomManager mTelecomManager;
+ @Mock
+ private InputMethodManager mInputMethodManager;
+ @Mock
+ private AssistManager mAssistManager;
@Rule
public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
- private AccessibilityManagerWrapper mAccessibilityWrapper =
- new AccessibilityManagerWrapper(mContext);
@Before
public void setup() throws Exception {
@@ -134,15 +158,19 @@ public class NavigationBarTest extends SysuiTestCase {
when(mEdgeBackGestureHandlerFactory.create(any(Context.class)))
.thenReturn(mEdgeBackGestureHandler);
- mCommandQueue = new CommandQueue(mContext);
+ when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController);
+ when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController);
setupSysuiDependency();
- mDependency.injectMockDependency(AssistManager.class);
+ // This class inflates views that call Dependency.get, thus these injections are still
+ // necessary.
+ mDependency.injectTestDependency(AssistManager.class, mAssistManager);
mDependency.injectMockDependency(KeyguardStateController.class);
- mDependency.injectMockDependency(StatusBarStateController.class);
+ mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
mDependency.injectMockDependency(NavigationBarController.class);
- mOverviewProxyService = mDependency.injectMockDependency(OverviewProxyService.class);
mDependency.injectTestDependency(EdgeBackGestureHandler.Factory.class,
mEdgeBackGestureHandlerFactory);
+ mDependency.injectTestDependency(OverviewProxyService.class, mOverviewProxyService);
+ mDependency.injectTestDependency(NavigationModeController.class, mNavigationModeController);
TestableLooper.get(this).runWithLooper(() -> {
mNavigationBar = createNavBar(mContext);
mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
@@ -161,22 +189,21 @@ public class NavigationBarTest extends SysuiTestCase {
mSysuiTestableContextExternal = (SysuiTestableContext) getContext().createDisplayContext(
display);
- WindowManager windowManager = mock(WindowManager.class);
- Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
- when(windowManager.getDefaultDisplay()).thenReturn(
- defaultDisplay);
+ Display defaultDisplay = mContext.getDisplay();
+ when(mWindowManager.getDefaultDisplay()).thenReturn(defaultDisplay);
WindowMetrics metrics = mContext.getSystemService(WindowManager.class)
.getMaximumWindowMetrics();
- when(windowManager.getMaximumWindowMetrics()).thenReturn(metrics);
- doNothing().when(windowManager).addView(any(), any());
- mContext.addMockSystemService(Context.WINDOW_SERVICE, windowManager);
- mSysuiTestableContextExternal.addMockSystemService(Context.WINDOW_SERVICE, windowManager);
-
- mDependency.injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper());
- mDependency.injectTestDependency(AccessibilityManagerWrapper.class, mAccessibilityWrapper);
-
+ when(mWindowManager.getMaximumWindowMetrics()).thenReturn(metrics);
+ WindowMetrics currentWindowMetrics = mContext.getSystemService(WindowManager.class)
+ .getCurrentWindowMetrics();
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(currentWindowMetrics);
+ doNothing().when(mWindowManager).addView(any(), any());
+ doNothing().when(mWindowManager).removeViewImmediate(any());
mMockSysUiState = mock(SysUiState.class);
when(mMockSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mMockSysUiState);
+
+ mContext.addMockSystemService(WindowManager.class, mWindowManager);
+ mSysuiTestableContextExternal.addMockSystemService(WindowManager.class, mWindowManager);
}
@Test
@@ -233,10 +260,8 @@ public class NavigationBarTest extends SysuiTestCase {
defaultNavBar.createView(null);
externalNavBar.createView(null);
- // Set IME window status for default NavBar.
- mCommandQueue.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
- BACK_DISPOSITION_DEFAULT, true, false);
- processAllMessages();
+ defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
+ BACK_DISPOSITION_DEFAULT, true);
// Verify IME window state will be updated in default NavBar & external NavBar state reset.
assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN,
@@ -244,11 +269,10 @@ public class NavigationBarTest extends SysuiTestCase {
assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
- // Set IME window status for external NavBar.
- mCommandQueue.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null,
- IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true, false);
- processAllMessages();
-
+ externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null, IME_VISIBLE,
+ BACK_DISPOSITION_DEFAULT, true);
+ defaultNavBar.setImeWindowStatus(
+ DEFAULT_DISPLAY, null, IME_INVISIBLE, BACK_DISPOSITION_DEFAULT, false);
// Verify IME window state will be updated in external NavBar & default NavBar state reset.
assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN,
externalNavBar.getNavigationIconHints());
@@ -256,30 +280,40 @@ public class NavigationBarTest extends SysuiTestCase {
assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0);
}
+ @Test
+ public void testA11yEventAfterDetach() {
+ View v = mNavigationBar.createView(null);
+ mNavigationBar.onViewAttachedToWindow(v);
+ verify(mNavigationBarA11yHelper).registerA11yEventListener(any(
+ NavigationBarA11yHelper.NavA11yEventListener.class));
+ mNavigationBar.onViewDetachedFromWindow(v);
+ verify(mNavigationBarA11yHelper).removeA11yEventListener(any(
+ NavigationBarA11yHelper.NavA11yEventListener.class));
+
+ // Should be safe even though the internal view is now null.
+ mNavigationBar.updateAccessibilityServicesState();
+ }
+
private NavigationBar createNavBar(Context context) {
DeviceProvisionedController deviceProvisionedController =
mock(DeviceProvisionedController.class);
when(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
- assertNotNull(mAccessibilityWrapper);
- return spy(new NavigationBar(context,
- mock(WindowManager.class),
- () -> mock(AssistManager.class),
+ NavigationBar.Factory factory = new NavigationBar.Factory(
+ () -> mAssistManager,
mock(AccessibilityManager.class),
- context.getDisplayId() == DEFAULT_DISPLAY ? mAccessibilityWrapper
- : mock(AccessibilityManagerWrapper.class),
deviceProvisionedController,
new MetricsLogger(),
mOverviewProxyService,
- mock(NavigationModeController.class),
+ mNavigationModeController,
mock(AccessibilityButtonModeObserver.class),
- mock(StatusBarStateController.class),
+ mStatusBarStateController,
mMockSysUiState,
mBroadcastDispatcher,
mCommandQueue,
Optional.of(mock(Pip.class)),
Optional.of(mock(LegacySplitScreen.class)),
Optional.of(mock(Recents.class)),
- () -> mock(StatusBar.class),
+ () -> Optional.of(mock(StatusBar.class)),
mock(ShadeController.class),
mock(NotificationRemoteInputManager.class),
mock(NotificationShadeDepthController.class),
@@ -287,7 +321,15 @@ public class NavigationBarTest extends SysuiTestCase {
mHandler,
mock(NavigationBarOverlayController.class),
mUiEventLogger,
- mock(UserTracker.class)));
+ mNavigationBarA11yHelper,
+ mock(UserTracker.class),
+ mLightBarController,
+ mLightBarcontrollerFactory,
+ mAutoHideController,
+ mAutoHideControllerFactory,
+ Optional.of(mTelecomManager),
+ mInputMethodManager);
+ return spy(factory.create(context));
}
private void processAllMessages() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
new file mode 100644
index 000000000000..36e02cb1df06
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
@@ -0,0 +1,128 @@
+package com.android.systemui.navigationbar.gestural
+
+import android.view.Gravity
+import android.view.Surface
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator
+import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@SmallTest
+internal class FloatingRotationButtonPositionCalculatorTest(private val testCase: TestCase)
+ : SysuiTestCase() {
+
+ private val calculator = FloatingRotationButtonPositionCalculator(
+ MARGIN_DEFAULT, MARGIN_TASKBAR_LEFT, MARGIN_TASKBAR_BOTTOM
+ )
+
+ @Test
+ fun calculatePosition() {
+ val position = calculator.calculatePosition(
+ testCase.rotation,
+ testCase.taskbarVisible,
+ testCase.taskbarStashed
+ )
+
+ assertThat(position).isEqualTo(testCase.expectedPosition)
+ }
+
+ internal class TestCase(
+ val rotation: Int,
+ val taskbarVisible: Boolean,
+ val taskbarStashed: Boolean,
+ val expectedPosition: Position
+ ) {
+ override fun toString(): String =
+ "when rotation = $rotation, " +
+ "taskbarVisible = $taskbarVisible, " +
+ "taskbarStashed = $taskbarStashed - " +
+ "expected $expectedPosition"
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<TestCase> =
+ listOf(
+ TestCase(
+ rotation = Surface.ROTATION_0,
+ taskbarVisible = false,
+ taskbarStashed = false,
+ expectedPosition = Position(
+ gravity = Gravity.BOTTOM or Gravity.LEFT,
+ translationX = MARGIN_DEFAULT,
+ translationY = -MARGIN_DEFAULT
+ )
+ ),
+ TestCase(
+ rotation = Surface.ROTATION_90,
+ taskbarVisible = false,
+ taskbarStashed = false,
+ expectedPosition = Position(
+ gravity = Gravity.BOTTOM or Gravity.RIGHT,
+ translationX = -MARGIN_DEFAULT,
+ translationY = -MARGIN_DEFAULT
+ )
+ ),
+ TestCase(
+ rotation = Surface.ROTATION_180,
+ taskbarVisible = false,
+ taskbarStashed = false,
+ expectedPosition = Position(
+ gravity = Gravity.TOP or Gravity.RIGHT,
+ translationX = -MARGIN_DEFAULT,
+ translationY = MARGIN_DEFAULT
+ )
+ ),
+ TestCase(
+ rotation = Surface.ROTATION_270,
+ taskbarVisible = false,
+ taskbarStashed = false,
+ expectedPosition = Position(
+ gravity = Gravity.TOP or Gravity.LEFT,
+ translationX = MARGIN_DEFAULT,
+ translationY = MARGIN_DEFAULT
+ )
+ ),
+ TestCase(
+ rotation = Surface.ROTATION_0,
+ taskbarVisible = true,
+ taskbarStashed = false,
+ expectedPosition = Position(
+ gravity = Gravity.BOTTOM or Gravity.LEFT,
+ translationX = MARGIN_TASKBAR_LEFT,
+ translationY = -MARGIN_TASKBAR_BOTTOM
+ )
+ ),
+ TestCase(
+ rotation = Surface.ROTATION_0,
+ taskbarVisible = true,
+ taskbarStashed = true,
+ expectedPosition = Position(
+ gravity = Gravity.BOTTOM or Gravity.LEFT,
+ translationX = MARGIN_DEFAULT,
+ translationY = -MARGIN_DEFAULT
+ )
+ ),
+ TestCase(
+ rotation = Surface.ROTATION_90,
+ taskbarVisible = true,
+ taskbarStashed = false,
+ expectedPosition = Position(
+ gravity = Gravity.BOTTOM or Gravity.RIGHT,
+ translationX = -MARGIN_TASKBAR_LEFT,
+ translationY = -MARGIN_TASKBAR_BOTTOM
+ )
+ )
+ )
+
+ private const val MARGIN_DEFAULT = 10
+ private const val MARGIN_TASKBAR_LEFT = 20
+ private const val MARGIN_TASKBAR_BOTTOM = 30
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 35620329467b..e73e5ff49dd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -58,6 +58,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.time.Duration;
+import java.util.Optional;
import java.util.concurrent.TimeUnit;
import dagger.Lazy;
@@ -89,7 +90,7 @@ public class PowerUITest extends SysuiTestCase {
private IThermalEventListener mSkinThermalEventListener;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private CommandQueue mCommandQueue;
- @Mock private Lazy<StatusBar> mStatusBarLazy;
+ @Mock private Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
@Mock private StatusBar mStatusBar;
@Before
@@ -98,7 +99,7 @@ public class PowerUITest extends SysuiTestCase {
mMockWarnings = mDependency.injectMockDependency(WarningsUI.class);
mEnhancedEstimates = mDependency.injectMockDependency(EnhancedEstimates.class);
- when(mStatusBarLazy.get()).thenReturn(mStatusBar);
+ when(mStatusBarOptionalLazy.get()).thenReturn(Optional.of(mStatusBar));
mContext.addMockSystemService(Context.POWER_SERVICE, mPowerManager);
@@ -688,7 +689,8 @@ public class PowerUITest extends SysuiTestCase {
}
private void createPowerUi() {
- mPowerUI = new PowerUI(mContext, mBroadcastDispatcher, mCommandQueue, mStatusBarLazy);
+ mPowerUI = new PowerUI(
+ mContext, mBroadcastDispatcher, mCommandQueue, mStatusBarOptionalLazy);
mPowerUI.mThermalService = mThermalServiceMock;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
new file mode 100644
index 000000000000..d2bba361b1f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -0,0 +1,125 @@
+package com.android.systemui.qs
+
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.ViewUtils
+import android.view.LayoutInflater
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.FakeMetricsLogger
+import com.android.systemui.Dependency
+import com.android.systemui.R
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.globalactions.GlobalActionsDialogLite
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.FooterActionsController.ExpansionState
+import com.android.systemui.statusbar.phone.MultiUserSwitchController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.UserInfoController
+import com.android.systemui.tuner.TunerService
+import com.android.systemui.utils.leaks.FakeTunerService
+import com.android.systemui.utils.leaks.LeakCheckedTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class FooterActionsControllerTest : LeakCheckedTest() {
+ @Mock
+ private lateinit var userManager: UserManager
+ @Mock
+ private lateinit var activityStarter: ActivityStarter
+ @Mock
+ private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock
+ private lateinit var userInfoController: UserInfoController
+ @Mock
+ private lateinit var qsPanelController: QSPanelController
+ @Mock
+ private lateinit var multiUserSwitchController: MultiUserSwitchController
+ @Mock
+ private lateinit var globalActionsDialog: GlobalActionsDialogLite
+ @Mock
+ private lateinit var uiEventLogger: UiEventLogger
+ @Mock
+ private lateinit var controller: FooterActionsController
+
+ private val metricsLogger: MetricsLogger = FakeMetricsLogger()
+ private lateinit var view: FooterActionsView
+ private val falsingManager: FalsingManagerFake = FalsingManagerFake()
+ private lateinit var testableLooper: TestableLooper
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+ injectLeakCheckedDependencies(*LeakCheckedTest.ALL_SUPPORTED_CLASSES)
+ val fakeTunerService = Dependency.get(TunerService::class.java) as FakeTunerService
+
+ view = LayoutInflater.from(context)
+ .inflate(R.layout.footer_actions, null) as FooterActionsView
+
+ controller = FooterActionsController(view, qsPanelController, activityStarter,
+ userManager, userInfoController, multiUserSwitchController,
+ deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService,
+ globalActionsDialog, uiEventLogger, showPMLiteButton = true,
+ buttonsVisibleState = ExpansionState.EXPANDED)
+ controller.init()
+ ViewUtils.attachView(view)
+ // View looper is the testable looper associated with the test
+ testableLooper.processAllMessages()
+ }
+
+ @After
+ fun tearDown() {
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun testLogPowerMenuClick() {
+ controller.expanded = true
+ falsingManager.setFalseTap(false)
+
+ view.findViewById<View>(R.id.pm_lite).performClick()
+ // Verify clicks are logged
+ verify(uiEventLogger, Mockito.times(1))
+ .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
+ }
+
+ @Test
+ fun testSettings_UserNotSetup() {
+ whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
+ view.findViewById<View>(R.id.settings_button).performClick()
+ // Verify Settings wasn't launched.
+ verify<ActivityStarter>(activityStarter, Mockito.never()).startActivity(any(), anyBoolean())
+ }
+
+ @Test
+ fun testMultiUserSwitchUpdatedWhenExpansionStarts() {
+ // When expansion starts, listening is set to true
+ val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch)
+
+ assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE)
+
+ whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true)
+
+ controller.setListening(true)
+ testableLooper.processAllMessages()
+
+ assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
index 6f7bf3b09daa..8b19c50f915e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
@@ -18,16 +18,11 @@ package com.android.systemui.qs;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ClipData;
import android.content.ClipboardManager;
-import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -35,21 +30,8 @@ import android.widget.TextView;
import androidx.test.filters.SmallTest;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.testing.FakeMetricsLogger;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.globalactions.GlobalActionsDialogLite;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.MultiUserSwitchController;
-import com.android.systemui.statusbar.phone.SettingsButton;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.utils.leaks.FakeTunerService;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
@@ -67,14 +49,6 @@ public class QSFooterViewControllerTest extends LeakCheckedTest {
@Mock
private QSFooterView mView;
@Mock
- private UserManager mUserManager;
- @Mock
- private ActivityStarter mActivityStarter;
- @Mock
- private DeviceProvisionedController mDeviceProvisionedController;
- @Mock
- private UserInfoController mUserInfoController;
- @Mock
private UserTracker mUserTracker;
@Mock
private QSPanelController mQSPanelController;
@@ -82,36 +56,19 @@ public class QSFooterViewControllerTest extends LeakCheckedTest {
private ClipboardManager mClipboardManager;
@Mock
private QuickQSPanelController mQuickQSPanelController;
- private FakeTunerService mFakeTunerService;
- private MetricsLogger mMetricsLogger = new FakeMetricsLogger();
- private FalsingManagerFake mFalsingManager;
-
- @Mock
- private SettingsButton mSettingsButton;
@Mock
private TextView mBuildText;
@Mock
- private View mEdit;
- @Mock
- private MultiUserSwitchController mMultiUserSwitchController;
- @Mock
- private View mPowerMenuLiteView;
- @Mock
- private GlobalActionsDialogLite mGlobalActionsDialog;
- @Mock
- private UiEventLogger mUiEventLogger;
+ private FooterActionsController mFooterActionsController;
private QSFooterViewController mController;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- mFalsingManager = new FalsingManagerFake();
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
- mFakeTunerService = (FakeTunerService) Dependency.get(TunerService.class);
-
mContext.addMockSystemService(ClipboardManager.class, mClipboardManager);
when(mView.getContext()).thenReturn(mContext);
@@ -119,16 +76,10 @@ public class QSFooterViewControllerTest extends LeakCheckedTest {
when(mUserTracker.getUserContext()).thenReturn(mContext);
when(mView.isAttachedToWindow()).thenReturn(true);
- when(mView.findViewById(R.id.settings_button)).thenReturn(mSettingsButton);
when(mView.findViewById(R.id.build)).thenReturn(mBuildText);
- when(mView.findViewById(android.R.id.edit)).thenReturn(mEdit);
- when(mView.findViewById(R.id.pm_lite)).thenReturn(mPowerMenuLiteView);
- mController = new QSFooterViewController(mView, mUserManager, mUserInfoController,
- mActivityStarter, mDeviceProvisionedController, mUserTracker, mQSPanelController,
- mMultiUserSwitchController, mQuickQSPanelController, mFakeTunerService,
- mMetricsLogger, mFalsingManager, false, mGlobalActionsDialog,
- mUiEventLogger);
+ mController = new QSFooterViewController(mView, mUserTracker, mQSPanelController,
+ mQuickQSPanelController, mFooterActionsController);
mController.init();
}
@@ -148,40 +99,4 @@ public class QSFooterViewControllerTest extends LeakCheckedTest {
verify(mClipboardManager).setPrimaryClip(captor.capture());
assertThat(captor.getValue().getItemAt(0).getText()).isEqualTo(text);
}
-
- @Test
- public void testSettings_UserNotSetup() {
- ArgumentCaptor<View.OnClickListener> onClickCaptor =
- ArgumentCaptor.forClass(View.OnClickListener.class);
- verify(mSettingsButton).setOnClickListener(onClickCaptor.capture());
-
- when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(false);
-
- onClickCaptor.getValue().onClick(mSettingsButton);
- // Verify Settings wasn't launched.
- verify(mActivityStarter, never()).startActivity(any(), anyBoolean());
- }
-
- @Test
- public void testLogPowerMenuClick() {
- // Enable power menu button
- mController = new QSFooterViewController(mView, mUserManager, mUserInfoController,
- mActivityStarter, mDeviceProvisionedController, mUserTracker, mQSPanelController,
- mMultiUserSwitchController, mQuickQSPanelController, mFakeTunerService,
- mMetricsLogger, new FalsingManagerFake(), true, mGlobalActionsDialog,
- mUiEventLogger);
- mController.init();
- mController.setExpanded(true);
- mFalsingManager.setFalseTap(false);
-
- ArgumentCaptor<View.OnClickListener> onClickCaptor =
- ArgumentCaptor.forClass(View.OnClickListener.class);
- verify(mPowerMenuLiteView).setOnClickListener(onClickCaptor.capture());
-
- onClickCaptor.getValue().onClick(mPowerMenuLiteView);
-
- // Verify clicks are logged
- verify(mUiEventLogger, times(1))
- .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS);
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 7f89b2629a6a..c4bab738cc03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -39,10 +39,10 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.CarrierText;
import com.android.systemui.Dependency;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -53,7 +53,6 @@ import com.android.systemui.qs.tileimpl.QSFactoryImpl;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -63,7 +62,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.InjectionInflationController;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
@@ -95,9 +93,9 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
@Mock
private KeyguardBypassController mBypassController;
@Mock
- private FeatureFlags mFeatureFlags;
- @Mock
private FalsingManager mFalsingManager;
+ @Mock
+ private FeatureFlags mFeatureFlags;
public QSFragmentTest() {
super(QSFragment.class);
@@ -177,10 +175,6 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
return new QSFragment(
new RemoteInputQuickSettingsDisabler(context, mock(ConfigurationController.class),
commandQueue),
- new InjectionInflationController(
- SystemUIFactory.getInstance()
- .getSysUIComponent()
- .createViewInstanceCreatorFactory()),
mock(QSTileHost.class),
mock(StatusBarStateController.class),
commandQueue,
@@ -189,7 +183,6 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
mQQSMediaHost,
mBypassController,
mQsComponentFactory,
- mFeatureFlags,
mFalsingManager,
mock(DumpManager.class));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index faef87069084..2e1fb07e6aa5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -50,7 +50,6 @@ import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.util.animation.DisappearParameters;
import org.junit.Before;
@@ -94,8 +93,6 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
@Mock
PagedTileLayout mPagedTileLayout;
@Mock
- FeatureFlags mFeatureFlags;
- @Mock
Resources mResources;
@Mock
Configuration mConfiguration;
@@ -109,9 +106,9 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
QSCustomizerController qsCustomizerController, MediaHost mediaHost,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager, FeatureFlags featureFlags) {
+ DumpManager dumpManager) {
super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
- qsLogger, dumpManager, featureFlags);
+ qsLogger, dumpManager);
}
@Override
@@ -141,7 +138,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
- mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags);
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
mController.init();
reset(mQSTileRevealController);
@@ -153,7 +150,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
QSPanelControllerBase<QSPanel> controller = new TestableQSPanelControllerBase(mQSPanel,
mQSTileHost, mQSCustomizerController, mMediaHost,
- mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags) {
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager) {
@Override
protected QSTileRevealController createTileRevealController() {
return mQSTileRevealController;
@@ -242,21 +239,20 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
when(mMediaHost.getVisible()).thenReturn(true);
- when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+ when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(false);
when(mQSPanel.getDumpableTag()).thenReturn("QSPanelLandscape");
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
- mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags);
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
mController.init();
assertThat(mController.shouldUseHorizontalLayout()).isTrue();
- when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
when(mQSPanel.getDumpableTag()).thenReturn("QSPanelPortrait");
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
- mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags);
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
mController.init();
assertThat(mController.shouldUseHorizontalLayout()).isFalse();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index 35ebacb85203..3242adbcfad8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -42,9 +42,9 @@ import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.brightness.BrightnessController;
-import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.settings.brightness.ToggleSlider;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.animation.DisappearParameters;
@@ -88,18 +88,18 @@ public class QSPanelControllerTest extends SysuiTestCase {
@Mock
private BrightnessController mBrightnessController;
@Mock
- private BrightnessSlider.Factory mToggleSliderViewControllerFactory;
+ private BrightnessSliderController.Factory mToggleSliderViewControllerFactory;
@Mock
- private BrightnessSlider mBrightnessSlider;
+ private BrightnessSliderController mBrightnessSliderController;
@Mock
QSTileImpl mQSTile;
@Mock
QSTileView mQSTileView;
@Mock
PagedTileLayout mPagedTileLayout;
- FalsingManagerFake mFalsingManager = new FalsingManagerFake();
@Mock
- FeatureFlags mFeatureFlags;
+ CommandQueue mCommandQueue;
+ FalsingManagerFake mFalsingManager = new FalsingManagerFake();
@Mock
Resources mResources;
@Mock
@@ -120,7 +120,7 @@ public class QSPanelControllerTest extends SysuiTestCase {
when(mQSTileHost.getTiles()).thenReturn(Collections.singleton(mQSTile));
when(mQSTileHost.createTileView(any(), eq(mQSTile), anyBoolean())).thenReturn(mQSTileView);
when(mToggleSliderViewControllerFactory.create(any(), any()))
- .thenReturn(mBrightnessSlider);
+ .thenReturn(mBrightnessSliderController);
when(mBrightnessControllerFactory.create(any(ToggleSlider.class)))
.thenReturn(mBrightnessController);
when(mQSTileRevealControllerFactory.create(any(), any()))
@@ -131,7 +131,7 @@ public class QSPanelControllerTest extends SysuiTestCase {
mQSTileHost, mQSCustomizerController, true, mMediaHost,
mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger,
mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory,
- mFalsingManager, mFeatureFlags
+ mFalsingManager, mCommandQueue
);
mController.init();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
deleted file mode 100644
index 16d4dddd67aa..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Configuration;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.qs.QSTileView;
-import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.util.animation.UniqueObjectHostView;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Collections;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class QSPanelTest extends SysuiTestCase {
-
- private TestableLooper mTestableLooper;
- private QSPanel mQsPanel;
- @Mock
- private QSTileHost mHost;
- @Mock
- private QSTileImpl dndTile;
- @Mock
- private QSPanelControllerBase.TileRecord mDndTileRecord;
- private ViewGroup mParentView;
- @Mock
- private QSDetail.Callback mCallback;
- @Mock
- private QSTileView mQSTileView;
-
- private UniqueObjectHostView mMediaView;
- private View mSecurityFooter;
- @Mock
- private FrameLayout mHeaderContainer;
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
- mTestableLooper = TestableLooper.get(this);
-
- mDndTileRecord.tile = dndTile;
- mDndTileRecord.tileView = mQSTileView;
-
- mMediaView = new UniqueObjectHostView(mContext);
- mSecurityFooter = new View(mContext);
-
- mTestableLooper.runWithLooper(() -> {
- mQsPanel = new QSPanel(mContext, null);
- mQsPanel.initialize();
- mQsPanel.onFinishInflate();
- // Provides a parent with non-zero size for QSPanel
- mParentView = new FrameLayout(mContext);
- mParentView.addView(mQsPanel);
-
- when(dndTile.getTileSpec()).thenReturn("dnd");
- when(mHost.getTiles()).thenReturn(Collections.emptyList());
- when(mHost.createTileView(any(), any(), anyBoolean())).thenReturn(mQSTileView);
- mQsPanel.addTile(mDndTileRecord);
- mQsPanel.setCallback(mCallback);
- mQsPanel.setHeaderContainer(mHeaderContainer);
- });
- }
-
- @Test
- public void testOpenDetailsWithExistingTile_NoException() {
- mTestableLooper.processAllMessages();
- mQsPanel.openDetails(dndTile);
- mTestableLooper.processAllMessages();
-
- verify(mCallback).onShowingDetail(any(), anyInt(), anyInt());
- }
-
- @Test
- public void testOpenDetailsWithNullParameter_NoException() {
- mTestableLooper.processAllMessages();
- mQsPanel.openDetails(null);
- mTestableLooper.processAllMessages();
-
- verify(mCallback, never()).onShowingDetail(any(), anyInt(), anyInt());
- }
-
- @Test
- public void testSecurityFooterAtEndNoMedia_portrait() {
- mTestableLooper.processAllMessages();
-
- mContext.getResources().getConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT;
-
- mQsPanel.setSecurityFooter(mSecurityFooter);
-
- int children = mQsPanel.getChildCount();
- assertEquals(children - 1, mQsPanel.indexOfChild(mSecurityFooter));
- }
-
- @Test
- public void testSecurityFooterRightBeforeMedia_portrait() {
- mTestableLooper.processAllMessages();
-
- mContext.getResources().getConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT;
-
- mQsPanel.addView(mMediaView);
-
- mQsPanel.setSecurityFooter(mSecurityFooter);
-
- int securityFooterIndex = mQsPanel.indexOfChild(mSecurityFooter);
- int mediaIndex = mQsPanel.indexOfChild(mMediaView);
-
- assertEquals(mediaIndex - 1, securityFooterIndex);
- }
-
- @Test
- public void testSecurityFooterRightBeforeMedia_portrait_configChange() {
- mTestableLooper.processAllMessages();
-
- mContext.getResources().getConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT;
-
- mQsPanel.addView(mMediaView);
-
- mQsPanel.setSecurityFooter(mSecurityFooter);
-
- mQsPanel.onConfigurationChanged(mContext.getResources().getConfiguration());
-
- int securityFooterIndex = mQsPanel.indexOfChild(mSecurityFooter);
- int mediaIndex = mQsPanel.indexOfChild(mMediaView);
-
- assertEquals(mediaIndex - 1, securityFooterIndex);
- }
-
- @Test
- public void testSecurityFooterInHeader_landscape() {
- mTestableLooper.processAllMessages();
-
- mContext.getResources().getConfiguration().orientation =
- Configuration.ORIENTATION_LANDSCAPE;
-
- mQsPanel.setSecurityFooter(mSecurityFooter);
-
- verify(mHeaderContainer).addView(mSecurityFooter, 0);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
new file mode 100644
index 000000000000..3500c183de39
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -0,0 +1,167 @@
+/*
+ * 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.systemui.qs
+
+import android.content.res.Configuration
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QSTileView
+import com.android.systemui.qs.QSPanelControllerBase.TileRecord
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+@SmallTest
+class QSPanelTest : SysuiTestCase() {
+ private lateinit var mTestableLooper: TestableLooper
+ private lateinit var mQsPanel: QSPanel
+
+ @Mock
+ private lateinit var mHost: QSTileHost
+
+ @Mock
+ private lateinit var dndTile: QSTileImpl<*>
+
+ @Mock
+ private lateinit var mDndTileRecord: TileRecord
+
+ @Mock
+ private lateinit var mQSLogger: QSLogger
+ private lateinit var mParentView: ViewGroup
+
+ @Mock
+ private lateinit var mCallback: QSDetail.Callback
+
+ @Mock
+ private lateinit var mQSTileView: QSTileView
+
+ private lateinit var mFooter: View
+
+ @Before
+ @Throws(Exception::class)
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mTestableLooper = TestableLooper.get(this)
+
+ mDndTileRecord.tile = dndTile
+ mDndTileRecord.tileView = mQSTileView
+ mTestableLooper.runWithLooper {
+ mQsPanel = QSPanel(mContext, null)
+ mQsPanel.initialize()
+ // QSPanel inflates a footer inside of it, mocking it here
+ mFooter = LinearLayout(mContext).apply { id = R.id.qs_footer }
+ mQsPanel.addView(mFooter)
+ mQsPanel.onFinishInflate()
+ mQsPanel.setSecurityFooter(View(mContext), false)
+ mQsPanel.setHeaderContainer(LinearLayout(mContext))
+ // Provides a parent with non-zero size for QSPanel
+ mParentView = FrameLayout(mContext).apply {
+ addView(mQsPanel)
+ }
+
+ whenever(dndTile.tileSpec).thenReturn("dnd")
+ whenever(mHost.tiles).thenReturn(emptyList())
+ whenever(mHost.createTileView(any(), any(), anyBoolean())).thenReturn(mQSTileView)
+ mQsPanel.addTile(mDndTileRecord)
+ mQsPanel.setCallback(mCallback)
+ }
+ }
+
+ @Test
+ fun testOpenDetailsWithExistingTile_NoException() {
+ mTestableLooper.runWithLooper {
+ mQsPanel.openDetails(dndTile)
+ }
+
+ verify(mCallback).onShowingDetail(any(), anyInt(), anyInt())
+ }
+
+ @Test
+ fun testOpenDetailsWithNullParameter_NoException() {
+ mTestableLooper.runWithLooper {
+ mQsPanel.openDetails(null)
+ }
+
+ verify(mCallback, never()).onShowingDetail(any(), anyInt(), anyInt())
+ }
+
+ @Test
+ fun testSecurityFooter_appearsOnBottomOnSplitShade() {
+ mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_LANDSCAPE))
+ mQsPanel.switchSecurityFooter(true)
+
+ mTestableLooper.runWithLooper {
+ mQsPanel.isExpanded = true
+ }
+
+ // After mFooter
+ assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(
+ mQsPanel.indexOfChild(mFooter) + 1
+ )
+ }
+
+ @Test
+ fun testSecurityFooter_appearsOnBottomIfPortrait() {
+ mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_PORTRAIT))
+ mQsPanel.switchSecurityFooter(false)
+
+ mTestableLooper.runWithLooper {
+ mQsPanel.isExpanded = true
+ }
+
+ // After mFooter
+ assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(
+ mQsPanel.indexOfChild(mFooter) + 1
+ )
+ }
+
+ @Test
+ fun testSecurityFooter_appearsOnTopIfSmallScreenAndLandscape() {
+ mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_LANDSCAPE))
+ mQsPanel.switchSecurityFooter(false)
+
+ mTestableLooper.runWithLooper {
+ mQsPanel.isExpanded = true
+ }
+
+ // -1 means that it is part of the mHeaderContainer
+ assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(-1)
+ }
+
+ private fun getNewOrientationConfig(@Configuration.Orientation newOrientation: Int) =
+ context.resources.configuration.apply { orientation = newOrientation }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt
new file mode 100644
index 000000000000..3059aa1ae658
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt
@@ -0,0 +1,62 @@
+package com.android.systemui.qs
+
+import android.testing.AndroidTestingRunner
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.tileimpl.QSTileViewImpl
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class QSSquishinessControllerTest : SysuiTestCase() {
+
+ @Mock private lateinit var qsTileHost: QSTileHost
+ @Mock private lateinit var qqsFooterActionsView: FooterActionsView
+ @Mock private lateinit var qqsFooterActionsViewLP: ViewGroup.MarginLayoutParams
+ @Mock private lateinit var qsAnimator: QSAnimator
+ @Mock private lateinit var quickQsPanelController: QuickQSPanelController
+ @Mock private lateinit var qstileView: QSTileViewImpl
+ @Mock private lateinit var qstile: QSTile
+ @Mock private lateinit var tileLayout: TileLayout
+
+ @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
+
+ private lateinit var qsSquishinessController: QSSquishinessController
+
+ @Before
+ fun setup() {
+ qsSquishinessController = QSSquishinessController(qsTileHost, qqsFooterActionsView,
+ qsAnimator, quickQsPanelController)
+ `when`(qsTileHost.tiles).thenReturn(mutableListOf(qstile))
+ `when`(quickQsPanelController.getTileView(any())).thenReturn(qstileView)
+ `when`(quickQsPanelController.tileLayout).thenReturn(tileLayout)
+ `when`(qqsFooterActionsView.layoutParams).thenReturn(qqsFooterActionsViewLP)
+ }
+
+ @Test
+ fun setSquishiness_requestsAnimatorUpdate() {
+ qsSquishinessController.squishiness = 0.5f
+ verify(qsAnimator, never()).requestAnimatorUpdate()
+
+ qsSquishinessController.squishiness = 0f
+ verify(qsAnimator).requestAnimatorUpdate()
+ }
+
+ @Test
+ fun setSquishiness_updatesTiles() {
+ qsSquishinessController.squishiness = 0.5f
+ verify(qstileView).squishinessFraction = 0.5f
+ verify(tileLayout).setSquishinessFraction(0.5f)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 0b67c9c51079..6bb2b9dda593 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -49,6 +49,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
@@ -60,7 +61,6 @@ import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSBrightnessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSBrightnessControllerTest.kt
new file mode 100644
index 000000000000..de1d86b08785
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSBrightnessControllerTest.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.systemui.qs
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.brightness.BrightnessController
+import com.android.systemui.statusbar.policy.BrightnessMirrorController
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.never
+import org.mockito.Mockito.mock
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+class QuickQSBrightnessControllerTest : SysuiTestCase() {
+
+ @Mock
+ lateinit var brightnessController: BrightnessController
+ @get:Rule
+ val mockito = MockitoJUnit.rule()
+
+ lateinit var quickQSBrightnessController: QuickQSBrightnessController
+
+ @Before
+ fun setUp() {
+ quickQSBrightnessController = QuickQSBrightnessController(
+ brightnessControllerFactory = { brightnessController })
+ }
+
+ @Test
+ fun testSliderIsShownWhenInitializedInSplitShade() {
+ quickQSBrightnessController.init(shouldUseSplitNotificationShade = true)
+
+ verify(brightnessController).showSlider()
+ }
+
+ @Test
+ fun testSliderIsShownWhenRefreshedInSplitShade() {
+ quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = true)
+
+ verify(brightnessController, times(1)).showSlider()
+ }
+
+ @Test
+ fun testSliderIsHiddenWhenRefreshedInNonSplitShade() {
+ // needs to be shown first
+ quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = true)
+ quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = false)
+
+ verify(brightnessController).hideSlider()
+ }
+
+ @Test
+ fun testSliderChangesVisibilityWhenRotating() {
+ quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = true)
+ verify(brightnessController, times(1)).showSlider()
+
+ quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = false)
+ verify(brightnessController, times(1)).hideSlider()
+ }
+
+ @Test
+ fun testCallbacksAreRegisteredOnlyOnce() {
+ // this flow simulates expanding shade in portrait...
+ quickQSBrightnessController.setListening(true)
+ quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = false)
+ // ... and rotating to landscape/split shade where slider is visible
+ quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = true)
+
+ verify(brightnessController, times(1)).registerCallbacks()
+ }
+
+ @Test
+ fun testCallbacksAreRegisteredOnlyOnceWhenRotatingPhone() {
+ quickQSBrightnessController.setListening(true)
+ quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = true)
+ quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = false)
+ quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = true)
+
+ verify(brightnessController, times(1)).registerCallbacks()
+ }
+
+ @Test
+ fun testCallbacksAreNotRegisteredWhenSliderNotVisible() {
+ quickQSBrightnessController.setListening(true)
+ quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = false)
+
+ verify(brightnessController, never()).registerCallbacks()
+ }
+
+ @Test
+ fun testMirrorIsSetWhenSliderIsShown() {
+ val mirrorController = mock(BrightnessMirrorController::class.java)
+ quickQSBrightnessController.setMirror(mirrorController)
+ quickQSBrightnessController.refreshVisibility(shouldUseSplitNotificationShade = true)
+
+ verify(brightnessController).setMirror(mirrorController)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 0604e1b42c9d..59948d310b4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -16,8 +16,9 @@
package com.android.systemui.qs
+import android.content.res.Configuration
+import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
@@ -27,12 +28,13 @@ import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.qs.QSTileView
import com.android.systemui.qs.customize.QSCustomizerController
import com.android.systemui.qs.logging.QSLogger
-import com.android.systemui.statusbar.FeatureFlags
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
@@ -65,7 +67,11 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
@Mock
private lateinit var tileView: QSTileView
@Mock
- private lateinit var featureFlags: FeatureFlags
+ private lateinit var quickQsBrightnessController: QuickQSBrightnessController
+ @Mock
+ private lateinit var footerActionsController: FooterActionsController
+ @Captor
+ private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
private lateinit var controller: QuickQSPanelController
@@ -74,7 +80,9 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
`when`(quickQSPanel.tileLayout).thenReturn(tileLayout)
+ `when`(quickQSPanel.isAttachedToWindow).thenReturn(true)
`when`(quickQSPanel.dumpableTag).thenReturn("")
+ `when`(quickQSPanel.resources).thenReturn(mContext.resources)
`when`(qsTileHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView)
controller = QuickQSPanelController(
@@ -87,7 +95,8 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
uiEventLogger,
qsLogger,
dumpManager,
- featureFlags
+ quickQsBrightnessController,
+ footerActionsController
)
controller.init()
@@ -117,4 +126,16 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
verify(quickQSPanel, times(limit)).addTile(any())
}
-} \ No newline at end of file
+
+ @Test
+ fun testBrightnessAndFooterVisibilityRefreshedWhenConfigurationChanged() {
+ // times(2) because both controller and base controller are registering their listeners
+ verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
+
+ captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) }
+
+ verify(quickQsBrightnessController).refreshVisibility(anyBoolean())
+ // times(2) because footer visibility is also refreshed on controller init
+ verify(footerActionsController, times(2)).refreshVisibility(anyBoolean())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 8b7e20ed0e5a..92b9f75936b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -23,8 +23,10 @@ import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.colorextraction.SysuiColorExtractor
import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyDialogController
@@ -32,7 +34,6 @@ import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController
-import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.policy.Clock
@@ -49,9 +50,9 @@ import org.junit.runner.RunWith
import org.mockito.Answers
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -93,6 +94,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
@Mock
private lateinit var variableDateViewController: VariableDateViewController
@Mock
+ private lateinit var batteryMeterViewController: BatteryMeterViewController
+ @Mock
private lateinit var clock: Clock
@Mock
private lateinit var variableDateView: VariableDateView
@@ -143,6 +146,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
colorExtractor,
privacyDialogController,
qsExpansionPathInterpolator,
+ batteryMeterViewController,
featureFlags,
variableDateViewControllerFactory
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index 72c7ddd3e5a5..8ae7100e2e60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -37,10 +37,10 @@ import android.widget.TextView;
import androidx.test.filters.SmallTest;
import com.android.keyguard.CarrierTextManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
+import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import com.android.systemui.utils.os.FakeHandler;
@@ -232,7 +232,7 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest {
MobileDataIndicators indicators = new MobileDataIndicators(
mock(NetworkController.IconState.class),
mock(NetworkController.IconState.class),
- 0, 0, true, true, "", "", "", true, 0, true, true);
+ 0, 0, true, true, "", "", "", 0, true, true);
mSignalCallback.setMobileDataIndicators(indicators);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index c5b67091d197..018806e816d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -53,12 +53,12 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.InstanceId;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 01fa222896d3..e3045eb2d63f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -43,12 +43,12 @@ import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index e939411e4a2a..f0bd06571eb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -43,12 +43,12 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index 94af10a485fd..98c7274aeba6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -52,13 +52,11 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.never
import org.mockito.Mockito.nullable
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
@@ -272,28 +270,7 @@ class DeviceControlsTileTest : SysuiTestCase() {
}
@Test
- fun handleClick_availableAndLocked_activityStarted() {
- verify(controlsListingController).observe(
- any(LifecycleOwner::class.java),
- capture(listingCallbackCaptor)
- )
- `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
- `when`(keyguardStateController.isUnlocked).thenReturn(false)
-
- listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
- testableLooper.processAllMessages()
-
- tile.click(null /* view */)
- testableLooper.processAllMessages()
-
- // The activity should be started right away and not require a keyguard dismiss.
- verifyZeroInteractions(activityStarter)
- verify(spiedContext).startActivity(intentCaptor.capture())
- assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
- }
-
- @Test
- fun handleClick_availableAndUnlocked_activityStarted() {
+ fun handleClick_available_shownOverLockscreenWhenLocked() {
verify(controlsListingController).observe(
any(LifecycleOwner::class.java),
capture(listingCallbackCaptor)
@@ -307,16 +284,16 @@ class DeviceControlsTileTest : SysuiTestCase() {
tile.click(null /* view */)
testableLooper.processAllMessages()
- verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt())
verify(activityStarter).startActivity(
intentCaptor.capture(),
eq(true) /* dismissShade */,
- nullable(ActivityLaunchAnimator.Controller::class.java))
+ nullable(ActivityLaunchAnimator.Controller::class.java),
+ eq(true) /* showOverLockscreenWhenLocked */)
assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
}
@Test
- fun handleClick_availableAfterUnlockAndIsLocked_keyguardDismissRequired() {
+ fun handleClick_availableAfterUnlock_notShownOverLockscreenWhenLocked() {
verify(controlsListingController).observe(
any(LifecycleOwner::class.java),
capture(listingCallbackCaptor)
@@ -331,38 +308,11 @@ class DeviceControlsTileTest : SysuiTestCase() {
tile.click(null /* view */)
testableLooper.processAllMessages()
- verify(activityStarter, never()).startActivity(
- any(),
- anyBoolean() /* dismissShade */,
- nullable(ActivityLaunchAnimator.Controller::class.java))
- verify(activityStarter).postStartActivityDismissingKeyguard(
- intentCaptor.capture(),
- anyInt(),
- nullable(ActivityLaunchAnimator.Controller::class.java))
- assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
- }
-
- @Test
- fun handleClick_availableAfterUnlockAndIsUnlocked_activityStarted() {
- verify(controlsListingController).observe(
- any(LifecycleOwner::class.java),
- capture(listingCallbackCaptor)
- )
- `when`(controlsComponent.getVisibility())
- .thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)
- `when`(keyguardStateController.isUnlocked).thenReturn(true)
-
- listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
- testableLooper.processAllMessages()
-
- tile.click(null /* view */)
- testableLooper.processAllMessages()
-
- verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt())
verify(activityStarter).startActivity(
intentCaptor.capture(),
- eq(true) /* dismissShade */,
- nullable(ActivityLaunchAnimator.Controller::class.java))
+ anyBoolean() /* dismissShade */,
+ nullable(ActivityLaunchAnimator.Controller::class.java),
+ eq(false) /* showOverLockscreenWhenLocked */)
assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index a70c2be4954e..8922b43b7447 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -228,7 +228,9 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
mTile.handleClick(null /* view */);
mTestableLooper.processAllMessages();
- verify(mSpiedContext).startActivity(mIntentCaptor.capture());
+ verify(mActivityStarter).startActivity(mIntentCaptor.capture(), eq(true) /* dismissShade */,
+ (ActivityLaunchAnimator.Controller) eq(null),
+ eq(true) /* showOverLockscreenWhenLocked */);
Intent nextStartedIntent = mIntentCaptor.getValue();
String walletClassName = "com.android.systemui.wallet.ui.WalletActivity";
@@ -246,7 +248,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
mTestableLooper.processAllMessages();
verify(mActivityStarter).startActivity(mIntentCaptor.capture(), eq(true) /* dismissShade */,
- (ActivityLaunchAnimator.Controller) eq(null));
+ (ActivityLaunchAnimator.Controller) eq(null),
+ eq(true) /* showOverLockscreenWhenLocked */);
Intent nextStartedIntent = mIntentCaptor.getValue();
String walletClassName = "com.android.systemui.wallet.ui.WalletActivity";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
deleted file mode 100644
index 49ab777624e3..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ /dev/null
@@ -1,209 +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.systemui.qs.tiles;
-
-import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
-import static android.provider.Settings.Secure.CAMERA_AUTOROTATE;
-
-import static junit.framework.TestCase.assertEquals;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.when;
-
-import android.Manifest;
-import android.content.pm.PackageManager;
-import android.hardware.SensorPrivacyManager;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.text.TextUtils;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSTileHost;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
-import com.android.systemui.util.settings.FakeSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@SmallTest
-public class RotationLockTileTest extends SysuiTestCase {
-
- private static final String PACKAGE_NAME = "package_name";
-
- @Mock
- private PackageManager mPackageManager;
- @Mock
- private ActivityStarter mActivityStarter;
- @Mock
- private QSTileHost mHost;
- @Mock
- private MetricsLogger mMetricsLogger;
- @Mock
- private StatusBarStateController mStatusBarStateController;
- @Mock
- private QSLogger mQSLogger;
- @Mock
- private SensorPrivacyManager mPrivacyManager;
- @Mock
- private BatteryController mBatteryController;
-
- private SecureSettings mSecureSettings;
- private RotationLockController mController;
- private TestableLooper mTestableLooper;
- private RotationLockTile mLockTile;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mTestableLooper = TestableLooper.get(this);
-
- when(mHost.getContext()).thenReturn(mContext);
- when(mHost.getUserContext()).thenReturn(mContext);
-
- mSecureSettings = new FakeSettings();
- mController = new RotationLockControllerImpl(mContext, mSecureSettings);
-
- mLockTile = new RotationLockTile(
- mHost,
- mTestableLooper.getLooper(),
- new Handler(mTestableLooper.getLooper()),
- new FalsingManagerFake(),
- mMetricsLogger,
- mStatusBarStateController,
- mActivityStarter,
- mQSLogger,
- mController,
- mPrivacyManager,
- mBatteryController,
- mSecureSettings
- );
-
- mLockTile.initialize();
-
- // We are not setting the mocks to listening, so we trigger a first refresh state to
- // set the initial state
- mLockTile.refreshState();
-
- mTestableLooper.processAllMessages();
-
- mContext.setMockPackageManager(mPackageManager);
- doReturn(PACKAGE_NAME).when(mPackageManager).getRotationResolverPackageName();
- doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
- Manifest.permission.CAMERA, PACKAGE_NAME);
-
- when(mBatteryController.isPowerSave()).thenReturn(false);
- when(mPrivacyManager.isSensorPrivacyEnabled(CAMERA)).thenReturn(false);
- enableAutoRotation();
- enableCameraBasedRotation();
-
- mLockTile.refreshState();
- mTestableLooper.processAllMessages();
- }
-
- @Test
- public void testSecondaryString_cameraRotateOn_returnsFaceBased() {
- assertEquals("On - Face-based", mLockTile.getState().secondaryLabel);
- }
-
- @Test
- public void testSecondaryString_rotateOff_isEmpty() {
- disableAutoRotation();
-
- mLockTile.refreshState();
- mTestableLooper.processAllMessages();
-
- assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
- }
-
- @Test
- public void testSecondaryString_cameraRotateOff_isEmpty() {
- disableCameraBasedRotation();
-
- mLockTile.refreshState();
- mTestableLooper.processAllMessages();
-
- assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
- }
-
- @Test
- public void testSecondaryString_powerSaveEnabled_isEmpty() {
- when(mBatteryController.isPowerSave()).thenReturn(true);
-
- mLockTile.refreshState();
- mTestableLooper.processAllMessages();
-
- assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
- }
-
- @Test
- public void testSecondaryString_cameraDisabled_isEmpty() {
- when(mPrivacyManager.isSensorPrivacyEnabled(CAMERA)).thenReturn(true);
-
- mLockTile.refreshState();
- mTestableLooper.processAllMessages();
-
- assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
- }
-
- @Test
- public void testSecondaryString_noCameraPermission_isEmpty() {
- doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
- Manifest.permission.CAMERA, PACKAGE_NAME);
-
- mLockTile.refreshState();
- mTestableLooper.processAllMessages();
-
- assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
- }
-
- private void enableAutoRotation() {
- Settings.System.putIntForUser(mContext.getContentResolver(),
- Settings.System.ACCELEROMETER_ROTATION, 1, UserHandle.USER_CURRENT);
- }
-
- private void disableAutoRotation() {
- Settings.System.putIntForUser(mContext.getContentResolver(),
- Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT);
- }
-
- private void enableCameraBasedRotation() {
- mSecureSettings.putIntForUser(
- CAMERA_AUTOROTATE, 1, UserHandle.USER_CURRENT);
- }
-
- private void disableCameraBasedRotation() {
- mSecureSettings.putIntForUser(
- CAMERA_AUTOROTATE, 0, UserHandle.USER_CURRENT);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 663edc7b373b..eb03b5ff2a6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -45,13 +45,14 @@ import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.wifi.WifiUtils;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.NetworkController.AccessPointController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
import com.android.systemui.toast.SystemUIToast;
import com.android.systemui.toast.ToastFactory;
import com.android.systemui.util.CarrierConfigTracker;
@@ -138,6 +139,8 @@ public class InternetDialogControllerTest extends SysuiTestCase {
private CarrierConfigTracker mCarrierConfigTracker;
@Mock
private LocationController mLocationController;
+ @Mock
+ private DialogLaunchAnimator mDialogLaunchAnimator;
private TestableResources mTestableResources;
private MockInternetDialogController mInternetDialogController;
@@ -174,7 +177,7 @@ public class InternetDialogControllerTest extends SysuiTestCase {
mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher,
mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController,
mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker,
- mLocationController);
+ mLocationController, mDialogLaunchAnimator);
mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
mInternetDialogController.mOnSubscriptionsChangedListener);
mInternetDialogController.onStart(mInternetDialogCallback, true);
@@ -654,12 +657,13 @@ public class InternetDialogControllerTest extends SysuiTestCase {
KeyguardStateController keyguardStateController, WindowManager windowManager,
ToastFactory toastFactory, Handler workerHandler,
CarrierConfigTracker carrierConfigTracker,
- LocationController locationController) {
+ LocationController locationController,
+ DialogLaunchAnimator dialogLaunchAnimator) {
super(context, uiEventLogger, starter, accessPointController, subscriptionManager,
telephonyManager, wifiManager, connectivityManager, handler, mainExecutor,
broadcastDispatcher, keyguardUpdateMonitor, globalSettings,
keyguardStateController, windowManager, toastFactory, workerHandler,
- carrierConfigTracker, locationController);
+ carrierConfigTracker, locationController, dialogLaunchAnimator);
mGlobalSettings = globalSettings;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt
new file mode 100644
index 000000000000..d5fe588b2115
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt
@@ -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.systemui.qs.user
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class UserDialogTest : SysuiTestCase() {
+
+ private lateinit var dialog: UserDialog
+
+ @Before
+ fun setUp() {
+ dialog = UserDialog(mContext)
+ }
+
+ @After
+ fun tearDown() {
+ dialog.dismiss()
+ }
+
+ @Test
+ fun doneButtonExists() {
+ assertThat(dialog.doneButton).isInstanceOf(View::class.java)
+ }
+
+ @Test
+ fun settingsButtonExists() {
+ assertThat(dialog.settingsButton).isInstanceOf(View::class.java)
+ }
+
+ @Test
+ fun gridExistsAndIsViewGroup() {
+ assertThat(dialog.grid).isInstanceOf(ViewGroup::class.java)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
new file mode 100644
index 000000000000..7e900c843cc3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -0,0 +1,207 @@
+/*
+ * 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.systemui.qs.user
+
+import android.content.Intent
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.PseudoGridView
+import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatcher
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.function.Consumer
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class UserSwitchDialogControllerTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var dialog: UserDialog
+ @Mock
+ private lateinit var falsingManager: FalsingManager
+ @Mock
+ private lateinit var settingsView: View
+ @Mock
+ private lateinit var doneView: View
+ @Mock
+ private lateinit var activityStarter: ActivityStarter
+ @Mock
+ private lateinit var userDetailViewAdapter: UserDetailView.Adapter
+ @Mock
+ private lateinit var launchView: View
+ @Mock
+ private lateinit var gridView: PseudoGridView
+ @Mock
+ private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+ @Captor
+ private lateinit var clickCaptor: ArgumentCaptor<View.OnClickListener>
+
+ private lateinit var controller: UserSwitchDialogController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(dialog.settingsButton).thenReturn(settingsView)
+ `when`(dialog.doneButton).thenReturn(doneView)
+ `when`(dialog.grid).thenReturn(gridView)
+
+ `when`(launchView.context).thenReturn(mContext)
+
+ controller = UserSwitchDialogController(
+ { userDetailViewAdapter },
+ activityStarter,
+ falsingManager,
+ dialogLaunchAnimator,
+ { dialog }
+ )
+ }
+
+ @Test
+ fun showDialog_callsDialogShow() {
+ controller.showDialog(launchView)
+ verify(dialogLaunchAnimator).showFromView(dialog, launchView)
+ }
+
+ @Test
+ fun createCalledBeforeDoneButton() {
+ controller.showDialog(launchView)
+ val inOrder = inOrder(dialog)
+ inOrder.verify(dialog).create()
+ inOrder.verify(dialog).doneButton
+ }
+
+ @Test
+ fun createCalledBeforeSettingsButton() {
+ controller.showDialog(launchView)
+ val inOrder = inOrder(dialog)
+ inOrder.verify(dialog).create()
+ inOrder.verify(dialog).settingsButton
+ }
+
+ @Test
+ fun createCalledBeforeGrid() {
+ controller.showDialog(launchView)
+ val inOrder = inOrder(dialog)
+ inOrder.verify(dialog).create()
+ inOrder.verify(dialog).grid
+ }
+
+ @Test
+ fun dialog_showForAllUsers() {
+ controller.showDialog(launchView)
+ verify(dialog).setShowForAllUsers(true)
+ }
+
+ @Test
+ fun dialog_cancelOnTouchOutside() {
+ controller.showDialog(launchView)
+ verify(dialog).setCanceledOnTouchOutside(true)
+ }
+
+ @Test
+ fun adapterAndGridLinked() {
+ controller.showDialog(launchView)
+ verify(userDetailViewAdapter).linkToViewGroup(gridView)
+ }
+
+ @Test
+ fun clickDoneButton_dismiss() {
+ controller.showDialog(launchView)
+
+ verify(doneView).setOnClickListener(capture(clickCaptor))
+
+ clickCaptor.value.onClick(doneView)
+
+ verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt())
+ verify(dialog).dismiss()
+ }
+
+ @Test
+ fun clickSettingsButton_noFalsing_opensSettingsAndDismisses() {
+ `when`(falsingManager.isFalseTap(anyInt())).thenReturn(false)
+
+ controller.showDialog(launchView)
+
+ verify(settingsView).setOnClickListener(capture(clickCaptor))
+
+ clickCaptor.value.onClick(settingsView)
+
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ argThat(IntentMatcher(Settings.ACTION_USER_SETTINGS)),
+ eq(0)
+ )
+ verify(dialog).dismiss()
+ }
+
+ @Test
+ fun clickSettingsButton_Falsing_notOpensSettingsAndDismisses() {
+ `when`(falsingManager.isFalseTap(anyInt())).thenReturn(true)
+
+ controller.showDialog(launchView)
+
+ verify(settingsView).setOnClickListener(capture(clickCaptor))
+
+ clickCaptor.value.onClick(settingsView)
+
+ verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt())
+ verify(dialog).dismiss()
+ }
+
+ @Test
+ fun callbackFromDetailView_dismissesDialog() {
+ val captor = argumentCaptor<Consumer<UserSwitcherController.UserRecord>>()
+
+ controller.showDialog(launchView)
+ verify(userDetailViewAdapter).injectCallback(capture(captor))
+
+ captor.value.accept(mock(UserSwitcherController.UserRecord::class.java))
+
+ verify(dialog).dismiss()
+ }
+
+ private class IntentMatcher(private val action: String) : ArgumentMatcher<Intent> {
+ override fun matches(argument: Intent?): Boolean {
+ return argument?.action == action
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index e0187bdf6ada..2b39354d99e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -45,7 +45,7 @@ import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
-class BrightnessSliderTest : SysuiTestCase() {
+class BrightnessSliderControllerTest : SysuiTestCase() {
@Mock
private lateinit var brightnessSliderView: BrightnessSliderView
@@ -66,7 +66,7 @@ class BrightnessSliderTest : SysuiTestCase() {
private lateinit var seekBar: SeekBar
private var mFalsingManager: FalsingManagerFake = FalsingManagerFake()
- private lateinit var mController: BrightnessSlider
+ private lateinit var mController: BrightnessSliderController
@Before
fun setUp() {
@@ -75,7 +75,7 @@ class BrightnessSliderTest : SysuiTestCase() {
whenever(mirrorController.toggleSlider).thenReturn(mirror)
whenever(motionEvent.copy()).thenReturn(motionEvent)
- mController = BrightnessSlider(brightnessSliderView, mFalsingManager)
+ mController = BrightnessSliderController(brightnessSliderView, mFalsingManager)
mController.init()
mController.setOnChangedListener(listener)
}
@@ -108,15 +108,6 @@ class BrightnessSliderTest : SysuiTestCase() {
}
@Test
- fun testNullMirrorControllerNotTrackingTouch() {
- mController.setMirrorControllerAndMirror(null)
-
- verify(brightnessSliderView, never()).max
- verify(brightnessSliderView, never()).value
- verify(brightnessSliderView).setOnDispatchTouchEventListener(isNull())
- }
-
- @Test
fun testNullMirrorNotTrackingTouch() {
whenever(mirrorController.toggleSlider).thenReturn(null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
new file mode 100644
index 000000000000..6a68b71f639b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.shared.animation
+
+import android.graphics.Point
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.Surface.ROTATION_0
+import android.view.Surface.ROTATION_90
+import android.view.View
+import android.view.WindowManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var windowManager: WindowManager
+
+ @get:Rule
+ val mockito = MockitoJUnit.rule()
+
+ private lateinit var animator: UnfoldMoveFromCenterAnimator
+
+ @Before
+ fun before() {
+ animator = UnfoldMoveFromCenterAnimator(windowManager)
+ }
+
+ @Test
+ fun testRegisterViewOnTheLeftOfVerticalFold_halfProgress_viewTranslatedToTheRight() {
+ givenScreen(width = 100, height = 100, rotation = ROTATION_0)
+ val view = createView(x = 20, width = 10, height = 10)
+ animator.registerViewForAnimation(view)
+ animator.onTransitionStarted()
+
+ animator.onTransitionProgress(0.5f)
+
+ // Positive translationX -> translated to the right
+ // 10x10 view center is 25px from the center,
+ // When progress is 0.5 it should be translated at:
+ // 25 * 0.3 * (1 - 0.5) = 3.75px
+ assertThat(view.translationX).isWithin(0.01f).of(3.75f)
+ }
+
+ @Test
+ fun testRegisterViewOnTheLeftOfVerticalFold_zeroProgress_viewTranslatedToTheRight() {
+ givenScreen(width = 100, height = 100, rotation = ROTATION_0)
+ val view = createView(x = 20, width = 10, height = 10)
+ animator.registerViewForAnimation(view)
+ animator.onTransitionStarted()
+
+ animator.onTransitionProgress(0f)
+
+ // Positive translationX -> translated to the right
+ // 10x10 view center is 25px from the center,
+ // When progress is 0 it should be translated at:
+ // 25 * 0.3 * (1 - 0) = 7.5px
+ assertThat(view.translationX).isWithin(0.01f).of(7.5f)
+ }
+
+ @Test
+ fun testRegisterViewOnTheLeftOfVerticalFold_fullProgress_viewTranslatedToTheOriginalPosition() {
+ givenScreen(width = 100, height = 100, rotation = ROTATION_0)
+ val view = createView(x = 20, width = 10, height = 10)
+ animator.registerViewForAnimation(view)
+ animator.onTransitionStarted()
+
+ animator.onTransitionProgress(1f)
+
+ // Positive translationX -> translated to the right
+ // 10x10 view center is 25px from the center,
+ // When progress is 1 it should be translated at:
+ // 25 * 0.3 * 0 = 0px
+ assertThat(view.translationX).isEqualTo(0f)
+ }
+
+ @Test
+ fun testViewOnTheLeftOfVerticalFoldWithTranslation_halfProgress_viewTranslatedToTheRight() {
+ givenScreen(width = 100, height = 100, rotation = ROTATION_0)
+ val view = createView(x = 20, width = 10, height = 10, translationX = 100f)
+ animator.registerViewForAnimation(view)
+ animator.onTransitionStarted()
+
+ animator.onTransitionProgress(0.5f)
+
+ // Positive translationX -> translated to the right, original translation is ignored
+ // 10x10 view center is 25px from the center,
+ // When progress is 0.5 it should be translated at:
+ // 25 * 0.3 * (1 - 0.5) = 3.75px
+ assertThat(view.translationX).isWithin(0.01f).of(3.75f)
+ }
+
+ @Test
+ fun testRegisterViewAndUnregister_halfProgress_viewIsNotUpdated() {
+ givenScreen(width = 100, height = 100, rotation = ROTATION_0)
+ val view = createView(x = 20, width = 10, height = 10)
+ animator.registerViewForAnimation(view)
+ animator.onTransitionStarted()
+ animator.clearRegisteredViews()
+
+ animator.onTransitionProgress(0.5f)
+
+ assertThat(view.translationX).isEqualTo(0f)
+ }
+
+ @Test
+ fun testRegisterViewUpdateProgressAndUnregister_halfProgress_viewIsNotUpdated() {
+ givenScreen(width = 100, height = 100, rotation = ROTATION_0)
+ val view = createView(x = 20, width = 10, height = 10)
+ animator.registerViewForAnimation(view)
+ animator.onTransitionStarted()
+ animator.onTransitionProgress(0.2f)
+ animator.clearRegisteredViews()
+
+ animator.onTransitionProgress(0.5f)
+
+ assertThat(view.translationX).isEqualTo(0f)
+ }
+
+ @Test
+ fun testRegisterViewOnTheTopOfHorizontalFold_halfProgress_viewTranslatedToTheBottom() {
+ givenScreen(width = 100, height = 100, rotation = ROTATION_90)
+ val view = createView(y = 20, width = 10, height = 10)
+ animator.registerViewForAnimation(view)
+ animator.onTransitionStarted()
+
+ animator.onTransitionProgress(0.5f)
+
+ // Positive translationY -> translated to the bottom
+ assertThat(view.translationY).isWithin(0.01f).of(3.75f)
+ }
+
+ @Test
+ fun testUpdateViewPositions_viewOnTheLeftAndMovedToTheRight_viewTranslatedToTheLeft() {
+ givenScreen(width = 100, height = 100, rotation = ROTATION_0)
+ val view = createView(x = 20)
+ animator.registerViewForAnimation(view)
+ animator.onTransitionStarted()
+ animator.onTransitionProgress(0.5f)
+ view.updateMock(x = 80) // view moved from the left side to the right
+
+ animator.updateViewPositions()
+
+ // Negative translationX -> translated to the left
+ assertThat(view.translationX).isWithin(0.1f).of(-5.25f)
+ }
+
+ private fun createView(
+ x: Int = 0,
+ y: Int = 0,
+ width: Int = 10,
+ height: Int = 10,
+ translationX: Float = 0f,
+ translationY: Float = 0f
+ ): View {
+ val view = spy(View(context))
+ doAnswer {
+ val location = (it.arguments[0] as IntArray)
+ location[0] = x
+ location[1] = y
+ Unit
+ }.`when`(view).getLocationOnScreen(any())
+
+ whenever(view.width).thenReturn(width)
+ whenever(view.height).thenReturn(height)
+
+ view.updateMock(x, y, width, height, translationX, translationY)
+
+ return view
+ }
+
+ private fun View.updateMock(
+ x: Int = 0,
+ y: Int = 0,
+ width: Int = 10,
+ height: Int = 10,
+ translationX: Float = 0f,
+ translationY: Float = 0f
+ ) {
+ doAnswer {
+ val location = (it.arguments[0] as IntArray)
+ location[0] = x
+ location[1] = y
+ Unit
+ }.`when`(this).getLocationOnScreen(any())
+
+ whenever(this.width).thenReturn(width)
+ whenever(this.height).thenReturn(height)
+
+ this.apply {
+ setTranslationX(translationX)
+ setTranslationY(translationY)
+ }
+ }
+
+ private fun givenScreen(
+ width: Int = 100,
+ height: Int = 100,
+ rotation: Int = ROTATION_0
+ ) {
+ val display = mock(Display::class.java)
+ whenever(display.getSize(any())).thenAnswer {
+ val size = (it.arguments[0] as Point)
+ size.set(width, height)
+ Unit
+ }
+ whenever(display.rotation).thenReturn(rotation)
+ whenever(windowManager.defaultDisplay).thenReturn(display)
+
+ animator.updateDisplayProperties()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
new file mode 100644
index 000000000000..05280fa826ed
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.plugins;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.annotations.Requires;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PluginActionManagerTest extends SysuiTestCase {
+
+ private static final String PRIVILEGED_PACKAGE = "com.android.systemui.shared.plugins";
+ private TestPlugin mMockPlugin;
+
+ private PackageManager mMockPm;
+ private PluginListener<TestPlugin> mMockListener;
+ private PluginActionManager<TestPlugin> mPluginActionManager;
+ private VersionInfo mMockVersionInfo;
+ private PluginEnabler mMockEnabler;
+ ComponentName mTestPluginComponentName =
+ new ComponentName(PRIVILEGED_PACKAGE, TestPlugin.class.getName());
+ private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+ NotificationManager mNotificationManager;
+ private PluginInstance<TestPlugin> mPluginInstance;
+ private PluginInstance.Factory mPluginInstanceFactory = new PluginInstance.Factory(
+ this.getClass().getClassLoader(),
+ new PluginInstance.InstanceFactory<>(), new PluginInstance.VersionChecker(),
+ Collections.emptyList(), false) {
+ @Override
+ public <T extends Plugin> PluginInstance<T> create(Context context, ApplicationInfo appInfo,
+ ComponentName componentName, Class<T> pluginClass) {
+ return (PluginInstance<T>) mPluginInstance;
+ }
+ };
+
+ private PluginActionManager.Factory mActionManagerFactory;
+
+ @Before
+ public void setup() throws Exception {
+ mContext = new MyContextWrapper(mContext);
+ mMockPm = mock(PackageManager.class);
+ mMockListener = mock(PluginListener.class);
+ mMockEnabler = mock(PluginEnabler.class);
+ mMockVersionInfo = mock(VersionInfo.class);
+ mNotificationManager = mock(NotificationManager.class);
+ mMockPlugin = mock(TestPlugin.class);
+ mPluginInstance = mock(PluginInstance.class);
+ when(mPluginInstance.getComponentName()).thenReturn(mTestPluginComponentName);
+ when(mPluginInstance.getPackage()).thenReturn(mTestPluginComponentName.getPackageName());
+ mActionManagerFactory = new PluginActionManager.Factory(getContext(), mMockPm,
+ mFakeExecutor, mFakeExecutor, mNotificationManager, mMockEnabler,
+ new ArrayList<>(), mPluginInstanceFactory);
+
+ mPluginActionManager = mActionManagerFactory.create("myAction", mMockListener,
+ TestPlugin.class, true, true);
+ when(mMockPlugin.getVersion()).thenReturn(1);
+ }
+
+ @Test
+ public void testNoPlugins() {
+ when(mMockPm.queryIntentServices(any(), anyInt())).thenReturn(
+ Collections.emptyList());
+ mPluginActionManager.loadAll();
+
+ mFakeExecutor.runAllReady();
+
+ verify(mMockListener, never()).onPluginConnected(any(), any());
+ }
+
+ @Test
+ public void testPluginCreate() throws Exception {
+ //Debug.waitForDebugger();
+ createPlugin();
+
+ // Verify startup lifecycle
+ verify(mPluginInstance).onCreate(mContext, mMockListener);
+ }
+
+ @Test
+ public void testPluginDestroy() throws Exception {
+ createPlugin(); // Get into valid created state.
+
+ mPluginActionManager.destroy();
+
+ mFakeExecutor.runAllReady();
+
+ // Verify shutdown lifecycle
+ verify(mPluginInstance).onDestroy(mMockListener);
+ }
+
+ @Test
+ public void testReloadOnChange() throws Exception {
+ createPlugin(); // Get into valid created state.
+
+ mPluginActionManager.reloadPackage(PRIVILEGED_PACKAGE);
+
+ mFakeExecutor.runAllReady();
+
+ // Verify the old one was destroyed.
+ verify(mPluginInstance).onDestroy(mMockListener);
+ verify(mPluginInstance, Mockito.times(2))
+ .onCreate(mContext, mMockListener);
+ }
+
+ @Test
+ public void testNonDebuggable() throws Exception {
+ // Create a version that thinks the build is not debuggable.
+ mPluginActionManager = mActionManagerFactory.create("myAction", mMockListener,
+ TestPlugin.class, true, false);
+ setupFakePmQuery();
+
+ mPluginActionManager.loadAll();
+
+ mFakeExecutor.runAllReady();
+
+ // Non-debuggable build should receive no plugins.
+ verify(mMockListener, never()).onPluginConnected(any(), any());
+ }
+
+ @Test
+ public void testNonDebuggable_privileged() throws Exception {
+ // Create a version that thinks the build is not debuggable.
+ PluginActionManager.Factory factory = new PluginActionManager.Factory(getContext(),
+ mMockPm, mFakeExecutor, mFakeExecutor, mNotificationManager,
+ mMockEnabler, Collections.singletonList(PRIVILEGED_PACKAGE),
+ mPluginInstanceFactory);
+ mPluginActionManager = factory.create("myAction", mMockListener,
+ TestPlugin.class, true, false);
+ setupFakePmQuery();
+
+ mPluginActionManager.loadAll();
+
+ mFakeExecutor.runAllReady();
+
+ // Verify startup lifecycle
+ verify(mPluginInstance).onCreate(mContext, mMockListener);
+ }
+
+ @Test
+ public void testCheckAndDisable() throws Exception {
+ createPlugin(); // Get into valid created state.
+
+ // Start with an unrelated class.
+ boolean result = mPluginActionManager.checkAndDisable(Activity.class.getName());
+ assertFalse(result);
+ verify(mMockEnabler, never()).setDisabled(any(ComponentName.class), anyInt());
+
+ // Now hand it a real class and make sure it disables the plugin.
+ result = mPluginActionManager.checkAndDisable(TestPlugin.class.getName());
+ assertTrue(result);
+ verify(mMockEnabler).setDisabled(
+ mTestPluginComponentName, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH);
+ }
+
+ @Test
+ public void testDisableAll() throws Exception {
+ createPlugin(); // Get into valid created state.
+
+ mPluginActionManager.disableAll();
+
+ verify(mMockEnabler).setDisabled(
+ mTestPluginComponentName, PluginEnabler.DISABLED_FROM_SYSTEM_CRASH);
+ }
+
+ @Test
+ public void testDisablePrivileged() throws Exception {
+ PluginActionManager.Factory factory = new PluginActionManager.Factory(getContext(),
+ mMockPm, mFakeExecutor, mFakeExecutor, mNotificationManager,
+ mMockEnabler, Collections.singletonList(PRIVILEGED_PACKAGE),
+ mPluginInstanceFactory);
+ mPluginActionManager = factory.create("myAction", mMockListener,
+ TestPlugin.class, true, false);
+
+ createPlugin(); // Get into valid created state.
+
+ mPluginActionManager.disableAll();
+
+ verify(mMockPm, never()).setComponentEnabledSetting(
+ ArgumentCaptor.forClass(ComponentName.class).capture(),
+ ArgumentCaptor.forClass(int.class).capture(),
+ ArgumentCaptor.forClass(int.class).capture());
+ }
+
+ private void setupFakePmQuery() throws Exception {
+ List<ResolveInfo> list = new ArrayList<>();
+ ResolveInfo info = new ResolveInfo();
+ info.serviceInfo = mock(ServiceInfo.class);
+ info.serviceInfo.packageName = mTestPluginComponentName.getPackageName();
+ info.serviceInfo.name = mTestPluginComponentName.getClassName();
+ when(info.serviceInfo.loadLabel(any())).thenReturn("Test Plugin");
+ list.add(info);
+ when(mMockPm.queryIntentServices(any(), Mockito.anyInt())).thenReturn(list);
+ when(mMockPm.getServiceInfo(any(), anyInt())).thenReturn(info.serviceInfo);
+
+ when(mMockPm.checkPermission(Mockito.anyString(), Mockito.anyString())).thenReturn(
+ PackageManager.PERMISSION_GRANTED);
+
+ when(mMockPm.getApplicationInfo(Mockito.anyString(), anyInt())).thenAnswer(
+ (Answer<ApplicationInfo>) invocation -> {
+ ApplicationInfo appInfo = getContext().getApplicationInfo();
+ appInfo.packageName = invocation.getArgument(0);
+ return appInfo;
+ });
+ when(mMockEnabler.isEnabled(mTestPluginComponentName)).thenReturn(true);
+ }
+
+ private void createPlugin() throws Exception {
+ setupFakePmQuery();
+
+ mPluginActionManager.loadAll();
+
+ mFakeExecutor.runAllReady();
+ }
+
+ // Real context with no registering/unregistering of receivers.
+ private static class MyContextWrapper extends SysuiTestableContext {
+ MyContextWrapper(Context base) {
+ super(base);
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ return null;
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent) {
+ // Do nothing.
+ }
+ }
+
+ // This target class doesn't matter, it just needs to have a Requires to hit the flow where
+ // the mock version info is called.
+ @Requires(target = PluginManagerTest.class, version = 1)
+ public static class TestPlugin implements Plugin {
+ @Override
+ public int getVersion() {
+ return 1;
+ }
+
+ @Override
+ public void onCreate(Context sysuiContext, Context pluginContext) {
+ }
+
+ @Override
+ public void onDestroy() {
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java
deleted file mode 100644
index 325d540ad741..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceManagerTest.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.shared.plugins;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Activity;
-import android.app.NotificationManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.os.HandlerThread;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.Plugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.annotations.Requires;
-import com.android.systemui.shared.plugins.PluginInstanceManager.PluginInfo;
-import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class PluginInstanceManagerTest extends SysuiTestCase {
-
- private static final String WHITELISTED_PACKAGE = "com.android.systemui";
- // Static since the plugin needs to be generated by the PluginInstanceManager using newInstance.
- private static Plugin sMockPlugin;
-
- private HandlerThread mHandlerThread;
- private Context mContextWrapper;
- private PackageManager mMockPm;
- private PluginListener mMockListener;
- private PluginInstanceManager mPluginInstanceManager;
- private PluginManagerImpl mMockManager;
- private VersionInfo mMockVersionInfo;
- private PluginEnabler mMockEnabler;
- ComponentName mTestPluginComponentName =
- new ComponentName(WHITELISTED_PACKAGE, TestPlugin.class.getName());
-
- @Before
- public void setup() throws Exception {
- mHandlerThread = new HandlerThread("test_thread");
- mHandlerThread.start();
- mContextWrapper = new MyContextWrapper(getContext());
- mMockPm = mock(PackageManager.class);
- mMockListener = mock(PluginListener.class);
- mMockManager = mock(PluginManagerImpl.class);
- when(mMockManager.getClassLoader(any())).thenReturn(getClass().getClassLoader());
- mMockEnabler = mock(PluginEnabler.class);
- when(mMockManager.getPluginEnabler()).thenReturn(mMockEnabler);
- mMockVersionInfo = mock(VersionInfo.class);
- mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
- mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo,
- mMockManager, true, new String[0]);
- sMockPlugin = mock(Plugin.class);
- when(sMockPlugin.getVersion()).thenReturn(1);
- }
-
- @After
- public void tearDown() throws Exception {
- mHandlerThread.quit();
- sMockPlugin = null;
- }
-
- @UiThreadTest
- @Test
- public void testGetPlugin() throws Exception {
- setupFakePmQuery();
- PluginInfo p = mPluginInstanceManager.getPlugin();
- assertNotNull(p.mPlugin);
- verify(sMockPlugin).onCreate(any(), any());
- }
-
- @Test
- public void testNoPlugins() throws Exception {
- when(mMockPm.queryIntentServices(any(), anyInt())).thenReturn(
- Collections.emptyList());
- mPluginInstanceManager.loadAll();
-
- waitForIdleSync(mPluginInstanceManager.mPluginHandler);
- waitForIdleSync(mPluginInstanceManager.mMainHandler);
-
- verify(mMockListener, never()).onPluginConnected(any(), any());
- }
-
- @Test
- public void testPluginCreate() throws Exception {
- createPlugin();
-
- // Verify startup lifecycle
- verify(sMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(),
- ArgumentCaptor.forClass(Context.class).capture());
- verify(mMockListener).onPluginConnected(any(), any());
- }
-
- @Test
- public void testPluginDestroy() throws Exception {
- createPlugin(); // Get into valid created state.
-
- mPluginInstanceManager.destroy();
-
- waitForIdleSync(mPluginInstanceManager.mPluginHandler);
- waitForIdleSync(mPluginInstanceManager.mMainHandler);
-
- // Verify shutdown lifecycle
- verify(mMockListener).onPluginDisconnected(ArgumentCaptor.forClass(Plugin.class).capture());
- verify(sMockPlugin).onDestroy();
- }
-
- @Test
- public void testIncorrectVersion() throws Exception {
- NotificationManager nm = mock(NotificationManager.class);
- mContext.addMockSystemService(Context.NOTIFICATION_SERVICE, nm);
- setupFakePmQuery();
- doThrow(new InvalidVersionException("", false)).when(mMockVersionInfo).checkVersion(any());
-
- mPluginInstanceManager.loadAll();
-
- waitForIdleSync(mPluginInstanceManager.mPluginHandler);
- waitForIdleSync(mPluginInstanceManager.mMainHandler);
-
- // Plugin shouldn't be connected because it is the wrong version.
- verify(mMockListener, never()).onPluginConnected(any(), any());
- verify(nm).notify(eq(SystemMessage.NOTE_PLUGIN), any());
- }
-
- @Test
- public void testReloadOnChange() throws Exception {
- createPlugin(); // Get into valid created state.
-
- mPluginInstanceManager.onPackageChange("com.android.systemui");
-
- waitForIdleSync(mPluginInstanceManager.mPluginHandler);
- waitForIdleSync(mPluginInstanceManager.mMainHandler);
-
- // Verify the old one was destroyed.
- verify(mMockListener).onPluginDisconnected(ArgumentCaptor.forClass(Plugin.class).capture());
- verify(sMockPlugin).onDestroy();
- // Also verify we got a second onCreate.
- verify(sMockPlugin, Mockito.times(2)).onCreate(
- ArgumentCaptor.forClass(Context.class).capture(),
- ArgumentCaptor.forClass(Context.class).capture());
- verify(mMockListener, Mockito.times(2)).onPluginConnected(any(), any());
- }
-
- @Test
- public void testNonDebuggable() throws Exception {
- // Create a version that thinks the build is not debuggable.
- mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
- mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo,
- mMockManager, false, new String[0]);
- setupFakePmQuery();
-
- mPluginInstanceManager.loadAll();
-
- waitForIdleSync(mPluginInstanceManager.mPluginHandler);
- waitForIdleSync(mPluginInstanceManager.mMainHandler);;
-
- // Non-debuggable build should receive no plugins.
- verify(mMockListener, never()).onPluginConnected(any(), any());
- }
-
- @Test
- public void testNonDebuggable_whitelist() throws Exception {
- // Create a version that thinks the build is not debuggable.
- mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
- mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo,
- mMockManager, false, new String[] {WHITELISTED_PACKAGE});
- setupFakePmQuery();
-
- mPluginInstanceManager.loadAll();
-
- waitForIdleSync(mPluginInstanceManager.mPluginHandler);
- waitForIdleSync(mPluginInstanceManager.mMainHandler);
-
- // Verify startup lifecycle
- verify(sMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(),
- ArgumentCaptor.forClass(Context.class).capture());
- verify(mMockListener).onPluginConnected(any(), any());
- }
-
- @Test
- public void testCheckAndDisable() throws Exception {
- createPlugin(); // Get into valid created state.
-
- // Start with an unrelated class.
- boolean result = mPluginInstanceManager.checkAndDisable(Activity.class.getName());
- assertFalse(result);
- verify(mMockEnabler, never()).setDisabled(any(ComponentName.class), anyInt());
-
- // Now hand it a real class and make sure it disables the plugin.
- result = mPluginInstanceManager.checkAndDisable(TestPlugin.class.getName());
- assertTrue(result);
- verify(mMockEnabler).setDisabled(
- mTestPluginComponentName, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH);
- }
-
- @Test
- public void testDisableAll() throws Exception {
- createPlugin(); // Get into valid created state.
-
- mPluginInstanceManager.disableAll();
-
- verify(mMockEnabler).setDisabled(
- mTestPluginComponentName, PluginEnabler.DISABLED_FROM_SYSTEM_CRASH);
- }
-
- @Test
- public void testDisableWhitelisted() throws Exception {
- mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
- mMockListener, true, mHandlerThread.getLooper(), mMockVersionInfo,
- mMockManager, false, new String[] {WHITELISTED_PACKAGE});
- createPlugin(); // Get into valid created state.
-
- mPluginInstanceManager.disableAll();
-
- verify(mMockPm, never()).setComponentEnabledSetting(
- ArgumentCaptor.forClass(ComponentName.class).capture(),
- ArgumentCaptor.forClass(int.class).capture(),
- ArgumentCaptor.forClass(int.class).capture());
- }
-
- private void setupFakePmQuery() throws Exception {
- List<ResolveInfo> list = new ArrayList<>();
- ResolveInfo info = new ResolveInfo();
- info.serviceInfo = mock(ServiceInfo.class);
- info.serviceInfo.packageName = mTestPluginComponentName.getPackageName();
- info.serviceInfo.name = mTestPluginComponentName.getClassName();
- when(info.serviceInfo.loadLabel(any())).thenReturn("Test Plugin");
- list.add(info);
- when(mMockPm.queryIntentServices(any(), Mockito.anyInt())).thenReturn(list);
- when(mMockPm.getServiceInfo(any(), anyInt())).thenReturn(info.serviceInfo);
-
- when(mMockPm.checkPermission(Mockito.anyString(), Mockito.anyString())).thenReturn(
- PackageManager.PERMISSION_GRANTED);
-
- ApplicationInfo appInfo = getContext().getApplicationInfo();
- when(mMockPm.getApplicationInfo(Mockito.anyString(), Mockito.anyInt())).thenReturn(
- appInfo);
- when(mMockEnabler.isEnabled(mTestPluginComponentName)).thenReturn(true);
- }
-
- private void createPlugin() throws Exception {
- setupFakePmQuery();
-
- mPluginInstanceManager.loadAll();
-
- waitForIdleSync(mPluginInstanceManager.mPluginHandler);
- waitForIdleSync(mPluginInstanceManager.mMainHandler);
- }
-
- // Real context with no registering/unregistering of receivers.
- private static class MyContextWrapper extends ContextWrapper {
- public MyContextWrapper(Context base) {
- super(base);
- }
-
- @Override
- public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
- return null;
- }
-
- @Override
- public void unregisterReceiver(BroadcastReceiver receiver) {
- }
-
- @Override
- public void sendBroadcast(Intent intent) {
- // Do nothing.
- }
- }
-
- // This target class doesn't matter, it just needs to have a Requires to hit the flow where
- // the mock version info is called.
- @Requires(target = PluginManagerTest.class, version = 1)
- public static class TestPlugin implements Plugin {
- @Override
- public int getVersion() {
- return sMockPlugin.getVersion();
- }
-
- @Override
- public void onCreate(Context sysuiContext, Context pluginContext) {
- sMockPlugin.onCreate(sysuiContext, pluginContext);
- }
-
- @Override
- public void onDestroy() {
- sMockPlugin.onDestroy();
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
new file mode 100644
index 000000000000..bb9a1e971fd0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.systemui.shared.plugins;
+
+import static junit.framework.Assert.assertNotNull;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+import com.android.systemui.plugins.annotations.Requires;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PluginInstanceTest extends SysuiTestCase {
+
+ private static final String PRIVILEGED_PACKAGE = "com.android.systemui.plugins";
+
+ @Mock
+ private TestPluginImpl mMockPlugin;
+ @Mock
+ private PluginListener<TestPlugin> mMockListener;
+ @Mock
+ private VersionInfo mVersionInfo;
+ ComponentName mTestPluginComponentName =
+ new ComponentName(PRIVILEGED_PACKAGE, TestPluginImpl.class.getName());
+ private PluginInstance<TestPlugin> mPluginInstance;
+ private PluginInstance.Factory mPluginInstanceFactory;
+
+ private ApplicationInfo mAppInfo;
+ private Context mPluginContext;
+ @Mock
+ private PluginInstance.VersionChecker mVersionChecker;
+
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mAppInfo = mContext.getApplicationInfo();
+ mAppInfo.packageName = mTestPluginComponentName.getPackageName();
+ when(mVersionChecker.checkVersion(any(), any(), any())).thenReturn(mVersionInfo);
+
+ mPluginInstanceFactory = new PluginInstance.Factory(
+ this.getClass().getClassLoader(),
+ new PluginInstance.InstanceFactory<TestPlugin>() {
+ @Override
+ TestPlugin create(Class cls) {
+ return mMockPlugin;
+ }
+ },
+ mVersionChecker,
+ Collections.singletonList(PRIVILEGED_PACKAGE),
+ false);
+
+ mPluginInstance = mPluginInstanceFactory.create(
+ mContext, mAppInfo, mTestPluginComponentName, TestPlugin.class);
+ mPluginContext = mPluginInstance.getPluginContext();
+ }
+
+ @Test
+ public void testCorrectVersion() {
+ assertNotNull(mPluginInstance);
+ }
+
+ @Test(expected = VersionInfo.InvalidVersionException.class)
+ public void testIncorrectVersion() throws Exception {
+
+ ComponentName wrongVersionTestPluginComponentName =
+ new ComponentName(PRIVILEGED_PACKAGE, TestPlugin.class.getName());
+
+ when(mVersionChecker.checkVersion(any(), any(), any())).thenThrow(
+ new VersionInfo.InvalidVersionException("test", true));
+
+ mPluginInstanceFactory.create(
+ mContext, mAppInfo, wrongVersionTestPluginComponentName, TestPlugin.class);
+ }
+
+ @Test
+ public void testOnCreate() {
+ mPluginInstance.onCreate(mContext, mMockListener);
+ verify(mMockPlugin).onCreate(mContext, mPluginContext);
+ verify(mMockListener).onPluginConnected(mMockPlugin, mPluginContext);
+ }
+
+ @Test
+ public void testOnDestroy() {
+ mPluginInstance.onDestroy(mMockListener);
+ verify(mMockListener).onPluginDisconnected(mMockPlugin);
+ verify(mMockPlugin).onDestroy();
+ }
+
+ // This target class doesn't matter, it just needs to have a Requires to hit the flow where
+ // the mock version info is called.
+ @ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
+ public interface TestPlugin extends Plugin {
+ int VERSION = 1;
+ String ACTION = "testAction";
+ }
+
+ @Requires(target = TestPlugin.class, version = TestPlugin.VERSION)
+ public static class TestPluginImpl implements TestPlugin {
+ @Override
+ public void onCreate(Context sysuiContext, Context pluginContext) {
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java
index 536c043bd7ae..1eadd522352e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginManagerTest.java
@@ -13,10 +13,9 @@
*/
package com.android.systemui.shared.plugins;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -30,19 +29,13 @@ import android.content.pm.PackageManager;
import android.net.Uri;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.Plugin;
-import com.android.systemui.plugins.PluginEnablerImpl;
-import com.android.systemui.plugins.PluginInitializerImpl;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.annotations.ProvidesInterface;
-import com.android.systemui.shared.plugins.PluginInstanceManager.PluginInfo;
-import com.android.systemui.shared.plugins.PluginManagerImpl.PluginInstanceManagerFactory;
import org.junit.Before;
import org.junit.Test;
@@ -51,19 +44,24 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Optional;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class PluginManagerTest extends SysuiTestCase {
- private static final String WHITELISTED_PACKAGE = "com.android.systemui";
+ private static final String PRIVILEGED_PACKAGE = "com.android.systemui";
- private PluginInstanceManagerFactory mMockFactory;
- private PluginInstanceManager mMockPluginInstance;
+ private PluginActionManager.Factory mMockFactory;
+ private PluginActionManager<TestPlugin> mMockPluginInstance;
private PluginManagerImpl mPluginManager;
- private PluginListener mMockListener;
+ private PluginListener<TestPlugin> mMockListener;
private PackageManager mMockPackageManager;
+ private PluginEnabler mPluginEnabler;
+ private PluginPrefs mPluginPrefs;
private UncaughtExceptionHandler mRealExceptionHandler;
private UncaughtExceptionHandler mMockExceptionHandler;
@@ -71,44 +69,25 @@ public class PluginManagerTest extends SysuiTestCase {
@Before
public void setup() throws Exception {
- mDependency.injectTestDependency(Dependency.BG_LOOPER,
- TestableLooper.get(this).getLooper());
mRealExceptionHandler = Thread.getUncaughtExceptionPreHandler();
mMockExceptionHandler = mock(UncaughtExceptionHandler.class);
- mMockFactory = mock(PluginInstanceManagerFactory.class);
- mMockPluginInstance = mock(PluginInstanceManager.class);
- when(mMockFactory.createPluginInstanceManager(Mockito.any(), Mockito.any(), Mockito.any(),
- Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.any()))
+ mMockFactory = mock(PluginActionManager.Factory.class);
+ mMockPluginInstance = mock(PluginActionManager.class);
+ mPluginEnabler = mock(PluginEnabler.class);
+ mPluginPrefs = mock(PluginPrefs.class);
+ when(mMockFactory.create(any(), any(), eq(TestPlugin.class), anyBoolean(), anyBoolean()))
.thenReturn(mMockPluginInstance);
mMockPackageManager = mock(PackageManager.class);
mPluginManager = new PluginManagerImpl(
getContext(), mMockFactory, true,
- mMockExceptionHandler, new PluginInitializerImpl() {
- @Override
- public String[] getWhitelistedPlugins(Context context) {
- return new String[0];
- }
-
- @Override
- public PluginEnabler getPluginEnabler(Context context) {
- return new PluginEnablerImpl(context, mMockPackageManager);
- }
- });
+ Optional.of(mMockExceptionHandler), mPluginEnabler,
+ mPluginPrefs, new ArrayList<>());
+
resetExceptionHandler();
mMockListener = mock(PluginListener.class);
}
- @RunWithLooper(setAsMainLooper = true)
- @Test
- public void testOneShot() {
- Plugin mockPlugin = mock(Plugin.class);
- when(mMockPluginInstance.getPlugin()).thenReturn(new PluginInfo(null, null, mockPlugin,
- null, null));
- Plugin result = mPluginManager.getOneShotPlugin("myAction", TestPlugin.class);
- assertSame(mockPlugin, result);
- }
-
@Test
public void testAddListener() {
mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
@@ -126,53 +105,49 @@ public class PluginManagerTest extends SysuiTestCase {
@Test
@RunWithLooper(setAsMainLooper = true)
- public void testNonDebuggable_noWhitelist() {
- mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, false,
- mMockExceptionHandler, new PluginInitializerImpl() {
- @Override
- public String[] getWhitelistedPlugins(Context context) {
- return new String[0];
- }
- });
+ public void testNonDebuggable_nonPrivileged() {
+ mPluginManager = new PluginManagerImpl(
+ getContext(), mMockFactory, false,
+ Optional.of(mMockExceptionHandler), mPluginEnabler,
+ mPluginPrefs, new ArrayList<>());
resetExceptionHandler();
String sourceDir = "myPlugin";
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.sourceDir = sourceDir;
- applicationInfo.packageName = WHITELISTED_PACKAGE;
+ applicationInfo.packageName = PRIVILEGED_PACKAGE;
mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
- assertNull(mPluginManager.getOneShotPlugin(sourceDir, TestPlugin.class));
- assertNull(mPluginManager.getClassLoader(applicationInfo));
+ verify(mMockFactory).create(eq("myAction"), eq(mMockListener), eq(TestPlugin.class),
+ eq(false), eq(false));
+ verify(mMockPluginInstance).loadAll();
}
@Test
@RunWithLooper(setAsMainLooper = true)
- public void testNonDebuggable_whitelistedPkg() {
- mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, false,
- mMockExceptionHandler, new PluginInitializerImpl() {
- @Override
- public String[] getWhitelistedPlugins(Context context) {
- return new String[] {WHITELISTED_PACKAGE};
- }
- });
+ public void testNonDebuggable_privilegedPackage() {
+ mPluginManager = new PluginManagerImpl(
+ getContext(), mMockFactory, false,
+ Optional.of(mMockExceptionHandler), mPluginEnabler,
+ mPluginPrefs, Collections.singletonList(PRIVILEGED_PACKAGE));
resetExceptionHandler();
String sourceDir = "myPlugin";
- ApplicationInfo whiteListedApplicationInfo = new ApplicationInfo();
- whiteListedApplicationInfo.sourceDir = sourceDir;
- whiteListedApplicationInfo.packageName = WHITELISTED_PACKAGE;
+ ApplicationInfo privilegedApplicationInfo = new ApplicationInfo();
+ privilegedApplicationInfo.sourceDir = sourceDir;
+ privilegedApplicationInfo.packageName = PRIVILEGED_PACKAGE;
ApplicationInfo invalidApplicationInfo = new ApplicationInfo();
invalidApplicationInfo.sourceDir = sourceDir;
invalidApplicationInfo.packageName = "com.android.invalidpackage";
mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
- assertNotNull(mPluginManager.getClassLoader(whiteListedApplicationInfo));
- assertNull(mPluginManager.getClassLoader(invalidApplicationInfo));
+ verify(mMockFactory).create(eq("myAction"), eq(mMockListener), eq(TestPlugin.class),
+ eq(false), eq(false));
+ verify(mMockPluginInstance).loadAll();
}
@Test
public void testExceptionHandler_foundPlugin() {
mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
- when(mMockPluginInstance.checkAndDisable(Mockito.any())).thenReturn(true);
+ when(mMockPluginInstance.checkAndDisable(any())).thenReturn(true);
mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable());
@@ -187,7 +162,7 @@ public class PluginManagerTest extends SysuiTestCase {
@Test
public void testExceptionHandler_noFoundPlugin() {
mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class);
- when(mMockPluginInstance.checkAndDisable(Mockito.any())).thenReturn(false);
+ when(mMockPluginInstance.checkAndDisable(any())).thenReturn(false);
mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable());
@@ -211,9 +186,7 @@ public class PluginManagerTest extends SysuiTestCase {
intent.setData(Uri.parse("package://" + testComponent.flattenToString()));
mPluginManager.onReceive(mContext, intent);
verify(nm).cancel(eq(testComponent.getClassName()), eq(SystemMessage.NOTE_PLUGIN));
- verify(mMockPackageManager).setComponentEnabledSetting(eq(testComponent),
- eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
- eq(PackageManager.DONT_KILL_APP));
+ verify(mPluginEnabler).setDisabled(testComponent, PluginEnabler.DISABLED_INVALID_VERSION);
}
private void resetExceptionHandler() {
@@ -223,8 +196,8 @@ public class PluginManagerTest extends SysuiTestCase {
}
@ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
- public static interface TestPlugin extends Plugin {
- public static final String ACTION = "testAction";
- public static final int VERSION = 1;
+ public interface TestPlugin extends Plugin {
+ String ACTION = "testAction";
+ int VERSION = 1;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index f3762c566731..af624ed1ea1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -15,6 +15,8 @@
package com.android.systemui.statusbar;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
+import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -33,6 +35,7 @@ import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.fingerprint.IUdfpsHbmListener;
import android.os.Bundle;
+import android.view.InsetsVisibilities;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -124,24 +127,25 @@ public class CommandQueueTest extends SysuiTestCase {
public void testOnSystemBarAttributesChanged() {
doTestOnSystemBarAttributesChanged(DEFAULT_DISPLAY, 1,
new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
- BEHAVIOR_DEFAULT, false);
+ BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test");
}
@Test
public void testOnSystemBarAttributesChangedForSecondaryDisplay() {
doTestOnSystemBarAttributesChanged(SECONDARY_DISPLAY, 1,
new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
- BEHAVIOR_DEFAULT, false);
+ BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test");
}
private void doTestOnSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, boolean isFullscreen) {
+ @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName) {
mCommandQueue.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
- navbarColorManagedByIme, behavior, isFullscreen);
+ navbarColorManagedByIme, behavior, requestedVisibilities, packageName);
waitForIdleSync();
verify(mCallbacks).onSystemBarAttributesChanged(eq(displayId), eq(appearance),
- eq(appearanceRegions), eq(navbarColorManagedByIme), eq(behavior), eq(isFullscreen));
+ eq(appearanceRegions), eq(navbarColorManagedByIme), eq(behavior),
+ eq(requestedVisibilities), eq(packageName));
}
@Test
@@ -186,8 +190,13 @@ public class CommandQueueTest extends SysuiTestCase {
@Test
public void testShowImeButtonForSecondaryDisplay() {
+ // First show in default display to update the "last updated ime display"
+ testShowImeButton();
+
mCommandQueue.setImeWindowStatus(SECONDARY_DISPLAY, null, 1, 2, true, false);
waitForIdleSync();
+ verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(null), eq(IME_INVISIBLE),
+ eq(BACK_DISPOSITION_DEFAULT), eq(false));
verify(mCallbacks).setImeWindowStatus(
eq(SECONDARY_DISPLAY), eq(null), eq(1), eq(2), eq(true));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt
new file mode 100644
index 000000000000..096efad50615
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt
@@ -0,0 +1,188 @@
+/*
+ * 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.systemui.statusbar
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Test
+
+@SmallTest
+class DisableFlagsLoggerTest : SysuiTestCase() {
+ private val disable1Flags = listOf(
+ DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'),
+ DisableFlagsLogger.DisableFlag(0b010, 'B', 'b'),
+ DisableFlagsLogger.DisableFlag(0b001, 'C', 'c'),
+ )
+ private val disable2Flags = listOf(
+ DisableFlagsLogger.DisableFlag(0b10, 'M', 'm'),
+ DisableFlagsLogger.DisableFlag(0b01, 'N', 'n'),
+ )
+
+ private val disableFlagsLogger = DisableFlagsLogger(disable1Flags, disable2Flags)
+
+ @Test
+ fun getDisableFlagsString_oldAndNewSame_statesLoggedButDiffsNotLogged() {
+ val state = DisableFlagsLogger.DisableState(
+ 0b111, // ABC
+ 0b01 // mN
+ )
+
+ val result = disableFlagsLogger.getDisableFlagsString(state, state)
+
+ assertThat(result).contains("Old: ABC.mN")
+ assertThat(result).contains("New: ABC.mN")
+ assertThat(result).doesNotContain("(")
+ assertThat(result).doesNotContain(")")
+ }
+
+ @Test
+ fun getDisableFlagsString_oldAndNewDifferent_statesAndDiffLogged() {
+ val result = disableFlagsLogger.getDisableFlagsString(
+ DisableFlagsLogger.DisableState(
+ 0b111, // ABC
+ 0b01, // mN
+ ),
+ DisableFlagsLogger.DisableState(
+ 0b001, // abC
+ 0b10 // Mn
+ )
+ )
+
+ assertThat(result).contains("Old: ABC.mN")
+ assertThat(result).contains("New: abC.Mn")
+ assertThat(result).contains("(ab.Mn)")
+ }
+
+ @Test
+ fun getDisableFlagsString_onlyDisable2Different_diffLoggedCorrectly() {
+ val result = disableFlagsLogger.getDisableFlagsString(
+ DisableFlagsLogger.DisableState(
+ 0b001, // abC
+ 0b01, // mN
+ ),
+ DisableFlagsLogger.DisableState(
+ 0b001, // abC
+ 0b00 // mn
+ )
+ )
+
+ assertThat(result).contains("(.n)")
+ }
+
+ @Test
+ fun getDisableFlagsString_nullLocalModification_localModNotLogged() {
+ val result = disableFlagsLogger.getDisableFlagsString(
+ DisableFlagsLogger.DisableState(0, 0),
+ DisableFlagsLogger.DisableState(1, 1),
+ newAfterLocalModification = null
+ )
+
+ assertThat(result).doesNotContain("local modification")
+ }
+
+ @Test
+ fun getDisableFlagsString_newAfterLocalModificationSameAsNew_localModNotLogged() {
+ val newState = DisableFlagsLogger.DisableState(
+ 0b001, // abC
+ 0b10 // mn
+ )
+
+ val result = disableFlagsLogger.getDisableFlagsString(
+ DisableFlagsLogger.DisableState(0, 0), newState, newState
+ )
+
+ assertThat(result).doesNotContain("local modification")
+ }
+
+ @Test
+ fun getDisableFlagsString_newAfterLocalModificationDifferent_localModAndDiffLogged() {
+ val result = disableFlagsLogger.getDisableFlagsString(
+ old = DisableFlagsLogger.DisableState(0, 0),
+ new = DisableFlagsLogger.DisableState(
+ 0b000, // abc
+ 0b00 // mn
+ ),
+ newAfterLocalModification = DisableFlagsLogger.DisableState(
+ 0b100, // Abc
+ 0b10 // Mn
+ )
+ )
+
+ assertThat(result).contains("local modification: Abc.Mn (A.M)")
+ }
+
+ @Test
+ fun constructor_defaultDisableFlags_noException() {
+ // Just creating the logger with the default params will trigger the exception if there
+ // is one.
+ DisableFlagsLogger()
+ }
+
+ @Test
+ fun constructor_disable1_FlagIsSetSymbolNotUnique_exception() {
+ assertThrows(IllegalArgumentException::class.java) {
+ DisableFlagsLogger(
+ disable1FlagsList = listOf(
+ DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'),
+ DisableFlagsLogger.DisableFlag(0b010, 'A', 'b'),
+ ),
+ listOf()
+ )
+ }
+ }
+
+ @Test
+ fun constructor_disable1_FlagNotSetSymbolNotUnique_exception() {
+ assertThrows(IllegalArgumentException::class.java) {
+ DisableFlagsLogger(
+ disable1FlagsList = listOf(
+ DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'),
+ DisableFlagsLogger.DisableFlag(0b010, 'B', 'a'),
+ ),
+ listOf()
+ )
+ }
+ }
+
+ @Test
+ fun constructor_disable2_FlagIsSetSymbolNotUnique_exception() {
+ assertThrows(IllegalArgumentException::class.java) {
+ DisableFlagsLogger(
+ disable1FlagsList = listOf(),
+ disable2FlagsList = listOf(
+ DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'),
+ DisableFlagsLogger.DisableFlag(0b010, 'A', 'b'),
+ ),
+ )
+ }
+ }
+
+ @Test
+ fun constructor_disable2_FlagNotSetSymbolNotUnique_exception() {
+ assertThrows(IllegalArgumentException::class.java) {
+ DisableFlagsLogger(
+ disable1FlagsList = listOf(),
+ disable2FlagsList = listOf(
+ DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'),
+ DisableFlagsLogger.DisableFlag(0b010, 'B', 'a'),
+ ),
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index f5cab1df9fa2..01f7fae05f76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -166,7 +166,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
private BroadcastReceiver mBroadcastReceiver;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
- private KeyguardIndicationTextView mTextView;
+ private KeyguardIndicationTextView mTextView; // AOD text
private KeyguardIndicationController mController;
private WakeLockFake.Builder mWakeLockBuilder;
@@ -412,41 +412,32 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Test
public void transientIndication_holdsWakeLock_whenDozing() {
+ // GIVEN animations are enabled and text is visible
+ mTextView.setAnimationsEnabled(true);
createController();
+ mController.setVisible(true);
+ // WHEN transient text is shown
mStatusBarStateListener.onDozingChanged(true);
mController.showTransientIndication("Test");
- assertTrue(mWakeLock.isHeld());
+ // THEN wake lock is held while the animation is running
+ assertTrue("WakeLock expected: HELD, was: RELEASED", mWakeLock.isHeld());
}
@Test
- public void transientIndication_releasesWakeLock_afterHiding() {
+ public void transientIndication_releasesWakeLock_whenDozing() {
+ // GIVEN animations aren't enabled
+ mTextView.setAnimationsEnabled(false);
createController();
+ mController.setVisible(true);
+ // WHEN we show the transient indication
mStatusBarStateListener.onDozingChanged(true);
mController.showTransientIndication("Test");
- mController.hideTransientIndication();
-
- assertFalse(mWakeLock.isHeld());
- }
-
- @Test
- public void transientIndication_releasesWakeLock_afterHidingDelayed() throws Throwable {
- mInstrumentation.runOnMainSync(() -> {
- createController();
-
- mStatusBarStateListener.onDozingChanged(true);
- mController.showTransientIndication("Test");
- mController.hideTransientIndicationDelayed(0);
- });
- mInstrumentation.waitForIdleSync();
- Boolean[] held = new Boolean[1];
- mInstrumentation.runOnMainSync(() -> {
- held[0] = mWakeLock.isHeld();
- });
- assertFalse("WakeLock expected: RELEASED, was: HELD", held[0]);
+ // THEN wake lock is RELEASED, not held
+ assertFalse("WakeLock expected: RELEASED, was: HELD", mWakeLock.isHeld());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 18cf1c8ebaa6..793851160dc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -1,10 +1,10 @@
package com.android.systemui.statusbar
+import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import android.util.DisplayMetrics
-import androidx.test.filters.SmallTest
import com.android.systemui.ExpandHelper
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
@@ -67,7 +67,6 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
@Mock lateinit var falsingManager: FalsingManager
@Mock lateinit var notificationPanelController: NotificationPanelViewController
@Mock lateinit var nsslController: NotificationStackScrollLayoutController
- @Mock lateinit var featureFlags: FeatureFlags
@Mock lateinit var depthController: NotificationShadeDepthController
@Mock lateinit var stackscroller: NotificationStackScrollLayout
@Mock lateinit var expandHelperCallback: ExpandHelper.Callback
@@ -92,11 +91,10 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
displayMetrics = displayMetrics,
mediaHierarchyManager = mediaHierarchyManager,
scrimController = scrimController,
- featureFlags = featureFlags,
+ depthController = depthController,
context = context,
configurationController = configurationController,
- falsingManager = falsingManager,
- depthController = depthController
+ falsingManager = falsingManager
)
whenever(nsslController.view).thenReturn(stackscroller)
whenever(nsslController.expandHelperCallback).thenReturn(expandHelperCallback)
@@ -211,7 +209,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat())
verify(notificationPanelController, never()).setTransitionToFullShadeAmount(anyFloat(),
anyBoolean(), anyLong())
- verify(qS, never()).setTransitionToFullShadeAmount(anyFloat(), anyBoolean())
+ verify(qS, never()).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
}
@Test
@@ -222,7 +220,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
verify(scrimController).setTransitionToFullShadeProgress(anyFloat())
verify(notificationPanelController).setTransitionToFullShadeAmount(anyFloat(),
anyBoolean(), anyLong())
- verify(qS).setTransitionToFullShadeAmount(anyFloat(), anyBoolean())
+ verify(qS).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
verify(depthController).transitionToFullShadeProgress = anyFloat()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 0c65830ea455..23cca727335e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -20,10 +20,10 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.content.Intent.ACTION_USER_SWITCHED;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -56,6 +56,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.KeyguardNotificationSuppressor;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -423,7 +424,8 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
mStatusBarStateController,
Handler.createAsync(Looper.myLooper()),
mDeviceProvisionedController,
- mKeyguardStateController);
+ mKeyguardStateController,
+ mock(DumpManager.class));
}
public BroadcastReceiver getBaseBroadcastReceiverForTest() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 8e949e7d1e37..4ed722470334 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -1,3 +1,18 @@
+/*
+ * 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.systemui.statusbar;
@@ -10,25 +25,25 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
-import android.app.RemoteInputHistoryItem;
import android.content.Context;
-import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationRemoteInputManager.RemoteInputActiveExtender;
-import com.android.systemui.statusbar.NotificationRemoteInputManager.RemoteInputHistoryExtender;
-import com.android.systemui.statusbar.NotificationRemoteInputManager.SmartReplyHistoryExtender;
+import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.RemoteInputActiveExtender;
+import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.RemoteInputHistoryExtender;
+import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.SmartReplyHistoryExtender;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -44,6 +59,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
import dagger.Lazy;
@SmallTest
@@ -73,19 +90,26 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
private RemoteInputHistoryExtender mRemoteInputHistoryExtender;
private SmartReplyHistoryExtender mSmartReplyHistoryExtender;
private RemoteInputActiveExtender mRemoteInputActiveExtender;
+ private TestableNotificationRemoteInputManager.FakeLegacyRemoteInputLifetimeExtender
+ mLegacyRemoteInputLifetimeExtender;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mRemoteInputManager = new TestableNotificationRemoteInputManager(mContext,
- mLockscreenUserManager, mSmartReplyController, mEntryManager,
- () -> mock(StatusBar.class),
+ mock(FeatureFlags.class),
+ mLockscreenUserManager,
+ mSmartReplyController,
+ mEntryManager,
+ mock(RemoteInputNotificationRebuilder.class),
+ () -> Optional.of(mock(StatusBar.class)),
mStateController,
Handler.createAsync(Looper.myLooper()),
mRemoteInputUriController,
mClickNotifier,
- mock(ActionClickLogger.class));
+ mock(ActionClickLogger.class),
+ mock(DumpManager.class));
mEntry = new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_NAME)
.setOpPkg(TEST_PACKAGE_NAME)
@@ -116,6 +140,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
public void testShouldExtendLifetime_remoteInputActive() {
when(mController.isRemoteInputActive(mEntry)).thenReturn(true);
+ assertTrue(mRemoteInputManager.isRemoteInputActive(mEntry));
assertTrue(mRemoteInputActiveExtender.shouldExtendLifetime(mEntry));
}
@@ -124,6 +149,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY = true;
when(mController.isSpinning(mEntry.getKey())).thenReturn(true);
+ assertTrue(mRemoteInputManager.shouldKeepForRemoteInputHistory(mEntry));
assertTrue(mRemoteInputHistoryExtender.shouldExtendLifetime(mEntry));
}
@@ -132,6 +158,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY = true;
mEntry.lastRemoteInputSent = SystemClock.elapsedRealtime();
+ assertTrue(mRemoteInputManager.shouldKeepForRemoteInputHistory(mEntry));
assertTrue(mRemoteInputHistoryExtender.shouldExtendLifetime(mEntry));
}
@@ -140,6 +167,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY = true;
when(mSmartReplyController.isSendingSmartReply(mEntry.getKey())).thenReturn(true);
+ assertTrue(mRemoteInputManager.shouldKeepForSmartReplyHistory(mEntry));
assertTrue(mSmartReplyHistoryExtender.shouldExtendLifetime(mEntry));
}
@@ -147,141 +175,45 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
public void testNotificationWithRemoteInputActiveIsRemovedOnCollapse() {
mRemoteInputActiveExtender.setShouldManageLifetime(mEntry, true /* shouldManage */);
- assertEquals(mRemoteInputManager.getEntriesKeptForRemoteInputActive(),
+ assertEquals(mLegacyRemoteInputLifetimeExtender.getEntriesKeptForRemoteInputActive(),
Sets.newArraySet(mEntry));
mRemoteInputManager.onPanelCollapsed();
- assertTrue(mRemoteInputManager.getEntriesKeptForRemoteInputActive().isEmpty());
- }
-
- @Test
- public void testRebuildWithRemoteInput_noExistingInput_image() {
- Uri uri = mock(Uri.class);
- String mimeType = "image/jpeg";
- String text = "image inserted";
- StatusBarNotification newSbn =
- mRemoteInputManager.rebuildNotificationWithRemoteInputInserted(
- mEntry, text, false, mimeType, uri);
- RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
- .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
- assertEquals(1, messages.length);
- assertEquals(text, messages[0].getText());
- assertEquals(mimeType, messages[0].getMimeType());
- assertEquals(uri, messages[0].getUri());
- }
-
- @Test
- public void testRebuildWithRemoteInput_noExistingInputNoSpinner() {
- StatusBarNotification newSbn =
- mRemoteInputManager.rebuildNotificationWithRemoteInputInserted(
- mEntry, "A Reply", false, null, null);
- RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
- .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
- assertEquals(1, messages.length);
- assertEquals("A Reply", messages[0].getText());
- assertFalse(newSbn.getNotification().extras
- .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false));
- assertTrue(newSbn.getNotification().extras
- .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false));
- }
-
- @Test
- public void testRebuildWithRemoteInput_noExistingInputWithSpinner() {
- StatusBarNotification newSbn =
- mRemoteInputManager.rebuildNotificationWithRemoteInputInserted(
- mEntry, "A Reply", true, null, null);
- RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
- .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
- assertEquals(1, messages.length);
- assertEquals("A Reply", messages[0].getText());
- assertTrue(newSbn.getNotification().extras
- .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false));
- assertTrue(newSbn.getNotification().extras
- .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false));
- }
-
- @Test
- public void testRebuildWithRemoteInput_withExistingInput() {
- // Setup a notification entry with 1 remote input.
- StatusBarNotification newSbn =
- mRemoteInputManager.rebuildNotificationWithRemoteInputInserted(
- mEntry, "A Reply", false, null, null);
- NotificationEntry entry = new NotificationEntryBuilder()
- .setSbn(newSbn)
- .build();
-
- // Try rebuilding to add another reply.
- newSbn = mRemoteInputManager.rebuildNotificationWithRemoteInputInserted(
- entry, "Reply 2", true, null, null);
- RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
- .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
- assertEquals(2, messages.length);
- assertEquals("Reply 2", messages[0].getText());
- assertEquals("A Reply", messages[1].getText());
+ assertTrue(
+ mLegacyRemoteInputLifetimeExtender.getEntriesKeptForRemoteInputActive().isEmpty());
}
- @Test
- public void testRebuildWithRemoteInput_withExistingInput_image() {
- // Setup a notification entry with 1 remote input.
- Uri uri = mock(Uri.class);
- String mimeType = "image/jpeg";
- String text = "image inserted";
- StatusBarNotification newSbn =
- mRemoteInputManager.rebuildNotificationWithRemoteInputInserted(
- mEntry, text, false, mimeType, uri);
- NotificationEntry entry = new NotificationEntryBuilder()
- .setSbn(newSbn)
- .build();
-
- // Try rebuilding to add another reply.
- newSbn = mRemoteInputManager.rebuildNotificationWithRemoteInputInserted(
- entry, "Reply 2", true, null, null);
- RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
- .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
- assertEquals(2, messages.length);
- assertEquals("Reply 2", messages[0].getText());
- assertEquals(text, messages[1].getText());
- assertEquals(mimeType, messages[1].getMimeType());
- assertEquals(uri, messages[1].getUri());
- }
-
- @Test
- public void testRebuildNotificationForCanceledSmartReplies() {
- // Try rebuilding to remove spinner and hide buttons.
- StatusBarNotification newSbn =
- mRemoteInputManager.rebuildNotificationForCanceledSmartReplies(mEntry);
- assertFalse(newSbn.getNotification().extras
- .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false));
- assertTrue(newSbn.getNotification().extras
- .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false));
- }
-
-
private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager {
TestableNotificationRemoteInputManager(
Context context,
+ FeatureFlags featureFlags,
NotificationLockscreenUserManager lockscreenUserManager,
SmartReplyController smartReplyController,
NotificationEntryManager notificationEntryManager,
- Lazy<StatusBar> statusBarLazy,
+ RemoteInputNotificationRebuilder rebuilder,
+ Lazy<Optional<StatusBar>> statusBarOptionalLazy,
StatusBarStateController statusBarStateController,
Handler mainHandler,
RemoteInputUriController remoteInputUriController,
NotificationClickNotifier clickNotifier,
- ActionClickLogger actionClickLogger) {
+ ActionClickLogger actionClickLogger,
+ DumpManager dumpManager) {
super(
context,
+ featureFlags,
lockscreenUserManager,
smartReplyController,
notificationEntryManager,
- statusBarLazy,
+ rebuilder,
+ statusBarOptionalLazy,
statusBarStateController,
mainHandler,
remoteInputUriController,
clickNotifier,
- actionClickLogger);
+ actionClickLogger,
+ dumpManager);
}
public void setUpWithPresenterForTest(Callback callback,
@@ -291,14 +223,28 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
mRemoteInputController = controller;
}
+ @NonNull
@Override
- protected void addLifetimeExtenders() {
- mRemoteInputActiveExtender = new RemoteInputActiveExtender();
- mRemoteInputHistoryExtender = new RemoteInputHistoryExtender();
- mSmartReplyHistoryExtender = new SmartReplyHistoryExtender();
- mLifetimeExtenders.add(mRemoteInputHistoryExtender);
- mLifetimeExtenders.add(mSmartReplyHistoryExtender);
- mLifetimeExtenders.add(mRemoteInputActiveExtender);
+ protected LegacyRemoteInputLifetimeExtender createLegacyRemoteInputLifetimeExtender(
+ Handler mainHandler,
+ NotificationEntryManager notificationEntryManager,
+ SmartReplyController smartReplyController) {
+ mLegacyRemoteInputLifetimeExtender = new FakeLegacyRemoteInputLifetimeExtender();
+ return mLegacyRemoteInputLifetimeExtender;
}
+
+ class FakeLegacyRemoteInputLifetimeExtender extends LegacyRemoteInputLifetimeExtender {
+
+ @Override
+ protected void addLifetimeExtenders() {
+ mRemoteInputActiveExtender = new RemoteInputActiveExtender();
+ mRemoteInputHistoryExtender = new RemoteInputHistoryExtender();
+ mSmartReplyHistoryExtender = new SmartReplyHistoryExtender();
+ mLifetimeExtenders.add(mRemoteInputHistoryExtender);
+ mLifetimeExtenders.add(mSmartReplyHistoryExtender);
+ mLifetimeExtenders.add(mRemoteInputActiveExtender);
+ }
+ }
+
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 465370b59553..dbd5168386de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar
-import android.app.WallpaperManager
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -25,13 +24,14 @@ import android.view.View
import android.view.ViewRootImpl
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.WallpaperController
import com.android.systemui.util.mockito.eq
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -47,10 +47,8 @@ import org.mockito.Mockito.any
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.anyString
import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.doThrow
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
-import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import java.util.function.Consumer
@@ -65,7 +63,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var choreographer: Choreographer
- @Mock private lateinit var wallpaperManager: WallpaperManager
+ @Mock private lateinit var wallpaperController: WallpaperController
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var root: View
@@ -101,7 +99,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
notificationShadeDepthController = NotificationShadeDepthController(
statusBarStateController, blurUtils, biometricUnlockController,
- keyguardStateController, choreographer, wallpaperManager,
+ keyguardStateController, choreographer, wallpaperController,
notificationShadeWindowController, dozeParameters, dumpManager)
notificationShadeDepthController.shadeAnimation = shadeAnimation
notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
@@ -209,8 +207,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
notificationShadeDepthController.qsPanelExpansion = 0.25f
notificationShadeDepthController.onPanelExpansionChanged(1f, tracking = false)
notificationShadeDepthController.updateBlurCallback.doFrame(0)
- verify(wallpaperManager).setWallpaperZoomOut(any(),
- eq(Interpolators.getNotificationScrimAlpha(0.25f, false /* notifications */)))
+ verify(wallpaperController).setNotificationShadeZoom(
+ eq(ShadeInterpolation.getNotificationScrimAlpha(0.25f)))
}
@Test
@@ -242,14 +240,14 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
notificationShadeDepthController.transitionToFullShadeProgress = 1f
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(blurUtils).applyBlur(any(), eq(0), eq(false))
- verify(wallpaperManager).setWallpaperZoomOut(any(), eq(1f))
+ verify(wallpaperController).setNotificationShadeZoom(eq(1f))
}
@Test
fun updateBlurCallback_setsBlurAndZoom() {
notificationShadeDepthController.addListener(listener)
notificationShadeDepthController.updateBlurCallback.doFrame(0)
- verify(wallpaperManager).setWallpaperZoomOut(any(), anyFloat())
+ verify(wallpaperController).setNotificationShadeZoom(anyFloat())
verify(listener).onWallpaperZoomOutChanged(anyFloat())
verify(blurUtils).applyBlur(any(), anyInt(), eq(false))
}
@@ -279,21 +277,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
}
@Test
- fun updateBlurCallback_invalidWindow() {
- `when`(root.isAttachedToWindow).thenReturn(false)
- notificationShadeDepthController.updateBlurCallback.doFrame(0)
- verify(wallpaperManager, times(0)).setWallpaperZoomOut(any(), anyFloat())
- }
-
- @Test
- fun updateBlurCallback_exception() {
- doThrow(IllegalArgumentException("test exception")).`when`(wallpaperManager)
- .setWallpaperZoomOut(any(), anyFloat())
- notificationShadeDepthController.updateBlurCallback.doFrame(0)
- verify(wallpaperManager).setWallpaperZoomOut(any(), anyFloat())
- }
-
- @Test
fun ignoreShadeBlurUntilHidden_schedulesFrame() {
notificationShadeDepthController.blursDisabledForAppLaunch = true
verify(choreographer).postFrameCallback(
@@ -322,7 +305,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(notificationShadeWindowController).setBackgroundBlurRadius(eq(0))
- verify(wallpaperManager).setWallpaperZoomOut(any(), eq(1f))
+ verify(wallpaperController).setNotificationShadeZoom(eq(1f))
verify(blurUtils).applyBlur(eq(viewRootImpl), eq(0), eq(false))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 7fb7b8667a1b..cf58c63e3d26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -36,6 +36,7 @@ import android.widget.LinearLayout;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.DynamicChildBindController;
@@ -75,6 +76,7 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
@Spy private FakeListContainer mListContainer = new FakeListContainer();
// Dependency mocks:
+ @Mock private FeatureFlags mFeatureFlags;
@Mock private NotificationEntryManager mEntryManager;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private NotificationGroupManagerLegacy mGroupManager;
@@ -101,10 +103,14 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
when(mVisualStabilityManager.areGroupChangesAllowed()).thenReturn(true);
when(mVisualStabilityManager.isReorderingAllowed()).thenReturn(true);
+ when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false);
+ when(mFeatureFlags.checkLegacyPipelineEnabled()).thenReturn(true);
+
mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
- mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager,
+ mHandler, mFeatureFlags, mLockscreenUserManager, mGroupManager,
+ mVisualStabilityManager,
mock(StatusBarStateControllerImpl.class), mEntryManager,
mock(KeyguardBypassController.class),
Optional.of(mock(Bubbles.class)),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
index ac699f7192c8..045e6f19c667 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -52,7 +52,7 @@ public class RankingBuilder {
private ArrayList<Notification.Action> mSmartActions = new ArrayList<>();
private ArrayList<CharSequence> mSmartReplies = new ArrayList<>();
private boolean mCanBubble = false;
- private boolean mIsVisuallyInterruptive = false;
+ private boolean mIsTextChanged = false;
private boolean mIsConversation = false;
private ShortcutInfo mShortcutInfo = null;
private int mRankingAdjustment = 0;
@@ -81,7 +81,7 @@ public class RankingBuilder {
mSmartActions = copyList(ranking.getSmartActions());
mSmartReplies = copyList(ranking.getSmartReplies());
mCanBubble = ranking.canBubble();
- mIsVisuallyInterruptive = ranking.visuallyInterruptive();
+ mIsTextChanged = ranking.isTextChanged();
mIsConversation = ranking.isConversation();
mShortcutInfo = ranking.getConversationShortcutInfo();
mRankingAdjustment = ranking.getRankingAdjustment();
@@ -110,7 +110,7 @@ public class RankingBuilder {
mSmartActions,
mSmartReplies,
mCanBubble,
- mIsVisuallyInterruptive,
+ mIsTextChanged,
mIsConversation,
mShortcutInfo,
mRankingAdjustment,
@@ -189,8 +189,8 @@ public class RankingBuilder {
return this;
}
- public RankingBuilder setVisuallyInterruptive(boolean interruptive) {
- mIsVisuallyInterruptive = interruptive;
+ public RankingBuilder setTextChanged(boolean textChanged) {
+ mIsTextChanged = textChanged;
return this;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
new file mode 100644
index 000000000000..ce11d6a62a8c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.systemui.statusbar;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+
+import android.app.Notification;
+import android.app.RemoteInputHistoryItem;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class RemoteInputNotificationRebuilderTest extends SysuiTestCase {
+ private static final String TEST_PACKAGE_NAME = "test";
+ private static final int TEST_UID = 0;
+ @Mock
+ private ExpandableNotificationRow mRow;
+
+ private RemoteInputNotificationRebuilder mRebuilder;
+ private NotificationEntry mEntry;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mRebuilder = new RemoteInputNotificationRebuilder(mContext);
+ mEntry = new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_NAME)
+ .setOpPkg(TEST_PACKAGE_NAME)
+ .setUid(TEST_UID)
+ .setNotification(new Notification())
+ .setUser(UserHandle.CURRENT)
+ .build();
+ mEntry.setRow(mRow);
+ }
+
+ @Test
+ public void testRebuildWithRemoteInput_noExistingInput_image() {
+ Uri uri = mock(Uri.class);
+ String mimeType = "image/jpeg";
+ String text = "image inserted";
+ StatusBarNotification newSbn =
+ mRebuilder.rebuildWithRemoteInputInserted(
+ mEntry, text, false, mimeType, uri);
+ RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
+ .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ assertEquals(1, messages.length);
+ assertEquals(text, messages[0].getText());
+ assertEquals(mimeType, messages[0].getMimeType());
+ assertEquals(uri, messages[0].getUri());
+ }
+
+ @Test
+ public void testRebuildWithRemoteInput_noExistingInputNoSpinner() {
+ StatusBarNotification newSbn =
+ mRebuilder.rebuildWithRemoteInputInserted(
+ mEntry, "A Reply", false, null, null);
+ RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
+ .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ assertEquals(1, messages.length);
+ assertEquals("A Reply", messages[0].getText());
+ assertFalse(newSbn.getNotification().extras
+ .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false));
+ assertTrue(newSbn.getNotification().extras
+ .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false));
+ }
+
+ @Test
+ public void testRebuildWithRemoteInput_noExistingInputWithSpinner() {
+ StatusBarNotification newSbn =
+ mRebuilder.rebuildWithRemoteInputInserted(
+ mEntry, "A Reply", true, null, null);
+ RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
+ .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ assertEquals(1, messages.length);
+ assertEquals("A Reply", messages[0].getText());
+ assertTrue(newSbn.getNotification().extras
+ .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false));
+ assertTrue(newSbn.getNotification().extras
+ .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false));
+ }
+
+ @Test
+ public void testRebuildWithRemoteInput_withExistingInput() {
+ // Setup a notification entry with 1 remote input.
+ StatusBarNotification newSbn =
+ mRebuilder.rebuildWithRemoteInputInserted(
+ mEntry, "A Reply", false, null, null);
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setSbn(newSbn)
+ .build();
+
+ // Try rebuilding to add another reply.
+ newSbn = mRebuilder.rebuildWithRemoteInputInserted(
+ entry, "Reply 2", true, null, null);
+ RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
+ .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ assertEquals(2, messages.length);
+ assertEquals("Reply 2", messages[0].getText());
+ assertEquals("A Reply", messages[1].getText());
+ }
+
+ @Test
+ public void testRebuildWithRemoteInput_withExistingInput_image() {
+ // Setup a notification entry with 1 remote input.
+ Uri uri = mock(Uri.class);
+ String mimeType = "image/jpeg";
+ String text = "image inserted";
+ StatusBarNotification newSbn =
+ mRebuilder.rebuildWithRemoteInputInserted(
+ mEntry, text, false, mimeType, uri);
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setSbn(newSbn)
+ .build();
+
+ // Try rebuilding to add another reply.
+ newSbn = mRebuilder.rebuildWithRemoteInputInserted(
+ entry, "Reply 2", true, null, null);
+ RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
+ .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+ assertEquals(2, messages.length);
+ assertEquals("Reply 2", messages[0].getText());
+ assertEquals(text, messages[1].getText());
+ assertEquals(mimeType, messages[1].getMimeType());
+ assertEquals(uri, messages[1].getUri());
+ }
+
+ @Test
+ public void testRebuildNotificationForCanceledSmartReplies() {
+ // Try rebuilding to remove spinner and hide buttons.
+ StatusBarNotification newSbn =
+ mRebuilder.rebuildForCanceledSmartReplies(mEntry);
+ assertFalse(newSbn.getNotification().extras
+ .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false));
+ assertTrue(newSbn.getNotification().extras
+ .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index 7cbc4e4e2c62..99c965a9e57f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
@@ -38,6 +38,8 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -51,6 +53,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
@@ -83,19 +87,26 @@ public class SmartReplyControllerTest extends SysuiTestCase {
mDependency.injectTestDependency(NotificationEntryManager.class,
mNotificationEntryManager);
- mSmartReplyController = new SmartReplyController(mNotificationEntryManager,
- mIStatusBarService, mClickNotifier);
+ mSmartReplyController = new SmartReplyController(
+ mock(DumpManager.class),
+ mNotificationEntryManager,
+ mIStatusBarService,
+ mClickNotifier);
mDependency.injectTestDependency(SmartReplyController.class,
mSmartReplyController);
mRemoteInputManager = new NotificationRemoteInputManager(mContext,
+ mock(FeatureFlags.class),
mock(NotificationLockscreenUserManager.class), mSmartReplyController,
- mNotificationEntryManager, () -> mock(StatusBar.class),
+ mNotificationEntryManager,
+ new RemoteInputNotificationRebuilder(mContext),
+ () -> Optional.of(mock(StatusBar.class)),
mStatusBarStateController,
Handler.createAsync(Looper.myLooper()),
mRemoteInputUriController,
mClickNotifier,
- mock(ActionClickLogger.class));
+ mock(ActionClickLogger.class),
+ mock(DumpManager.class));
mRemoteInputManager.setUpWithCallback(mCallback, mDelegate);
mNotification = new Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index fca6bc50f6e2..c974882ff305 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -21,10 +21,12 @@ import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -37,7 +39,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
@Before
fun setUp() {
uiEventLogger = UiEventLoggerFake()
- controller = StatusBarStateControllerImpl(uiEventLogger)
+ controller = StatusBarStateControllerImpl(uiEventLogger, mock(DumpManager::class.java))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
index 85ec3faa4013..f2671b763b56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
@@ -22,7 +22,7 @@ import android.view.WindowManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/AccessPointControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
index 4068f93f5ee3..7896a26badbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/AccessPointControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy
+package com.android.systemui.statusbar.connectivity
import android.os.UserManager
import android.test.suitebuilder.annotation.SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
index 24182434f1ba..11a53c55c024 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.connectivity;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
@@ -28,11 +28,11 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
-import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
+import com.android.systemui.statusbar.connectivity.NetworkController.EmergencyListener;
+import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
+import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
+import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
import com.android.systemui.tests.R;
import org.junit.Before;
@@ -124,7 +124,7 @@ public class CallbackHandlerTest extends SysuiTestCase {
boolean roaming = true;
MobileDataIndicators indicators = new MobileDataIndicators(
status, qs, type, qsType, in, out, typeDescription,
- typeDescriptionHtml, description, wide, subId, roaming, true);
+ typeDescriptionHtml, description, subId, roaming, true);
mHandler.setMobileDataIndicators(indicators);
waitForCallbacks();
@@ -141,8 +141,7 @@ public class CallbackHandlerTest extends SysuiTestCase {
assertEquals(out, expected.activityOut);
assertEquals(typeDescription, expected.typeContentDescription);
assertEquals(typeDescriptionHtml, expected.typeContentDescriptionHtml);
- assertEquals(description, expected.description);
- assertEquals(wide, expected.isWide);
+ assertEquals(description, expected.qsDescription);
assertEquals(subId, expected.subId);
assertTrue(expected.roaming);
assertTrue(expected.showTriangle);
@@ -183,7 +182,8 @@ public class CallbackHandlerTest extends SysuiTestCase {
@Test
public void testSignalCallback_setIsAirplaneMode() {
- IconState state = new IconState(true, R.drawable.stat_sys_airplane_mode, "Test Description");
+ IconState state =
+ new IconState(true, R.drawable.stat_sys_airplane_mode, "Test Description");
mHandler.setIsAirplaneMode(state);
waitForCallbacks();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 6c4ec223969a..b23d07a314b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.connectivity;
import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
@@ -71,11 +71,12 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
+import com.android.systemui.statusbar.connectivity.NetworkController.MobileDataIndicators;
+import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -215,7 +216,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
mCallbackHandler = mock(CallbackHandler.class);
mMockProvisionController = mock(DeviceProvisionedController.class);
- when(mMockProvisionController.isUserSetup(anyInt())).thenReturn(true);
+ when(mMockProvisionController.isCurrentUserSetup()).thenReturn(true);
doAnswer(invocation -> {
mUserCallback = (DeviceProvisionedListener) invocation.getArguments()[0];
mUserCallback.onUserSetupChanged();
@@ -268,7 +269,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
setSubscriptions(mSubId);
mMobileSignalController = mNetworkController.mMobileSignalControllers.get(mSubId);
ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackArg =
- ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+ ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
verify(mMockCm, atLeastOnce())
.registerDefaultNetworkCallback(callbackArg.capture(), isA(Handler.class));
int captureSize = callbackArg.getAllValues().size();
@@ -404,7 +405,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
}
private static void setConnectivityCommon(NetworkCapabilities.Builder builder,
- int networkType, boolean validated, boolean isConnected){
+ int networkType, boolean validated, boolean isConnected) {
// TODO: Separate out into several NetworkCapabilities.
if (isConnected) {
builder.addTransportType(networkType);
@@ -538,7 +539,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
}
protected void verifyLastMobileDataIndicators(boolean visible, int icon, int typeIcon,
- boolean roaming, boolean inet) {
+ boolean roaming, boolean inet) {
ArgumentCaptor<MobileDataIndicators> indicatorsArg =
ArgumentCaptor.forClass(MobileDataIndicators.class);
@@ -646,7 +647,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
}
protected void assertNetworkNameEquals(String expected) {
- assertEquals("Network name", expected, mMobileSignalController.getState().networkName);
+ assertEquals("Network name", expected, mMobileSignalController.getState().networkName);
}
protected void assertDataNetworkNameEquals(String expected) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index 3433a14f54e7..12f8282c7aa0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -1,11 +1,26 @@
-package com.android.systemui.statusbar.policy;
+/*
+ * 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.systemui.statusbar.connectivity;
import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -22,6 +37,7 @@ import android.testing.TestableLooper.RunWithLooper;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
import org.junit.Test;
@@ -194,7 +210,7 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest {
updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
setConnectivityViaCallbackInNetworkController(
NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null);
- when(mMockProvisionController.isUserSetup(anyInt())).thenReturn(false);
+ when(mMockProvisionController.isCurrentUserSetup()).thenReturn(false);
mUserCallback.onUserSetupChanged();
TestableLooper.get(this).processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
index 6aab9c762a95..675d755ad3e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
@@ -1,4 +1,20 @@
-package com.android.systemui.statusbar.policy;
+/*
+ * 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.systemui.statusbar.connectivity;
import static junit.framework.Assert.assertEquals;
@@ -7,7 +23,7 @@ import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.connectivity.NetworkController.IconState;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index 4ff13011567b..73eddd166f88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.connectivity;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -42,6 +42,7 @@ import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
import org.junit.Test;
@@ -280,7 +281,7 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
// TODO: Put this somewhere else, maybe in its own file.
@Test
public void testHasCorrectMobileControllers() {
- int[] testSubscriptions = new int[] { 1, 5, 3 };
+ int[] testSubscriptions = new int[]{1, 5, 3};
int notTestSubscription = 0;
MobileSignalController mobileSignalController = Mockito.mock(MobileSignalController.class);
@@ -312,8 +313,8 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
// We will not add one subscription to make sure it's controller gets removed.
int indexToSkipSubscription = 1;
- int[] testSubscriptions = new int[] { 1, 5, 3 };
- MobileSignalController[] mobileSignalControllers = new MobileSignalController[] {
+ int[] testSubscriptions = new int[]{1, 5, 3};
+ MobileSignalController[] mobileSignalControllers = new MobileSignalController[]{
Mockito.mock(MobileSignalController.class),
Mockito.mock(MobileSignalController.class),
Mockito.mock(MobileSignalController.class),
@@ -401,24 +402,24 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
@Test
public void testOnReceive_stringsUpdatedAction_bothFalse() {
Intent intent = createStringsUpdatedIntent(false /* showSpn */,
- "Irrelevant" /* spn */,
- false /* showPlmn */,
- "Irrelevant" /* plmn */);
+ "Irrelevant" /* spn */,
+ false /* showPlmn */,
+ "Irrelevant" /* plmn */);
mNetworkController.onReceive(mContext, intent);
String defaultNetworkName = mMobileSignalController
.getTextIfExists(
- com.android.internal.R.string.lockscreen_carrier_default).toString();
+ com.android.internal.R.string.lockscreen_carrier_default).toString();
assertNetworkNameEquals(defaultNetworkName);
}
@Test
public void testOnReceive_stringsUpdatedAction_bothTrueAndNull() {
Intent intent = createStringsUpdatedIntent(true /* showSpn */,
- null /* spn */,
- true /* showPlmn */,
- null /* plmn */);
+ null /* spn */,
+ true /* showPlmn */,
+ null /* plmn */);
mNetworkController.onReceive(mContext, intent);
@@ -433,15 +434,15 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
String plmn = "Test2";
Intent intent = createStringsUpdatedIntent(true /* showSpn */,
- spn /* spn */,
- true /* showPlmn */,
- plmn /* plmn */);
+ spn /* spn */,
+ true /* showPlmn */,
+ plmn /* plmn */);
mNetworkController.onReceive(mContext, intent);
assertNetworkNameEquals(plmn
+ mMobileSignalController.getTextIfExists(
- R.string.status_bar_network_name_separator).toString()
+ R.string.status_bar_network_name_separator).toString()
+ spn);
}
@@ -477,145 +478,149 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
@Test
public void testOnUpdateDataActivity_dataOut() {
- setupDefaultSignal();
+ setupDefaultSignal();
- updateDataActivity(TelephonyManager.DATA_ACTIVITY_OUT);
+ updateDataActivity(TelephonyManager.DATA_ACTIVITY_OUT);
- verifyLastQsMobileDataIndicators(true /* visible */,
- DEFAULT_LEVEL /* icon */,
- DEFAULT_QS_ICON /* typeIcon */,
- false /* dataIn */,
- true /* dataOut */);
+ verifyLastQsMobileDataIndicators(true /* visible */,
+ DEFAULT_LEVEL /* icon */,
+ DEFAULT_QS_ICON /* typeIcon */,
+ false /* dataIn */,
+ true /* dataOut */);
}
@Test
public void testOnUpdateDataActivity_dataInOut() {
- setupDefaultSignal();
+ setupDefaultSignal();
- updateDataActivity(TelephonyManager.DATA_ACTIVITY_INOUT);
+ updateDataActivity(TelephonyManager.DATA_ACTIVITY_INOUT);
- verifyLastQsMobileDataIndicators(true /* visible */,
- DEFAULT_LEVEL /* icon */,
- DEFAULT_QS_ICON /* typeIcon */,
- true /* dataIn */,
- true /* dataOut */);
+ verifyLastQsMobileDataIndicators(true /* visible */,
+ DEFAULT_LEVEL /* icon */,
+ DEFAULT_QS_ICON /* typeIcon */,
+ true /* dataIn */,
+ true /* dataOut */);
}
@Test
public void testOnUpdateDataActivity_dataActivityNone() {
- setupDefaultSignal();
+ setupDefaultSignal();
- updateDataActivity(TelephonyManager.DATA_ACTIVITY_NONE);
+ updateDataActivity(TelephonyManager.DATA_ACTIVITY_NONE);
- verifyLastQsMobileDataIndicators(true /* visible */,
- DEFAULT_LEVEL /* icon */,
- DEFAULT_QS_ICON /* typeIcon */,
- false /* dataIn */,
- false /* dataOut */);
+ verifyLastQsMobileDataIndicators(true /* visible */,
+ DEFAULT_LEVEL /* icon */,
+ DEFAULT_QS_ICON /* typeIcon */,
+ false /* dataIn */,
+ false /* dataOut */);
}
@Test
public void testCarrierNetworkChange_carrierNetworkChange() {
- int strength = SignalStrength.SIGNAL_STRENGTH_GREAT;
+ int strength = SignalStrength.SIGNAL_STRENGTH_GREAT;
- setupDefaultSignal();
- setLevel(strength);
+ setupDefaultSignal();
+ setLevel(strength);
- // Verify baseline
- verifyLastMobileDataIndicators(true /* visible */,
- strength /* strengthIcon */,
- DEFAULT_ICON /* typeIcon */);
+ // Verify baseline
+ verifyLastMobileDataIndicators(true /* visible */,
+ strength /* strengthIcon */,
+ DEFAULT_ICON /* typeIcon */);
- // API call is made
- setCarrierNetworkChange(true /* enabled */);
+ // API call is made
+ setCarrierNetworkChange(true /* enabled */);
- // Carrier network change is true, show special indicator
- verifyLastMobileDataIndicators(true /* visible */,
- SignalDrawable.getCarrierChangeState(CellSignalStrength.getNumSignalStrengthLevels()),
- 0 /* typeIcon */);
+ // Carrier network change is true, show special indicator
+ verifyLastMobileDataIndicators(true /* visible */,
+ SignalDrawable.getCarrierChangeState(
+ CellSignalStrength.getNumSignalStrengthLevels()),
+ 0 /* typeIcon */);
- // Revert back
- setCarrierNetworkChange(false /* enabled */);
+ // Revert back
+ setCarrierNetworkChange(false /* enabled */);
- // Verify back in previous state
- verifyLastMobileDataIndicators(true /* visible */,
- strength /* strengthIcon */,
- DEFAULT_ICON /* typeIcon */);
+ // Verify back in previous state
+ verifyLastMobileDataIndicators(true /* visible */,
+ strength /* strengthIcon */,
+ DEFAULT_ICON /* typeIcon */);
}
@Test
public void testCarrierNetworkChange_roamingBeforeNetworkChange() {
- int strength = SignalStrength.SIGNAL_STRENGTH_GREAT;
+ int strength = SignalStrength.SIGNAL_STRENGTH_GREAT;
- setupDefaultSignal();
- setLevel(strength);
- setGsmRoaming(true);
+ setupDefaultSignal();
+ setLevel(strength);
+ setGsmRoaming(true);
- // Verify baseline
- verifyLastMobileDataIndicators(true /* visible */,
- strength /* strengthIcon */,
- DEFAULT_ICON /* typeIcon */,
- true /* roaming */);
+ // Verify baseline
+ verifyLastMobileDataIndicators(true /* visible */,
+ strength /* strengthIcon */,
+ DEFAULT_ICON /* typeIcon */,
+ true /* roaming */);
- // API call is made
- setCarrierNetworkChange(true /* enabled */);
+ // API call is made
+ setCarrierNetworkChange(true /* enabled */);
- // Carrier network change is true, show special indicator, no roaming.
- verifyLastMobileDataIndicators(true /* visible */,
- SignalDrawable.getCarrierChangeState(CellSignalStrength.getNumSignalStrengthLevels()),
- 0 /* typeIcon */,
- false /* roaming */);
+ // Carrier network change is true, show special indicator, no roaming.
+ verifyLastMobileDataIndicators(true /* visible */,
+ SignalDrawable.getCarrierChangeState(
+ CellSignalStrength.getNumSignalStrengthLevels()),
+ 0 /* typeIcon */,
+ false /* roaming */);
- // Revert back
- setCarrierNetworkChange(false /* enabled */);
+ // Revert back
+ setCarrierNetworkChange(false /* enabled */);
- // Verify back in previous state
- verifyLastMobileDataIndicators(true /* visible */,
- strength /* strengthIcon */,
- DEFAULT_ICON /* typeIcon */,
- true /* roaming */);
+ // Verify back in previous state
+ verifyLastMobileDataIndicators(true /* visible */,
+ strength /* strengthIcon */,
+ DEFAULT_ICON /* typeIcon */,
+ true /* roaming */);
}
@Test
public void testCarrierNetworkChange_roamingAfterNetworkChange() {
- int strength = SignalStrength.SIGNAL_STRENGTH_GREAT;
-
- setupDefaultSignal();
- setLevel(strength);
-
- // Verify baseline
- verifyLastMobileDataIndicators(true /* visible */,
- strength /* strengthIcon */,
- DEFAULT_ICON /* typeIcon */,
- false /* roaming */);
-
- // API call is made
- setCarrierNetworkChange(true /* enabled */);
-
- // Carrier network change is true, show special indicator, no roaming.
- verifyLastMobileDataIndicators(true /* visible */,
- SignalDrawable.getCarrierChangeState(CellSignalStrength.getNumSignalStrengthLevels()),
- 0 /* typeIcon */,
- false /* roaming */);
-
- setGsmRoaming(true);
-
- // Roaming should not show.
- verifyLastMobileDataIndicators(true /* visible */,
- SignalDrawable.getCarrierChangeState(CellSignalStrength.getNumSignalStrengthLevels()),
- 0 /* typeIcon */,
- false /* roaming */);
-
- // Revert back
- setCarrierNetworkChange(false /* enabled */);
-
- // Verify back in previous state
- verifyLastMobileDataIndicators(true /* visible */,
- strength /* strengthIcon */,
- DEFAULT_ICON /* typeIcon */,
- true /* roaming */);
+ int strength = SignalStrength.SIGNAL_STRENGTH_GREAT;
+
+ setupDefaultSignal();
+ setLevel(strength);
+
+ // Verify baseline
+ verifyLastMobileDataIndicators(true /* visible */,
+ strength /* strengthIcon */,
+ DEFAULT_ICON /* typeIcon */,
+ false /* roaming */);
+
+ // API call is made
+ setCarrierNetworkChange(true /* enabled */);
+
+ // Carrier network change is true, show special indicator, no roaming.
+ verifyLastMobileDataIndicators(true /* visible */,
+ SignalDrawable.getCarrierChangeState(
+ CellSignalStrength.getNumSignalStrengthLevels()),
+ 0 /* typeIcon */,
+ false /* roaming */);
+
+ setGsmRoaming(true);
+
+ // Roaming should not show.
+ verifyLastMobileDataIndicators(true /* visible */,
+ SignalDrawable.getCarrierChangeState(
+ CellSignalStrength.getNumSignalStrengthLevels()),
+ 0 /* typeIcon */,
+ false /* roaming */);
+
+ // Revert back
+ setCarrierNetworkChange(false /* enabled */);
+
+ // Verify back in previous state
+ verifyLastMobileDataIndicators(true /* visible */,
+ strength /* strengthIcon */,
+ DEFAULT_ICON /* typeIcon */,
+ true /* roaming */);
}
private void verifyEmergencyOnly(boolean isEmergencyOnly) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
index 4a5770d12239..ffeaf207942b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
@@ -1,10 +1,25 @@
-package com.android.systemui.statusbar.policy;
+/*
+ * 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.systemui.statusbar.connectivity;
import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Matchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -21,7 +36,7 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import com.android.settingslib.mobile.TelephonyIcons;
-import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
+import com.android.systemui.statusbar.connectivity.NetworkController.WifiIndicators;
import org.junit.Before;
import org.junit.Test;
@@ -165,7 +180,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest {
}
@Test
- public void testWifiIconDisconnectedViaCallback(){
+ public void testWifiIconDisconnectedViaCallback() {
// Setup normal connection
String testSsid = "Test SSID";
int testLevel = 2;
@@ -183,7 +198,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest {
}
@Test
- public void testVpnWithUnderlyingWifi(){
+ public void testVpnWithUnderlyingWifi() {
String testSsid = "Test SSID";
int testLevel = 2;
setWifiEnabled(true);
@@ -299,7 +314,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest {
protected void setWifiLevel(int level) {
float amountPerLevel = (MAX_RSSI - MIN_RSSI) / (WifiIcons.WIFI_LEVEL_COUNT - 1);
- int rssi = (int)(MIN_RSSI + level * amountPerLevel);
+ int rssi = (int) (MIN_RSSI + level * amountPerLevel);
// Put RSSI in the middle of the range.
rssi += amountPerLevel / 2;
when(mWifiInfo.getRssi()).thenReturn(rssi);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 9e103d68dd1e..ff91978c54bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -41,7 +41,7 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener
import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -53,7 +53,6 @@ import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.time.FakeSystemClock
-import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
@@ -121,6 +120,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
private lateinit var statusBarStateListener: StateListener
private lateinit var deviceProvisionedListener: DeviceProvisionedListener
+ private lateinit var smartspaceView: SmartspaceView
+
private val clock = FakeSystemClock()
private val executor = FakeExecutor(clock)
private val execution = FakeExecution()
@@ -148,7 +149,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
`when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING))
.thenReturn(fakePrivateLockscreenSettingUri)
`when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession)
- `when`(plugin.getView(any())).thenReturn(fakeSmartspaceView)
+ `when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView())
`when`(userTracker.userProfiles).thenReturn(userList)
`when`(statusBarStateController.dozeAmount).thenReturn(0.5f)
`when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
@@ -198,8 +199,9 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
`when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(false)
`when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(false)
- // WHEN a connection attempt is made
- controller.buildAndConnectView(fakeParent)
+ // WHEN a connection attempt is made and view is attached
+ val view = controller.buildAndConnectView(fakeParent)
+ controller.stateChangeListener.onViewAttachedToWindow(view)
// THEN no session is created
verify(smartspaceManager, never()).createSmartspaceSession(any())
@@ -244,6 +246,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
clearInvocations(plugin)
// WHEN the session is closed
+ controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View)
controller.disconnect()
// THEN the listener receives an empty list of targets
@@ -283,7 +286,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
configChangeListener.onThemeChanged()
// We update the new text color to match the wallpaper color
- verify(fakeSmartspaceView).setPrimaryTextColor(anyInt())
+ verify(smartspaceView).setPrimaryTextColor(anyInt())
}
@Test
@@ -295,7 +298,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f)
// We pass that along to the view
- verify(fakeSmartspaceView).setDozeAmount(0.7f)
+ verify(smartspaceView).setDozeAmount(0.7f)
}
@Test
@@ -415,6 +418,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
connectSession()
// WHEN we are told to cleanup
+ controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View)
controller.disconnect()
// THEN we disconnect from the session and unregister any listeners
@@ -427,35 +431,21 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
}
@Test
- fun testBuildViewIsIdempotent() {
- // GIVEN a connected session
- connectSession()
- clearInvocations(plugin)
-
- // WHEN we disconnect and then reconnect
- controller.disconnect()
- controller.buildAndConnectView(fakeParent)
-
- // THEN the view is not rebuilt
- verify(plugin, never()).getView(any())
- assertEquals(fakeSmartspaceView, controller.view)
- }
-
- @Test
- fun testDoubleConnectIsIgnored() {
+ fun testMultipleViewsUseSameSession() {
// GIVEN a connected session
connectSession()
clearInvocations(smartspaceManager)
clearInvocations(plugin)
- // WHEN we're asked to connect a second time and add to a parent
+ // WHEN we're asked to connect a second time and add to a parent. If the same view
+ // was created the ViewGroup will throw an exception
val view = controller.buildAndConnectView(fakeParent)
fakeParent.addView(view)
+ val smartspaceView2 = view as SmartspaceView
- // THEN the existing view and session are reused
+ // THEN the existing session is reused and views are registered
verify(smartspaceManager, never()).createSmartspaceSession(any())
- verify(plugin, never()).getView(any())
- assertEquals(fakeSmartspaceView, controller.view)
+ verify(smartspaceView2).registerDataProvider(plugin)
}
@Test
@@ -473,8 +463,12 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
}
private fun connectSession() {
- controller.buildAndConnectView(fakeParent)
+ val view = controller.buildAndConnectView(fakeParent)
+ smartspaceView = view as SmartspaceView
+
+ controller.stateChangeListener.onViewAttachedToWindow(view)
+ verify(smartspaceView).registerDataProvider(plugin)
verify(smartspaceSession)
.addOnTargetsAvailableListener(any(), capture(sessionListenerCaptor))
sessionListener = sessionListenerCaptor.value
@@ -498,11 +492,11 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
verify(smartspaceSession).requestSmartspaceUpdate()
clearInvocations(smartspaceSession)
- verify(fakeSmartspaceView).setPrimaryTextColor(anyInt())
- verify(fakeSmartspaceView).setDozeAmount(0.5f)
- clearInvocations(fakeSmartspaceView)
+ verify(smartspaceView).setPrimaryTextColor(anyInt())
+ verify(smartspaceView).setDozeAmount(0.5f)
+ clearInvocations(view)
- fakeParent.addView(fakeSmartspaceView)
+ fakeParent.addView(view)
}
private fun setActiveUser(userHandle: UserHandle) {
@@ -538,31 +532,33 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
).thenReturn(if (value) 1 else 0)
}
- private val fakeSmartspaceView = spy(object : View(context), SmartspaceView {
- override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
- }
+ private fun createSmartspaceView(): SmartspaceView {
+ return spy(object : View(context), SmartspaceView {
+ override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
+ }
- override fun setPrimaryTextColor(color: Int) {
- }
+ override fun setPrimaryTextColor(color: Int) {
+ }
- override fun setDozeAmount(amount: Float) {
- }
+ override fun setDozeAmount(amount: Float) {
+ }
- override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {
- }
+ override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {
+ }
- override fun setFalsingManager(falsingManager: FalsingManager?) {
- }
+ override fun setFalsingManager(falsingManager: FalsingManager?) {
+ }
- override fun setDnd(image: Drawable?, description: String?) {
- }
+ override fun setDnd(image: Drawable?, description: String?) {
+ }
- override fun setNextAlarm(image: Drawable?, description: String?) {
- }
+ override fun setNextAlarm(image: Drawable?, description: String?) {
+ }
- override fun setMediaTarget(target: SmartspaceTarget?) {
- }
- })
+ override fun setMediaTarget(target: SmartspaceTarget?) {
+ }
+ })
+ }
}
private const val PRIVATE_LOCKSCREEN_SETTING =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 1be14b66e968..902d11575597 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -32,6 +32,7 @@ import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
@@ -64,8 +65,10 @@ import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
+import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -79,6 +82,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationRankin
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -92,6 +96,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -118,6 +123,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
@Mock private KeyguardEnvironment mEnvironment;
@Mock private ExpandableNotificationRow mRow;
@Mock private NotificationEntryListener mEntryListener;
+ @Mock private NotifCollectionListener mNotifCollectionListener;
@Mock private NotificationRemoveInterceptor mRemoveInterceptor;
@Mock private HeadsUpManager mHeadsUpManager;
@Mock private RankingMap mRankingMap;
@@ -130,6 +136,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
@Mock private LeakDetector mLeakDetector;
@Mock private NotificationMediaManager mNotificationMediaManager;
@Mock private NotificationRowBinder mNotificationRowBinder;
+ @Mock private NotificationListener mNotificationListener;
private int mId;
private NotificationEntry mEntry;
@@ -195,9 +202,11 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
() -> mRemoteInputManager,
mLeakDetector,
mock(ForegroundServiceDismissalFeatureController.class),
- mock(IStatusBarService.class)
+ mock(IStatusBarService.class),
+ mock(DumpManager.class)
);
- mEntryManager.setRanker(
+ mEntryManager.initialize(
+ mNotificationListener,
new NotificationRankingManager(
() -> mNotificationMediaManager,
mGroupManager,
@@ -210,6 +219,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
mEnvironment));
mEntryManager.setUpWithPresenter(mPresenter);
mEntryManager.addNotificationEntryListener(mEntryListener);
+ mEntryManager.addCollectionListener(mNotifCollectionListener);
mEntryManager.addNotificationRemoveInterceptor(mRemoveInterceptor);
setUserSentiment(mSbn.getKey(), Ranking.USER_SENTIMENT_NEUTRAL);
@@ -313,13 +323,20 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
eq(mEntry), any(), eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
}
+ /** Regression test for b/201097913. */
@Test
- public void testRemoveNotification_whilePending() {
+ public void testRemoveNotification_whilePending_onlyCollectionListenerNotified() {
+ // Add and then remove a pending entry (entry that hasn't been inflated).
mEntryManager.addNotification(mSbn, mRankingMap);
mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
+ // Verify that only the listener for the NEW pipeline is notified.
+ // Old pipeline:
verify(mEntryListener, never()).onEntryRemoved(
- eq(mEntry), any(), eq(false /* removedByUser */), eq(UNDEFINED_DISMISS_REASON));
+ argThat(matchEntryOnSbn()), any(), anyBoolean(), anyInt());
+ // New pipeline:
+ verify(mNotifCollectionListener).onEntryRemoved(
+ argThat(matchEntryOnSbn()), anyInt());
}
@Test
@@ -634,6 +651,11 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
PendingIntent.FLAG_IMMUTABLE)).build();
}
+ // TODO(b/201321631): Update more tests to use this function instead of eq(mEntry).
+ private ArgumentMatcher<NotificationEntry> matchEntryOnSbn() {
+ return e -> e.getSbn().equals(mSbn);
+ }
+
private static class FakeNotificationLifetimeExtender implements NotificationLifetimeExtender {
private NotificationSafeToRemoveCallback mCallback;
private boolean mExtendLifetimes = true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index b1eef4b67a6f..b02a336b4396 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -42,6 +42,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -119,7 +120,8 @@ public class NotificationFilterTest extends SysuiTestCase {
new NotificationGroupManagerLegacy(
mock(StatusBarStateController.class),
() -> mock(PeopleNotificationIdentifier.class),
- Optional.of(mock(Bubbles.class))));
+ Optional.of(mock(Bubbles.class)),
+ mock(DumpManager.class)));
mDependency.injectMockDependency(ShadeController.class);
mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
index baeedcfc3fc5..a737ce5a2ae5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
@@ -32,6 +32,7 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -70,7 +71,8 @@ public class VisualStabilityManagerTest extends SysuiTestCase {
mock(NotificationEntryManager.class),
new Handler(mTestableLooper.getLooper()),
statusBarStateController,
- wakefulnessLifecycle);
+ wakefulnessLifecycle,
+ mock(DumpManager.class));
mVisualStabilityManager.setVisibilityLocationProvider(mLocationProvider);
mEntry = new NotificationEntryBuilder().build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
index 62667bc5281f..da956ec67696 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
@@ -30,3 +30,7 @@ inline fun modifyEntry(
modifier(builder)
builder.apply(entry)
}
+
+fun getAttachState(entry: ListEntry): ListAttachState {
+ return entry.attachState
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 9a5482c33501..f08a74ab1316 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -35,6 +35,7 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
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;
import static org.mockito.Mockito.clearInvocations;
@@ -50,6 +51,7 @@ import static java.util.Objects.requireNonNull;
import android.annotation.Nullable;
import android.app.Notification;
+import android.os.Handler;
import android.os.RemoteException;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
@@ -60,6 +62,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.internal.statusbar.IStatusBarService;
@@ -67,7 +70,7 @@ import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.LogBufferEulogizer;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent;
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
@@ -76,6 +79,7 @@ import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoa
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
@@ -106,6 +110,7 @@ public class NotifCollectionTest extends SysuiTestCase {
@Mock private FeatureFlags mFeatureFlags;
@Mock private NotifCollectionLogger mLogger;
@Mock private LogBufferEulogizer mEulogizer;
+ @Mock private Handler mMainHandler;
@Mock private GroupCoalescer mGroupCoalescer;
@Spy private RecordingCollectionListener mCollectionListener;
@@ -151,6 +156,7 @@ public class NotifCollectionTest extends SysuiTestCase {
mClock,
mFeatureFlags,
mLogger,
+ mMainHandler,
mEulogizer,
mock(DumpManager.class));
mCollection.attach(mGroupCoalescer);
@@ -1321,6 +1327,78 @@ public class NotifCollectionTest extends SysuiTestCase {
verify(mCollectionListener, never()).onEntryRemoved(any(NotificationEntry.class), anyInt());
}
+ private Runnable getInternalNotifUpdateRunnable(StatusBarNotification sbn) {
+ InternalNotifUpdater updater = mCollection.getInternalNotifUpdater("Test");
+ updater.onInternalNotificationUpdate(sbn, "reason");
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mMainHandler).post(runnableCaptor.capture());
+ return runnableCaptor.getValue();
+ }
+
+ @Test
+ public void testGetInternalNotifUpdaterPostsToMainHandler() {
+ InternalNotifUpdater updater = mCollection.getInternalNotifUpdater("Test");
+ updater.onInternalNotificationUpdate(mock(StatusBarNotification.class), "reason");
+ verify(mMainHandler).post(any());
+ }
+
+ @Test
+ public void testSecondPostCallsUpdateWithTrue() {
+ // GIVEN a pipeline with one notification
+ NotifEvent notifEvent = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotificationEntry entry = mCollectionListener.getEntry(notifEvent.key);
+
+ // KNOWING that it already called listener methods once
+ verify(mCollectionListener).onEntryAdded(eq(entry));
+ verify(mCollectionListener).onRankingApplied();
+
+ // WHEN we update the notification via the system
+ mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+
+ // THEN entry updated gets called, added does not, and ranking is called again
+ verify(mCollectionListener).onEntryUpdated(eq(entry));
+ verify(mCollectionListener).onEntryUpdated(eq(entry), eq(true));
+ verify(mCollectionListener).onEntryAdded((entry));
+ verify(mCollectionListener, times(2)).onRankingApplied();
+ }
+
+ @Test
+ public void testInternalNotifUpdaterCallsUpdate() {
+ // GIVEN a pipeline with one notification
+ NotifEvent notifEvent = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotificationEntry entry = mCollectionListener.getEntry(notifEvent.key);
+
+ // KNOWING that it will call listener methods once
+ verify(mCollectionListener).onEntryAdded(eq(entry));
+ verify(mCollectionListener).onRankingApplied();
+
+ // WHEN we update that notification internally
+ StatusBarNotification sbn = notifEvent.sbn;
+ getInternalNotifUpdateRunnable(sbn).run();
+
+ // THEN only entry updated gets called a second time
+ verify(mCollectionListener).onEntryAdded(eq(entry));
+ verify(mCollectionListener).onRankingApplied();
+ verify(mCollectionListener).onEntryUpdated(eq(entry));
+ verify(mCollectionListener).onEntryUpdated(eq(entry), eq(false));
+ }
+
+ @Test
+ public void testInternalNotifUpdaterIgnoresNew() {
+ // GIVEN a pipeline without any notifications
+ StatusBarNotification sbn = buildNotif(TEST_PACKAGE, 47, "myTag").build().getSbn();
+
+ // WHEN we internally update an unknown notification
+ getInternalNotifUpdateRunnable(sbn).run();
+
+ // THEN only entry updated gets called a second time
+ verify(mCollectionListener, never()).onEntryAdded(any());
+ verify(mCollectionListener, never()).onRankingUpdate(any());
+ verify(mCollectionListener, never()).onRankingApplied();
+ verify(mCollectionListener, never()).onEntryUpdated(any());
+ verify(mCollectionListener, never()).onEntryUpdated(any(), anyBoolean());
+ }
+
private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) {
return new NotificationEntryBuilder()
.setPkg(pkg)
@@ -1371,6 +1449,11 @@ public class NotifCollectionTest extends SysuiTestCase {
}
@Override
+ public void onEntryUpdated(NotificationEntry entry, boolean fromSystem) {
+ onEntryUpdated(entry);
+ }
+
+ @Override
public void onEntryRemoved(NotificationEntry entry, int reason) {
}
@@ -1405,25 +1488,26 @@ public class NotifCollectionTest extends SysuiTestCase {
mName = name;
}
+ @NonNull
@Override
public String getName() {
return mName;
}
@Override
- public void setCallback(OnEndLifetimeExtensionCallback callback) {
+ public void setCallback(@NonNull OnEndLifetimeExtensionCallback callback) {
this.callback = callback;
}
@Override
public boolean shouldExtendLifetime(
- NotificationEntry entry,
+ @NonNull NotificationEntry entry,
@CancellationReason int reason) {
return shouldExtendLifetime;
}
@Override
- public void cancelLifetimeExtension(NotificationEntry entry) {
+ public void cancelLifetimeExtension(@NonNull NotificationEntry entry) {
if (onCancelLifetimeExtension != null) {
onCancelLifetimeExtension.run();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 2ce22a6f71c9..190c3521e83c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -33,6 +33,7 @@ import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static java.util.Collections.singletonList;
@@ -42,6 +43,7 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
+import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -54,6 +56,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
@@ -78,6 +81,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
@SmallTest
@@ -608,6 +612,30 @@ public class ShadeListBuilderTest extends SysuiTestCase {
}
@Test
+ public void testNotifSectionsChildrenUpdated() {
+ AtomicBoolean validChildren = new AtomicBoolean(false);
+ final NotifSectioner pkg1Sectioner = spy(new PackageSectioner(PACKAGE_1) {
+ @Nullable
+ @Override
+ public void onEntriesUpdated(List<ListEntry> entries) {
+ super.onEntriesUpdated(entries);
+ validChildren.set(entries.size() == 2);
+ }
+ });
+ mListBuilder.setSectioners(Arrays.asList(pkg1Sectioner));
+
+ addNotif(0, PACKAGE_4);
+ addNotif(1, PACKAGE_1);
+ addNotif(2, PACKAGE_1);
+ addNotif(3, PACKAGE_3);
+
+ dispatchBuild();
+
+ verify(pkg1Sectioner, times(1)).onEntriesUpdated(any());
+ assertTrue(validChildren.get());
+ }
+
+ @Test
public void testNotifSections() {
// GIVEN a filter that removes all PACKAGE_4 notifs and sections that divide
// notifs based on package name
@@ -802,13 +830,13 @@ public class ShadeListBuilderTest extends SysuiTestCase {
.onBeforeTransformGroups(anyList());
inOrder.verify(promoter, atLeastOnce())
.shouldPromoteToTopLevel(any(NotificationEntry.class));
+ inOrder.verify(mOnBeforeFinalizeFilterListener).onBeforeFinalizeFilter(anyList());
+ inOrder.verify(preRenderFilter, atLeastOnce())
+ .shouldFilterOut(any(NotificationEntry.class), anyLong());
inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList());
inOrder.verify(section, atLeastOnce()).isInSection(any(ListEntry.class));
inOrder.verify(comparator, atLeastOnce())
.compare(any(ListEntry.class), any(ListEntry.class));
- inOrder.verify(mOnBeforeFinalizeFilterListener).onBeforeFinalizeFilter(anyList());
- inOrder.verify(preRenderFilter, atLeastOnce())
- .shouldFilterOut(any(NotificationEntry.class), anyLong());
inOrder.verify(mOnBeforeRenderListListener).onBeforeRenderList(anyList());
inOrder.verify(mOnRenderListListener).onRenderList(anyList());
}
@@ -820,11 +848,13 @@ public class ShadeListBuilderTest extends SysuiTestCase {
NotifPromoter idPromoter = new IdPromoter(4);
NotifSectioner section = new PackageSectioner(PACKAGE_1);
NotifComparator hypeComparator = new HypeComparator(PACKAGE_2);
+ Invalidator preRenderInvalidator = new Invalidator("PreRenderInvalidator") {};
mListBuilder.addPreGroupFilter(packageFilter);
mListBuilder.addPromoter(idPromoter);
mListBuilder.setSectioners(singletonList(section));
mListBuilder.setComparators(singletonList(hypeComparator));
+ mListBuilder.addPreRenderInvalidator(preRenderInvalidator);
// GIVEN a set of random notifs
addNotif(0, PACKAGE_1);
@@ -849,6 +879,10 @@ public class ShadeListBuilderTest extends SysuiTestCase {
clearInvocations(mOnRenderListListener);
hypeComparator.invalidateList();
verify(mOnRenderListListener).onRenderList(anyList());
+
+ clearInvocations(mOnRenderListListener);
+ preRenderInvalidator.invalidateList();
+ verify(mOnRenderListListener).onRenderList(anyList());
}
@Test
@@ -1045,12 +1079,32 @@ public class ShadeListBuilderTest extends SysuiTestCase {
// because group changes aren't allowed by the stability manager
verifyBuiltList(
notif(0),
+ notif(2),
group(
summary(3),
child(4),
child(5)
- ),
- notif(2)
+ )
+ );
+ }
+
+ @Test
+ public void testBrokenGroupNotificationOrdering() {
+ // GIVEN two group children with different sections & without a summary yet
+
+ addGroupChild(0, PACKAGE_2, GROUP_1);
+ addNotif(1, PACKAGE_1);
+ addGroupChild(2, PACKAGE_2, GROUP_1);
+ addGroupChild(3, PACKAGE_2, GROUP_1);
+
+ dispatchBuild();
+
+ // THEN all notifications are not grouped and posted in order by index
+ verifyBuiltList(
+ notif(0),
+ notif(1),
+ notif(2),
+ notif(3)
);
}
@@ -1613,7 +1667,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
private final String mPackage;
PackageSectioner(String pkg) {
- super("PackageSection_" + pkg);
+ super("PackageSection_" + pkg, 0);
mPackage = pkg;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
new file mode 100644
index 000000000000..0cba07033c63
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.systemui.statusbar.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener
+import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager
+import com.android.systemui.statusbar.notification.row.NotificationGuts
+import com.android.systemui.util.mockito.argumentCaptor
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class GutsCoordinatorTest : SysuiTestCase() {
+ private lateinit var coordinator: GutsCoordinator
+ private lateinit var notifLifetimeExtender: NotifLifetimeExtender
+ private lateinit var notifGutsViewListener: NotifGutsViewListener
+
+ private lateinit var entry1: NotificationEntry
+ private lateinit var entry2: NotificationEntry
+
+ @Mock private lateinit var notifGutsViewManager: NotifGutsViewManager
+ @Mock private lateinit var pipeline: NotifPipeline
+ @Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var logger: GutsCoordinatorLogger
+ @Mock private lateinit var lifetimeExtenderCallback: OnEndLifetimeExtensionCallback
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+ coordinator = GutsCoordinator(notifGutsViewManager, logger, dumpManager)
+ coordinator.attach(pipeline)
+ notifLifetimeExtender = argumentCaptor<NotifLifetimeExtender>().let {
+ verify(pipeline).addNotificationLifetimeExtender(it.capture())
+ it.value!!
+ }
+ notifGutsViewListener = argumentCaptor<NotifGutsViewListener>().let {
+ verify(notifGutsViewManager).setGutsListener(it.capture())
+ it.value!!
+ }
+ notifLifetimeExtender.setCallback(lifetimeExtenderCallback)
+ entry1 = NotificationEntryBuilder().setId(1).build()
+ entry2 = NotificationEntryBuilder().setId(2).build()
+ }
+
+ @Test
+ fun testSimpleLifetimeExtension() {
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+ notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java))
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue()
+ notifGutsViewListener.onGutsClose(entry1)
+ verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1)
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+ }
+
+ @Test
+ fun testDoubleOpenLifetimeExtension() {
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+ notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java))
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue()
+ notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java))
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue()
+ notifGutsViewListener.onGutsClose(entry1)
+ verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1)
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+ }
+
+ @Test
+ fun testTwoEntryLifetimeExtension() {
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse()
+ notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java))
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue()
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse()
+ notifGutsViewListener.onGutsOpen(entry2, mock(NotificationGuts::class.java))
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue()
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isTrue()
+ notifGutsViewListener.onGutsClose(entry1)
+ verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1)
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isTrue()
+ notifGutsViewListener.onGutsClose(entry2)
+ verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry2)
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+ assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java
index 7e771cecaf66..a3569e4060d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java
@@ -31,7 +31,6 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -72,7 +71,6 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase {
@Mock private HeadsUpViewBinder mHeadsUpViewBinder;
@Mock private NotificationInterruptStateProvider mNotificationInterruptStateProvider;
@Mock private NotificationRemoteInputManager mRemoteInputManager;
- @Mock private RemoteInputController mRemoteInputController;
@Mock private NotifLifetimeExtender.OnEndLifetimeExtensionCallback mEndLifetimeExtension;
@Mock private NodeController mHeaderController;
@@ -81,7 +79,6 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
mCoordinator = new HeadsUpCoordinator(
mHeadsUpManager,
@@ -215,7 +212,7 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase {
// WHEN mEntry is removed from the notification collection
mCollectionListener.onEntryRemoved(mEntry, /* cancellation reason */ 0);
- when(mRemoteInputController.isSpinning(any())).thenReturn(false);
+ when(mRemoteInputManager.isSpinning(any())).thenReturn(false);
// THEN heads up manager should remove the entry
verify(mHeadsUpManager).removeNotification(mEntry.getKey(), false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index 1031d6befc36..8f241a37c5ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -21,17 +21,22 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICAT
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.Notification;
+import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
+import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -39,6 +44,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
+import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import org.junit.Before;
import org.junit.Test;
@@ -46,8 +52,11 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.Arrays;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class RankingCoordinatorTest extends SysuiTestCase {
@@ -56,7 +65,8 @@ public class RankingCoordinatorTest extends SysuiTestCase {
@Mock private HighPriorityProvider mHighPriorityProvider;
@Mock private NotifPipeline mNotifPipeline;
@Mock private NodeController mAlertingHeaderController;
- @Mock private NodeController mSilentHeaderController;
+ @Mock private NodeController mSilentNodeController;
+ @Mock private SectionHeaderController mSilentHeaderController;
@Captor private ArgumentCaptor<NotifFilter> mNotifFilterCaptor;
@@ -72,7 +82,7 @@ public class RankingCoordinatorTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
RankingCoordinator rankingCoordinator = new RankingCoordinator(
mStatusBarStateController, mHighPriorityProvider, mAlertingHeaderController,
- mSilentHeaderController);
+ mSilentHeaderController, mSilentNodeController);
mEntry = new NotificationEntryBuilder().build();
rankingCoordinator.attach(mNotifPipeline);
@@ -85,6 +95,28 @@ public class RankingCoordinatorTest extends SysuiTestCase {
}
@Test
+ public void testSilentHeaderClearableChildrenUpdate() {
+ StatusBarNotification sbn = Mockito.mock(StatusBarNotification.class);
+ Mockito.doReturn("key").when(sbn).getKey();
+ Mockito.doReturn(Mockito.mock(Notification.class)).when(sbn).getNotification();
+ NotificationEntry entry = new NotificationEntryBuilder().setSbn(sbn).build();
+ ListEntry listEntry = new ListEntry("key", 0L) {
+ @Nullable
+ @Override
+ public NotificationEntry getRepresentativeEntry() {
+ return entry;
+ }
+ };
+ Mockito.doReturn(true).when(sbn).isClearable();
+ mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry));
+ verify(mSilentHeaderController).setClearSectionEnabled(eq(true));
+
+ Mockito.doReturn(false).when(sbn).isClearable();
+ mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry));
+ verify(mSilentHeaderController).setClearSectionEnabled(eq(false));
+ }
+
+ @Test
public void testUnfilteredState() {
// GIVEN no suppressed visual effects + app not suspended
mEntry.setRanking(getRankingForUnfilteredNotif().build());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
new file mode 100644
index 000000000000..0ce6ada51f23
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.systemui.statusbar.notification.collection.coordinator
+
+import android.os.Handler
+import android.service.notification.StatusBarNotification
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.NotificationRemoteInputManager.RemoteInputListener
+import com.android.systemui.statusbar.RemoteInputNotificationRebuilder
+import com.android.systemui.statusbar.SmartReplyController
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class RemoteInputCoordinatorTest : SysuiTestCase() {
+ private lateinit var coordinator: RemoteInputCoordinator
+ private lateinit var listener: RemoteInputListener
+ private lateinit var collectionListener: NotifCollectionListener
+
+ private lateinit var entry1: NotificationEntry
+ private lateinit var entry2: NotificationEntry
+
+ @Mock private lateinit var lifetimeExtensionCallback: OnEndLifetimeExtensionCallback
+ @Mock private lateinit var rebuilder: RemoteInputNotificationRebuilder
+ @Mock private lateinit var remoteInputManager: NotificationRemoteInputManager
+ @Mock private lateinit var mainHandler: Handler
+ @Mock private lateinit var smartReplyController: SmartReplyController
+ @Mock private lateinit var pipeline: NotifPipeline
+ @Mock private lateinit var notifUpdater: InternalNotifUpdater
+ @Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var sbn: StatusBarNotification
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+ coordinator = RemoteInputCoordinator(
+ dumpManager,
+ rebuilder,
+ remoteInputManager,
+ mainHandler,
+ smartReplyController
+ )
+ `when`(pipeline.addNotificationLifetimeExtender(any())).thenAnswer {
+ (it.arguments[0] as NotifLifetimeExtender).setCallback(lifetimeExtensionCallback)
+ }
+ `when`(pipeline.getInternalNotifUpdater(any())).thenReturn(notifUpdater)
+ coordinator.attach(pipeline)
+ listener = withArgCaptor {
+ verify(remoteInputManager).setRemoteInputListener(capture())
+ }
+ collectionListener = withArgCaptor {
+ verify(pipeline).addCollectionListener(capture())
+ }
+ entry1 = NotificationEntryBuilder().setId(1).build()
+ entry2 = NotificationEntryBuilder().setId(2).build()
+ `when`(rebuilder.rebuildForCanceledSmartReplies(any())).thenReturn(sbn)
+ `when`(rebuilder.rebuildForRemoteInputReply(any())).thenReturn(sbn)
+ `when`(rebuilder.rebuildForSendingSmartReply(any(), any())).thenReturn(sbn)
+ }
+
+ val remoteInputActiveExtender get() = coordinator.mRemoteInputActiveExtender
+ val remoteInputHistoryExtender get() = coordinator.mRemoteInputHistoryExtender
+ val smartReplyHistoryExtender get() = coordinator.mSmartReplyHistoryExtender
+
+ @Test
+ fun testRemoteInputActive() {
+ `when`(remoteInputManager.isRemoteInputActive(entry1)).thenReturn(true)
+ assertThat(remoteInputActiveExtender.shouldExtendLifetime(entry1, 0)).isTrue()
+ assertThat(remoteInputHistoryExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+ assertThat(smartReplyHistoryExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+ assertThat(listener.isNotificationKeptForRemoteInputHistory(entry1.key)).isFalse()
+ }
+
+ @Test
+ fun testRemoteInputHistory() {
+ `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry1)).thenReturn(true)
+ assertThat(remoteInputActiveExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+ assertThat(remoteInputHistoryExtender.shouldExtendLifetime(entry1, 0)).isTrue()
+ assertThat(smartReplyHistoryExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+ assertThat(listener.isNotificationKeptForRemoteInputHistory(entry1.key)).isTrue()
+ }
+
+ @Test
+ fun testSmartReplyHistory() {
+ `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry1)).thenReturn(true)
+ assertThat(remoteInputActiveExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+ assertThat(remoteInputHistoryExtender.shouldExtendLifetime(entry1, 0)).isFalse()
+ assertThat(smartReplyHistoryExtender.shouldExtendLifetime(entry1, 0)).isTrue()
+ assertThat(listener.isNotificationKeptForRemoteInputHistory(entry1.key)).isTrue()
+ }
+
+ @Test
+ fun testNotificationWithRemoteInputActiveIsRemovedOnCollapse() {
+ `when`(remoteInputManager.isRemoteInputActive(entry1)).thenReturn(true)
+ assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isFalse()
+
+ // Nothing should happen on panel collapse before we start extending the lifetime
+ listener.onPanelCollapsed()
+ assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isFalse()
+ verify(lifetimeExtensionCallback, never()).onEndLifetimeExtension(any(), any())
+
+ // Start extending lifetime & validate that the extension is ended
+ assertThat(remoteInputActiveExtender.shouldExtendLifetime(entry1, 0)).isTrue()
+ assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isTrue()
+ listener.onPanelCollapsed()
+ verify(lifetimeExtensionCallback).onEndLifetimeExtension(remoteInputActiveExtender, entry1)
+ assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
new file mode 100644
index 000000000000..5fd4174af164
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -0,0 +1,209 @@
+/*
+ * 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.systemui.statusbar.notification.collection.coordinator
+
+import android.os.UserHandle
+import android.service.notification.StatusBarNotification
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.notification.DynamicPrivacyController
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.mockito.mock
+import org.junit.Test
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+class SensitiveContentCoordinatorTest : SysuiTestCase() {
+
+ val dynamicPrivacyController: DynamicPrivacyController = mock()
+ val lockscreenUserManager: NotificationLockscreenUserManager = mock()
+ val pipeline: NotifPipeline = mock()
+
+ val coordinator: SensitiveContentCoordinator = SensitiveContentCoordinatorModule
+ .provideCoordinator(dynamicPrivacyController, lockscreenUserManager)
+
+ @Test
+ fun onDynamicPrivacyChanged_invokeInvalidationListener() {
+ coordinator.attach(pipeline)
+ val invalidator = withArgCaptor<Invalidator> {
+ verify(pipeline).addPreRenderInvalidator(capture())
+ }
+ val dynamicPrivacyListener = withArgCaptor<DynamicPrivacyController.Listener> {
+ verify(dynamicPrivacyController).addListener(capture())
+ }
+
+ val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>()
+ invalidator.setInvalidationListener(invalidationListener)
+
+ dynamicPrivacyListener.onDynamicPrivacyChanged()
+
+ verify(invalidationListener).onPluggableInvalidated(invalidator)
+ }
+
+ @Test
+ fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, false)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, false)
+ }
+
+ @Test
+ fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, false)
+ }
+
+ @Test
+ fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, false)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, false)
+ }
+
+ @Test
+ fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, false)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, true)
+ }
+
+ @Test
+ fun onBeforeRenderList_deviceLocked_notifNeedsRedaction() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+
+ @Test
+ fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+ val entry = fakeNotification(1, true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, true)
+ }
+
+ @Test
+ fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+ whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true)
+
+ val entry = fakeNotification(2, true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+
+ private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry {
+ val mockUserHandle = mock<UserHandle>().apply {
+ whenever(identifier).thenReturn(notifUserId)
+ }
+ val mockSbn: StatusBarNotification = mock<StatusBarNotification>().apply {
+ whenever(user).thenReturn(mockUserHandle)
+ }
+ val mockEntry = mock<NotificationEntry>().apply {
+ whenever(sbn).thenReturn(mockSbn)
+ }
+ whenever(lockscreenUserManager.needsRedaction(mockEntry)).thenReturn(needsRedaction)
+ whenever(mockEntry.rowExists()).thenReturn(true)
+ return object : ListEntry("key", 0) {
+ override fun getRepresentativeEntry(): NotificationEntry = mockEntry
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
new file mode 100644
index 000000000000..5915cd7823f0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.systemui.statusbar.notification.collection.coordinator
+
+import android.service.notification.NotificationListenerService.REASON_APP_CANCEL
+import android.service.notification.NotificationListenerService.REASON_CANCEL
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.util.mockito.argumentCaptor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class ShadeEventCoordinatorTest : SysuiTestCase() {
+ private lateinit var coordinator: ShadeEventCoordinator
+ private lateinit var notifCollectionListener: NotifCollectionListener
+ private lateinit var onBeforeRenderListListener: OnBeforeRenderListListener
+
+ private lateinit var entry1: NotificationEntry
+ private lateinit var entry2: NotificationEntry
+
+ @Mock private lateinit var pipeline: NotifPipeline
+ @Mock private lateinit var logger: ShadeEventCoordinatorLogger
+ @Mock private lateinit var notifRemovedByUserCallback: Runnable
+ @Mock private lateinit var shadeEmptiedCallback: Runnable
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+ coordinator = ShadeEventCoordinator(logger)
+ coordinator.attach(pipeline)
+ notifCollectionListener = argumentCaptor<NotifCollectionListener>().let {
+ verify(pipeline).addCollectionListener(it.capture())
+ it.value!!
+ }
+ onBeforeRenderListListener = argumentCaptor<OnBeforeRenderListListener>().let {
+ verify(pipeline).addOnBeforeRenderListListener(it.capture())
+ it.value!!
+ }
+ coordinator.setNotifRemovedByUserCallback(notifRemovedByUserCallback)
+ coordinator.setShadeEmptiedCallback(shadeEmptiedCallback)
+ entry1 = NotificationEntryBuilder().setId(1).build()
+ entry2 = NotificationEntryBuilder().setId(2).build()
+ }
+
+ @Test
+ fun testUserCancelLastNotification() {
+ notifCollectionListener.onEntryRemoved(entry1, REASON_CANCEL)
+ verify(shadeEmptiedCallback, never()).run()
+ verify(notifRemovedByUserCallback, never()).run()
+ onBeforeRenderListListener.onBeforeRenderList(listOf())
+ verify(shadeEmptiedCallback).run()
+ verify(notifRemovedByUserCallback).run()
+ }
+
+ @Test
+ fun testAppCancelLastNotification() {
+ notifCollectionListener.onEntryRemoved(entry1, REASON_APP_CANCEL)
+ onBeforeRenderListListener.onBeforeRenderList(listOf())
+ verify(shadeEmptiedCallback).run()
+ verify(notifRemovedByUserCallback, never()).run()
+ }
+
+ @Test
+ fun testUserCancelOneOfTwoNotifications() {
+ notifCollectionListener.onEntryRemoved(entry1, REASON_CANCEL)
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry2))
+ verify(shadeEmptiedCallback, never()).run()
+ verify(notifRemovedByUserCallback).run()
+ }
+
+ @Test
+ fun testAppCancelOneOfTwoNotifications() {
+ notifCollectionListener.onEntryRemoved(entry1, REASON_APP_CANCEL)
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry2))
+ verify(shadeEmptiedCallback, never()).run()
+ verify(notifRemovedByUserCallback, never()).run()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt
new file mode 100644
index 000000000000..37ad8357aa95
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt
@@ -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.systemui.statusbar.notification.collection.notifcollection
+
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+import java.util.function.Consumer
+import java.util.function.Predicate
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class SelfTrackingLifetimeExtenderTest : SysuiTestCase() {
+ private lateinit var extender: TestableSelfTrackingLifetimeExtender
+
+ private lateinit var entry1: NotificationEntry
+ private lateinit var entry2: NotificationEntry
+
+ @Mock
+ private lateinit var callback: OnEndLifetimeExtensionCallback
+ @Mock
+ private lateinit var mainHandler: Handler
+ @Mock
+ private lateinit var shouldExtend: Predicate<NotificationEntry>
+ @Mock
+ private lateinit var onStarted: Consumer<NotificationEntry>
+ @Mock
+ private lateinit var onCanceled: Consumer<NotificationEntry>
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+ extender = TestableSelfTrackingLifetimeExtender()
+ extender.setCallback(callback)
+ entry1 = NotificationEntryBuilder().setId(1).build()
+ entry2 = NotificationEntryBuilder().setId(2).build()
+ }
+
+ @Test
+ fun testName() {
+ assertThat(extender.name).isEqualTo("Testable")
+ }
+
+ @Test
+ fun testNoExtend() {
+ `when`(shouldExtend.test(entry1)).thenReturn(false)
+ assertThat(extender.shouldExtendLifetime(entry1, 0)).isFalse()
+ assertThat(extender.isExtending(entry1.key)).isFalse()
+ verify(onStarted, never()).accept(entry1)
+ verify(onCanceled, never()).accept(entry1)
+ }
+
+ @Test
+ fun testExtendThenCancelForRepost() {
+ `when`(shouldExtend.test(entry1)).thenReturn(true)
+ assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue()
+ verify(onStarted).accept(entry1)
+ verify(onCanceled, never()).accept(entry1)
+ assertThat(extender.isExtending(entry1.key)).isTrue()
+ extender.cancelLifetimeExtension(entry1)
+ verify(onCanceled).accept(entry1)
+ }
+
+ @Test
+ fun testExtendThenCancel_thenEndDoesNothing() {
+ testExtendThenCancelForRepost()
+ assertThat(extender.isExtending(entry1.key)).isFalse()
+
+ extender.endLifetimeExtension(entry1.key)
+ extender.endLifetimeExtensionAfterDelay(entry1.key, 1000)
+ verify(callback, never()).onEndLifetimeExtension(any(), any())
+ verify(mainHandler, never()).postDelayed(any(), anyLong())
+ }
+
+ @Test
+ fun testExtendThenEnd() {
+ `when`(shouldExtend.test(entry1)).thenReturn(true)
+ assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue()
+ verify(onStarted).accept(entry1)
+ assertThat(extender.isExtending(entry1.key)).isTrue()
+ extender.endLifetimeExtension(entry1.key)
+ verify(callback).onEndLifetimeExtension(extender, entry1)
+ verify(onCanceled, never()).accept(entry1)
+ }
+
+ @Test
+ fun testExtendThenEndAfterDelay() {
+ `when`(shouldExtend.test(entry1)).thenReturn(true)
+ assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue()
+ verify(onStarted).accept(entry1)
+ assertThat(extender.isExtending(entry1.key)).isTrue()
+
+ // Call the method and capture the posted runnable
+ extender.endLifetimeExtensionAfterDelay(entry1.key, 1234)
+ val runnable = withArgCaptor<Runnable> {
+ verify(mainHandler).postDelayed(capture(), eq(1234.toLong()))
+ }
+ assertThat(extender.isExtending(entry1.key)).isTrue()
+ verify(callback, never()).onEndLifetimeExtension(any(), any())
+
+ // now run the posted runnable and ensure it works as expected
+ runnable.run()
+ verify(callback).onEndLifetimeExtension(extender, entry1)
+ assertThat(extender.isExtending(entry1.key)).isFalse()
+ verify(onCanceled, never()).accept(entry1)
+ }
+
+ @Test
+ fun testExtendThenEndAll() {
+ `when`(shouldExtend.test(entry1)).thenReturn(true)
+ `when`(shouldExtend.test(entry2)).thenReturn(true)
+ assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue()
+ verify(onStarted).accept(entry1)
+ assertThat(extender.isExtending(entry1.key)).isTrue()
+ assertThat(extender.isExtending(entry2.key)).isFalse()
+ assertThat(extender.shouldExtendLifetime(entry2, 0)).isTrue()
+ verify(onStarted).accept(entry2)
+ assertThat(extender.isExtending(entry1.key)).isTrue()
+ assertThat(extender.isExtending(entry2.key)).isTrue()
+ extender.endAllLifetimeExtensions()
+ verify(callback).onEndLifetimeExtension(extender, entry1)
+ verify(callback).onEndLifetimeExtension(extender, entry2)
+ verify(onCanceled, never()).accept(entry1)
+ verify(onCanceled, never()).accept(entry2)
+ }
+
+ @Test
+ fun testExtendWithinEndCanReExtend() {
+ `when`(shouldExtend.test(entry1)).thenReturn(true)
+ assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue()
+ verify(onStarted, times(1)).accept(entry1)
+
+ `when`(callback.onEndLifetimeExtension(extender, entry1)).thenAnswer {
+ assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue()
+ }
+ extender.endLifetimeExtension(entry1.key)
+ verify(onStarted, times(2)).accept(entry1)
+ assertThat(extender.isExtending(entry1.key)).isTrue()
+ }
+
+ @Test
+ fun testExtendWithinEndCanNotReExtend() {
+ `when`(shouldExtend.test(entry1)).thenReturn(true, false)
+ assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue()
+ verify(onStarted, times(1)).accept(entry1)
+
+ `when`(callback.onEndLifetimeExtension(extender, entry1)).thenAnswer {
+ assertThat(extender.shouldExtendLifetime(entry1, 0)).isFalse()
+ }
+ extender.endLifetimeExtension(entry1.key)
+ verify(onStarted, times(1)).accept(entry1)
+ assertThat(extender.isExtending(entry1.key)).isFalse()
+ }
+
+ @Test
+ fun testExtendWithinEndAllCanReExtend() {
+ `when`(shouldExtend.test(entry1)).thenReturn(true)
+ assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue()
+ verify(onStarted, times(1)).accept(entry1)
+
+ `when`(callback.onEndLifetimeExtension(extender, entry1)).thenAnswer {
+ assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue()
+ }
+ extender.endAllLifetimeExtensions()
+ verify(onStarted, times(2)).accept(entry1)
+ assertThat(extender.isExtending(entry1.key)).isTrue()
+ }
+
+ @Test
+ fun testExtendWithinEndAllCanNotReExtend() {
+ `when`(shouldExtend.test(entry1)).thenReturn(true, false)
+ assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue()
+ verify(onStarted, times(1)).accept(entry1)
+
+ `when`(callback.onEndLifetimeExtension(extender, entry1)).thenAnswer {
+ assertThat(extender.shouldExtendLifetime(entry1, 0)).isFalse()
+ }
+ extender.endAllLifetimeExtensions()
+ verify(onStarted, times(1)).accept(entry1)
+ assertThat(extender.isExtending(entry1.key)).isFalse()
+ }
+
+ inner class TestableSelfTrackingLifetimeExtender(debug: Boolean = false) :
+ SelfTrackingLifetimeExtender("Test", "Testable", debug, mainHandler) {
+
+ override fun queryShouldExtendLifetime(entry: NotificationEntry) =
+ shouldExtend.test(entry)
+
+ override fun onStartedLifetimeExtension(entry: NotificationEntry) {
+ onStarted.accept(entry)
+ }
+
+ override fun onCanceledLifetimeExtension(entry: NotificationEntry) {
+ onCanceled.accept(entry)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
new file mode 100644
index 000000000000..ed48452eccc7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
@@ -0,0 +1,323 @@
+/*
+ * 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.systemui.statusbar.notification.collection.render
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.getAttachState
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
+import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
+import com.android.systemui.statusbar.notification.stack.PriorityBucket
+import com.android.systemui.util.mockito.any
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class NodeSpecBuilderTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var viewBarn: NotifViewBarn
+
+ private var rootController: NodeController = buildFakeController("rootController")
+ private var headerController0: NodeController = buildFakeController("header0")
+ private var headerController1: NodeController = buildFakeController("header1")
+ private var headerController2: NodeController = buildFakeController("header2")
+
+ private val section0Bucket = BUCKET_PEOPLE
+ private val section1Bucket = BUCKET_ALERTING
+ private val section2Bucket = BUCKET_SILENT
+
+ private val section0 = buildSection(0, section0Bucket, headerController0)
+ private val section0NoHeader = buildSection(0, section0Bucket, null)
+ private val section1 = buildSection(1, section1Bucket, headerController1)
+ private val section1NoHeader = buildSection(1, section1Bucket, null)
+ private val section2 = buildSection(2, section2Bucket, headerController2)
+
+ private val fakeViewBarn = FakeViewBarn()
+
+ private lateinit var specBuilder: NodeSpecBuilder
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(viewBarn.requireView(any())).thenAnswer {
+ fakeViewBarn.getViewByEntry(it.getArgument(0))
+ }
+
+ specBuilder = NodeSpecBuilder(viewBarn)
+ }
+
+ @Test
+ fun testSimpleMapping() {
+ checkOutput(
+ // GIVEN a simple flat list of notifications all in the same headerless section
+ listOf(
+ notif(0, section0NoHeader),
+ notif(1, section0NoHeader),
+ notif(2, section0NoHeader),
+ notif(3, section0NoHeader)
+ ),
+
+ // THEN we output a similarly simple flag list of nodes
+ tree(
+ notifNode(0),
+ notifNode(1),
+ notifNode(2),
+ notifNode(3)
+ )
+ )
+ }
+
+ @Test
+ fun testHeaderInjection() {
+ checkOutput(
+ // GIVEN a flat list of notifications, spread across three sections
+ listOf(
+ notif(0, section0),
+ notif(1, section0),
+ notif(2, section1),
+ notif(3, section2)
+ ),
+
+ // THEN each section has its header injected
+ tree(
+ node(headerController0),
+ notifNode(0),
+ notifNode(1),
+ node(headerController1),
+ notifNode(2),
+ node(headerController2),
+ notifNode(3)
+ )
+ )
+ }
+
+ @Test
+ fun testGroups() {
+ checkOutput(
+ // GIVEN a mixed list of top-level notifications and groups
+ listOf(
+ notif(0, section0),
+ group(1, section1,
+ notif(2),
+ notif(3),
+ notif(4)
+ ),
+ notif(5, section2),
+ group(6, section2,
+ notif(7),
+ notif(8),
+ notif(9)
+ )
+ ),
+
+ // THEN we properly construct all the nodes
+ tree(
+ node(headerController0),
+ notifNode(0),
+ node(headerController1),
+ notifNode(1,
+ notifNode(2),
+ notifNode(3),
+ notifNode(4)
+ ),
+ node(headerController2),
+ notifNode(5),
+ notifNode(6,
+ notifNode(7),
+ notifNode(8),
+ notifNode(9)
+ )
+ )
+ )
+ }
+
+ @Test
+ fun testSecondSectionWithNoHeader() {
+ checkOutput(
+ // GIVEN a middle section with no associated header view
+ listOf(
+ notif(0, section0),
+ notif(1, section1NoHeader),
+ group(2, section1NoHeader,
+ notif(3),
+ notif(4)
+ ),
+ notif(5, section2)
+ ),
+
+ // THEN the header view is left out of the tree (but the notifs are still present)
+ tree(
+ node(headerController0),
+ notifNode(0),
+ notifNode(1),
+ notifNode(2,
+ notifNode(3),
+ notifNode(4)
+ ),
+ node(headerController2),
+ notifNode(5)
+ )
+ )
+ }
+
+ @Test(expected = RuntimeException::class)
+ fun testRepeatedSectionsThrow() {
+ checkOutput(
+ // GIVEN a malformed list where sections are not contiguous
+ listOf(
+ notif(0, section0),
+ notif(1, section1),
+ notif(2, section0)
+ ),
+
+ // THEN an exception is thrown
+ tree()
+ )
+ }
+
+ private fun checkOutput(list: List<ListEntry>, desiredTree: NodeSpecImpl) {
+ checkTree(desiredTree, specBuilder.buildNodeSpec(rootController, list))
+ }
+
+ private fun checkTree(desiredTree: NodeSpec, actualTree: NodeSpec) {
+ try {
+ checkNode(desiredTree, actualTree)
+ } catch (e: AssertionError) {
+ throw AssertionError("Trees don't match: ${e.message}\nActual tree:\n" +
+ treeSpecToStr(actualTree))
+ }
+ }
+
+ private fun checkNode(desiredTree: NodeSpec, actualTree: NodeSpec) {
+ if (actualTree.controller != desiredTree.controller) {
+ throw AssertionError("Node {${actualTree.controller.nodeLabel}} should " +
+ "be ${desiredTree.controller.nodeLabel}")
+ }
+ for (i in 0 until desiredTree.children.size) {
+ if (i >= actualTree.children.size) {
+ throw AssertionError("Node {${actualTree.controller.nodeLabel}}" +
+ " is missing child ${desiredTree.children[i].controller.nodeLabel}")
+ }
+ checkNode(desiredTree.children[i], actualTree.children[i])
+ }
+ }
+
+ private fun notif(id: Int, section: NotifSection? = null): NotificationEntry {
+ val entry = NotificationEntryBuilder()
+ .setId(id)
+ .build()
+ if (section != null) {
+ getAttachState(entry).section = section
+ }
+ fakeViewBarn.buildNotifView(id, entry)
+ return entry
+ }
+
+ private fun group(
+ id: Int,
+ section: NotifSection,
+ vararg children: NotificationEntry
+ ): GroupEntry {
+ val group = GroupEntryBuilder()
+ .setKey("group_$id")
+ .setSummary(
+ NotificationEntryBuilder()
+ .setId(id)
+ .build())
+ .setChildren(children.asList())
+ .build()
+ getAttachState(group).section = section
+ fakeViewBarn.buildNotifView(id, group.summary!!)
+
+ for (child in children) {
+ getAttachState(child).section = section
+ }
+ return group
+ }
+
+ private fun tree(vararg children: NodeSpecImpl): NodeSpecImpl {
+ return node(rootController, *children)
+ }
+
+ private fun node(view: NodeController, vararg children: NodeSpecImpl): NodeSpecImpl {
+ val node = NodeSpecImpl(null, view)
+ node.children.addAll(children)
+ return node
+ }
+
+ private fun notifNode(id: Int, vararg children: NodeSpecImpl): NodeSpecImpl {
+ return node(fakeViewBarn.getViewById(id), *children)
+ }
+}
+
+private class FakeViewBarn {
+ private val entries = mutableMapOf<Int, NotificationEntry>()
+ private val views = mutableMapOf<NotificationEntry, NodeController>()
+
+ fun buildNotifView(id: Int, entry: NotificationEntry) {
+ if (entries.contains(id)) {
+ throw RuntimeException("ID $id is already in use")
+ }
+ entries[id] = entry
+ views[entry] = buildFakeController("Entry $id")
+ }
+
+ fun getViewById(id: Int): NodeController {
+ return views[entries[id] ?: throw RuntimeException("No view with ID $id")]!!
+ }
+
+ fun getViewByEntry(entry: NotificationEntry): NodeController {
+ return views[entry] ?: throw RuntimeException("No view defined for key ${entry.key}")
+ }
+}
+
+private fun buildFakeController(name: String): NodeController {
+ val controller = Mockito.mock(NodeController::class.java)
+ `when`(controller.nodeLabel).thenReturn(name)
+ return controller
+}
+
+private fun buildSection(
+ index: Int,
+ @PriorityBucket bucket: Int,
+ nodeController: NodeController?
+): NotifSection {
+ return NotifSection(object : NotifSectioner("Section $index (bucket=$bucket)", bucket) {
+
+ override fun isInSection(entry: ListEntry?): Boolean {
+ throw NotImplementedError("This should never be called")
+ }
+
+ override fun getHeaderNodeController(): NodeController? {
+ return nodeController
+ }
+ }, index)
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
new file mode 100644
index 000000000000..24a0ad3de196
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.systemui.statusbar.notification.row;
+
+import static android.view.DragEvent.ACTION_DRAG_STARTED;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.DragEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.mockito.ArgumentMatchers.any;
+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 static org.mockito.Mockito.when;
+
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase {
+
+ private ExpandableNotificationRow mRow;
+ private ExpandableNotificationRow mGroupRow;
+ private ExpandableNotificationRowDragController mController;
+ private NotificationTestHelper mNotificationTestHelper;
+
+ private NotificationGutsManager mGutsManager = mock(NotificationGutsManager.class);
+ private HeadsUpManager mHeadsUpManager = mock(HeadsUpManager.class);
+ private NotificationMenuRow mMenuRow = mock(NotificationMenuRow.class);
+ private NotificationMenuRowPlugin.MenuItem mMenuItem =
+ mock(NotificationMenuRowPlugin.MenuItem.class);
+
+ @Before
+ public void setUp() throws Exception {
+ allowTestableLooperAsMainThread();
+
+ mDependency.injectMockDependency(ShadeController.class);
+
+ mNotificationTestHelper = new NotificationTestHelper(
+ mContext,
+ mDependency,
+ TestableLooper.get(this));
+ mRow = mNotificationTestHelper.createRow();
+ mGroupRow = mNotificationTestHelper.createGroup(4);
+ when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem);
+
+ mController = new ExpandableNotificationRowDragController(mContext, mHeadsUpManager);
+ }
+
+ @Test
+ public void testDoStartDragHeadsUpNotif_startDragAndDrop() throws Exception {
+ ExpandableNotificationRowDragController controller = createSpyController();
+ mRow.setDragController(controller);
+ mRow.setHeadsUp(true);
+ mRow.setPinned(true);
+
+ mRow.doLongClickCallback(0, 0);
+ mRow.doDragCallback(0, 0);
+ verify(controller).startDragAndDrop(mRow);
+
+ // Simulate the drag start
+ mRow.dispatchDragEvent(DragEvent.obtain(ACTION_DRAG_STARTED, 0, 0, 0, 0, null, null, null,
+ null, null, false));
+ verify(mHeadsUpManager, times(1)).releaseAllImmediately();
+ }
+
+ @Test
+ public void testDoStartDragNotif() throws Exception {
+ ExpandableNotificationRowDragController controller = createSpyController();
+ mRow.setDragController(controller);
+
+ mDependency.get(ShadeController.class).instantExpandNotificationsPanel();
+ mRow.doDragCallback(0, 0);
+ verify(controller).startDragAndDrop(mRow);
+
+ // Simulate the drag start
+ mRow.dispatchDragEvent(DragEvent.obtain(ACTION_DRAG_STARTED, 0, 0, 0, 0, null, null, null,
+ null, null, false));
+ verify(mDependency.get(ShadeController.class)).animateCollapsePanels(0, true);
+ }
+
+ private ExpandableNotificationRowDragController createSpyController() {
+ return spy(mController);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index cea49b71f009..d3738f42e020 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -47,11 +47,13 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -119,6 +121,7 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase {
throw new RuntimeException("Timed out waiting to inflate");
};
+ @Mock private NotificationListener mNotificationListener;
@Mock private NotificationPresenter mPresenter;
@Mock private NotificationEntryManager.KeyguardEnvironment mEnvironment;
@Mock private NotificationListContainer mListContainer;
@@ -186,9 +189,11 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase {
() -> mRemoteInputManager,
mLeakDetector,
mock(ForegroundServiceDismissalFeatureController.class),
- mock(IStatusBarService.class)
+ mock(IStatusBarService.class),
+ mock(DumpManager.class)
);
- mEntryManager.setRanker(
+ mEntryManager.initialize(
+ mNotificationListener,
new NotificationRankingManager(
() -> mock(NotificationMediaManager.class),
mGroupMembershipManager,
@@ -265,7 +270,8 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase {
new FalsingManagerFake(),
new FalsingCollectorFake(),
mPeopleNotificationIdentifier,
- Optional.of(mock(BubblesManager.class))
+ Optional.of(mock(BubblesManager.class)),
+ mock(ExpandableNotificationRowDragController.class)
));
when(mNotificationRowComponentBuilder.activatableNotificationView(any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 9f537f5b6afc..7d8e0d26464c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -68,6 +68,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.settings.UserContextProvider;
@@ -91,7 +92,6 @@ import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
@@ -99,8 +99,6 @@ import org.mockito.junit.MockitoRule;
import java.util.Optional;
-import javax.inject.Provider;
-
/**
* Tests for {@link NotificationGutsManager}.
*/
@@ -157,11 +155,12 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
mGutsManager = new NotificationGutsManager(mContext,
- () -> mStatusBar, mHandler, mHandler, mAccessibilityManager, mHighPriorityProvider,
- mINotificationManager, mNotificationEntryManager, mPeopleSpaceWidgetManager,
- mLauncherApps, mShortcutManager, mChannelEditorDialogController, mContextTracker,
- mAssistantFeedbackController, Optional.of(mBubblesManager),
- new UiEventLoggerFake(), mOnUserInteractionCallback, mShadeController);
+ () -> Optional.of(mStatusBar), mHandler, mHandler, mAccessibilityManager,
+ mHighPriorityProvider, mINotificationManager, mNotificationEntryManager,
+ mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
+ mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
+ Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback,
+ mShadeController, mock(DumpManager.class));
mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
mCheckSaveListener, mOnSettingsClickListener);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 0bb66fc14553..42aecfdc11bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -46,6 +46,7 @@ import android.widget.RemoteViews;
import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -125,7 +126,8 @@ public class NotificationTestHelper {
mGroupMembershipManager = new NotificationGroupManagerLegacy(
mStatusBarStateController,
() -> mock(PeopleNotificationIdentifier.class),
- Optional.of((mock(Bubbles.class))));
+ Optional.of((mock(Bubbles.class))),
+ mock(DumpManager.class));
mGroupExpansionManager = mGroupMembershipManager;
mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarStateController,
mock(KeyguardBypassController.class), mock(NotificationGroupManagerLegacy.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 0772c03d10d0..73560be1d265 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -61,6 +62,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
private ExpandableNotificationRow mSecond;
@Mock
private KeyguardBypassController mBypassController;
+ @Mock
+ private FeatureFlags mFeatureFlags;
private float mSmallRadiusRatio;
@Before
@@ -71,7 +74,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase {
/ resources.getDimension(R.dimen.notification_corner_radius);
mRoundnessManager = new NotificationRoundnessManager(
mBypassController,
- new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext));
+ new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext),
+ mFeatureFlags);
allowTestableLooperAsMainThread();
NotificationTestHelper testHelper = new NotificationTestHelper(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index c1d2ea88a1b1..f11f8c476433 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -18,11 +18,11 @@ package com.android.systemui.statusbar.notification.stack;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_FOREGROUND_SERVICE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_HEADS_UP;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
import static com.google.common.truth.Truth.assertThat;
@@ -64,7 +64,6 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -608,7 +607,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
}
}
- private View mockNotification(int bucket, boolean isGone) {
+ private View mockNotification(@PriorityBucket int bucket, boolean isGone) {
ExpandableNotificationRow notifRow =
mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 42f38891b1bb..baed694e6fa0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -45,11 +46,11 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -93,7 +94,7 @@ import org.mockito.MockitoAnnotations;
*/
@SmallTest
@RunWith(AndroidTestingRunner.class)
-public class NotificationStackScrollerControllerTest extends SysuiTestCase {
+public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private NotificationGutsManager mNotificationGutsManager;
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@@ -130,7 +131,6 @@ public class NotificationStackScrollerControllerTest extends SysuiTestCase {
@Mock private ForegroundServiceDungeonView mForegroundServiceDungeonView;
@Mock private LayoutInflater mLayoutInflater;
@Mock private NotificationRemoteInputManager mRemoteInputManager;
- @Mock private RemoteInputController mRemoteInputController;
@Mock private VisualStabilityManager mVisualStabilityManager;
@Mock private ShadeController mShadeController;
@@ -147,7 +147,6 @@ public class NotificationStackScrollerControllerTest extends SysuiTestCase {
when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
when(mFgServicesSectionController.createView(mLayoutInflater))
.thenReturn(mForegroundServiceDungeonView);
- when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
mController = new NotificationStackScrollLayoutController(
true,
@@ -235,16 +234,15 @@ public class NotificationStackScrollerControllerTest extends SysuiTestCase {
reset(mNotificationStackScrollLayout);
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- true /* visible */,
-
- true /* notifVisibleInShade */);
+ /* visible= */ true,
+ /* notifVisibleInShade= */ true);
setupShowEmptyShadeViewState(stateListener, false);
reset(mNotificationStackScrollLayout);
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- false /* visible */,
- true /* notifVisibleInShade */);
+ /* visible= */ false,
+ /* notifVisibleInShade= */ true);
}
@Test
@@ -260,15 +258,42 @@ public class NotificationStackScrollerControllerTest extends SysuiTestCase {
reset(mNotificationStackScrollLayout);
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- true /* visible */,
- false /* notifVisibleInShade */);
+ /* visible= */ true,
+ /* notifVisibleInShade= */ false);
setupShowEmptyShadeViewState(stateListener, false);
reset(mNotificationStackScrollLayout);
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- false /* visible */,
- false /* notifVisibleInShade */);
+ /* visible= */ false,
+ /* notifVisibleInShade= */ false);
+ }
+
+ @Test
+ public void testUpdateEmptyShadeView_splitShadeMode_alwaysShowEmptyView() {
+ when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
+ mController.attach(mNotificationStackScrollLayout);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+ when(mNotificationStackScrollLayout.isUsingSplitNotificationShade()).thenReturn(true);
+ stateListener.onStateChanged(SHADE);
+ mController.getView().removeAllViews();
+
+ mController.setQsExpanded(false);
+ reset(mNotificationStackScrollLayout);
+ mController.updateShowEmptyShadeView();
+ verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+ /* visible= */ true,
+ /* notifVisibleInShade= */ false);
+
+ mController.setQsExpanded(true);
+ reset(mNotificationStackScrollLayout);
+ mController.updateShowEmptyShadeView();
+ verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+ /* visible= */ true,
+ /* notifVisibleInShade= */ false);
}
@Test
@@ -379,6 +404,19 @@ public class NotificationStackScrollerControllerTest extends SysuiTestCase {
any(ForegroundServiceDungeonView.class));
}
+ @Test
+ public void testUpdateFooter_remoteInput() {
+ ArgumentCaptor<RemoteInputController.Callback> callbackCaptor =
+ ArgumentCaptor.forClass(RemoteInputController.Callback.class);
+ doNothing().when(mRemoteInputManager).addControllerCallback(callbackCaptor.capture());
+ when(mRemoteInputManager.isRemoteInputActive()).thenReturn(false);
+ mController.attach(mNotificationStackScrollLayout);
+ verify(mNotificationStackScrollLayout).setIsRemoteInputActive(false);
+ RemoteInputController.Callback callback = callbackCaptor.getValue();
+ callback.onRemoteInputActive(true);
+ verify(mNotificationStackScrollLayout).setIsRemoteInputActive(true);
+ }
+
private LogMaker logMatcher(int category, int type) {
return argThat(new LogMatcher(category, type));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 4151ab2044f4..185d9cd8733e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -52,18 +52,16 @@ import com.android.systemui.ExpandHelper;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.EmptyShadeView;
-import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationShelfController;
-import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.FooterView;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.KeyguardBypassEnabledProvider;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -98,17 +96,12 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Mock private NotificationGroupManagerLegacy mGroupExpansionManager;
@Mock private ExpandHelper mExpandHelper;
@Mock private EmptyShadeView mEmptyShadeView;
- @Mock private NotificationRemoteInputManager mRemoteInputManager;
- @Mock private RemoteInputController mRemoteInputController;
@Mock private NotificationRoundnessManager mNotificationRoundnessManager;
- @Mock private KeyguardBypassEnabledProvider mKeyguardBypassEnabledProvider;
@Mock private KeyguardBypassController mBypassController;
@Mock private NotificationSectionsManager mNotificationSectionsManager;
@Mock private NotificationSection mNotificationSection;
- @Mock private SysuiStatusBarStateController mStatusBarStateController;
@Mock private NotificationSwipeHelper mNotificationSwipeHelper;
@Mock private NotificationStackScrollLayoutController mStackScrollLayoutController;
- @Mock private FeatureFlags mFeatureFlags;
@Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@Before
@@ -119,9 +112,20 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
Settings.Secure.putIntForUser(mContext.getContentResolver(), NOTIFICATION_HISTORY_ENABLED,
1, UserHandle.USER_CURRENT);
+
+ // Interact with real instance of AmbientState.
+ mAmbientState = new AmbientState(mContext, mNotificationSectionsManager, mBypassController);
+
// Inject dependencies before initializing the layout
mDependency.injectTestDependency(SysuiStatusBarStateController.class, mBarState);
mDependency.injectMockDependency(ShadeController.class);
+ mDependency.injectTestDependency(
+ NotificationSectionsManager.class, mNotificationSectionsManager);
+ mDependency.injectTestDependency(GroupMembershipManager.class, mGroupMembershipManger);
+ mDependency.injectTestDependency(GroupExpansionManager.class, mGroupExpansionManager);
+ mDependency.injectTestDependency(AmbientState.class, mAmbientState);
+ mDependency.injectTestDependency(
+ UnlockedScreenOffAnimationController.class, mUnlockedScreenOffAnimationController);
NotificationShelfController notificationShelfController =
mock(NotificationShelfController.class);
@@ -131,27 +135,14 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
new NotificationSection[]{
mNotificationSection
});
- when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
-
- // Interact with real instance of AmbientState.
- mAmbientState = new AmbientState(mContext, mNotificationSectionsManager, mBypassController);
// The actual class under test. You may need to work with this class directly when
// testing anonymous class members of mStackScroller, like mMenuEventListener,
// which refer to members of NotificationStackScrollLayout. The spy
// holds a copy of the CUT's instances of these KeyguardBypassController, so they still
// refer to the CUT's member variables, not the spy's member variables.
- mStackScrollerInternal = new NotificationStackScrollLayout(
- getContext(),
- null,
- mNotificationSectionsManager,
- mGroupMembershipManger,
- mGroupExpansionManager,
- mAmbientState,
- mFeatureFlags,
- mUnlockedScreenOffAnimationController);
- mStackScrollerInternal.initView(getContext(), mKeyguardBypassEnabledProvider,
- mNotificationSwipeHelper);
+ mStackScrollerInternal = new NotificationStackScrollLayout(getContext(), null);
+ mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper);
mStackScroller = spy(mStackScrollerInternal);
mStackScroller.setShelfController(notificationShelfController);
mStackScroller.setStatusBar(mBar);
@@ -159,7 +150,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
when(mStackScrollLayoutController.getNoticationRoundessManager())
.thenReturn(mNotificationRoundnessManager);
mStackScroller.setController(mStackScrollLayoutController);
- mStackScroller.setRemoteInputManager(mRemoteInputManager);
// Stub out functionality that isn't necessary to test.
doNothing().when(mBar)
@@ -233,21 +223,24 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Test
@UiThreadTest
public void testSetExpandedHeight_withSplitShade_doesntInterpolateStackHeight() {
- when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
final int[] expectedStackHeight = {0};
mStackScroller.addOnExpandedHeightChangedListener((expandedHeight, appear) -> {
assertWithMessage("Given shade enabled: %s",
- mFeatureFlags.isTwoColumnNotificationShadeEnabled())
+ true)
.that(mStackScroller.getHeight())
.isEqualTo(expectedStackHeight[0]);
});
- when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_use_split_notification_shade, /* value= */ false);
expectedStackHeight[0] = 0;
mStackScroller.setExpandedHeight(100f);
- when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
expectedStackHeight[0] = 100;
mStackScroller.setExpandedHeight(100f);
}
@@ -307,7 +300,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
when(row.canViewBeDismissed()).thenReturn(true);
when(mStackScroller.getChildCount()).thenReturn(1);
when(mStackScroller.getChildAt(anyInt())).thenReturn(row);
- when(mRemoteInputController.isRemoteInputActive()).thenReturn(true);
+ mStackScroller.setIsRemoteInputActive(true);
when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
.thenReturn(true);
when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
new file mode 100644
index 000000000000..5b60c9e07342
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -0,0 +1,58 @@
+package com.android.systemui.statusbar.notification.stack
+
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.BypassController
+import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+class StackScrollAlgorithmTest : SysuiTestCase() {
+
+ private val hostView = FrameLayout(context)
+ private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
+ private val expandableViewState = ExpandableViewState()
+ private val notificationRow = mock(ExpandableNotificationRow::class.java)
+ private val ambientState = AmbientState(
+ context,
+ SectionProvider { _, _ -> false },
+ BypassController { false })
+
+ @Before
+ fun setUp() {
+ whenever(notificationRow.viewState).thenReturn(expandableViewState)
+ hostView.addView(notificationRow)
+ }
+
+ @Test
+ fun testUpTranslationSetToDefaultValue() {
+ whenever(notificationRow.isPinned).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+
+ stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+ assertThat(expandableViewState.yTranslation).isEqualTo(stackScrollAlgorithm.mHeadsUpInset)
+ }
+
+ @Test
+ fun testHeadsUpTranslationChangesBasedOnStackMargin() {
+ whenever(notificationRow.isPinned).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+ val minHeadsUpTranslation = context.resources
+ .getDimensionPixelSize(R.dimen.notification_side_paddings)
+
+ // split shade case with top margin introduced by shade's status bar
+ ambientState.stackTopMargin = 100
+ stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+ // top margin presence should decrease heads up translation up to minHeadsUpTranslation
+ assertThat(expandableViewState.yTranslation).isEqualTo(minHeadsUpTranslation)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLoggerTest.kt
new file mode 100644
index 000000000000..f3136c7be967
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLoggerTest.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.systemui.statusbar.phone
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.statusbar.DisableFlagsLogger
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.Mockito.mock
+import java.io.PrintWriter
+import java.io.StringWriter
+
+@SmallTest
+class CollapsedStatusBarFragmentLoggerTest : SysuiTestCase() {
+
+ private val buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
+ .create("buffer", 10)
+ private val disableFlagsLogger = DisableFlagsLogger(
+ listOf(DisableFlagsLogger.DisableFlag(0b001, 'A', 'a')),
+ listOf(DisableFlagsLogger.DisableFlag(0b001, 'B', 'b'))
+ )
+ private val logger = CollapsedStatusBarFragmentLogger(buffer, disableFlagsLogger)
+
+ @Test
+ fun logToBuffer_bufferHasStates() {
+ val state = DisableFlagsLogger.DisableState(0, 1)
+
+ logger.logDisableFlagChange(state, state)
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+ val expectedLogString = disableFlagsLogger.getDisableFlagsString(state, state)
+
+ assertThat(actualString).contains(expectedLogString)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
index bc5307450ed5..f23f14801484 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
@@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone;
import static android.view.Display.DEFAULT_DISPLAY;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
@@ -37,13 +38,17 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogcatEchoTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.DisableFlagsLogger;
+import com.android.systemui.statusbar.OperatorNameViewController;
+import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.NetworkController;
import org.junit.Before;
import org.junit.Ignore;
@@ -51,6 +56,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+import java.util.Optional;
+
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -69,6 +76,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
private final StatusBar mStatusBar = mock(StatusBar.class);
private final CommandQueue mCommandQueue = mock(CommandQueue.class);
+ private OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
+ private OperatorNameViewController mOperatorNameViewController;
public CollapsedStatusBarFragmentTest() {
super(CollapsedStatusBarFragment.class);
@@ -76,6 +85,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Before
public void setup() {
+ mStatusBarStateController = mDependency
+ .injectMockDependency(StatusBarStateController.class);
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
when(mStatusBar.getPanelController()).thenReturn(
mock(NotificationPanelViewController.class));
@@ -237,6 +248,11 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
mNetworkController = mock(NetworkController.class);
mStatusBarStateController = mock(StatusBarStateController.class);
mKeyguardStateController = mock(KeyguardStateController.class);
+ mOperatorNameViewController = mock(OperatorNameViewController.class);
+ mOperatorNameViewControllerFactory = mock(OperatorNameViewController.Factory.class);
+ when(mOperatorNameViewControllerFactory.create(any()))
+ .thenReturn(mOperatorNameViewController);
+
setUpNotificationIconAreaController();
return new CollapsedStatusBarFragment(
mOngoingCallController,
@@ -244,12 +260,17 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
mLocationPublisher,
mMockNotificationAreaController,
mock(FeatureFlags.class),
+ () -> Optional.of(mStatusBar),
mStatusBarIconController,
mKeyguardStateController,
mNetworkController,
mStatusBarStateController,
- mStatusBar,
- mCommandQueue);
+ mCommandQueue,
+ new CollapsedStatusBarFragmentLogger(
+ new LogBuffer("TEST", 1, 1, mock(LogcatEchoTracker.class)),
+ new DisableFlagsLogger()
+ ),
+ mOperatorNameViewControllerFactory);
}
private void setUpNotificationIconAreaController() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 5bf1bb3c573f..7a0b3669991c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -38,7 +38,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.tuner.TunerService;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 8b5ba3848500..38d7ce76f1b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -164,7 +164,7 @@ public class DozeServiceHostTest extends SysuiTestCase {
@Test
public void testPulseWhileDozing_notifyAuthInterrupt() {
HashSet<Integer> reasonsWantingAuth = new HashSet<>(
- Collections.singletonList(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN));
+ Collections.singletonList(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH));
HashSet<Integer> reasonsSkippingAuth = new HashSet<>(
Arrays.asList(DozeLog.PULSE_REASON_INTENT,
DozeLog.PULSE_REASON_NOTIFICATION,
@@ -173,7 +173,7 @@ public class DozeServiceHostTest extends SysuiTestCase {
DozeLog.REASON_SENSOR_DOUBLE_TAP,
DozeLog.PULSE_REASON_SENSOR_LONG_PRESS,
DozeLog.PULSE_REASON_DOCKING,
- DozeLog.REASON_SENSOR_WAKE_UP,
+ DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE,
DozeLog.REASON_SENSOR_QUICK_PICKUP,
DozeLog.REASON_SENSOR_TAP));
HashSet<Integer> reasonsThatDontPulse = new HashSet<>(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index bca1227b7d35..bafbccdb87d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -198,7 +198,6 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
mHeadsUpAppearanceController.destroy();
verify(mHeadsUpManager).removeListener(any());
verify(mDarkIconDispatcher).removeDarkReceiver((DarkIconDispatcher.DarkReceiver) any());
- verify(mPanelView).setVerticalTranslationListener(isNull());
verify(mPanelView).removeTrackingHeadsUpListener(any());
verify(mPanelView).setHeadsUpAppearanceController(isNull());
verify(mStackScrollerController).removeOnExpandedHeightChangedListener(any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 1043faa8b1bc..624bedc30be9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -45,30 +45,26 @@ import org.mockito.MockitoSession;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
-
private static final int SCREEN_HEIGHT = 2000;
private static final int EMPTY_HEIGHT = 0;
private static final float ZERO_DRAG = 0.f;
private static final float OPAQUE = 1.f;
private static final float TRANSPARENT = 0.f;
- private static final boolean HAS_CUSTOM_CLOCK = false;
- private static final boolean HAS_VISIBLE_NOTIFS = false;
@Mock
private Resources mResources;
private KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
private KeyguardClockPositionAlgorithm.Result mClockPosition;
+
private MockitoSession mStaticMockSession;
- private int mNotificationStackHeight;
+
private float mPanelExpansion;
private int mKeyguardStatusBarHeaderHeight;
private int mKeyguardStatusHeight;
private float mDark;
- private boolean mHasCustomClock;
- private boolean mHasVisibleNotifs;
private float mQsExpansion;
- private int mCutoutTopInset = 0; // in pixels
+ private int mCutoutTopInset = 0;
private boolean mIsSplitShade = false;
private float mUdfpsTop = -1;
private float mClockBottom = SCREEN_HEIGHT / 2;
@@ -86,9 +82,6 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
mClockPositionAlgorithm.loadDimens(mResources);
mClockPosition = new KeyguardClockPositionAlgorithm.Result();
-
- mHasCustomClock = HAS_CUSTOM_CLOCK;
- mHasVisibleNotifs = HAS_VISIBLE_NOTIFS;
}
@After
@@ -98,9 +91,8 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
@Test
public void clockPositionTopOfScreenOnAOD() {
- // GIVEN on AOD and both stack scroll and clock have 0 height
+ // GIVEN on AOD and clock has 0 height
givenAOD();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = EMPTY_HEIGHT;
// WHEN the clock position algorithm is run
positionClock();
@@ -113,9 +105,8 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
@Test
public void clockPositionBelowCutout() {
- // GIVEN on AOD and both stack scroll and clock have 0 height
+ // GIVEN on AOD and clock has 0 height
givenAOD();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = EMPTY_HEIGHT;
mCutoutTopInset = 300;
// WHEN the clock position algorithm is run
@@ -131,7 +122,6 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
public void clockPositionAdjustsForKeyguardStatusOnAOD() {
// GIVEN on AOD with a clock of height 100
givenAOD();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = 100;
// WHEN the clock position algorithm is run
positionClock();
@@ -146,7 +136,6 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
public void clockPositionLargeClockOnAOD() {
// GIVEN on AOD with a full screen clock
givenAOD();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = SCREEN_HEIGHT;
// WHEN the clock position algorithm is run
positionClock();
@@ -159,9 +148,8 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
@Test
public void clockPositionTopOfScreenOnLockScreen() {
- // GIVEN on lock screen with stack scroll and clock of 0 height
+ // GIVEN on lock screen with clock of 0 height
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = EMPTY_HEIGHT;
// WHEN the clock position algorithm is run
positionClock();
@@ -172,24 +160,9 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
}
@Test
- public void clockPositionWithStackScrollExpandOnLockScreen() {
- // GIVEN on lock screen with stack scroll of height 500
- givenLockScreen();
- mNotificationStackHeight = 500;
- mKeyguardStatusHeight = EMPTY_HEIGHT;
- // WHEN the clock position algorithm is run
- positionClock();
- // THEN the clock Y position stays to the top
- assertThat(mClockPosition.clockY).isEqualTo(0);
- // AND the clock is positioned on the left.
- assertThat(mClockPosition.clockX).isEqualTo(0);
- }
-
- @Test
public void clockPositionWithPartialDragOnLockScreen() {
// GIVEN dragging up on lock screen
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = EMPTY_HEIGHT;
mPanelExpansion = 0.5f;
// WHEN the clock position algorithm is run
@@ -205,7 +178,6 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
public void clockPositionWithFullDragOnLockScreen() {
// GIVEN the lock screen is dragged up
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = EMPTY_HEIGHT;
mPanelExpansion = 0.f;
// WHEN the clock position algorithm is run
@@ -218,7 +190,6 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
public void largeClockOnLockScreenIsTransparent() {
// GIVEN on lock screen with a full screen clock
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = SCREEN_HEIGHT;
// WHEN the clock position algorithm is run
positionClock();
@@ -228,9 +199,8 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
@Test
public void notifPositionTopOfScreenOnAOD() {
- // GIVEN on AOD and both stack scroll and clock have 0 height
+ // GIVEN on AOD and clock has 0 height
givenAOD();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = EMPTY_HEIGHT;
// WHEN the position algorithm is run
positionClock();
@@ -242,7 +212,6 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
public void notifPositionIndependentOfKeyguardStatusHeightOnAOD() {
// GIVEN on AOD and clock has a nonzero height
givenAOD();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = 100;
// WHEN the position algorithm is run
positionClock();
@@ -254,7 +223,6 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
public void notifPositionWithLargeClockOnAOD() {
// GIVEN on AOD and clock has a nonzero height
givenAOD();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = SCREEN_HEIGHT;
// WHEN the position algorithm is run
positionClock();
@@ -264,9 +232,8 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
@Test
public void notifPositionMiddleOfScreenOnLockScreen() {
- // GIVEN on lock screen and both stack scroll and clock have 0 height
+ // GIVEN on lock screen and clock has 0 height
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = EMPTY_HEIGHT;
// WHEN the position algorithm is run
positionClock();
@@ -275,58 +242,54 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
}
@Test
- public void notifPositionAdjustsForStackHeightOnLockScreen() {
+ public void notifPositionAdjustsForClockHeightOnLockScreen() {
// GIVEN on lock screen and stack scroller has a nonzero height
givenLockScreen();
- mNotificationStackHeight = 500;
- mKeyguardStatusHeight = EMPTY_HEIGHT;
+ mKeyguardStatusHeight = 200;
// WHEN the position algorithm is run
positionClock();
- // THEN the notif padding adjusts for keyguard status height
- assertThat(mClockPosition.stackScrollerPadding).isEqualTo(0);
+ assertThat(mClockPosition.stackScrollerPadding).isEqualTo(200);
}
@Test
- public void notifPositionAdjustsForClockHeightOnLockScreen() {
- // GIVEN on lock screen and stack scroller has a nonzero height
+ public void notifPositionAlignedWithClockInSplitShadeMode() {
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
+ mIsSplitShade = true;
mKeyguardStatusHeight = 200;
// WHEN the position algorithm is run
positionClock();
- // THEN the notif padding adjusts for both clock and notif stack.
- assertThat(mClockPosition.stackScrollerPadding).isEqualTo(200);
+ // THEN the notif padding DOESN'T adjust for keyguard status height.
+ assertThat(mClockPosition.stackScrollerPadding).isEqualTo(0);
}
@Test
- public void notifPositionAdjustsForStackHeightAndClockHeightOnLockScreen() {
- // GIVEN on lock screen and stack scroller has a nonzero height
+ public void notifPaddingExpandedAlignedWithClockInSplitShadeMode() {
givenLockScreen();
- mNotificationStackHeight = 500;
+ mIsSplitShade = true;
mKeyguardStatusHeight = 200;
// WHEN the position algorithm is run
positionClock();
- // THEN the notifs are placed below the statusview
- assertThat(mClockPosition.stackScrollerPadding).isEqualTo(200);
+ // THEN the padding DOESN'T adjust for keyguard status height.
+ assertThat(mClockPosition.stackScrollerPaddingExpanded)
+ .isEqualTo(mClockPosition.clockYFullyDozing);
}
@Test
- public void notifPositionAlignedWithClockInSplitShadeMode() {
- // GIVEN on lock screen and split shade mode
+ public void notifMinPaddingAlignedWithClockInSplitShadeMode() {
givenLockScreen();
mIsSplitShade = true;
- mHasCustomClock = true;
+ mKeyguardStatusHeight = 200;
// WHEN the position algorithm is run
positionClock();
- // THEN the notif padding DOESN'T adjust for keyguard status height.
- assertThat(mClockPosition.stackScrollerPadding).isEqualTo(0);
+ // THEN the padding DOESN'T adjust for keyguard status height.
+ assertThat(mClockPositionAlgorithm.getMinStackScrollerPadding())
+ .isEqualTo(mKeyguardStatusBarHeaderHeight);
}
@Test
public void notifPositionWithLargeClockOnLockScreen() {
// GIVEN on lock screen and clock has a nonzero height
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = SCREEN_HEIGHT;
// WHEN the position algorithm is run
positionClock();
@@ -338,7 +301,6 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
public void notifPositionWithFullDragOnLockScreen() {
// GIVEN the lock screen is dragged up
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = EMPTY_HEIGHT;
mPanelExpansion = 0.f;
// WHEN the clock position algorithm is run
@@ -351,19 +313,17 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
public void notifPositionWithLargeClockFullDragOnLockScreen() {
// GIVEN the lock screen is dragged up and a full screen clock
givenLockScreen();
- mNotificationStackHeight = EMPTY_HEIGHT;
mKeyguardStatusHeight = SCREEN_HEIGHT;
mPanelExpansion = 0.f;
// WHEN the clock position algorithm is run
positionClock();
- // THEN the notif padding is zero.
assertThat(mClockPosition.stackScrollerPadding).isEqualTo(
(int) (mKeyguardStatusHeight * .667f));
}
@Test
public void clockHiddenWhenQsIsExpanded() {
- // GIVEN on the lock screen with a custom clock and visible notifications
+ // GIVEN on the lock screen with visible notifications
givenLockScreen();
mQsExpansion = 1;
// WHEN the clock position algorithm is run
@@ -397,8 +357,8 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
givenLowestBurnInOffset();
positionClock();
- // THEN lowest case starts at mCutoutTopInset
- assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset);
+ // THEN lowest case starts at 0
+ assertThat(mClockPosition.clockY).isEqualTo(0);
}
@Test
@@ -516,7 +476,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
}
private void givenMaxBurnInOffset(int offset) {
- when(mResources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y_large_clock))
+ when(mResources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y_clock))
.thenReturn(offset);
mClockPositionAlgorithm.loadDimens(mResources);
}
@@ -539,15 +499,10 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
private void positionClock() {
mClockPositionAlgorithm.setup(
mKeyguardStatusBarHeaderHeight,
- SCREEN_HEIGHT,
- mNotificationStackHeight,
mPanelExpansion,
- SCREEN_HEIGHT,
mKeyguardStatusHeight,
0 /* userSwitchHeight */,
0 /* userSwitchPreferredY */,
- mHasCustomClock,
- mHasVisibleNotifs,
mDark,
ZERO_DRAG,
false /* bypassEnabled */,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
new file mode 100644
index 000000000000..faf968b4ff44
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -0,0 +1,384 @@
+/*
+ * 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.systemui.statusbar.phone;
+
+
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.CarrierTextController;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.battery.BatteryMeterViewController;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
+ @Mock
+ private CarrierTextController mCarrierTextController;
+ @Mock
+ private ConfigurationController mConfigurationController;
+ @Mock
+ private SystemStatusAnimationScheduler mAnimationScheduler;
+ @Mock
+ private BatteryController mBatteryController;
+ @Mock
+ private UserInfoController mUserInfoController;
+ @Mock
+ private StatusBarIconController mStatusBarIconController;
+ @Mock
+ private FeatureFlags mFeatureFlags;
+ @Mock
+ private BatteryMeterViewController mBatteryMeterViewController;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private KeyguardBypassController mKeyguardBypassController;
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private BiometricUnlockController mBiometricUnlockController;
+ @Mock
+ private SysuiStatusBarStateController mStatusBarStateController;
+
+ private TestNotificationPanelViewStateProvider mNotificationPanelViewStateProvider;
+ private KeyguardStatusBarView mKeyguardStatusBarView;
+ private KeyguardStatusBarViewController mController;
+
+ @Before
+ public void setup() throws Exception {
+ mNotificationPanelViewStateProvider = new TestNotificationPanelViewStateProvider();
+
+ MockitoAnnotations.initMocks(this);
+
+ allowTestableLooperAsMainThread();
+ TestableLooper.get(this).runWithLooper(() -> {
+ mKeyguardStatusBarView =
+ (KeyguardStatusBarView) LayoutInflater.from(mContext)
+ .inflate(R.layout.keyguard_status_bar, null);
+ });
+
+ mController = new KeyguardStatusBarViewController(
+ mKeyguardStatusBarView,
+ mCarrierTextController,
+ mConfigurationController,
+ mAnimationScheduler,
+ mBatteryController,
+ mUserInfoController,
+ mStatusBarIconController,
+ new StatusBarIconController.TintedIconManager.Factory(mFeatureFlags),
+ mBatteryMeterViewController,
+ mNotificationPanelViewStateProvider,
+ mKeyguardStateController,
+ mKeyguardBypassController,
+ mKeyguardUpdateMonitor,
+ mBiometricUnlockController,
+ mStatusBarStateController
+ );
+ }
+
+ @Test
+ public void onViewAttached_callbacksRegistered() {
+ mController.onViewAttached();
+
+ verify(mConfigurationController).addCallback(any());
+ verify(mAnimationScheduler).addCallback(any());
+ verify(mUserInfoController).addCallback(any());
+ verify(mStatusBarIconController).addIconGroup(any());
+ }
+
+ @Test
+ public void onViewDetached_callbacksUnregistered() {
+ // Set everything up first.
+ mController.onViewAttached();
+
+ mController.onViewDetached();
+
+ verify(mConfigurationController).removeCallback(any());
+ verify(mAnimationScheduler).removeCallback(any());
+ verify(mUserInfoController).removeCallback(any());
+ verify(mStatusBarIconController).removeIconGroup(any());
+ }
+
+ @Test
+ public void setBatteryListening_true_callbackAdded() {
+ mController.setBatteryListening(true);
+
+ verify(mBatteryController).addCallback(any());
+ }
+
+ @Test
+ public void setBatteryListening_false_callbackRemoved() {
+ // First set to true so that we know setting to false is a change in state.
+ mController.setBatteryListening(true);
+
+ mController.setBatteryListening(false);
+
+ verify(mBatteryController).removeCallback(any());
+ }
+
+ @Test
+ public void setBatteryListening_trueThenTrue_callbackAddedOnce() {
+ mController.setBatteryListening(true);
+ mController.setBatteryListening(true);
+
+ verify(mBatteryController).addCallback(any());
+ }
+
+ @Test
+ public void updateTopClipping_viewClippingUpdated() {
+ int viewTop = 20;
+ mKeyguardStatusBarView.setTop(viewTop);
+ int notificationPanelTop = 30;
+
+ mController.updateTopClipping(notificationPanelTop);
+
+ assertThat(mKeyguardStatusBarView.getClipBounds().top).isEqualTo(
+ notificationPanelTop - viewTop);
+ }
+
+ @Test
+ public void setNotTopClipping_viewClippingUpdatedToZero() {
+ // Start out with some amount of top clipping.
+ mController.updateTopClipping(50);
+ assertThat(mKeyguardStatusBarView.getClipBounds().top).isGreaterThan(0);
+
+ mController.setNoTopClipping();
+
+ assertThat(mKeyguardStatusBarView.getClipBounds().top).isEqualTo(0);
+ }
+
+ @Test
+ public void updateViewState_alphaAndVisibilityGiven_viewUpdated() {
+ // Verify the initial values so we know the method triggers changes.
+ assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(1f);
+ assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
+
+ float newAlpha = 0.5f;
+ int newVisibility = View.INVISIBLE;
+ mController.updateViewState(newAlpha, newVisibility);
+
+ assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(newAlpha);
+ assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(newVisibility);
+ }
+
+ @Test
+ public void updateViewState_notKeyguardState_nothingUpdated() {
+ mController.onViewAttached();
+ updateStateToNotKeyguard();
+
+ float oldAlpha = mKeyguardStatusBarView.getAlpha();
+
+ mController.updateViewState();
+
+ assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(oldAlpha);
+ }
+
+ @Test
+ public void updateViewState_bypassEnabledAndShouldListenForFace_viewHidden() {
+ mController.onViewAttached();
+ updateStateToKeyguard();
+ assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
+
+ when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(true);
+ when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
+ onFinishedGoingToSleep();
+
+ mController.updateViewState();
+
+ assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
+ }
+
+ @Test
+ public void updateViewState_bypassNotEnabled_viewShown() {
+ mController.onViewAttached();
+ updateStateToKeyguard();
+
+ when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(true);
+ when(mKeyguardBypassController.getBypassEnabled()).thenReturn(false);
+ onFinishedGoingToSleep();
+
+ mController.updateViewState();
+
+ assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateViewState_shouldNotListenForFace_viewShown() {
+ mController.onViewAttached();
+ updateStateToKeyguard();
+
+ when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(false);
+ when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
+ onFinishedGoingToSleep();
+
+ mController.updateViewState();
+
+ assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateViewState_panelExpandedHeightZero_viewHidden() {
+ mController.onViewAttached();
+ updateStateToKeyguard();
+
+ mNotificationPanelViewStateProvider.setPanelViewExpandedHeight(0);
+
+ mController.updateViewState();
+
+ assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
+ }
+
+ @Test
+ public void updateViewState_qsExpansionOne_viewHidden() {
+ mController.onViewAttached();
+ updateStateToKeyguard();
+
+ mNotificationPanelViewStateProvider.setQsExpansionFraction(1f);
+
+ mController.updateViewState();
+
+ assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
+ }
+
+ // TODO(b/195442899): Add more tests for #updateViewState once CLs are finalized.
+
+ @Test
+ public void updateForHeadsUp_headsUpShouldBeVisible_viewHidden() {
+ mController.onViewAttached();
+ updateStateToKeyguard();
+ mKeyguardStatusBarView.setVisibility(View.VISIBLE);
+
+ mNotificationPanelViewStateProvider.setShouldHeadsUpBeVisible(true);
+ mController.updateForHeadsUp(/* animate= */ false);
+
+ assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
+ }
+
+ @Test
+ public void updateForHeadsUp_headsUpShouldNotBeVisible_viewShown() {
+ mController.onViewAttached();
+ updateStateToKeyguard();
+
+ // Start with the opposite state.
+ mNotificationPanelViewStateProvider.setShouldHeadsUpBeVisible(true);
+ mController.updateForHeadsUp(/* animate= */ false);
+
+ mNotificationPanelViewStateProvider.setShouldHeadsUpBeVisible(false);
+ mController.updateForHeadsUp(/* animate= */ false);
+
+ assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ private void updateStateToNotKeyguard() {
+ updateStatusBarState(SHADE);
+ }
+
+ private void updateStateToKeyguard() {
+ updateStatusBarState(KEYGUARD);
+ }
+
+ private void updateStatusBarState(int state) {
+ ArgumentCaptor<StatusBarStateController.StateListener> statusBarStateListenerCaptor =
+ ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+ verify(mStatusBarStateController).addCallback(statusBarStateListenerCaptor.capture());
+ StatusBarStateController.StateListener callback = statusBarStateListenerCaptor.getValue();
+
+ callback.onStateChanged(state);
+ }
+
+ /**
+ * Calls {@link com.android.keyguard.KeyguardUpdateMonitorCallback#onFinishedGoingToSleep(int)}
+ * to ensure values are updated properly.
+ */
+ private void onFinishedGoingToSleep() {
+ ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateCallbackCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ verify(mKeyguardUpdateMonitor).registerCallback(keyguardUpdateCallbackCaptor.capture());
+ KeyguardUpdateMonitorCallback callback = keyguardUpdateCallbackCaptor.getValue();
+
+ callback.onFinishedGoingToSleep(0);
+ }
+
+ private static class TestNotificationPanelViewStateProvider
+ implements NotificationPanelViewController.NotificationPanelViewStateProvider {
+
+ TestNotificationPanelViewStateProvider() {}
+
+ private float mPanelViewExpandedHeight = 100f;
+ private float mQsExpansionFraction = 0f;
+ private boolean mShouldHeadsUpBeVisible = false;
+
+ @Override
+ public float getPanelViewExpandedHeight() {
+ return mPanelViewExpandedHeight;
+ }
+
+ @Override
+ public float getQsExpansionFraction() {
+ return mQsExpansionFraction;
+ }
+
+ @Override
+ public boolean shouldHeadsUpBeVisible() {
+ return mShouldHeadsUpBeVisible;
+ }
+
+ public void setPanelViewExpandedHeight(float panelViewExpandedHeight) {
+ this.mPanelViewExpandedHeight = panelViewExpandedHeight;
+ }
+
+ public void setQsExpansionFraction(float qsExpansionFraction) {
+ this.mQsExpansionFraction = qsExpansionFraction;
+ }
+
+ public void setShouldHeadsUpBeVisible(boolean shouldHeadsUpBeVisible) {
+ this.mShouldHeadsUpBeVisible = shouldHeadsUpBeVisible;
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
new file mode 100644
index 000000000000..3108ed9e7b98
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.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.systemui.statusbar.phone;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class KeyguardStatusBarViewTest extends SysuiTestCase {
+
+ private KeyguardStatusBarView mKeyguardStatusBarView;
+
+ @Before
+ public void setup() throws Exception {
+ allowTestableLooperAsMainThread();
+ TestableLooper.get(this).runWithLooper(() -> {
+ mKeyguardStatusBarView =
+ (KeyguardStatusBarView) LayoutInflater.from(mContext)
+ .inflate(R.layout.keyguard_status_bar, null);
+ });
+ }
+
+ @Test
+ public void setTopClipping_clippingUpdated() {
+ int topClipping = 40;
+
+ mKeyguardStatusBarView.setTopClipping(topClipping);
+
+ assertThat(mKeyguardStatusBarView.getClipBounds().top).isEqualTo(topClipping);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index ccdc69aeaa18..7e33c01572e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -34,6 +34,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.view.AppearanceRegion;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -56,8 +57,12 @@ public class LightBarControllerTest extends SysuiTestCase {
mLightBarTransitionsController = mock(LightBarTransitionsController.class);
when(mStatusBarIconController.getTransitionsController()).thenReturn(
mLightBarTransitionsController);
- mLightBarController = new LightBarController(mContext, mStatusBarIconController,
- mock(BatteryController.class), mock(NavigationModeController.class));
+ mLightBarController = new LightBarController(
+ mContext,
+ mStatusBarIconController,
+ mock(BatteryController.class),
+ mock(NavigationModeController.class),
+ mock(DumpManager.class));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
index cdfab1eec609..74f08aba9cf6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
@@ -102,7 +102,8 @@ public class LightsOutNotifControllerTest extends SysuiTestCase {
null /* appearanceRegions */,
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
- false /* isFullscreen */);
+ null /* requestedVisibilities */,
+ null /* packageName */);
assertTrue(mLightsOutNotifController.areLightsOut());
}
@@ -114,7 +115,8 @@ public class LightsOutNotifControllerTest extends SysuiTestCase {
null /* appearanceRegions */,
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
- false /* isFullscreen */);
+ null /* requestedVisibilities */,
+ null /* packageName */);
assertFalse(mLightsOutNotifController.areLightsOut());
}
@@ -144,7 +146,8 @@ public class LightsOutNotifControllerTest extends SysuiTestCase {
null /* appearanceRegions */,
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
- false /* isFullscreen */);
+ null /* requestedVisibilities */,
+ null /* packageName */);
// THEN we should show dot
assertTrue(mLightsOutNotifController.shouldShowDot());
@@ -163,7 +166,8 @@ public class LightsOutNotifControllerTest extends SysuiTestCase {
null /* appearanceRegions */,
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
- false /* isFullscreen */);
+ null /* requestedVisibilities */,
+ null /* packageName */);
// THEN we shouldn't show the dot
assertFalse(mLightsOutNotifController.shouldShowDot());
@@ -182,7 +186,8 @@ public class LightsOutNotifControllerTest extends SysuiTestCase {
null /* appearanceRegions */,
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
- false /* isFullscreen */);
+ null /* requestedVisibilities */,
+ null /* packageName */);
// THEN we shouldn't show the dot
assertFalse(mLightsOutNotifController.shouldShowDot());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index 88852f111c4b..80d9c0876ec1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -37,6 +37,7 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -93,7 +94,8 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
mGroupManager = new NotificationGroupManagerLegacy(
mock(StatusBarStateController.class),
() -> mPeopleNotificationIdentifier,
- Optional.of(mock(Bubbles.class)));
+ Optional.of(mock(Bubbles.class)),
+ mock(DumpManager.class));
mDependency.injectTestDependency(NotificationGroupManagerLegacy.class, mGroupManager);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
index 0110d7b58cef..1be27da27d25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
@@ -33,6 +33,7 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
@@ -76,7 +77,8 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase {
mGroupManager = new NotificationGroupManagerLegacy(
mock(StatusBarStateController.class),
() -> mPeopleNotificationIdentifier,
- Optional.of(mock(Bubbles.class)));
+ Optional.of(mock(Bubbles.class)),
+ mock(DumpManager.class));
mGroupManager.setHeadsUpManager(mHeadsUpManager);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index f2477886eb68..ead32910f943 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static com.android.keyguard.KeyguardClockSwitch.LARGE;
+import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
@@ -35,6 +37,7 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -44,7 +47,6 @@ import android.content.ContentResolver;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
-import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
@@ -87,6 +89,7 @@ import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.doze.DozeLog;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.media.KeyguardMediaController;
@@ -97,7 +100,6 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.QSDetailDisplayer;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -106,7 +108,6 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -155,8 +156,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
private KeyguardBottomAreaView mQsFrame;
private KeyguardStatusView mKeyguardStatusView;
@Mock
- private ViewGroup mBigClockContainer;
- @Mock
private NotificationIconAreaController mNotificationAreaController;
@Mock
private HeadsUpManagerPhone mHeadsUpManager;
@@ -228,8 +227,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock
private ConversationNotificationManager mConversationNotificationManager;
@Mock
- private BiometricUnlockController mBiometricUnlockController;
- @Mock
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock
private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
@@ -264,8 +261,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock
private MediaDataManager mMediaDataManager;
@Mock
- private FeatureFlags mFeatureFlags;
- @Mock
private AmbientState mAmbientState;
@Mock
private UserManager mUserManager;
@@ -282,6 +277,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock
private SecureSettings mSecureSettings;
@Mock
+ private SplitShadeHeaderController mSplitShadeHeaderController;
+ @Mock
private ContentResolver mContentResolver;
@Mock
private TapAgainViewController mTapAgainViewController;
@@ -296,11 +293,15 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock
private NotificationRemoteInputManager mNotificationRemoteInputManager;
@Mock
- private RemoteInputController mRemoteInputController;
- @Mock
private RecordingController mRecordingController;
@Mock
private ControlsComponent mControlsComponent;
+ @Mock
+ private LockscreenGestureLogger mLockscreenGestureLogger;
+ @Mock
+ private DumpManager mDumpManager;
+ @Mock
+ private NotificationsQSContainerController mNotificationsQSContainerController;
private SysuiStatusBarStateController mStatusBarStateController;
private NotificationPanelViewController mNotificationPanelViewController;
@@ -312,7 +313,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger);
+ mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager);
mKeyguardStatusView = new KeyguardStatusView(mContext);
mKeyguardStatusView.setId(R.id.keyguard_status_view);
@@ -337,8 +338,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
when(mView.findViewById(R.id.keyguard_clock_container)).thenReturn(mKeyguardClockSwitch);
when(mView.findViewById(R.id.notification_stack_scroller))
.thenReturn(mNotificationStackScrollLayout);
- when(mNotificationStackScrollLayout.getController())
- .thenReturn(mNotificationStackScrollLayoutController);
when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000);
when(mNotificationStackScrollLayoutController.getHeadsUpCallback())
.thenReturn(mHeadsUpCallback);
@@ -346,7 +345,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
when(mKeyguardBottomArea.getLeftView()).thenReturn(mock(KeyguardAffordanceView.class));
when(mKeyguardBottomArea.getRightView()).thenReturn(mock(KeyguardAffordanceView.class));
when(mKeyguardBottomArea.animate()).thenReturn(mock(ViewPropertyAnimator.class));
- when(mView.findViewById(R.id.big_clock_container)).thenReturn(mBigClockContainer);
when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
when(mView.findViewById(R.id.keyguard_status_view))
.thenReturn(mock(KeyguardStatusView.class));
@@ -354,6 +352,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame));
mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller));
mNotificationContainerParent.addView(mKeyguardStatusView);
+ mNotificationContainerParent.onFinishInflate();
when(mView.findViewById(R.id.notification_container_parent))
.thenReturn(mNotificationContainerParent);
when(mFragmentService.getFragmentHostManager(mView)).thenReturn(mFragmentHostManager);
@@ -368,7 +367,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
NotificationWakeUpCoordinator coordinator =
new NotificationWakeUpCoordinator(
mock(HeadsUpManagerPhone.class),
- new StatusBarStateControllerImpl(new UiEventLoggerFake()),
+ new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager),
mKeyguardBypassController,
mDozeParameters,
mUnlockedScreenOffAnimationController);
@@ -389,7 +388,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
.thenReturn(mKeyguardClockSwitchController);
when(mKeyguardStatusViewComponent.getKeyguardStatusViewController())
.thenReturn(mKeyguardStatusViewController);
- when(mKeyguardStatusBarViewComponentFactory.build(any()))
+ when(mKeyguardStatusBarViewComponentFactory.build(any(), any()))
.thenReturn(mKeyguardStatusBarViewComponent);
when(mKeyguardStatusBarViewComponent.getKeyguardStatusBarViewController())
.thenReturn(mKeyguardStatusBarViewController);
@@ -397,8 +396,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
.thenReturn(mKeyguardStatusView);
when(mLayoutInflater.inflate(eq(R.layout.keyguard_bottom_area), any(), anyBoolean()))
.thenReturn(mKeyguardBottomArea);
- when(mNotificationRemoteInputManager.getController()).thenReturn(mRemoteInputController);
- when(mRemoteInputController.isRemoteInputActive()).thenReturn(false);
+ when(mNotificationRemoteInputManager.isRemoteInputActive()).thenReturn(false);
reset(mView);
@@ -415,7 +413,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mMetricsLogger, mActivityManager, mConfigurationController,
() -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
mConversationNotificationManager, mMediaHiearchyManager,
- mBiometricUnlockController, mStatusBarKeyguardViewManager,
+ mStatusBarKeyguardViewManager,
+ mNotificationsQSContainerController,
mNotificationStackScrollLayoutController,
mKeyguardStatusViewComponentFactory,
mKeyguardQsUserSwitchComponentFactory,
@@ -432,7 +431,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mNotificationShadeDepthController,
mAmbientState,
mLockIconViewController,
- mFeatureFlags,
mKeyguardMediaController,
mPrivacyDotViewController,
mTapAgainViewController,
@@ -443,11 +441,14 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mRecordingController,
new FakeExecutor(new FakeSystemClock()),
mSecureSettings,
+ mSplitShadeHeaderController,
mUnlockedScreenOffAnimationController,
+ mLockscreenGestureLogger,
mNotificationRemoteInputManager,
mControlsComponent);
mNotificationPanelViewController.initDependencies(
mStatusBar,
+ () -> {},
mNotificationShelfController);
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
mNotificationPanelViewController.setBar(mPanelBar);
@@ -470,8 +471,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
}
@Test
- public void testSetMinFraction() {
- mNotificationPanelViewController.setMinFraction(0.5f);
+ public void testSetPanelScrimMinFraction() {
+ mNotificationPanelViewController.setPanelScrimMinFraction(0.5f);
verify(mNotificationShadeDepthController).setPanelPullDownMinFraction(eq(0.5f));
}
@@ -516,17 +517,48 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
}
@Test
- public void testKeyguardStatusBarVisibility_hiddenForBypass() {
- when(mUpdateMonitor.shouldListenForFace()).thenReturn(true);
- mNotificationPanelViewController.mKeyguardUpdateCallback.onBiometricRunningStateChanged(
- true, BiometricSourceType.FACE);
- verify(mKeyguardStatusBar, never()).setVisibility(View.VISIBLE);
+ public void onTouchForwardedFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
+ when(mCommandQueue.panelsEnabled()).thenReturn(false);
+
+ boolean returnVal = mTouchHandler.onTouchForwardedFromStatusBar(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
+
+ assertThat(returnVal).isFalse();
+ verify(mView, never()).dispatchTouchEvent(any());
+ }
+
+ @Test
+ public void onTouchForwardedFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() {
+ when(mCommandQueue.panelsEnabled()).thenReturn(true);
+ when(mView.isEnabled()).thenReturn(false);
+
+ boolean returnVal = mTouchHandler.onTouchForwardedFromStatusBar(
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
+
+ assertThat(returnVal).isTrue();
+ verify(mView, never()).dispatchTouchEvent(any());
+ }
+
+ @Test
+ public void onTouchForwardedFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() {
+ when(mCommandQueue.panelsEnabled()).thenReturn(true);
+ when(mView.isEnabled()).thenReturn(false);
+ MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0);
+
+ mTouchHandler.onTouchForwardedFromStatusBar(event);
+
+ verify(mView).dispatchTouchEvent(event);
+ }
+
+ @Test
+ public void onTouchForwardedFromStatusBar_panelAndViewEnabled_viewReceivesEvent() {
+ when(mCommandQueue.panelsEnabled()).thenReturn(true);
+ when(mView.isEnabled()).thenReturn(true);
+ MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0);
+
+ mTouchHandler.onTouchForwardedFromStatusBar(event);
- when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
- mNotificationPanelViewController.mKeyguardUpdateCallback.onFinishedGoingToSleep(0);
- mNotificationPanelViewController.mKeyguardUpdateCallback.onBiometricRunningStateChanged(
- true, BiometricSourceType.FACE);
- verify(mKeyguardStatusBar, never()).setVisibility(View.VISIBLE);
+ verify(mView).dispatchTouchEvent(event);
}
@Test
@@ -564,7 +596,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Test
public void testAllChildrenOfNotificationContainer_haveIds() {
- enableSplitShade();
+ enableSplitShade(/* enabled= */ true);
mNotificationContainerParent.removeAllViews();
mNotificationContainerParent.addView(newViewWithId(1));
mNotificationContainerParent.addView(newViewWithId(View.NO_ID));
@@ -577,7 +609,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Test
public void testSinglePaneShadeLayout_isAlignedToParent() {
- when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+ enableSplitShade(/* enabled= */ false);
mNotificationPanelViewController.updateResources();
@@ -588,17 +620,19 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
}
@Test
- public void testKeyguardStatusView_isAlignedToGuidelineInSplitShadeMode() {
- mNotificationPanelViewController.updateResources();
+ public void testKeyguardStatusViewInSplitShade_changesConstraintsDependingOnNotifications() {
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+ mNotificationPanelViewController.updateResources();
assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd)
- .isEqualTo(ConstraintSet.PARENT_ID);
+ .isEqualTo(R.id.qs_edge_guideline);
- enableSplitShade();
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
mNotificationPanelViewController.updateResources();
-
assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd)
- .isEqualTo(R.id.qs_edge_guideline);
+ .isEqualTo(ConstraintSet.PARENT_ID);
}
@Test
@@ -635,7 +669,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Test
public void testSplitShadeLayout_isAlignedToGuideline() {
- enableSplitShade();
+ enableSplitShade(/* enabled= */ true);
mNotificationPanelViewController.updateResources();
@@ -647,7 +681,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Test
public void testSinglePaneShadeLayout_childrenHaveConstantWidth() {
- when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+ enableSplitShade(/* enabled= */ false);
mNotificationPanelViewController.updateResources();
@@ -659,7 +693,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Test
public void testSplitShadeLayout_childrenHaveZeroWidth() {
- enableSplitShade();
+ enableSplitShade(/* enabled= */ true);
mNotificationPanelViewController.updateResources();
@@ -668,18 +702,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
}
@Test
- public void testOnDragDownEvent_horizontalTranslationIsZeroForSplitShade() {
- when(mNotificationStackScrollLayoutController.getWidth()).thenReturn(350f);
- when(mView.getWidth()).thenReturn(800);
- enableSplitShade();
-
- onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN,
- 200f /* x position */, 0f, 0));
-
- verify(mQsFrame).setTranslationX(0);
- }
-
- @Test
public void testCanCollapsePanelOnTouch_trueForKeyGuard() {
mStatusBarStateController.setState(KEYGUARD);
@@ -705,7 +727,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Test
public void testCanCollapsePanelOnTouch_falseInDualPaneShade() {
mStatusBarStateController.setState(SHADE);
- enableSplitShade();
+ enableSplitShade(/* enabled= */ true);
mNotificationPanelViewController.setQsExpanded(true);
assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isFalse();
@@ -759,6 +781,68 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
verify(mTapAgainViewController).show();
}
+ @Test
+ public void testSwitchesToCorrectClockInSinglePaneShade() {
+ mStatusBarStateController.setState(KEYGUARD);
+
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+ triggerPositionClockAndNotifications();
+ verify(mKeyguardStatusViewController).displayClock(LARGE);
+
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
+ mNotificationPanelViewController.closeQs();
+ verify(mKeyguardStatusViewController).displayClock(SMALL);
+ }
+
+ @Test
+ public void testSwitchesToCorrectClockInSplitShade() {
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+ triggerPositionClockAndNotifications();
+ verify(mKeyguardStatusViewController).displayClock(LARGE);
+
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
+ triggerPositionClockAndNotifications();
+ verify(mKeyguardStatusViewController, times(2)).displayClock(LARGE);
+ verify(mKeyguardStatusViewController, never()).displayClock(SMALL);
+ }
+
+ @Test
+ public void testSwitchesToBigClockInSplitShadeOnAod() {
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+ when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+
+ mNotificationPanelViewController.setDozing(true, false, null);
+
+ verify(mKeyguardStatusViewController).displayClock(LARGE);
+ }
+
+ @Test
+ public void testDisplaysSmallClockOnLockscreenInSplitShadeWhenMediaIsPlaying() {
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+ when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+
+ // one notification + media player visible
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
+ triggerPositionClockAndNotifications();
+ verify(mKeyguardStatusViewController).displayClock(SMALL);
+
+ // only media player visible
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+ triggerPositionClockAndNotifications();
+ verify(mKeyguardStatusViewController, times(2)).displayClock(SMALL);
+ verify(mKeyguardStatusViewController, never()).displayClock(LARGE);
+ }
+
+ private void triggerPositionClockAndNotifications() {
+ mNotificationPanelViewController.closeQs();
+ }
+
private FalsingManager.FalsingTapListener getFalsingTapListener() {
for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) {
listener.onViewAttachedToWindow(mView);
@@ -789,9 +873,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
return constraintSet.getConstraint(id).layout;
}
- private void enableSplitShade() {
- when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
- when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+ private void enableSplitShade(boolean enabled) {
+ when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(enabled);
mNotificationPanelViewController.updateResources();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
new file mode 100644
index 000000000000..337e64592750
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
@@ -0,0 +1,254 @@
+package com.android.systemui.statusbar.phone
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.WindowInsets
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.function.Consumer
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class NotificationQSContainerControllerTest : SysuiTestCase() {
+
+ companion object {
+ const val STABLE_INSET_BOTTOM = 100
+ const val CUTOUT_HEIGHT = 50
+ const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+ const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+ const val NOTIFICATIONS_MARGIN = 50
+ }
+
+ @Mock
+ private lateinit var navigationModeController: NavigationModeController
+ @Mock
+ private lateinit var overviewProxyService: OverviewProxyService
+ @Mock
+ private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer
+ @Captor
+ lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
+ @Captor
+ lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
+ @Captor
+ lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
+
+ private lateinit var notificationsQSContainerController: NotificationsQSContainerController
+ private lateinit var navigationModeCallback: ModeChangedListener
+ private lateinit var taskbarVisibilityCallback: OverviewProxyListener
+ private lateinit var windowInsetsCallback: Consumer<WindowInsets>
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ notificationsQSContainerController = NotificationsQSContainerController(
+ notificationsQSContainer,
+ navigationModeController,
+ overviewProxyService
+ )
+ whenever(notificationsQSContainer.defaultNotificationsMarginBottom)
+ .thenReturn(NOTIFICATIONS_MARGIN)
+ whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
+ .thenReturn(GESTURES_NAVIGATION)
+ doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
+ doNothing().`when`(notificationsQSContainer)
+ .setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
+
+ notificationsQSContainerController.init()
+ notificationsQSContainerController.onViewAttached()
+
+ navigationModeCallback = navigationModeCaptor.value
+ taskbarVisibilityCallback = taskbarVisibilityCaptor.value
+ windowInsetsCallback = windowInsetsCallbackCaptor.value
+ }
+
+ @Test
+ fun testTaskbarVisibleInSplitShade() {
+ notificationsQSContainerController.splitShadeEnabled = true
+ given(taskbarVisible = true,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
+ expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
+
+ given(taskbarVisible = true,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = STABLE_INSET_BOTTOM,
+ expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSplitShade() {
+ // when taskbar is not visible, it means we're on the home screen
+ notificationsQSContainerController.splitShadeEnabled = true
+ given(taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = 0)
+
+ given(taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSplitShadeWithCutout() {
+ notificationsQSContainerController.splitShadeEnabled = true
+ given(taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withCutout())
+ then(expectedContainerPadding = CUTOUT_HEIGHT)
+
+ given(taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withCutout().withStableBottom())
+ then(expectedContainerPadding = 0,
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
+ }
+
+ @Test
+ fun testTaskbarVisibleInSinglePaneShade() {
+ notificationsQSContainerController.splitShadeEnabled = false
+ given(taskbarVisible = true,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = 0)
+
+ given(taskbarVisible = true,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = STABLE_INSET_BOTTOM)
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSinglePaneShade() {
+ notificationsQSContainerController.splitShadeEnabled = false
+ given(taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = emptyInsets())
+ then(expectedContainerPadding = 0)
+
+ given(taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withCutout().withStableBottom())
+ then(expectedContainerPadding = CUTOUT_HEIGHT)
+
+ given(taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = 0,
+ expectedQsPadding = STABLE_INSET_BOTTOM)
+ }
+
+ @Test
+ fun testCustomizingInSinglePaneShade() {
+ notificationsQSContainerController.splitShadeEnabled = false
+ notificationsQSContainerController.setCustomizerShowing(true)
+ // always sets spacings to 0
+ given(taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = 0,
+ expectedNotificationsMargin = 0)
+
+ given(taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = emptyInsets())
+ then(expectedContainerPadding = 0,
+ expectedNotificationsMargin = 0)
+ }
+
+ @Test
+ fun testDetailShowingInSinglePaneShade() {
+ notificationsQSContainerController.splitShadeEnabled = false
+ notificationsQSContainerController.setDetailShowing(true)
+ // always sets spacings to 0
+ given(taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = 0,
+ expectedNotificationsMargin = 0)
+
+ given(taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = emptyInsets())
+ then(expectedContainerPadding = 0,
+ expectedNotificationsMargin = 0)
+ }
+
+ @Test
+ fun testDetailShowingInSplitShade() {
+ notificationsQSContainerController.splitShadeEnabled = true
+ given(taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom())
+ then(expectedContainerPadding = 0)
+
+ notificationsQSContainerController.setDetailShowing(true)
+ // should not influence spacing
+ given(taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = emptyInsets())
+ then(expectedContainerPadding = 0)
+ }
+
+ private fun given(
+ taskbarVisible: Boolean,
+ navigationMode: Int,
+ insets: WindowInsets
+ ) {
+ Mockito.clearInvocations(notificationsQSContainer)
+ taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
+ navigationModeCallback.onNavigationModeChanged(navigationMode)
+ windowInsetsCallback.accept(insets)
+ }
+
+ fun then(
+ expectedContainerPadding: Int,
+ expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
+ expectedQsPadding: Int = 0
+ ) {
+ verify(notificationsQSContainer)
+ .setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
+ verify(notificationsQSContainer).setNotificationsMarginBottom(expectedNotificationsMargin)
+ verify(notificationsQSContainer).setQSScrollPaddingBottom(expectedQsPadding)
+ Mockito.clearInvocations(notificationsQSContainer)
+ }
+
+ private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
+
+ private fun emptyInsets() = mock(WindowInsets::class.java)
+
+ private fun WindowInsets.withCutout(): WindowInsets {
+ whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+ return this
+ }
+
+ private fun WindowInsets.withStableBottom(): WindowInsets {
+ whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
+ return this
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index aafaebd959f0..6e9bb2d30ed3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -34,7 +34,6 @@ import androidx.test.filters.SmallTest;
import com.android.keyguard.LockIconViewController;
import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.dock.DockManager;
@@ -47,7 +46,6 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -56,7 +54,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.InjectionInflationController;
import org.junit.Before;
import org.junit.Test;
@@ -93,7 +90,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
@Mock private NotificationPanelViewController mNotificationPanelViewController;
@Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
@Mock private NotificationShadeDepthController mNotificationShadeDepthController;
- @Mock private SuperStatusBarViewFactory mStatusBarViewFactory;
+ @Mock private StatusBarWindowView mStatusBarWindowView;
@Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -118,10 +115,6 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
when(mDockManager.isDocked()).thenReturn(false);
mController = new NotificationShadeWindowViewController(
- new InjectionInflationController(
- SystemUIFactory.getInstance()
- .getSysUIComponent()
- .createViewInstanceCreatorFactory()),
mCoordinator,
mPulseExpansionHandler,
mDynamicPrivacyController,
@@ -142,7 +135,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
mNotificationShadeDepthController,
mView,
mNotificationPanelViewController,
- mStatusBarViewFactory,
+ mStatusBarWindowView,
mNotificationStackScrollLayoutController,
mStatusBarKeyguardViewManager,
mLockIconViewController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
new file mode 100644
index 000000000000..310a8baadb78
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.systemui.statusbar.phone
+
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import android.view.ViewTreeObserver.OnPreDrawListener
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class PhoneStatusBarViewControllerTest : SysuiTestCase() {
+
+ private val touchEventHandler = TestTouchEventHandler()
+
+ @Mock
+ private lateinit var panelViewController: PanelViewController
+ @Mock
+ private lateinit var panelView: ViewGroup
+ @Mock
+ private lateinit var scrimController: ScrimController
+
+ @Mock
+ private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController
+ @Mock
+ private lateinit var progressProvider: ScopedUnfoldTransitionProgressProvider
+
+ private lateinit var view: PhoneStatusBarView
+ private lateinit var controller: PhoneStatusBarViewController
+
+ private val unfoldConfig = UnfoldConfig()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ `when`(panelViewController.view).thenReturn(panelView)
+
+ // create the view on main thread as it requires main looper
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ val parent = FrameLayout(mContext) // add parent to keep layout params
+ view = LayoutInflater.from(mContext)
+ .inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView
+ view.setScrimController(scrimController)
+ view.setBar(mock(StatusBar::class.java))
+ }
+
+ controller = createController(view)
+ }
+
+ @Test
+ fun constructor_setsTouchHandlerOnView() {
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+
+ view.onTouchEvent(event)
+
+ assertThat(touchEventHandler.lastEvent).isEqualTo(event)
+ }
+
+ @Test
+ fun onViewAttachedAndDrawn_moveFromCenterAnimationEnabled_moveFromCenterAnimationInitialized() {
+ val view = createViewMock()
+ val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
+ unfoldConfig.isEnabled = true
+ controller = createController(view)
+ controller.init()
+
+ verify(view.viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture())
+ argumentCaptor.value.onPreDraw()
+
+ verify(moveFromCenterAnimation).onViewsReady(any(), any())
+ }
+
+ private fun createViewMock(): PhoneStatusBarView {
+ val view = spy(view)
+ val viewTreeObserver = mock(ViewTreeObserver::class.java)
+ `when`(view.viewTreeObserver).thenReturn(viewTreeObserver)
+ `when`(view.isAttachedToWindow).thenReturn(true)
+ return view
+ }
+
+ private fun createController(view: PhoneStatusBarView): PhoneStatusBarViewController {
+ return PhoneStatusBarViewController.Factory(
+ { progressProvider },
+ { moveFromCenterAnimation },
+ unfoldConfig
+ ).create(view, touchEventHandler)
+ }
+
+ private class UnfoldConfig : UnfoldTransitionConfig {
+ override var isEnabled: Boolean = false
+ override var isHingeAngleEnabled: Boolean = false
+ }
+
+ private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler {
+ var lastEvent: MotionEvent? = null
+ override fun handleTouchEvent(event: MotionEvent?): Boolean {
+ lastEvent = event
+ return false
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
new file mode 100644
index 000000000000..fe3490399e81
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.systemui.statusbar.phone
+
+import android.view.MotionEvent
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class PhoneStatusBarViewTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var panelViewController: PanelViewController
+ @Mock
+ private lateinit var panelView: ViewGroup
+ @Mock
+ private lateinit var scrimController: ScrimController
+ @Mock
+ private lateinit var statusBar: StatusBar
+
+ private lateinit var view: PhoneStatusBarView
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ // TODO(b/197137564): Setting up a panel view and its controller feels unnecessary when
+ // testing just [PhoneStatusBarView].
+ `when`(panelViewController.view).thenReturn(panelView)
+
+ view = PhoneStatusBarView(mContext, null)
+ view.setScrimController(scrimController)
+ view.setBar(statusBar)
+ }
+
+ @Test
+ fun panelExpansionChanged_expansionChangeListenerNotified() {
+ val listener = TestExpansionChangedListener()
+ view.setExpansionChangedListeners(listOf(listener))
+ val fraction = 0.4f
+ val isExpanded = true
+
+ view.panelExpansionChanged(fraction, isExpanded)
+
+ assertThat(listener.fraction).isEqualTo(fraction)
+ assertThat(listener.isExpanded).isEqualTo(isExpanded)
+ }
+
+ @Test
+ fun panelExpansionChanged_noListeners_noCrash() {
+ view.panelExpansionChanged(1f, false)
+ // No assert needed, just testing no crash
+ }
+
+ @Test
+ fun panelStateChanged_toStateOpening_listenerNotified() {
+ val listener = TestStateChangedListener()
+ view.setPanelStateChangeListener(listener)
+
+ view.panelExpansionChanged(0.5f, true)
+
+ assertThat(listener.state).isEqualTo(PanelBar.STATE_OPENING)
+ }
+
+ @Test
+ fun panelStateChanged_toStateOpen_listenerNotified() {
+ val listener = TestStateChangedListener()
+ view.setPanelStateChangeListener(listener)
+
+ view.panelExpansionChanged(1f, true)
+
+ assertThat(listener.state).isEqualTo(PanelBar.STATE_OPEN)
+ }
+
+ @Test
+ fun panelStateChanged_toStateClosed_listenerNotified() {
+ val listener = TestStateChangedListener()
+ view.setPanelStateChangeListener(listener)
+
+ // First, open the panel
+ view.panelExpansionChanged(1f, true)
+
+ // Then, close it again
+ view.panelExpansionChanged(0f, false)
+
+ assertThat(listener.state).isEqualTo(PanelBar.STATE_CLOSED)
+ }
+
+ @Test
+ fun onTouchEvent_listenerNotified() {
+ val handler = TestTouchEventHandler()
+ view.setTouchEventHandler(handler)
+
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+ view.onTouchEvent(event)
+
+ assertThat(handler.lastEvent).isEqualTo(event)
+ }
+
+ @Test
+ fun onTouchEvent_listenerReturnsTrue_viewReturnsTrue() {
+ val handler = TestTouchEventHandler()
+ view.setTouchEventHandler(handler)
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+
+ handler.returnValue = true
+
+ assertThat(view.onTouchEvent(event)).isTrue()
+ }
+
+ @Test
+ fun onTouchEvent_listenerReturnsFalse_viewReturnsFalse() {
+ val handler = TestTouchEventHandler()
+ view.setTouchEventHandler(handler)
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+
+ handler.returnValue = false
+
+ assertThat(view.onTouchEvent(event)).isFalse()
+ }
+
+ @Test
+ fun onTouchEvent_noListener_noCrash() {
+ view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+ // No assert needed, just testing no crash
+ }
+
+ private class TestExpansionChangedListener
+ : StatusBar.ExpansionChangedListener {
+ var fraction: Float = 0f
+ var isExpanded: Boolean = false
+
+ override fun onExpansionChanged(expansion: Float, expanded: Boolean) {
+ this.fraction = expansion
+ this.isExpanded = expanded
+ }
+ }
+
+ private class TestStateChangedListener : PanelBar.PanelStateChangeListener {
+ var state: Int = 0
+ override fun onStateChanged(state: Int) {
+ this.state = state
+ }
+ }
+
+ private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler {
+ var lastEvent: MotionEvent? = null
+ var returnValue: Boolean = false
+ override fun handleTouchEvent(event: MotionEvent?): Boolean {
+ lastEvent = event
+ return returnValue
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 47c8806be6d4..6849dab1a19a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -86,7 +86,6 @@ public class ScrimControllerTest extends SysuiTestCase {
private ScrimView mScrimBehind;
private ScrimView mNotificationsScrim;
private ScrimView mScrimInFront;
- private ScrimView mScrimForBubble;
private ScrimState mScrimState;
private float mScrimBehindAlpha;
private GradientColors mScrimInFrontColor;
@@ -170,7 +169,6 @@ public class ScrimControllerTest extends SysuiTestCase {
endAnimation(mNotificationsScrim);
endAnimation(mScrimBehind);
endAnimation(mScrimInFront);
- endAnimation(mScrimForBubble);
assertEquals("Animators did not finish",
mAnimatorListener.getNumStarts(), mAnimatorListener.getNumEnds());
@@ -193,7 +191,6 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimBehind = spy(new ScrimView(getContext()));
mScrimInFront = new ScrimView(getContext());
- mScrimForBubble = new ScrimView(getContext());
mNotificationsScrim = new ScrimView(getContext());
mAlwaysOnEnabled = true;
mLooper = TestableLooper.get(this);
@@ -229,8 +226,7 @@ public class ScrimControllerTest extends SysuiTestCase {
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mUnlockedScreenOffAnimationController);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
- mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront,
- mScrimForBubble);
+ mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
mScrimController.setHasBackdrop(false);
@@ -260,8 +256,8 @@ public class ScrimControllerTest extends SysuiTestCase {
assertScrimTinted(Map.of(
mScrimInFront, true,
- mScrimBehind, true,
- mScrimForBubble, false));
+ mScrimBehind, true
+ ));
}
@Test
@@ -277,8 +273,7 @@ public class ScrimControllerTest extends SysuiTestCase {
assertScrimTinted(Map.of(
mScrimInFront, false,
- mScrimBehind, true,
- mScrimForBubble, false
+ mScrimBehind, true
));
}
@@ -296,8 +291,7 @@ public class ScrimControllerTest extends SysuiTestCase {
assertScrimTinted(Map.of(
mScrimInFront, false,
- mScrimBehind, true,
- mScrimForBubble, false
+ mScrimBehind, true
));
}
@@ -312,8 +306,7 @@ public class ScrimControllerTest extends SysuiTestCase {
assertScrimTinted(Map.of(
mScrimInFront, true,
- mScrimBehind, true,
- mScrimForBubble, false
+ mScrimBehind, true
));
assertEquals(1f, mScrimController.getState().getMaxLightRevealScrimAlpha(), 0f);
@@ -332,8 +325,7 @@ public class ScrimControllerTest extends SysuiTestCase {
assertScrimTinted(Map.of(
mScrimInFront, true,
- mScrimBehind, true,
- mScrimForBubble, false
+ mScrimBehind, true
));
}
@@ -372,8 +364,7 @@ public class ScrimControllerTest extends SysuiTestCase {
assertScrimTinted(Map.of(
mScrimInFront, true,
- mScrimBehind, true,
- mScrimForBubble, false
+ mScrimBehind, true
));
}
@@ -392,8 +383,7 @@ public class ScrimControllerTest extends SysuiTestCase {
assertScrimTinted(Map.of(
mScrimInFront, true,
- mScrimBehind, true,
- mScrimForBubble, false
+ mScrimBehind, true
));
}
@@ -513,8 +503,7 @@ public class ScrimControllerTest extends SysuiTestCase {
assertScrimTinted(Map.of(
mScrimInFront, true,
- mScrimBehind, true,
- mScrimForBubble, false
+ mScrimBehind, true
));
// ... and when ambient goes dark, front scrim should be semi-transparent
@@ -552,8 +541,7 @@ public class ScrimControllerTest extends SysuiTestCase {
assertScrimTinted(Map.of(
mScrimInFront, false,
mScrimBehind, false,
- mNotificationsScrim, false,
- mScrimForBubble, false
+ mNotificationsScrim, false
));
}
@@ -573,8 +561,50 @@ public class ScrimControllerTest extends SysuiTestCase {
assertScrimTinted(Map.of(
mScrimInFront, false,
mScrimBehind, true,
- mNotificationsScrim, false,
- mScrimForBubble, false
+ mNotificationsScrim, false
+ ));
+ }
+
+ @Test
+ public void disableClipQsScrimWithoutStateTransition_updatesTintAndAlpha() {
+ mScrimController.setClipsQsScrim(true);
+ mScrimController.transitionTo(ScrimState.BOUNCER);
+
+ mScrimController.setClipsQsScrim(false);
+
+ finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be visible without tint
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, TRANSPARENT,
+ mScrimBehind, OPAQUE));
+ assertScrimTinted(Map.of(
+ mScrimInFront, false,
+ mScrimBehind, false,
+ mNotificationsScrim, false
+ ));
+ }
+
+ @Test
+ public void enableClipQsScrimWithoutStateTransition_updatesTintAndAlpha() {
+ mScrimController.setClipsQsScrim(false);
+ mScrimController.transitionTo(ScrimState.BOUNCER);
+
+ mScrimController.setClipsQsScrim(true);
+
+ finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be clipping QS
+ // Notif scrim should be visible without tint
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, OPAQUE,
+ mScrimBehind, OPAQUE));
+ assertScrimTinted(Map.of(
+ mScrimInFront, false,
+ mScrimBehind, true,
+ mNotificationsScrim, false
));
}
@@ -587,14 +617,13 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimBehind, TRANSPARENT));
assertScrimTinted(Map.of(
mScrimInFront, false,
- mScrimBehind, false,
- mScrimForBubble, false
+ mScrimBehind, false
));
}
@Test
public void transitionToUnlocked() {
- mScrimController.setPanelExpansion(0f);
+ mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -605,39 +634,17 @@ public class ScrimControllerTest extends SysuiTestCase {
assertScrimTinted(Map.of(
mNotificationsScrim, false,
mScrimInFront, false,
- mScrimBehind, true,
- mScrimForBubble, false
+ mScrimBehind, true
));
// Back scrim should be visible after start dragging
- mScrimController.setPanelExpansion(0.3f);
+ mScrimController.setRawPanelExpansionFraction(0.3f);
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mNotificationsScrim, SEMI_TRANSPARENT,
mScrimBehind, SEMI_TRANSPARENT));
}
- @Test
- public void transitionToBubbleExpanded() {
- mScrimController.transitionTo(ScrimState.BUBBLE_EXPANDED);
- finishAnimationsImmediately();
-
- assertScrimTinted(Map.of(
- mScrimInFront, false,
- mScrimBehind, true,
- mScrimForBubble, true
- ));
-
- // Front scrim should be transparent
- assertEquals(ScrimController.TRANSPARENT,
- mScrimInFront.getViewAlpha(), 0.0f);
- // Back scrim should be visible
- assertEquals(ScrimController.BUSY_SCRIM_ALPHA,
- mScrimBehind.getViewAlpha(), 0.0f);
- // Bubble scrim should be visible
- assertEquals(ScrimController.BUBBLE_SCRIM_ALPHA,
- mScrimForBubble.getViewAlpha(), 0.0f);
- }
@Test
public void scrimStateCallback() {
@@ -656,20 +663,20 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void panelExpansion() {
- mScrimController.setPanelExpansion(0f);
- mScrimController.setPanelExpansion(0.5f);
+ mScrimController.setRawPanelExpansionFraction(0f);
+ mScrimController.setRawPanelExpansionFraction(0.5f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
reset(mScrimBehind);
- mScrimController.setPanelExpansion(0f);
- mScrimController.setPanelExpansion(1.0f);
+ mScrimController.setRawPanelExpansionFraction(0f);
+ mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
assertEquals("Scrim alpha should change after setPanelExpansion",
mScrimBehindAlpha, mScrimBehind.getViewAlpha(), 0.01f);
- mScrimController.setPanelExpansion(0f);
+ mScrimController.setRawPanelExpansionFraction(0f);
finishAnimationsImmediately();
assertEquals("Scrim alpha should change after setPanelExpansion",
@@ -716,21 +723,21 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void panelExpansionAffectsAlpha() {
- mScrimController.setPanelExpansion(0f);
- mScrimController.setPanelExpansion(0.5f);
+ mScrimController.setRawPanelExpansionFraction(0f);
+ mScrimController.setRawPanelExpansionFraction(0.5f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
final float scrimAlpha = mScrimBehind.getViewAlpha();
reset(mScrimBehind);
mScrimController.setExpansionAffectsAlpha(false);
- mScrimController.setPanelExpansion(0.8f);
+ mScrimController.setRawPanelExpansionFraction(0.8f);
verifyZeroInteractions(mScrimBehind);
assertEquals("Scrim opacity shouldn't change when setExpansionAffectsAlpha "
+ "is false", scrimAlpha, mScrimBehind.getViewAlpha(), 0.01f);
mScrimController.setExpansionAffectsAlpha(true);
- mScrimController.setPanelExpansion(0.1f);
+ mScrimController.setRawPanelExpansionFraction(0.1f);
finishAnimationsImmediately();
Assert.assertNotEquals("Scrim opacity should change when setExpansionAffectsAlpha "
+ "is true", scrimAlpha, mScrimBehind.getViewAlpha(), 0.01f);
@@ -740,7 +747,7 @@ public class ScrimControllerTest extends SysuiTestCase {
public void transitionToUnlockedFromOff() {
// Simulate unlock with fingerprint without AOD
mScrimController.transitionTo(ScrimState.OFF);
- mScrimController.setPanelExpansion(0f);
+ mScrimController.setRawPanelExpansionFraction(0f);
finishAnimationsImmediately();
mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -749,14 +756,12 @@ public class ScrimControllerTest extends SysuiTestCase {
// All scrims should be transparent at the end of fade transition.
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
- mScrimBehind, TRANSPARENT,
- mScrimForBubble, TRANSPARENT));
+ mScrimBehind, TRANSPARENT));
// Make sure at the very end of the animation, we're reset to transparent
assertScrimTinted(Map.of(
mScrimInFront, false,
- mScrimBehind, true,
- mScrimForBubble, false
+ mScrimBehind, true
));
}
@@ -764,7 +769,7 @@ public class ScrimControllerTest extends SysuiTestCase {
public void transitionToUnlockedFromAod() {
// Simulate unlock with fingerprint
mScrimController.transitionTo(ScrimState.AOD);
- mScrimController.setPanelExpansion(0f);
+ mScrimController.setRawPanelExpansionFraction(0f);
finishAnimationsImmediately();
mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -796,8 +801,7 @@ public class ScrimControllerTest extends SysuiTestCase {
+ mScrimInFront.getViewAlpha(), mScrimInFront.getViewAlpha() > 0);
assertScrimTinted(Map.of(
mScrimInFront, true,
- mScrimBehind, true,
- mScrimForBubble, true
+ mScrimBehind, true
));
Assert.assertSame("Scrim should be visible during transition.",
mScrimVisibility, OPAQUE);
@@ -944,7 +948,7 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void testConservesExpansionOpacityAfterTransition() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
- mScrimController.setPanelExpansion(0.5f);
+ mScrimController.setRawPanelExpansionFraction(0.5f);
finishAnimationsImmediately();
final float expandedAlpha = mScrimBehind.getViewAlpha();
@@ -1050,8 +1054,6 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimInFront.getDefaultFocusHighlightEnabled());
Assert.assertFalse("Scrim shouldn't have focus highlight",
mScrimBehind.getDefaultFocusHighlightEnabled());
- Assert.assertFalse("Scrim shouldn't have focus highlight",
- mScrimForBubble.getDefaultFocusHighlightEnabled());
}
@Test
@@ -1061,7 +1063,7 @@ public class ScrimControllerTest extends SysuiTestCase {
HashSet<ScrimState> regularStates = new HashSet<>(Arrays.asList(
ScrimState.UNINITIALIZED, ScrimState.KEYGUARD, ScrimState.BOUNCER,
ScrimState.BOUNCER_SCRIMMED, ScrimState.BRIGHTNESS_MIRROR, ScrimState.UNLOCKED,
- ScrimState.BUBBLE_EXPANDED, ScrimState.SHADE_LOCKED, ScrimState.AUTH_SCRIMMED));
+ ScrimState.SHADE_LOCKED, ScrimState.AUTH_SCRIMMED, ScrimState.AUTH_SCRIMMED_SHADE));
for (ScrimState state : ScrimState.values()) {
if (!lowPowerModeStates.contains(state) && !regularStates.contains(state)) {
@@ -1073,11 +1075,31 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void testScrimsOpaque_whenShadeFullyExpanded() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
- mScrimController.setPanelExpansion(1);
+ mScrimController.setRawPanelExpansionFraction(1);
+ // notifications scrim alpha change require calling setQsPosition
+ mScrimController.setQsPosition(0, 300);
+ finishAnimationsImmediately();
+
+ assertEquals("Behind scrim should be opaque",
+ mScrimBehind.getViewAlpha(), 1, 0.0);
+ assertEquals("Notifications scrim should be opaque",
+ mNotificationsScrim.getViewAlpha(), 1, 0.0);
+ }
+
+ @Test
+ public void testAuthScrim_notifScrimOpaque_whenShadeFullyExpanded() {
+ // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
+ // with the camera app occluding the keyguard)
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.setRawPanelExpansionFraction(1);
// notifications scrim alpha change require calling setQsPosition
mScrimController.setQsPosition(0, 300);
finishAnimationsImmediately();
+ // WHEN the user triggers the auth bouncer
+ mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
+ finishAnimationsImmediately();
+
assertEquals("Behind scrim should be opaque",
mScrimBehind.getViewAlpha(), 1, 0.0);
assertEquals("Notifications scrim should be opaque",
@@ -1085,9 +1107,27 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ public void testAuthScrimKeyguard() {
+ // GIVEN device is on the keyguard
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ finishAnimationsImmediately();
+
+ // WHEN the user triggers the auth bouncer
+ mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
+ finishAnimationsImmediately();
+
+ // THEN the front scrim is updated and the KEYGUARD scrims are the same as the
+ // KEYGUARD scrim state
+ assertScrimAlpha(Map.of(
+ mScrimInFront, SEMI_TRANSPARENT,
+ mScrimBehind, SEMI_TRANSPARENT,
+ mNotificationsScrim, TRANSPARENT));
+ }
+
+ @Test
public void testScrimsVisible_whenShadeVisible() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
- mScrimController.setPanelExpansion(0.3f);
+ mScrimController.setRawPanelExpansionFraction(0.3f);
// notifications scrim alpha change require calling setQsPosition
mScrimController.setQsPosition(0, 300);
finishAnimationsImmediately();
@@ -1122,7 +1162,7 @@ public class ScrimControllerTest extends SysuiTestCase {
public void testScrimsVisible_whenShadeVisible_clippingQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.transitionTo(ScrimState.UNLOCKED);
- mScrimController.setPanelExpansion(0.3f);
+ mScrimController.setRawPanelExpansionFraction(0.3f);
// notifications scrim alpha change require calling setQsPosition
mScrimController.setQsPosition(0.5f, 300);
finishAnimationsImmediately();
@@ -1148,7 +1188,7 @@ public class ScrimControllerTest extends SysuiTestCase {
public void testNotificationScrimTransparent_whenOnLockscreen() {
mScrimController.transitionTo(ScrimState.KEYGUARD);
// even if shade is not pulled down, panel has expansion of 1 on the lockscreen
- mScrimController.setPanelExpansion(1);
+ mScrimController.setRawPanelExpansionFraction(1);
mScrimController.setQsPosition(0f, /*qs panel bottom*/ 0);
assertScrimAlpha(Map.of(
@@ -1158,7 +1198,7 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void testNotificationScrimVisible_afterOpeningShadeFromLockscreen() {
- mScrimController.setPanelExpansion(1);
+ mScrimController.setRawPanelExpansionFraction(1);
mScrimController.transitionTo(ScrimState.SHADE_LOCKED);
finishAnimationsImmediately();
@@ -1201,11 +1241,11 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void testNotificationTransparency_followsTransitionToFullShade() {
mScrimController.transitionTo(ScrimState.SHADE_LOCKED);
- mScrimController.setPanelExpansion(1.0f);
+ mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
float shadeLockedAlpha = mNotificationsScrim.getViewAlpha();
mScrimController.transitionTo(ScrimState.KEYGUARD);
- mScrimController.setPanelExpansion(1.0f);
+ mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
float keyguardAlpha = mNotificationsScrim.getViewAlpha();
@@ -1225,7 +1265,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
- mScrimController.setPanelExpansion(expansion);
+ mScrimController.setRawPanelExpansionFraction(expansion);
finishAnimationsImmediately();
// alpha is not changing linearly thus 0.2 of leeway when asserting
assertEquals(expectedAlpha, mNotificationsScrim.getViewAlpha(), 0.2);
@@ -1249,21 +1289,16 @@ public class ScrimControllerTest extends SysuiTestCase {
return "behind";
} else if (scrim == mNotificationsScrim) {
return "notifications";
- } else if (scrim == mScrimForBubble) {
- return "bubble";
}
return "unknown_scrim";
}
/**
- * If {@link #mScrimForBubble} or {@link #mNotificationsScrim} is not passed in the map
+ * If {@link #mNotificationsScrim} is not passed in the map
* we assume it must be transparent
*/
private void assertScrimAlpha(Map<ScrimView, Integer> scrimToAlpha) {
// Check single scrim visibility.
- if (!scrimToAlpha.containsKey(mScrimForBubble)) {
- assertScrimAlpha(mScrimForBubble, TRANSPARENT);
- }
if (!scrimToAlpha.containsKey(mNotificationsScrim)) {
assertScrimAlpha(mNotificationsScrim, TRANSPARENT);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt
new file mode 100644
index 000000000000..a9e8164e8b87
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt
@@ -0,0 +1,87 @@
+package com.android.systemui.statusbar.phone
+
+import android.content.Context
+import android.content.res.Resources
+import android.test.suitebuilder.annotation.SmallTest
+import android.view.View
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ShadeInterpolation
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.qs.carrier.QSCarrierGroupController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+class SplitShadeHeaderControllerTest : SysuiTestCase() {
+
+ @Mock private lateinit var view: View
+ @Mock private lateinit var statusIcons: StatusIconContainer
+ @Mock private lateinit var statusBarIconController: StatusBarIconController
+ @Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController
+ @Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
+ @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var batteryMeterView: BatteryMeterView
+ @Mock private lateinit var batteryMeterViewController: BatteryMeterViewController
+ @Mock private lateinit var resources: Resources
+ @Mock private lateinit var context: Context
+ @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
+ var viewVisibility = View.GONE
+
+ private lateinit var splitShadeHeaderController: SplitShadeHeaderController
+
+ @Before
+ fun setup() {
+ whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon))
+ .thenReturn(batteryMeterView)
+ whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
+ whenever(statusIcons.context).thenReturn(context)
+ whenever(context.resources).thenReturn(resources)
+ whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any()))
+ .thenReturn(qsCarrierGroupControllerBuilder)
+ whenever(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
+ whenever(view.setVisibility(anyInt())).then {
+ viewVisibility = it.arguments[0] as Int
+ null
+ }
+ whenever(view.visibility).thenAnswer { _ -> viewVisibility }
+ splitShadeHeaderController = SplitShadeHeaderController(view, statusBarIconController,
+ qsCarrierGroupControllerBuilder, featureFlags, batteryMeterViewController)
+ }
+
+ @Test
+ fun setVisible_onlyInSplitShade() {
+ splitShadeHeaderController.splitShadeMode = true
+ splitShadeHeaderController.shadeExpanded = true
+ assertThat(viewVisibility).isEqualTo(View.VISIBLE)
+
+ splitShadeHeaderController.splitShadeMode = false
+ assertThat(viewVisibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun updateListeners_registersWhenVisible() {
+ splitShadeHeaderController.splitShadeMode = true
+ splitShadeHeaderController.shadeExpanded = true
+ verify(qsCarrierGroupController).setListening(true)
+ verify(statusBarIconController).addIconGroup(any())
+ }
+
+ @Test
+ fun shadeExpandedFraction_updatesAlpha() {
+ splitShadeHeaderController.splitShadeMode = true
+ splitShadeHeaderController.shadeExpanded = true
+ splitShadeHeaderController.shadeExpandedFraction = 0.5f
+ verify(view).setAlpha(ShadeInterpolation.getContentAlpha(0.5f))
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
new file mode 100644
index 000000000000..8555306bae04
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.systemui.statusbar.phone;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.StatusBarManager;
+import android.os.PowerManager;
+import android.os.Vibrator;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.testing.FakeMetricsLogger;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.DisableFlagsLogger;
+import com.android.systemui.statusbar.StatusBarStateControllerImpl;
+import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
+import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class StatusBarCommandQueueCallbacksTest extends SysuiTestCase {
+ @Mock private StatusBar mStatusBar;
+ @Mock private ShadeController mShadeController;
+ @Mock private CommandQueue mCommandQueue;
+ @Mock private NotificationPanelViewController mNotificationPanelViewController;
+ @Mock private LegacySplitScreen mLegacySplitScreen;
+ @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
+ private final MetricsLogger mMetricsLogger = new FakeMetricsLogger();
+ @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock private KeyguardStateController mKeyguardStateController;
+ @Mock private HeadsUpManagerPhone mHeadsUpManager;
+ @Mock private WakefulnessLifecycle mWakefulnessLifecycle;
+ @Mock private DeviceProvisionedController mDeviceProvisionedController;
+ @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock private AssistManager mAssistManager;
+ @Mock private DozeServiceHost mDozeServiceHost;
+ @Mock private StatusBarStateControllerImpl mStatusBarStateController;
+ @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
+ @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
+ @Mock private PowerManager mPowerManager;
+ @Mock private VibratorHelper mVibratorHelper;
+ @Mock private Vibrator mVibrator;
+ @Mock private LightBarController mLightBarController;
+
+ StatusBarCommandQueueCallbacks mSbcqCallbacks;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mSbcqCallbacks = new StatusBarCommandQueueCallbacks(
+ mStatusBar,
+ mContext,
+ mContext.getResources(),
+ mShadeController,
+ mCommandQueue,
+ mNotificationPanelViewController,
+ Optional.of(mLegacySplitScreen),
+ mRemoteInputQuickSettingsDisabler,
+ mMetricsLogger,
+ mKeyguardUpdateMonitor,
+ mKeyguardStateController,
+ mHeadsUpManager,
+ mWakefulnessLifecycle,
+ mDeviceProvisionedController,
+ mStatusBarKeyguardViewManager,
+ mAssistManager,
+ mDozeServiceHost,
+ mStatusBarStateController,
+ mNotificationShadeWindowView,
+ mNotificationStackScrollLayoutController,
+ mPowerManager,
+ mVibratorHelper,
+ Optional.of(mVibrator),
+ mLightBarController,
+ new DisableFlagsLogger(),
+ DEFAULT_DISPLAY);
+
+ when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
+ when(mRemoteInputQuickSettingsDisabler.adjustDisableFlags(anyInt()))
+ .thenAnswer((Answer<Integer>) invocation -> invocation.getArgument(0));
+ }
+
+ @Test
+ public void testDisableNotificationShade() {
+ when(mStatusBar.getDisabled1()).thenReturn(StatusBarManager.DISABLE_NONE);
+ when(mStatusBar.getDisabled2()).thenReturn(StatusBarManager.DISABLE_NONE);
+ when(mCommandQueue.panelsEnabled()).thenReturn(false);
+ mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
+ StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
+
+ verify(mStatusBar).updateQsExpansionEnabled();
+ verify(mShadeController).animateCollapsePanels();
+
+ // Trying to open it does nothing.
+ mSbcqCallbacks.animateExpandNotificationsPanel();
+ verify(mNotificationPanelViewController, never()).expandWithoutQs();
+ mSbcqCallbacks.animateExpandSettingsPanel(null);
+ verify(mNotificationPanelViewController, never()).expand(anyBoolean());
+ }
+
+ @Test
+ public void testEnableNotificationShade() {
+ when(mStatusBar.getDisabled1()).thenReturn(StatusBarManager.DISABLE_NONE);
+ when(mStatusBar.getDisabled2()).thenReturn(StatusBarManager.DISABLE2_NOTIFICATION_SHADE);
+ when(mCommandQueue.panelsEnabled()).thenReturn(true);
+ mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
+ StatusBarManager.DISABLE2_NONE, false);
+ verify(mStatusBar).updateQsExpansionEnabled();
+ verify(mShadeController, never()).animateCollapsePanels();
+
+ // Can now be opened.
+ mSbcqCallbacks.animateExpandNotificationsPanel();
+ verify(mNotificationPanelViewController).expandWithoutQs();
+ mSbcqCallbacks.animateExpandSettingsPanel(null);
+ verify(mNotificationPanelViewController).expandWithQs();
+ }
+
+ @Test
+ public void testSuppressAmbientDisplay_suppress() {
+ mSbcqCallbacks.suppressAmbientDisplay(true);
+ verify(mDozeServiceHost).setDozeSuppressed(true);
+ }
+
+ @Test
+ public void testSuppressAmbientDisplay_unsuppress() {
+ mSbcqCallbacks.suppressAmbientDisplay(false);
+ verify(mDozeServiceHost).setDozeSuppressed(false);
+ }
+
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index 10eb71f41c1e..e5158e74759c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -16,32 +16,54 @@
package com.android.systemui.statusbar.phone
+import android.content.Context
+import android.content.res.Configuration
import android.graphics.Rect
import android.test.suitebuilder.annotation.SmallTest
+import android.view.Display
import android.view.DisplayCutout
-import android.view.WindowMetrics
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.leak.RotationUtils
import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE
import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE
import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
import com.android.systemui.util.leak.RotationUtils.Rotation
+import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
+import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
@SmallTest
class StatusBarContentInsetsProviderTest : SysuiTestCase() {
+
@Mock private lateinit var dc: DisplayCutout
- @Mock private lateinit var windowMetrics: WindowMetrics
+ @Mock private lateinit var contextMock: Context
+ @Mock private lateinit var display: Display
+ private lateinit var configurationController: ConfigurationController
+
+ private val configuration = Configuration()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ `when`(contextMock.display).thenReturn(display)
+
+ context.ensureTestableResources()
+ `when`(contextMock.resources).thenReturn(context.resources)
+ `when`(contextMock.resources.configuration).thenReturn(configuration)
+ `when`(contextMock.createConfigurationContext(any())).thenAnswer {
+ context.createConfigurationContext(it.arguments[0] as Configuration)
+ }
+
+ configurationController = ConfigurationControllerImpl(contextMock)
}
@Test
@@ -55,18 +77,18 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
val chipWidth = 30
val dotWidth = 10
- `when`(windowMetrics.bounds).thenReturn(screenBounds)
-
var isRtl = false
var targetRotation = ROTATION_NONE
var bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
null,
- windowMetrics,
+ screenBounds,
sbHeightPortrait,
minLeftPadding,
- minRightPadding)
+ minRightPadding,
+ isRtl,
+ dotWidth)
var chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
/* 1080 - 20 (rounded corner) - 30 (chip),
@@ -92,10 +114,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
currentRotation,
targetRotation,
dc,
- windowMetrics,
+ screenBounds,
sbHeightLandscape,
minLeftPadding,
- minRightPadding)
+ minRightPadding,
+ isRtl,
+ dotWidth)
chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
/* 2160 - 20 (rounded corner) - 30 (chip),
@@ -126,8 +150,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
val sbHeightPortrait = 100
val sbHeightLandscape = 60
val currentRotation = ROTATION_NONE
+ val isRtl = false
+ val dotWidth = 10
- `when`(windowMetrics.bounds).thenReturn(screenBounds)
`when`(dc.boundingRects).thenReturn(listOf(dcBounds))
// THEN rotations which share a short side should use the greater value between rounded
@@ -142,10 +167,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
currentRotation,
targetRotation,
dc,
- windowMetrics,
+ screenBounds,
sbHeightPortrait,
minLeftPadding,
- minRightPadding)
+ minRightPadding,
+ isRtl,
+ dotWidth)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -159,10 +186,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
currentRotation,
targetRotation,
dc,
- windowMetrics,
+ screenBounds,
sbHeightLandscape,
minLeftPadding,
- minRightPadding)
+ minRightPadding,
+ isRtl,
+ dotWidth)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -178,10 +207,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
currentRotation,
targetRotation,
dc,
- windowMetrics,
+ screenBounds,
sbHeightPortrait,
minLeftPadding,
- minRightPadding)
+ minRightPadding,
+ isRtl,
+ dotWidth)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -189,17 +220,19 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
targetRotation = ROTATION_SEASCAPE
expectedBounds = Rect(minLeftPadding,
0,
- screenBounds.height() - dcBounds.height(),
+ screenBounds.height() - dcBounds.height() - dotWidth,
sbHeightLandscape)
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
dc,
- windowMetrics,
+ screenBounds,
sbHeightLandscape,
minLeftPadding,
- minRightPadding)
+ minRightPadding,
+ isRtl,
+ dotWidth)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
}
@@ -218,8 +251,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
val sbHeightPortrait = 100
val sbHeightLandscape = 60
val currentRotation = ROTATION_NONE
+ val isRtl = false
+ val dotWidth = 10
- `when`(windowMetrics.bounds).thenReturn(screenBounds)
`when`(dc.boundingRects).thenReturn(listOf(dcBounds))
// THEN only the landscape/seascape rotations should avoid the cutout area because of the
@@ -234,10 +268,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
currentRotation,
targetRotation,
dc,
- windowMetrics,
+ screenBounds,
sbHeightPortrait,
minLeftPadding,
- minRightPadding)
+ minRightPadding,
+ isRtl,
+ dotWidth)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -251,10 +287,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
currentRotation,
targetRotation,
dc,
- windowMetrics,
+ screenBounds,
sbHeightLandscape,
minLeftPadding,
- minRightPadding)
+ minRightPadding,
+ isRtl,
+ dotWidth)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -268,27 +306,31 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
currentRotation,
targetRotation,
dc,
- windowMetrics,
+ screenBounds,
sbHeightPortrait,
minLeftPadding,
- minRightPadding)
+ minRightPadding,
+ isRtl,
+ dotWidth)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
targetRotation = ROTATION_SEASCAPE
expectedBounds = Rect(minLeftPadding,
0,
- screenBounds.height() - dcBounds.height(),
+ screenBounds.height() - dcBounds.height() - dotWidth,
sbHeightLandscape)
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
dc,
- windowMetrics,
+ screenBounds,
sbHeightLandscape,
minLeftPadding,
- minRightPadding)
+ minRightPadding,
+ isRtl,
+ dotWidth)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
}
@@ -302,8 +344,8 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
val minRightPadding = 20
val sbHeightPortrait = 100
val sbHeightLandscape = 60
-
- `when`(windowMetrics.bounds).thenReturn(screenBounds)
+ val isRtl = false
+ val dotWidth = 10
// THEN content insets should only use rounded corner padding
var targetRotation = ROTATION_NONE
@@ -316,10 +358,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
currentRotation,
targetRotation,
null, /* no cutout */
- windowMetrics,
+ screenBounds,
sbHeightPortrait,
minLeftPadding,
- minRightPadding)
+ minRightPadding,
+ isRtl,
+ dotWidth)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
targetRotation = ROTATION_LANDSCAPE
@@ -332,10 +376,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
currentRotation,
targetRotation,
null, /* no cutout */
- windowMetrics,
+ screenBounds,
sbHeightLandscape,
minLeftPadding,
- minRightPadding)
+ minRightPadding,
+ isRtl,
+ dotWidth)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
targetRotation = ROTATION_UPSIDE_DOWN
@@ -348,10 +394,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
currentRotation,
targetRotation,
null, /* no cutout */
- windowMetrics,
+ screenBounds,
sbHeightPortrait,
minLeftPadding,
- minRightPadding)
+ minRightPadding,
+ isRtl,
+ dotWidth)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
targetRotation = ROTATION_LANDSCAPE
@@ -364,10 +412,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
currentRotation,
targetRotation,
null, /* no cutout */
- windowMetrics,
+ screenBounds,
sbHeightLandscape,
minLeftPadding,
- minRightPadding)
+ minRightPadding,
+ isRtl,
+ dotWidth)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
}
@@ -381,8 +431,9 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
val sbHeightPortrait = 100
val sbHeightLandscape = 60
val currentRotation = ROTATION_NONE
+ val isRtl = false
+ val dotWidth = 10
- `when`(windowMetrics.bounds).thenReturn(screenBounds)
`when`(dc.boundingRects).thenReturn(listOf(dcBounds))
// THEN left should be set to the display cutout width, and right should use the minRight
@@ -396,14 +447,77 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
currentRotation,
targetRotation,
dc,
- windowMetrics,
+ screenBounds,
sbHeightPortrait,
minLeftPadding,
- minRightPadding)
+ minRightPadding,
+ isRtl,
+ dotWidth)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
}
+ @Test
+ fun testDisplayChanged_returnsUpdatedInsets() {
+ // GIVEN: get insets on the first display and switch to the second display
+ val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+ mock(DumpManager::class.java))
+
+ givenDisplay(
+ screenBounds = Rect(0, 0, 1080, 2160),
+ displayUniqueId = "1"
+ )
+ val firstDisplayInsets = provider.getStatusBarContentInsetsForRotation(ROTATION_NONE)
+ givenDisplay(
+ screenBounds = Rect(0, 0, 800, 600),
+ displayUniqueId = "2"
+ )
+ configurationController.onConfigurationChanged(configuration)
+
+ // WHEN: get insets on the second display
+ val secondDisplayInsets = provider.getStatusBarContentInsetsForRotation(ROTATION_NONE)
+
+ // THEN: insets are updated
+ assertThat(firstDisplayInsets).isNotEqualTo(secondDisplayInsets)
+ }
+
+ @Test
+ fun testDisplayChangedAndReturnedBack_returnsTheSameInsets() {
+ // GIVEN: get insets on the first display, switch to the second display,
+ // get insets and switch back
+ val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+ mock(DumpManager::class.java))
+ givenDisplay(
+ screenBounds = Rect(0, 0, 1080, 2160),
+ displayUniqueId = "1"
+ )
+ val firstDisplayInsetsFirstCall = provider
+ .getStatusBarContentInsetsForRotation(ROTATION_NONE)
+ givenDisplay(
+ screenBounds = Rect(0, 0, 800, 600),
+ displayUniqueId = "2"
+ )
+ configurationController.onConfigurationChanged(configuration)
+ provider.getStatusBarContentInsetsForRotation(ROTATION_NONE)
+ givenDisplay(
+ screenBounds = Rect(0, 0, 1080, 2160),
+ displayUniqueId = "1"
+ )
+ configurationController.onConfigurationChanged(configuration)
+
+ // WHEN: get insets on the first display again
+ val firstDisplayInsetsSecondCall = provider
+ .getStatusBarContentInsetsForRotation(ROTATION_NONE)
+
+ // THEN: insets for the first and second calls for the first display are the same
+ assertThat(firstDisplayInsetsFirstCall).isEqualTo(firstDisplayInsetsSecondCall)
+ }
+
+ private fun givenDisplay(screenBounds: Rect, displayUniqueId: String) {
+ `when`(display.uniqueId).thenReturn(displayUniqueId)
+ configuration.windowConfiguration.maxBounds = screenBounds
+ }
+
private fun assertRects(
expected: Rect,
actual: Rect,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index e3263d4ca6b3..0f1c40bacb7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -30,8 +30,8 @@ import android.widget.LinearLayout;
import androidx.test.filters.SmallTest;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 5ed69853e40f..2d944aa707bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -24,7 +23,6 @@ import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -45,8 +43,8 @@ import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
@@ -62,7 +60,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
+import dagger.Lazy;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -92,8 +90,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock
private KeyguardBypassController mBypassController;
@Mock
- private FaceAuthScreenBrightnessController mFaceAuthScreenBrightnessController;
- @Mock
private KeyguardBouncer.Factory mKeyguardBouncerFactory;
@Mock
private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
@@ -105,6 +101,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor;
@Mock
private KeyguardMessageArea mKeyguardMessageArea;
+ @Mock
+ private Lazy<ShadeController> mShadeController;
private WakefulnessLifecycle mWakefulnessLifecycle;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -116,9 +114,12 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
any(ViewGroup.class),
any(KeyguardBouncer.BouncerExpansionCallback.class)))
.thenReturn(mBouncer);
-
+ when(mStatusBar.getBouncerContainer()).thenReturn(mContainer);
when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
- mWakefulnessLifecycle = new WakefulnessLifecycle(getContext(), null);
+ mWakefulnessLifecycle = new WakefulnessLifecycle(
+ getContext(),
+ null,
+ mock(DumpManager.class));
mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager(
getContext(),
mViewMediatorCallback,
@@ -130,13 +131,13 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mock(DockManager.class),
mock(NotificationShadeWindowController.class),
mKeyguardStateController,
- Optional.of(mFaceAuthScreenBrightnessController),
mock(NotificationMediaManager.class),
mKeyguardBouncerFactory,
mWakefulnessLifecycle,
mUnlockedScreenOffAnimationController,
- mKeyguardMessageAreaFactory);
- mStatusBarKeyguardViewManager.registerStatusBar(mStatusBar, mContainer,
+ mKeyguardMessageAreaFactory,
+ mShadeController);
+ mStatusBarKeyguardViewManager.registerStatusBar(mStatusBar,
mNotificationPanelView, mBiometrucUnlockController,
mNotificationContainer, mBypassController);
mStatusBarKeyguardViewManager.show(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 37a6d21b690e..72a3d664a6ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -53,15 +53,14 @@ import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -111,8 +110,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
@Mock
private NotificationRemoteInputManager mRemoteInputManager;
@Mock
- private RemoteInputController mRemoteInputController;
- @Mock
private StatusBar mStatusBar;
@Mock
private KeyguardStateController mKeyguardStateController;
@@ -153,8 +150,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
-
when(mContentIntent.isActivity()).thenReturn(true);
when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1));
when(mContentIntent.getIntent()).thenReturn(mContentIntentInner);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index ce45f2618f2b..4e6b0a26609b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -31,12 +31,13 @@ import android.testing.TestableLooper.RunWithLooper;
import androidx.test.filters.SmallTest;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.logging.testing.FakeMetricsLogger;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.ForegroundServiceNotificationListener;
import com.android.systemui.InitController;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -46,13 +47,12 @@ import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
-import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -60,6 +60,7 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
@@ -67,14 +68,10 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import java.util.ArrayList;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper()
public class StatusBarNotificationPresenterTest extends SysuiTestCase {
-
-
private StatusBarNotificationPresenter mStatusBarNotificationPresenter;
private NotificationInterruptStateProvider mNotificationInterruptStateProvider =
mock(NotificationInterruptStateProvider.class);
@@ -87,29 +84,16 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
@Before
public void setup() {
- NotificationRemoteInputManager notificationRemoteInputManager =
- mock(NotificationRemoteInputManager.class);
- when(notificationRemoteInputManager.getController())
- .thenReturn(mock(RemoteInputController.class));
mMetricsLogger = new FakeMetricsLogger();
- mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+ LockscreenGestureLogger lockscreenGestureLogger = new LockscreenGestureLogger(
+ mMetricsLogger);
mCommandQueue = new CommandQueue(mContext);
mDependency.injectTestDependency(StatusBarStateController.class,
mock(SysuiStatusBarStateController.class));
mDependency.injectTestDependency(ShadeController.class, mShadeController);
- mDependency.injectTestDependency(NotificationRemoteInputManager.class,
- notificationRemoteInputManager);
- mDependency.injectMockDependency(NotificationViewHierarchyManager.class);
mDependency.injectMockDependency(NotificationRemoteInputManager.Callback.class);
- mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
- mDependency.injectMockDependency(NotificationMediaManager.class);
- mDependency.injectMockDependency(VisualStabilityManager.class);
- mDependency.injectMockDependency(NotificationGutsManager.class);
mDependency.injectMockDependency(NotificationShadeWindowController.class);
mDependency.injectMockDependency(ForegroundServiceNotificationListener.class);
- NotificationEntryManager entryManager =
- mDependency.injectMockDependency(NotificationEntryManager.class);
- when(entryManager.getActiveNotificationsForCurrentUser()).thenReturn(new ArrayList<>());
NotificationShadeWindowView notificationShadeWindowView =
mock(NotificationShadeWindowView.class);
@@ -127,10 +111,24 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
mock(DozeScrimController.class), mock(ScrimController.class),
mock(NotificationShadeWindowController.class), mock(DynamicPrivacyController.class),
mock(KeyguardStateController.class),
- mock(KeyguardIndicationController.class), mStatusBar,
+ mock(KeyguardIndicationController.class),
+ mock(FeatureFlags.class),
+ mStatusBar,
mock(ShadeControllerImpl.class), mock(LockscreenShadeTransitionController.class),
- mCommandQueue, mInitController,
- mNotificationInterruptStateProvider);
+ mCommandQueue,
+ mock(NotificationViewHierarchyManager.class),
+ mock(NotificationLockscreenUserManager.class),
+ mock(SysuiStatusBarStateController.class),
+ mock(NotifShadeEventSource.class),
+ mock(NotificationEntryManager.class),
+ mock(NotificationMediaManager.class),
+ mock(NotificationGutsManager.class),
+ mock(KeyguardUpdateMonitor.class),
+ lockscreenGestureLogger,
+ mInitController,
+ mNotificationInterruptStateProvider,
+ mock(NotificationRemoteInputManager.class),
+ mock(ConfigurationController.class));
mInitController.executePostInitTasks();
ArgumentCaptor<NotificationInterruptSuppressor> suppressorCaptor =
ArgumentCaptor.forClass(NotificationInterruptSuppressor.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 6051c5615f8d..f14b1266dd2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.phone;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
-import static android.view.Display.DEFAULT_DISPLAY;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -40,7 +39,7 @@ import static org.mockito.Mockito.when;
import android.app.IWallpaperManager;
import android.app.Notification;
-import android.app.StatusBarManager;
+import android.app.WallpaperManager;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -78,28 +77,28 @@ import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.InitController;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
+import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.demomode.DemoModeController;
-import com.android.systemui.keyguard.DismissCallbackRegistry;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.ScreenPinningRequest;
-import com.android.systemui.settings.brightness.BrightnessSlider;
+import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationListener;
@@ -109,22 +108,20 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
+import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.charging.WiredChargingRippleController;
+import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
@@ -142,11 +139,16 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
+import com.android.systemui.unfold.UnfoldTransitionWallpaperController;
+import com.android.systemui.unfold.config.UnfoldTransitionConfig;
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
+import com.android.systemui.util.WallpaperController;
import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.concurrency.MessageRouterImpl;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.wmshell.BubblesManager;
@@ -164,8 +166,6 @@ import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.util.Optional;
-import javax.inject.Provider;
-
import dagger.Lazy;
@SmallTest
@@ -180,7 +180,6 @@ public class StatusBarTest extends SysuiTestCase {
@Mock private NotificationsController mNotificationsController;
@Mock private LightBarController mLightBarController;
- @Mock private StatusBarSignalPolicy mStatusBarSignalPolicy;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock private KeyguardStateController mKeyguardStateController;
@Mock private KeyguardIndicationController mKeyguardIndicationController;
@@ -201,12 +200,10 @@ public class StatusBarTest extends SysuiTestCase {
@Mock private KeyguardViewMediator mKeyguardViewMediator;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private NotificationRemoteInputManager mRemoteInputManager;
- @Mock private RemoteInputController mRemoteInputController;
@Mock private StatusBarStateControllerImpl mStatusBarStateController;
@Mock private BatteryController mBatteryController;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private StatusBarNotificationPresenter mNotificationPresenter;
- @Mock private NotificationEntryListener mEntryListener;
@Mock private NotificationFilter mNotificationFilter;
@Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
@Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
@@ -214,10 +211,11 @@ public class StatusBarTest extends SysuiTestCase {
@Mock private NotificationShadeWindowView mNotificationShadeWindowView;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private AssistManager mAssistManager;
+ @Mock private NotifShadeEventSource mNotifShadeEventSource;
+ @Mock private NotificationEntryManager mNotificationEntryManager;
@Mock private NotificationGutsManager mNotificationGutsManager;
@Mock private NotificationMediaManager mNotificationMediaManager;
@Mock private NavigationBarController mNavigationBarController;
- @Mock private AccessibilityFloatingMenuController mAccessibilityFloatingMenuController;
@Mock private BypassHeadsUpNotifier mBypassHeadsUpNotifier;
@Mock private SysuiColorExtractor mColorExtractor;
@Mock private ColorExtractor.GradientColors mGradientColors;
@@ -229,7 +227,6 @@ public class StatusBarTest extends SysuiTestCase {
@Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager;
@Mock private UserSwitcherController mUserSwitcherController;
@Mock private NetworkController mNetworkController;
- @Mock private VibratorHelper mVibratorHelper;
@Mock private BubblesManager mBubblesManager;
@Mock private Bubbles mBubbles;
@Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@@ -237,26 +234,24 @@ public class StatusBarTest extends SysuiTestCase {
@Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
@Mock private DozeParameters mDozeParameters;
@Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
+ @Mock private LockscreenGestureLogger mLockscreenGestureLogger;
@Mock private LockscreenWallpaper mLockscreenWallpaper;
@Mock private DozeServiceHost mDozeServiceHost;
@Mock private ViewMediatorCallback mKeyguardVieMediatorCallback;
- @Mock private KeyguardLiftController mKeyguardLiftController;
@Mock private VolumeComponent mVolumeComponent;
@Mock private CommandQueue mCommandQueue;
- @Mock private Provider<StatusBarComponent.Builder> mStatusBarComponentBuilderProvider;
- @Mock private StatusBarComponent.Builder mStatusBarComponentBuilder;
+ @Mock private CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger;
+ @Mock private StatusBarComponent.Factory mStatusBarComponentFactory;
@Mock private StatusBarComponent mStatusBarComponent;
@Mock private PluginManager mPluginManager;
@Mock private LegacySplitScreen mLegacySplitScreen;
- @Mock private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
+ @Mock private StatusBarWindowView mStatusBarWindowView;
@Mock private LightsOutNotifController mLightsOutNotifController;
@Mock private ViewMediatorCallback mViewMediatorCallback;
- @Mock private DismissCallbackRegistry mDismissCallbackRegistry;
@Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
@Mock private ScreenPinningRequest mScreenPinningRequest;
@Mock private StatusBarNotificationActivityStarter.Builder
mStatusBarNotificationActivityStarterBuilder;
- @Mock private DarkIconDispatcher mDarkIconDispatcher;
@Mock private PluginDependencyProvider mPluginDependencyProvider;
@Mock private KeyguardDismissUtil mKeyguardDismissUtil;
@Mock private ExtensionController mExtensionController;
@@ -264,20 +259,33 @@ public class StatusBarTest extends SysuiTestCase {
@Mock private PhoneStatusBarPolicy mPhoneStatusBarPolicy;
@Mock private DemoModeController mDemoModeController;
@Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
- @Mock private BrightnessSlider.Factory mBrightnessSliderFactory;
- @Mock private WiredChargingRippleController mWiredChargingRippleController;
+ @Mock private BrightnessSliderController.Factory mBrightnessSliderFactory;
+ @Mock private UnfoldTransitionConfig mUnfoldTransitionConfig;
+ @Mock private Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimationLazy;
+ @Mock private Lazy<NaturalRotationUnfoldProgressProvider> mNaturalRotationProgressProvider;
+ @Mock private Lazy<UnfoldTransitionWallpaperController> mUnfoldWallpaperController;
+ @Mock private WallpaperController mWallpaperController;
@Mock private OngoingCallController mOngoingCallController;
@Mock private SystemStatusAnimationScheduler mAnimationScheduler;
@Mock private StatusBarLocationPublisher mLocationPublisher;
@Mock private StatusBarIconController mIconController;
@Mock private LockscreenShadeTransitionController mLockscreenTransitionController;
@Mock private FeatureFlags mFeatureFlags;
- @Mock private IWallpaperManager mWallpaperManager;
+ @Mock private WallpaperManager mWallpaperManager;
+ @Mock private IWallpaperManager mIWallpaperManager;
@Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ @Mock private TunerService mTunerService;
@Mock private StartingSurface mStartingSurface;
+ @Mock private OperatorNameViewController mOperatorNameViewController;
+ @Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
+ @Mock private PhoneStatusBarViewController.Factory mPhoneStatusBarViewControllerFactory;
+ @Mock private ActivityLaunchAnimator mActivityLaunchAnimator;
+ @Mock private DialogLaunchAnimator mDialogLaunchAnimator;
private ShadeController mShadeController;
- private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
+ private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
+ private FakeExecutor mUiBgExecutor = new FakeExecutor(mFakeSystemClock);
private InitController mInitController = new InitController();
@Before
@@ -310,7 +318,6 @@ public class StatusBarTest extends SysuiTestCase {
mContext.setTheme(R.style.Theme_SystemUI_LightWallpaper);
- when(mStackScroller.getController()).thenReturn(mStackScrollerController);
when(mStackScrollerController.getView()).thenReturn(mStackScroller);
when(mStackScrollerController.getNotificationListContainer()).thenReturn(
mNotificationListContainer);
@@ -332,10 +339,8 @@ public class StatusBarTest extends SysuiTestCase {
return null;
}).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
- when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
-
WakefulnessLifecycle wakefulnessLifecycle =
- new WakefulnessLifecycle(mContext, mWallpaperManager);
+ new WakefulnessLifecycle(mContext, mIWallpaperManager, mock(DumpManager.class));
wakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
wakefulnessLifecycle.dispatchFinishedWakingUp();
@@ -346,15 +351,17 @@ public class StatusBarTest extends SysuiTestCase {
when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper);
when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
- when(mStatusBarComponentBuilderProvider.get()).thenReturn(mStatusBarComponentBuilder);
- when(mStatusBarComponentBuilder.build()).thenReturn(mStatusBarComponent);
+ when(mStatusBarComponentFactory.create()).thenReturn(mStatusBarComponent);
when(mStatusBarComponent.getNotificationShadeWindowViewController()).thenReturn(
mNotificationShadeWindowViewController);
mShadeController = new ShadeControllerImpl(mCommandQueue,
mStatusBarStateController, mNotificationShadeWindowController,
mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class),
- () -> mStatusBar, () -> mAssistManager, Optional.of(mBubbles));
+ () -> Optional.of(mStatusBar), () -> mAssistManager, Optional.of(mBubbles));
+
+ when(mOperatorNameViewControllerFactory.create(any()))
+ .thenReturn(mOperatorNameViewController);
mStatusBar = new StatusBar(
mContext,
@@ -362,7 +369,6 @@ public class StatusBarTest extends SysuiTestCase {
mLightBarController,
mAutoHideController,
mKeyguardUpdateMonitor,
- mStatusBarSignalPolicy,
mPulseExpansionHandler,
mNotificationWakeUpCoordinator,
mKeyguardBypassController,
@@ -373,11 +379,8 @@ public class StatusBarTest extends SysuiTestCase {
new FalsingManagerFake(),
new FalsingCollectorFake(),
mBroadcastDispatcher,
- new RemoteInputQuickSettingsDisabler(
- mContext,
- configurationController,
- mCommandQueue
- ),
+ mNotifShadeEventSource,
+ mNotificationEntryManager,
mNotificationGutsManager,
notificationLogger,
mNotificationInterruptStateProvider,
@@ -393,36 +396,35 @@ public class StatusBarTest extends SysuiTestCase {
mNetworkController,
mBatteryController,
mColorExtractor,
- new ScreenLifecycle(),
+ new ScreenLifecycle(mock(DumpManager.class)),
wakefulnessLifecycle,
mStatusBarStateController,
- mVibratorHelper,
Optional.of(mBubblesManager),
Optional.of(mBubbles),
mVisualStabilityManager,
mDeviceProvisionedController,
mNavigationBarController,
- mAccessibilityFloatingMenuController,
() -> mAssistManager,
configurationController,
mNotificationShadeWindowController,
mDozeParameters,
mScrimController,
- mKeyguardLiftController,
mLockscreenWallpaperLazy,
+ mLockscreenGestureLogger,
mBiometricUnlockControllerLazy,
mDozeServiceHost,
mPowerManager, mScreenPinningRequest,
mDozeScrimController,
mVolumeComponent,
mCommandQueue,
- mStatusBarComponentBuilderProvider,
+ mCollapsedStatusBarFragmentLogger,
+ mStatusBarComponentFactory,
mPluginManager,
Optional.of(mLegacySplitScreen),
mLightsOutNotifController,
mStatusBarNotificationActivityStarterBuilder,
mShadeController,
- mSuperStatusBarViewFactory,
+ mStatusBarWindowView,
mStatusBarKeyguardViewManager,
mViewMediatorCallback,
mInitController,
@@ -431,15 +433,20 @@ public class StatusBarTest extends SysuiTestCase {
mKeyguardDismissUtil,
mExtensionController,
mUserInfoControllerImpl,
+ mOperatorNameViewControllerFactory,
+ mPhoneStatusBarViewControllerFactory,
mPhoneStatusBarPolicy,
mKeyguardIndicationController,
- mDismissCallbackRegistry,
mDemoModeController,
mNotificationShadeDepthControllerLazy,
mStatusBarTouchableRegionManager,
mNotificationIconAreaController,
mBrightnessSliderFactory,
- mWiredChargingRippleController,
+ mUnfoldTransitionConfig,
+ mUnfoldLightRevealOverlayAnimationLazy,
+ mUnfoldWallpaperController,
+ mNaturalRotationProgressProvider,
+ mWallpaperController,
mOngoingCallController,
mAnimationScheduler,
mLocationPublisher,
@@ -447,9 +454,17 @@ public class StatusBarTest extends SysuiTestCase {
mLockscreenTransitionController,
mFeatureFlags,
mKeyguardUnlockAnimationController,
+ new Handler(TestableLooper.get(this).getLooper()),
+ mMainExecutor,
+ new MessageRouterImpl(mMainExecutor),
+ mWallpaperManager,
mUnlockedScreenOffAnimationController,
- Optional.of(mStartingSurface));
- when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class),
+ Optional.of(mStartingSurface),
+ mTunerService,
+ mock(DumpManager.class),
+ mActivityLaunchAnimator,
+ mDialogLaunchAnimator);
+ when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class),
any(NotificationPanelViewController.class), any(BiometricUnlockController.class),
any(ViewGroup.class), any(KeyguardBypassController.class)))
.thenReturn(mStatusBarKeyguardViewManager);
@@ -711,7 +726,7 @@ public class StatusBarTest extends SysuiTestCase {
} catch (RemoteException e) {
fail();
}
- TestableLooper.get(this).processAllMessages();
+ mMainExecutor.runAllReady();
}
@Test
@@ -730,7 +745,7 @@ public class StatusBarTest extends SysuiTestCase {
} catch (RemoteException e) {
fail();
}
- TestableLooper.get(this).processAllMessages();
+ mMainExecutor.runAllReady();
}
@Test
@@ -748,32 +763,7 @@ public class StatusBarTest extends SysuiTestCase {
} catch (RemoteException e) {
fail();
}
- TestableLooper.get(this).processAllMessages();
- }
-
- @Test
- public void testDisableExpandStatusBar() {
- mStatusBar.setBarStateForTest(StatusBarState.SHADE);
- mStatusBar.setUserSetupForTest(true);
- when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
-
- when(mCommandQueue.panelsEnabled()).thenReturn(false);
- mStatusBar.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
- StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
- verify(mNotificationPanelViewController).setQsExpansionEnabledPolicy(false);
- mStatusBar.animateExpandNotificationsPanel();
- verify(mNotificationPanelViewController, never()).expand(anyBoolean());
- mStatusBar.animateExpandSettingsPanel(null);
- verify(mNotificationPanelViewController, never()).expand(anyBoolean());
-
- when(mCommandQueue.panelsEnabled()).thenReturn(true);
- mStatusBar.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
- StatusBarManager.DISABLE2_NONE, false);
- verify(mNotificationPanelViewController).setQsExpansionEnabledPolicy(true);
- mStatusBar.animateExpandNotificationsPanel();
- verify(mNotificationPanelViewController).expandWithoutQs();
- mStatusBar.animateExpandSettingsPanel(null);
- verify(mNotificationPanelViewController).expandWithQs();
+ mMainExecutor.runAllReady();
}
@Test
@@ -788,15 +778,6 @@ public class StatusBarTest extends SysuiTestCase {
}
@Test
- @RunWithLooper(setAsMainLooper = true)
- public void testUpdateKeyguardState_DoesNotCrash() {
- mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
- when(mLockscreenUserManager.getCurrentProfiles()).thenReturn(
- new SparseArray<>());
- mStatusBar.onStateChanged(StatusBarState.SHADE);
- }
-
- @Test
public void testFingerprintNotification_UpdatesScrims() {
mStatusBar.notifyBiometricAuthModeChanged();
verify(mScrimController).transitionTo(any(), any());
@@ -955,18 +936,6 @@ public class StatusBarTest extends SysuiTestCase {
}
@Test
- public void testSuppressAmbientDisplay_suppress() {
- mStatusBar.suppressAmbientDisplay(true);
- verify(mDozeServiceHost).setDozeSuppressed(true);
- }
-
- @Test
- public void testSuppressAmbientDisplay_unsuppress() {
- mStatusBar.suppressAmbientDisplay(false);
- verify(mDozeServiceHost).setDozeSuppressed(false);
- }
-
- @Test
public void testUpdateResources_updatesBouncer() {
mStatusBar.updateResources();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
new file mode 100644
index 000000000000..a8a33dabf1aa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.systemui.statusbar.phone
+
+import android.animation.Animator
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.StatusBarStateControllerImpl
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.settings.GlobalSettings
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.spy
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
+
+ private lateinit var controller: UnlockedScreenOffAnimationController
+ @Mock
+ private lateinit var keyguardViewMediator: KeyguardViewMediator
+ @Mock
+ private lateinit var dozeParameters: DozeParameters
+ @Mock
+ private lateinit var keyguardStateController: KeyguardStateController
+ @Mock
+ private lateinit var globalSettings: GlobalSettings
+ @Mock
+ private lateinit var statusbar: StatusBar
+ @Mock
+ private lateinit var lightRevealScrim: LightRevealScrim
+ @Mock
+ private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock
+ private lateinit var statusBarStateController: StatusBarStateControllerImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ controller = UnlockedScreenOffAnimationController(
+ context,
+ wakefulnessLifecycle,
+ statusBarStateController,
+ dagger.Lazy<KeyguardViewMediator> { keyguardViewMediator },
+ keyguardStateController,
+ dagger.Lazy<DozeParameters> { dozeParameters },
+ globalSettings
+ )
+ controller.initialize(statusbar, lightRevealScrim)
+ }
+
+ @Test
+ fun testAnimClearsEndListener() {
+ val keyguardView = View(context)
+ val animator = spy(keyguardView.animate())
+ val keyguardSpy = spy(keyguardView)
+ Mockito.`when`(keyguardSpy.animate()).thenReturn(animator)
+ val listener = ArgumentCaptor.forClass(Animator.AnimatorListener::class.java)
+ controller.animateInKeyguard(keyguardSpy, Runnable {})
+ Mockito.verify(animator).setListener(listener.capture())
+ // Verify that the listener is cleared when it ends
+ listener.value.onAnimationEnd(null)
+ Mockito.verify(animator).setListener(null)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index b7c4d0a7d223..3d2ff47e3cdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -33,12 +33,16 @@ import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.phone.StatusBarWindowController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.time.FakeSystemClock
@@ -48,8 +52,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.nullable
+import org.mockito.ArgumentMatchers.*
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.eq
@@ -59,6 +62,7 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import java.util.Optional
private const val CALL_UID = 900
@@ -80,9 +84,13 @@ class OngoingCallControllerTest : SysuiTestCase() {
private lateinit var controller: OngoingCallController
private lateinit var notifCollectionListener: NotifCollectionListener
+ @Mock private lateinit var mockFeatureFlags: FeatureFlags
+ @Mock private lateinit var mockSwipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler
@Mock private lateinit var mockOngoingCallListener: OngoingCallListener
@Mock private lateinit var mockActivityStarter: ActivityStarter
@Mock private lateinit var mockIActivityManager: IActivityManager
+ @Mock private lateinit var mockStatusBarWindowController: StatusBarWindowController
+ @Mock private lateinit var mockStatusBarStateController: StatusBarStateController
private lateinit var chipView: View
@@ -94,18 +102,22 @@ class OngoingCallControllerTest : SysuiTestCase() {
}
MockitoAnnotations.initMocks(this)
- val featureFlags = mock(FeatureFlags::class.java)
- `when`(featureFlags.isOngoingCallStatusBarChipEnabled).thenReturn(true)
+ `when`(mockFeatureFlags.isOngoingCallStatusBarChipEnabled).thenReturn(true)
val notificationCollection = mock(CommonNotifCollection::class.java)
controller = OngoingCallController(
notificationCollection,
- featureFlags,
+ mockFeatureFlags,
clock,
mockActivityStarter,
mainExecutor,
mockIActivityManager,
- OngoingCallLogger(uiEventLoggerFake))
+ OngoingCallLogger(uiEventLoggerFake),
+ DumpManager(),
+ Optional.of(mockStatusBarWindowController),
+ Optional.of(mockSwipeStatusBarAwayGestureHandler),
+ mockStatusBarStateController,
+ )
controller.init()
controller.addCallback(mockOngoingCallListener)
controller.setChipView(chipView)
@@ -131,6 +143,13 @@ class OngoingCallControllerTest : SysuiTestCase() {
}
@Test
+ fun onEntryUpdated_isOngoingCallNotif_windowControllerUpdated() {
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(true)
+ }
+
+ @Test
fun onEntryUpdated_notOngoingCallNotif_listenerNotNotified() {
notifCollectionListener.onEntryUpdated(createNotCallNotifEntry())
@@ -221,16 +240,14 @@ class OngoingCallControllerTest : SysuiTestCase() {
verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
}
- /** Regression test for b/201097913. */
@Test
- fun onEntryCleanUp_callNotifAddedThenRemoved_listenerNotified() {
+ fun onEntryUpdated_callNotifAddedThenRemoved_windowControllerUpdated() {
val ongoingCallNotifEntry = createOngoingCallNotifEntry()
notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
- reset(mockOngoingCallListener)
- notifCollectionListener.onEntryCleanUp(ongoingCallNotifEntry)
+ notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
- verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
+ verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(false)
}
/** Regression test for b/188491504. */
@@ -250,7 +267,6 @@ class OngoingCallControllerTest : SysuiTestCase() {
verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
}
-
@Test
fun onEntryRemoved_notifKeyDoesNotMatchOngoingCallNotif_listenerNotNotified() {
notifCollectionListener.onEntryAdded(createOngoingCallNotifEntry())
@@ -376,7 +392,7 @@ class OngoingCallControllerTest : SysuiTestCase() {
// Update the process to visible.
uidObserver.onUidStateChanged(CALL_UID, PROC_STATE_VISIBLE, 0, 0)
mainExecutor.advanceClockToLast()
- mainExecutor.runAllReady();
+ mainExecutor.runAllReady()
verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
}
@@ -397,7 +413,7 @@ class OngoingCallControllerTest : SysuiTestCase() {
// Update the process to invisible.
uidObserver.onUidStateChanged(CALL_UID, PROC_STATE_INVISIBLE, 0, 0)
mainExecutor.advanceClockToLast()
- mainExecutor.runAllReady();
+ mainExecutor.runAllReady()
verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
}
@@ -424,6 +440,120 @@ class OngoingCallControllerTest : SysuiTestCase() {
// Other tests for notifyChipVisibilityChanged are in [OngoingCallLogger], since
// [OngoingCallController.notifyChipVisibilityChanged] just delegates to that class.
+ @Test
+ fun callNotificationAdded_chipIsClickable() {
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun fullscreenIsTrue_thenCallNotificationAdded_chipNotClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false)
+
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ assertThat(chipView.hasOnClickListeners()).isFalse()
+ }
+
+ @Test
+ fun callNotificationAdded_thenFullscreenIsTrue_chipNotClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+
+ assertThat(chipView.hasOnClickListeners()).isFalse()
+ }
+
+ @Test
+ fun fullscreenChangesToFalse_chipClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ // First, update to true
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+ // Then, update to false
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ false)
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun fullscreenIsTrue_butChipClickInImmersiveEnabled_chipClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(true)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
+ // Swipe gesture tests
+
+ @Test
+ fun callStartedInImmersiveMode_swipeGestureCallbackAdded() {
+ getStateListener().onFullscreenStateChanged(true)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ verify(mockSwipeStatusBarAwayGestureHandler)
+ .addOnGestureDetectedCallback(anyString(), any())
+ }
+
+ @Test
+ fun callStartedNotInImmersiveMode_swipeGestureCallbackNotAdded() {
+ getStateListener().onFullscreenStateChanged(false)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ verify(mockSwipeStatusBarAwayGestureHandler, never())
+ .addOnGestureDetectedCallback(anyString(), any())
+ }
+
+ @Test
+ fun transitionToImmersiveMode_swipeGestureCallbackAdded() {
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ getStateListener().onFullscreenStateChanged(true)
+
+ verify(mockSwipeStatusBarAwayGestureHandler)
+ .addOnGestureDetectedCallback(anyString(), any())
+ }
+
+ @Test
+ fun transitionOutOfImmersiveMode_swipeGestureCallbackRemoved() {
+ getStateListener().onFullscreenStateChanged(true)
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ reset(mockSwipeStatusBarAwayGestureHandler)
+
+ getStateListener().onFullscreenStateChanged(false)
+
+ verify(mockSwipeStatusBarAwayGestureHandler)
+ .removeOnGestureDetectedCallback(anyString())
+ }
+
+ @Test
+ fun callEndedWhileInImmersiveMode_swipeGestureCallbackRemoved() {
+ getStateListener().onFullscreenStateChanged(true)
+ val ongoingCallNotifEntry = createOngoingCallNotifEntry()
+ notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
+ reset(mockSwipeStatusBarAwayGestureHandler)
+
+ notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
+
+ verify(mockSwipeStatusBarAwayGestureHandler)
+ .removeOnGestureDetectedCallback(anyString())
+ }
+
+ // TODO(b/195839150): Add test
+ // swipeGesturedTriggeredPreviously_entersImmersiveModeAgain_callbackNotAdded(). That's
+ // difficult to add now because we have no way to trigger [SwipeStatusBarAwayGestureHandler]'s
+ // callbacks in test.
+
+ // END swipe gesture tests
+
private fun createOngoingCallNotifEntry() = createCallNotifEntry(ongoingCallStyle)
private fun createScreeningCallNotifEntry() = createCallNotifEntry(screeningCallStyle)
@@ -448,6 +578,13 @@ class OngoingCallControllerTest : SysuiTestCase() {
}
private fun createNotCallNotifEntry() = NotificationEntryBuilder().build()
+
+ private fun getStateListener(): StatusBarStateController.StateListener {
+ val statusBarStateListenerCaptor = ArgumentCaptor.forClass(
+ StatusBarStateController.StateListener::class.java)
+ verify(mockStatusBarStateController).addCallback(statusBarStateListenerCaptor.capture())
+ return statusBarStateListenerCaptor.value!!
+ }
}
private val person = Person.Builder().setName("name").build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
new file mode 100644
index 000000000000..5129f8584165
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
@@ -0,0 +1,241 @@
+/*
+ * 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.systemui.statusbar.policy
+
+import android.os.Handler
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class DeviceProvisionedControllerImplTest : SysuiTestCase() {
+
+ companion object {
+ private const val START_USER = 0
+ }
+
+ private lateinit var controller: DeviceProvisionedControllerImpl
+
+ @Mock
+ private lateinit var userTracker: UserTracker
+ @Mock
+ private lateinit var dumpManager: DumpManager
+ @Mock
+ private lateinit var listener: DeviceProvisionedController.DeviceProvisionedListener
+ @Captor
+ private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
+
+ private lateinit var mainExecutor: FakeExecutor
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var settings: FakeSettings
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+ mainExecutor = FakeExecutor(FakeSystemClock())
+ settings = FakeSettings()
+ `when`(userTracker.userId).thenReturn(START_USER)
+
+ controller = DeviceProvisionedControllerImpl(
+ settings,
+ settings,
+ userTracker,
+ dumpManager,
+ Handler(testableLooper.looper),
+ mainExecutor
+ )
+ }
+
+ @Test
+ fun testNotProvisionedByDefault() {
+ init()
+ assertThat(controller.isDeviceProvisioned).isFalse()
+ }
+
+ @Test
+ fun testNotUserSetupByDefault() {
+ init()
+ assertThat(controller.isUserSetup(START_USER)).isFalse()
+ }
+
+ @Test
+ fun testProvisionedWhenCreated() {
+ settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+ init()
+
+ assertThat(controller.isDeviceProvisioned).isTrue()
+ }
+
+ @Test
+ fun testUserSetupWhenCreated() {
+ settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
+ init()
+
+ assertThat(controller.isUserSetup(START_USER))
+ }
+
+ @Test
+ fun testDeviceProvisionedChange() {
+ init()
+
+ settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+ testableLooper.processAllMessages() // background observer
+
+ assertThat(controller.isDeviceProvisioned).isTrue()
+ }
+
+ @Test
+ fun testUserSetupChange() {
+ init()
+
+ settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
+ testableLooper.processAllMessages() // background observer
+
+ assertThat(controller.isUserSetup(START_USER)).isTrue()
+ }
+
+ @Test
+ fun testUserSetupChange_otherUser() {
+ init()
+ val otherUser = 10
+
+ settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser)
+ testableLooper.processAllMessages() // background observer
+
+ assertThat(controller.isUserSetup(START_USER)).isFalse()
+ assertThat(controller.isUserSetup(otherUser)).isTrue()
+ }
+
+ @Test
+ fun testCurrentUserSetup() {
+ val otherUser = 10
+ settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser)
+ init()
+
+ assertThat(controller.isCurrentUserSetup).isFalse()
+ switchUser(otherUser)
+ testableLooper.processAllMessages()
+
+ assertThat(controller.isCurrentUserSetup).isTrue()
+ }
+
+ @Test
+ fun testListenerNotCalledOnAdd() {
+ init()
+ controller.addCallback(listener)
+
+ mainExecutor.runAllReady()
+
+ verify(listener, never()).onDeviceProvisionedChanged()
+ verify(listener, never()).onUserSetupChanged()
+ verify(listener, never()).onUserSwitched()
+ }
+
+ @Test
+ fun testListenerCalledOnUserSwitched() {
+ init()
+ controller.addCallback(listener)
+
+ switchUser(10)
+
+ testableLooper.processAllMessages()
+ mainExecutor.runAllReady()
+
+ verify(listener).onUserSwitched()
+ verify(listener, never()).onUserSetupChanged()
+ verify(listener, never()).onDeviceProvisionedChanged()
+ }
+
+ @Test
+ fun testListenerCalledOnUserSetupChanged() {
+ init()
+ controller.addCallback(listener)
+
+ settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
+ testableLooper.processAllMessages()
+ mainExecutor.runAllReady()
+
+ verify(listener, never()).onUserSwitched()
+ verify(listener).onUserSetupChanged()
+ verify(listener, never()).onDeviceProvisionedChanged()
+ }
+
+ @Test
+ fun testListenerCalledOnDeviceProvisionedChanged() {
+ init()
+ controller.addCallback(listener)
+
+ settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+ testableLooper.processAllMessages()
+ mainExecutor.runAllReady()
+
+ verify(listener, never()).onUserSwitched()
+ verify(listener, never()).onUserSetupChanged()
+ verify(listener).onDeviceProvisionedChanged()
+ }
+
+ @Test
+ fun testRemoveListener() {
+ init()
+ controller.addCallback(listener)
+ controller.removeCallback(listener)
+
+ switchUser(10)
+ settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
+ settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+
+ testableLooper.processAllMessages()
+ mainExecutor.runAllReady()
+
+ verify(listener, never()).onDeviceProvisionedChanged()
+ verify(listener, never()).onUserSetupChanged()
+ verify(listener, never()).onUserSwitched()
+ }
+
+ private fun init() {
+ controller.init()
+ verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any())
+ }
+
+ private fun switchUser(toUser: Int) {
+ `when`(userTracker.userId).thenReturn(toUser)
+ userTrackerCallbackCaptor.value.onUserChanged(toUser, mContext)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
new file mode 100644
index 000000000000..30717f431f5b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
@@ -0,0 +1,239 @@
+/*
+ * 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.systemui.statusbar.policy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableResources;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.view.RotationPolicy;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.util.wrapper.RotationPolicyWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase {
+
+ private static final String[] DEFAULT_SETTINGS = new String[]{
+ "0:0",
+ "1:2"
+ };
+
+ private final FakeSettings mFakeSettings = new FakeSettings();
+ private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
+ @Mock DeviceStateManager mDeviceStateManager;
+ RotationPolicyWrapper mFakeRotationPolicy = new FakeRotationPolicy();
+ DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
+ private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(/* testClass= */ this);
+ TestableResources resources = mContext.getOrCreateTestableResources();
+
+ ArgumentCaptor<DeviceStateManager.DeviceStateCallback> deviceStateCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(
+ DeviceStateManager.DeviceStateCallback.class);
+
+ mDeviceStateRotationLockSettingController = new DeviceStateRotationLockSettingController(
+ mFakeSettings,
+ mFakeRotationPolicy,
+ mDeviceStateManager,
+ mFakeExecutor,
+ DEFAULT_SETTINGS
+ );
+
+ mDeviceStateRotationLockSettingController.setListening(true);
+ verify(mDeviceStateManager).registerCallback(any(),
+ deviceStateCallbackArgumentCaptor.capture());
+ mDeviceStateCallback = deviceStateCallbackArgumentCaptor.getValue();
+ }
+
+ @Test
+ public void whenSavedSettingsEmpty_defaultsLoadedAndSaved() {
+ mFakeSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, "",
+ UserHandle.USER_CURRENT);
+
+ mDeviceStateRotationLockSettingController.initialize();
+
+ assertThat(mFakeSettings
+ .getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ UserHandle.USER_CURRENT))
+ .isEqualTo("0:0:1:2");
+ }
+
+ @Test
+ public void whenNoSavedValueForDeviceState_assumeIgnored() {
+ mFakeSettings.putStringForUser(
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ /* value= */"0:2:1:2",
+ UserHandle.USER_CURRENT);
+ mFakeRotationPolicy.setRotationLock(true);
+ mDeviceStateRotationLockSettingController.initialize();
+
+ mDeviceStateCallback.onStateChanged(1);
+ assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
+
+ // Settings only exist for state 0 and 1
+ mDeviceStateCallback.onStateChanged(2);
+
+ assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
+ }
+
+ @Test
+ public void whenDeviceStateSwitched_loadCorrectSetting() {
+ mFakeSettings.putStringForUser(
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ /* value= */"0:2:1:1",
+ UserHandle.USER_CURRENT);
+ mFakeRotationPolicy.setRotationLock(true);
+ mDeviceStateRotationLockSettingController.initialize();
+
+ mDeviceStateCallback.onStateChanged(0);
+ assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
+
+ mDeviceStateCallback.onStateChanged(1);
+ assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
+
+ }
+
+ @Test
+ public void whenUserChangesSetting_saveSettingForCurrentState() {
+ mFakeSettings.putStringForUser(
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ /* value= */"0:1:1:2",
+ UserHandle.USER_CURRENT);
+ mFakeRotationPolicy.setRotationLock(true);
+ mDeviceStateRotationLockSettingController.initialize();
+
+ mDeviceStateCallback.onStateChanged(0);
+ assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
+
+ mDeviceStateRotationLockSettingController
+ .onRotationLockStateChanged(/* rotationLocked= */false,
+ /* affordanceVisible= */ true);
+
+ assertThat(mFakeSettings
+ .getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ UserHandle.USER_CURRENT))
+ .isEqualTo("0:2:1:2");
+ }
+
+
+ @Test
+ public void whenDeviceStateSwitchedToIgnoredState_usePreviousSetting() {
+ mFakeSettings.putStringForUser(
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ /* value= */"0:0:1:2",
+ UserHandle.USER_CURRENT);
+ mFakeRotationPolicy.setRotationLock(true);
+ mDeviceStateRotationLockSettingController.initialize();
+
+ mDeviceStateCallback.onStateChanged(1);
+ assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
+
+ mDeviceStateCallback.onStateChanged(0);
+ assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
+ }
+
+ @Test
+ public void whenDeviceStateSwitchedToIgnoredState_newSettingsSaveForPreviousState() {
+ mFakeSettings.putStringForUser(
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ /* value= */"0:0:1:2",
+ UserHandle.USER_CURRENT);
+ mFakeRotationPolicy.setRotationLock(true);
+ mDeviceStateRotationLockSettingController.initialize();
+
+ mDeviceStateCallback.onStateChanged(1);
+ assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
+
+ mDeviceStateCallback.onStateChanged(0);
+ assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
+
+ mDeviceStateRotationLockSettingController
+ .onRotationLockStateChanged(/* rotationLocked= */true,
+ /* affordanceVisible= */ true);
+
+ assertThat(mFakeSettings
+ .getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ UserHandle.USER_CURRENT))
+ .isEqualTo("0:0:1:1");
+ }
+
+ private static class FakeRotationPolicy implements RotationPolicyWrapper {
+
+ private boolean mRotationLock;
+
+ @Override
+ public void setRotationLock(boolean enabled) {
+ mRotationLock = enabled;
+ }
+
+ @Override
+ public void setRotationLockAtAngle(boolean enabled, int rotation) {
+ mRotationLock = enabled;
+ }
+
+ @Override
+ public int getRotationLockOrientation() {
+ throw new AssertionError("Not implemented");
+ }
+
+ @Override
+ public boolean isRotationLockToggleVisible() {
+ throw new AssertionError("Not implemented");
+ }
+
+ @Override
+ public boolean isRotationLocked() {
+ return mRotationLock;
+ }
+
+ @Override
+ public void registerRotationPolicyListener(RotationPolicy.RotationPolicyListener listener,
+ int userHandle) {
+ throw new AssertionError("Not implemented");
+ }
+
+ @Override
+ public void unregisterRotationPolicyListener(
+ RotationPolicy.RotationPolicyListener listener) {
+ throw new AssertionError("Not implemented");
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
index 1240a4815b98..14cc032b1cec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
@@ -63,8 +63,12 @@ public class ExtensionControllerImplTest extends SysuiTestCase {
mPluginManager = mDependency.injectMockDependency(PluginManager.class);
mTunerService = mDependency.injectMockDependency(TunerService.class);
mConfigurationController = mDependency.injectMockDependency(ConfigurationController.class);
- mExtensionController = new ExtensionControllerImpl(mContext,
- mock(LeakDetector.class), mPluginManager, mTunerService, mConfigurationController);
+ mExtensionController = new ExtensionControllerImpl(
+ mContext,
+ mock(LeakDetector.class),
+ mPluginManager,
+ mTunerService,
+ mConfigurationController);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
index 57714722aea4..4f1fb02ecdcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
@@ -37,6 +37,7 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import org.junit.Before;
import org.junit.Test;
@@ -57,6 +58,8 @@ import java.util.concurrent.Executor;
public class HotspotControllerImplTest extends SysuiTestCase {
@Mock
+ private DumpManager mDumpManager;
+ @Mock
private TetheringManager mTetheringManager;
@Mock
private WifiManager mWifiManager;
@@ -95,7 +98,7 @@ public class HotspotControllerImplTest extends SysuiTestCase {
Handler handler = new Handler(mLooper.getLooper());
- mController = new HotspotControllerImpl(mContext, handler, handler);
+ mController = new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
verify(mTetheringManager)
.registerTetheringEventCallback(any(), mTetheringCallbackCaptor.capture());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index 4b87ec895773..8ccaf9362454 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
import org.junit.Before;
@@ -52,12 +53,18 @@ public class KeyguardStateControllerTest extends SysuiTestCase {
private KeyguardStateController mKeyguardStateController;
@Mock
private SmartspaceTransitionController mSmartSpaceTransitionController;
+ @Mock
+ private DumpManager mDumpManager;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mKeyguardStateController = new KeyguardStateControllerImpl(mContext,
- mKeyguardUpdateMonitor, mLockPatternUtils, mSmartSpaceTransitionController);
+ mKeyguardStateController = new KeyguardStateControllerImpl(
+ mContext,
+ mKeyguardUpdateMonitor,
+ mLockPatternUtils,
+ mSmartSpaceTransitionController,
+ mDumpManager);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index dd8354dedafd..97e1edb9ac74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -205,7 +205,7 @@ public class RemoteInputViewTest extends SysuiTestCase {
ExpandableNotificationRow row = helper.createRow();
RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
- view.setOnVisibilityChangedListener(null);
+ view.addOnVisibilityChangedListener(null);
view.setVisibility(View.INVISIBLE);
view.setVisibility(View.VISIBLE);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
new file mode 100644
index 000000000000..0581264d18e2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.systemui.statusbar.policy;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableResources;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.view.RotationPolicy;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.wrapper.RotationPolicyWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class RotationLockControllerImplTest extends SysuiTestCase {
+
+ private static final String[] DEFAULT_SETTINGS = new String[]{
+ "0:0",
+ "1:2"
+ };
+
+ @Mock RotationPolicyWrapper mRotationPolicyWrapper;
+ @Mock DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
+
+ private TestableResources mResources;
+ private ArgumentCaptor<RotationPolicy.RotationPolicyListener>
+ mRotationPolicyListenerCaptor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(/* testClass= */ this);
+ mResources = mContext.getOrCreateTestableResources();
+
+ mRotationPolicyListenerCaptor = ArgumentCaptor.forClass(
+ RotationPolicy.RotationPolicyListener.class);
+ }
+
+ @Test
+ public void whenFlagOff_doesntInteractWithDeviceStateRotationController() {
+ createRotationLockController(new String[0]);
+
+ verifyZeroInteractions(mDeviceStateRotationLockSettingController);
+ }
+
+ @Test
+ public void whenFlagOn_setListeningSetsListeningOnDeviceStateRotationController() {
+ createRotationLockController();
+
+ verify(mDeviceStateRotationLockSettingController).setListening(/* listening= */ true);
+ }
+
+ @Test
+ public void whenFlagOn_initializesDeviceStateRotationController() {
+ createRotationLockController();
+
+ verify(mDeviceStateRotationLockSettingController).initialize();
+ }
+
+ @Test
+ public void whenFlagOn_dviceStateRotationControllerAddedToCallbacks() {
+ createRotationLockController();
+ captureRotationPolicyListener().onChange();
+
+ verify(mDeviceStateRotationLockSettingController)
+ .onRotationLockStateChanged(anyBoolean(), anyBoolean());
+ }
+
+ private RotationPolicy.RotationPolicyListener captureRotationPolicyListener() {
+ verify(mRotationPolicyWrapper)
+ .registerRotationPolicyListener(mRotationPolicyListenerCaptor.capture(), anyInt());
+ return mRotationPolicyListenerCaptor.getValue();
+ }
+
+ private void createRotationLockController() {
+ createRotationLockController(DEFAULT_SETTINGS);
+ }
+ private void createRotationLockController(String[] deviceStateRotationLockDefaults) {
+ new RotationLockControllerImpl(
+ mRotationPolicyWrapper,
+ mDeviceStateRotationLockSettingController,
+ deviceStateRotationLockDefaults
+ );
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index c38a54771e12..d44cdb24421a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -49,6 +49,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -56,6 +57,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Arrays;
@@ -105,7 +107,8 @@ public class SecurityControllerTest extends SysuiTestCase {
mContext,
mHandler,
mBroadcastDispatcher,
- mBgExecutor);
+ mBgExecutor,
+ Mockito.mock(DumpManager.class));
verify(mBroadcastDispatcher).registerReceiverWithHandler(
brCaptor.capture(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index 3431a9d895d3..69ab9c51db81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy
+import android.app.IActivityManager
import android.app.IActivityTaskManager
import android.app.admin.DevicePolicyManager
import android.content.Context
@@ -31,12 +32,15 @@ import android.os.UserManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.util.LatencyTracker
import com.android.internal.util.UserIcons
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.QSUserSwitcherEvent
@@ -53,10 +57,11 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
+import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.anyString
import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -64,6 +69,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
class UserSwitcherControllerTest : SysuiTestCase() {
@Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var activityManager: IActivityManager
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Mock private lateinit var handler: Handler
@@ -76,6 +82,9 @@ class UserSwitcherControllerTest : SysuiTestCase() {
@Mock private lateinit var telephonyListenerManager: TelephonyListenerManager
@Mock private lateinit var secureSettings: SecureSettings
@Mock private lateinit var falsingManager: FalsingManager
+ @Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor
+ @Mock private lateinit var latencyTracker: LatencyTracker
private lateinit var testableLooper: TestableLooper
private lateinit var uiBgExecutor: FakeExecutor
private lateinit var uiEventLogger: UiEventLoggerFake
@@ -106,7 +115,9 @@ class UserSwitcherControllerTest : SysuiTestCase() {
`when`(userManager.canAddMoreUsers()).thenReturn(true)
- userSwitcherController = UserSwitcherController(context,
+ userSwitcherController = UserSwitcherController(
+ context,
+ activityManager,
userManager,
userTracker,
keyguardStateController,
@@ -121,14 +132,17 @@ class UserSwitcherControllerTest : SysuiTestCase() {
activityTaskManager,
userDetailAdapter,
secureSettings,
- uiBgExecutor)
+ uiBgExecutor,
+ interactionJankMonitor,
+ latencyTracker,
+ dumpManager)
userSwitcherController.mPauseRefreshUsers = true
picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
}
@Test
- fun testAddGuest_okButtonPressed_isLogged() {
+ fun testAddGuest_okButtonPressed() {
val emptyGuestUserRecord = UserSwitcherController.UserRecord(
null,
null,
@@ -144,6 +158,9 @@ class UserSwitcherControllerTest : SysuiTestCase() {
userSwitcherController.onUserListItemClicked(emptyGuestUserRecord)
testableLooper.processAllMessages()
+ verify(interactionJankMonitor).begin(any())
+ verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH)
+ verify(activityManager).switchUser(guestInfo.id)
assertEquals(1, uiEventLogger.numLogs())
assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_ADD.id, uiEventLogger.eventId(0))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index dcf0ef781b07..336f2b16aeb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -34,6 +34,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.policy.ZenModeController.Callback;
import org.junit.Before;
@@ -54,6 +55,8 @@ public class ZenModeControllerImplTest extends SysuiTestCase {
ZenModeConfig mConfig;
@Mock
BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ DumpManager mDumpManager;
private ZenModeControllerImpl mController;
@@ -63,8 +66,11 @@ public class ZenModeControllerImplTest extends SysuiTestCase {
mContext.addMockSystemService(NotificationManager.class, mNm);
when(mNm.getZenModeConfig()).thenReturn(mConfig);
- mController = new ZenModeControllerImpl(mContext, Handler.createAsync(Looper.myLooper()),
- mBroadcastDispatcher);
+ mController = new ZenModeControllerImpl(
+ mContext,
+ Handler.createAsync(Looper.myLooper()),
+ mBroadcastDispatcher,
+ mDumpManager);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 07d3fc20983f..f89bbe898e35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -52,9 +52,9 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.util.settings.SecureSettings;
@@ -167,7 +167,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
.isEqualTo(new OverlayIdentifier("ffff0000"));
assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
- .isEqualTo(new OverlayIdentifier("ff0000ff"));
+ .isEqualTo(new OverlayIdentifier("ffff0000"));
// Should not ask again if changed to same value
mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index 5efe05f6db46..84e6df23e740 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -60,9 +60,9 @@ import com.android.internal.util.IntPair;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FeatureFlags;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
new file mode 100644
index 000000000000..c31640279305
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
@@ -0,0 +1,32 @@
+package com.android.systemui.unfold
+
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+
+class TestUnfoldTransitionProvider : UnfoldTransitionProgressProvider, TransitionProgressListener {
+
+ private val listeners = arrayListOf<TransitionProgressListener>()
+
+ override fun destroy() {
+ listeners.clear()
+ }
+
+ override fun addCallback(listener: TransitionProgressListener) {
+ listeners.add(listener)
+ }
+
+ override fun removeCallback(listener: TransitionProgressListener) {
+ listeners.remove(listener)
+ }
+
+ override fun onTransitionStarted() {
+ listeners.forEach { it.onTransitionStarted() }
+ }
+
+ override fun onTransitionFinished() {
+ listeners.forEach { it.onTransitionFinished() }
+ }
+
+ override fun onTransitionProgress(progress: Float) {
+ listeners.forEach { it.onTransitionProgress(progress) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
new file mode 100644
index 000000000000..6ec0251d41a5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
@@ -0,0 +1,51 @@
+package com.android.systemui.unfold
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.WallpaperController
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.AdditionalMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class UnfoldTransitionWallpaperControllerTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var wallpaperController: WallpaperController
+
+ private val progressProvider = TestUnfoldTransitionProvider()
+
+ @JvmField
+ @Rule
+ val mockitoRule = MockitoJUnit.rule()
+
+ private lateinit var unfoldWallpaperController: UnfoldTransitionWallpaperController
+
+ @Before
+ fun setup() {
+ unfoldWallpaperController = UnfoldTransitionWallpaperController(progressProvider,
+ wallpaperController)
+ unfoldWallpaperController.init()
+ }
+
+ @Test
+ fun onTransitionProgress_zoomsIn() {
+ progressProvider.onTransitionProgress(0.8f)
+
+ verify(wallpaperController).setUnfoldTransitionZoom(eq(0.2f, 0.001f))
+ }
+
+ @Test
+ fun onTransitionFinished_resetsZoom() {
+ progressProvider.onTransitionFinished()
+
+ verify(wallpaperController).setUnfoldTransitionZoom(eq(0f, 0.001f))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
new file mode 100644
index 000000000000..a1d9a7b50d81
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -0,0 +1,178 @@
+/*
+ * 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.systemui.unfold.updates
+
+import android.hardware.devicestate.DeviceStateManager
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DeviceFoldStateProviderTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var hingeAngleProvider: HingeAngleProvider
+
+ @Mock
+ private lateinit var screenStatusProvider: ScreenStatusProvider
+
+ @Mock
+ private lateinit var deviceStateManager: DeviceStateManager
+
+ private lateinit var foldStateProvider: FoldStateProvider
+
+ private val foldUpdates: MutableList<Int> = arrayListOf()
+ private val hingeAngleUpdates: MutableList<Float> = arrayListOf()
+
+ private val foldStateListenerCaptor = ArgumentCaptor.forClass(FoldStateListener::class.java)
+ private var foldedDeviceState: Int = 0
+ private var unfoldedDeviceState: Int = 0
+
+ private val screenOnListenerCaptor = ArgumentCaptor.forClass(ScreenListener::class.java)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val foldedDeviceStates: IntArray = context.resources.getIntArray(
+ com.android.internal.R.array.config_foldedDeviceStates)
+ assumeTrue("Test should be launched on a foldable device",
+ foldedDeviceStates.isNotEmpty())
+
+ foldedDeviceState = foldedDeviceStates.maxOrNull()!!
+ unfoldedDeviceState = foldedDeviceState + 1
+
+ foldStateProvider = DeviceFoldStateProvider(
+ context,
+ hingeAngleProvider,
+ screenStatusProvider,
+ deviceStateManager,
+ context.mainExecutor
+ )
+
+ foldStateProvider.addCallback(object : FoldStateProvider.FoldUpdatesListener {
+ override fun onHingeAngleUpdate(angle: Float) {
+ hingeAngleUpdates.add(angle)
+ }
+
+ override fun onFoldUpdate(update: Int) {
+ foldUpdates.add(update)
+ }
+ })
+ foldStateProvider.start()
+
+ verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
+ verify(screenStatusProvider).addCallback(screenOnListenerCaptor.capture())
+ }
+
+ @Test
+ fun testOnFolded_emitsFinishClosedEvent() {
+ setFoldState(folded = true)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_FINISH_CLOSED)
+ }
+
+ @Test
+ fun testOnUnfolded_emitsStartOpeningEvent() {
+ setFoldState(folded = false)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING)
+ }
+
+ @Test
+ fun testOnFolded_stopsHingeAngleProvider() {
+ setFoldState(folded = true)
+
+ verify(hingeAngleProvider).stop()
+ }
+
+ @Test
+ fun testOnUnfolded_startsHingeAngleProvider() {
+ setFoldState(folded = false)
+
+ verify(hingeAngleProvider).start()
+ }
+
+ @Test
+ fun testFirstScreenOnEventWhenFolded_doesNotEmitEvents() {
+ setFoldState(folded = true)
+ foldUpdates.clear()
+
+ fireScreenOnEvent()
+
+ // Power button turn on
+ assertThat(foldUpdates).isEmpty()
+ }
+
+ @Test
+ fun testFirstScreenOnEventWhenUnfolded_doesNotEmitEvents() {
+ setFoldState(folded = false)
+ foldUpdates.clear()
+
+ fireScreenOnEvent()
+
+ assertThat(foldUpdates).isEmpty()
+ }
+
+ @Test
+ fun testFirstScreenOnEventAfterFoldAndUnfold_emitsUnfoldedScreenAvailableEvent() {
+ setFoldState(folded = false)
+ setFoldState(folded = true)
+ fireScreenOnEvent()
+ setFoldState(folded = false)
+ foldUpdates.clear()
+
+ fireScreenOnEvent()
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE)
+ }
+
+ @Test
+ fun testSecondScreenOnEventWhenUnfolded_doesNotEmitEvents() {
+ setFoldState(folded = false)
+ fireScreenOnEvent()
+ foldUpdates.clear()
+
+ fireScreenOnEvent()
+
+ // No events as this is power button turn on
+ assertThat(foldUpdates).isEmpty()
+ }
+
+ private fun setFoldState(folded: Boolean) {
+ val state = if (folded) foldedDeviceState else unfoldedDeviceState
+ foldStateListenerCaptor.value.onStateChanged(state)
+ }
+
+ private fun fireScreenOnEvent() {
+ screenOnListenerCaptor.value.onScreenTurnedOn()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
new file mode 100644
index 000000000000..a3f17aa5ba55
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.systemui.unfold.util
+
+import android.testing.AndroidTestingRunner
+import android.view.IRotationWatcher
+import android.view.IWindowManager
+import android.view.Surface
+import androidx.test.filters.SmallTest
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.util.mockito.any
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
+
+ @Mock
+ lateinit var windowManager: IWindowManager
+
+ @Mock
+ lateinit var sourceProvider: UnfoldTransitionProgressProvider
+
+ @Mock
+ lateinit var transitionListener: TransitionProgressListener
+
+ lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
+
+ private val sourceProviderListenerCaptor =
+ ArgumentCaptor.forClass(TransitionProgressListener::class.java)
+ private val rotationWatcherCaptor =
+ ArgumentCaptor.forClass(IRotationWatcher.Stub::class.java)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ progressProvider = NaturalRotationUnfoldProgressProvider(
+ context,
+ windowManager,
+ sourceProvider
+ )
+
+ progressProvider.init()
+
+ verify(sourceProvider).addCallback(sourceProviderListenerCaptor.capture())
+ verify(windowManager).watchRotation(rotationWatcherCaptor.capture(), any())
+
+ progressProvider.addCallback(transitionListener)
+ }
+
+ @Test
+ fun testNaturalRotation0_sendTransitionStartedEvent_eventReceived() {
+ onRotationChanged(Surface.ROTATION_0)
+
+ source.onTransitionStarted()
+
+ verify(transitionListener).onTransitionStarted()
+ }
+
+ @Test
+ fun testNaturalRotation0_sendTransitionProgressEvent_eventReceived() {
+ onRotationChanged(Surface.ROTATION_0)
+
+ source.onTransitionProgress(0.5f)
+
+ verify(transitionListener).onTransitionProgress(0.5f)
+ }
+
+ @Test
+ fun testNotNaturalRotation90_sendTransitionStartedEvent_eventNotReceived() {
+ onRotationChanged(Surface.ROTATION_90)
+
+ source.onTransitionStarted()
+
+ verify(transitionListener, never()).onTransitionStarted()
+ }
+
+ @Test
+ fun testNaturalRotation90_sendTransitionProgressEvent_eventNotReceived() {
+ onRotationChanged(Surface.ROTATION_90)
+
+ source.onTransitionProgress(0.5f)
+
+ verify(transitionListener, never()).onTransitionProgress(0.5f)
+ }
+
+ @Test
+ fun testRotationBecameUnnaturalDuringTransition_sendsTransitionFinishedEvent() {
+ onRotationChanged(Surface.ROTATION_0)
+ source.onTransitionStarted()
+ clearInvocations(transitionListener)
+
+ onRotationChanged(Surface.ROTATION_90)
+
+ verify(transitionListener).onTransitionFinished()
+ }
+
+ @Test
+ fun testRotationBecameNaturalDuringTransition_sendsTransitionStartedEvent() {
+ onRotationChanged(Surface.ROTATION_90)
+ source.onTransitionStarted()
+ clearInvocations(transitionListener)
+
+ onRotationChanged(Surface.ROTATION_0)
+
+ verify(transitionListener).onTransitionStarted()
+ }
+
+ private fun onRotationChanged(rotation: Int) {
+ rotationWatcherCaptor.value.onRotationChanged(rotation)
+ }
+
+ private val source: TransitionProgressListener
+ get() = sourceProviderListenerCaptor.value
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
index eebcbe63d004..3d554880ed58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
@@ -31,7 +31,6 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import java.lang.Exception
/**
* UsbPermissionActivityTest
@@ -54,7 +53,9 @@ class UsbPermissionActivityTest : SysuiTestCase() {
putExtra(Intent.EXTRA_INTENT, PendingIntent.getBroadcast(
mContext,
334,
- Intent("NO_ACTION"),
+ Intent("NO_ACTION").apply {
+ setPackage("com.android.systemui.tests")
+ },
PendingIntent.FLAG_MUTABLE))
putExtra(UsbManager.EXTRA_ACCESSORY, UsbAccessory(
"manufacturer",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java
index f1c9980f12c4..c7bcdefdc4c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java
@@ -22,7 +22,6 @@ import static org.mockito.Mockito.verify;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
-import android.provider.Settings;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArraySet;
@@ -68,51 +67,4 @@ public class ChannelsTest extends SysuiTestCase {
list.forEach((chan) -> assertTrue(ALL_CHANNELS.contains(chan.getId())));
}
- @Test
- public void testChannelSetup_noLegacyScreenshot() {
- // Assert old channel cleaned up.
- // TODO: remove that code + this test after P.
- NotificationChannels.createAll(mContext);
- ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
- verify(mMockNotificationManager).deleteNotificationChannel(
- NotificationChannels.SCREENSHOTS_LEGACY);
- }
-
- @Test
- public void testInheritFromLegacy_keepsUserLockedLegacySettings() {
- NotificationChannel legacyChannel = new NotificationChannel("id", "oldName",
- NotificationManager.IMPORTANCE_MIN);
- legacyChannel.setImportance(NotificationManager.IMPORTANCE_NONE);;
- legacyChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
- legacyChannel.getAudioAttributes());
- legacyChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE |
- NotificationChannel.USER_LOCKED_SOUND);
- NotificationChannel newChannel =
- NotificationChannels.createScreenshotChannel("newName", legacyChannel);
- // NONE importance user locked, so don't use HIGH for new channel.
- assertEquals(NotificationManager.IMPORTANCE_NONE, newChannel.getImportance());
- assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, newChannel.getSound());
- }
-
- @Test
- public void testInheritFromLegacy_dropsUnlockedLegacySettings() {
- NotificationChannel legacyChannel = new NotificationChannel("id", "oldName",
- NotificationManager.IMPORTANCE_MIN);
- NotificationChannel newChannel =
- NotificationChannels.createScreenshotChannel("newName", legacyChannel);
- assertEquals(null, newChannel.getSound());
- assertEquals("newName", newChannel.getName());
- // MIN importance not user locked, so HIGH wins out.
- assertEquals(NotificationManager.IMPORTANCE_HIGH, newChannel.getImportance());
- }
-
- @Test
- public void testInheritFromLegacy_noLegacyExists() {
- NotificationChannel newChannel =
- NotificationChannels.createScreenshotChannel("newName", null);
- assertEquals(null, newChannel.getSound());
- assertEquals("newName", newChannel.getName());
- assertEquals(NotificationManager.IMPORTANCE_HIGH, newChannel.getImportance());
- }
-
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
new file mode 100644
index 000000000000..2662da201460
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
@@ -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.systemui.util
+
+import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ListenerSetTest : SysuiTestCase() {
+
+ var runnableSet: ListenerSet<Runnable> = ListenerSet()
+
+ @Before
+ fun setup() {
+ runnableSet = ListenerSet()
+ }
+
+ @Test
+ fun addIfAbsent_doesNotDoubleAdd() {
+ // setup & preconditions
+ val runnable1 = Runnable { }
+ val runnable2 = Runnable { }
+ assertThat(runnableSet.toList()).isEmpty()
+
+ // Test that an element can be added
+ assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
+ assertThat(runnableSet.toList()).containsExactly(runnable1)
+
+ // Test that a second element can be added
+ assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
+ assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+
+ // Test that re-adding the first element does nothing and returns false
+ assertThat(runnableSet.addIfAbsent(runnable1)).isFalse()
+ assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+ }
+
+ @Test
+ fun remove_removesListener() {
+ // setup and preconditions
+ val runnable1 = Runnable { }
+ val runnable2 = Runnable { }
+ assertThat(runnableSet.toList()).isEmpty()
+ runnableSet.addIfAbsent(runnable1)
+ runnableSet.addIfAbsent(runnable2)
+ assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+
+ // Test that removing the first runnable only removes that one runnable
+ assertThat(runnableSet.remove(runnable1)).isTrue()
+ assertThat(runnableSet.toList()).containsExactly(runnable2)
+
+ // Test that removing a non-present runnable does not error
+ assertThat(runnableSet.remove(runnable1)).isFalse()
+ assertThat(runnableSet.toList()).containsExactly(runnable2)
+
+ // Test that removing the other runnable succeeds
+ assertThat(runnableSet.remove(runnable2)).isTrue()
+ assertThat(runnableSet.toList()).isEmpty()
+ }
+
+ @Test
+ fun remove_isReentrantSafe() {
+ // Setup and preconditions
+ val runnablesCalled = mutableListOf<Int>()
+ // runnable1 is configured to remove itself
+ val runnable1 = object : Runnable {
+ override fun run() {
+ runnableSet.remove(this)
+ runnablesCalled.add(1)
+ }
+ }
+ val runnable2 = Runnable {
+ runnablesCalled.add(2)
+ }
+ assertThat(runnableSet.toList()).isEmpty()
+ runnableSet.addIfAbsent(runnable1)
+ runnableSet.addIfAbsent(runnable2)
+ assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+
+ // Test that both runnables are called and 1 was removed
+ for (runnable in runnableSet) {
+ runnable.run()
+ }
+ assertThat(runnablesCalled).containsExactly(1, 2)
+ assertThat(runnableSet.toList()).containsExactly(runnable2)
+ }
+
+ @Test
+ fun addIfAbsent_isReentrantSafe() {
+ // Setup and preconditions
+ val runnablesCalled = mutableListOf<Int>()
+ val runnable99 = Runnable {
+ runnablesCalled.add(99)
+ }
+ // runnable1 is configured to add runnable99
+ val runnable1 = Runnable {
+ runnableSet.addIfAbsent(runnable99)
+ runnablesCalled.add(1)
+ }
+ val runnable2 = Runnable {
+ runnablesCalled.add(2)
+ }
+ assertThat(runnableSet.toList()).isEmpty()
+ runnableSet.addIfAbsent(runnable1)
+ runnableSet.addIfAbsent(runnable2)
+ assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+
+ // Test that both original runnables are called and 99 was added but not called
+ for (runnable in runnableSet) {
+ runnable.run()
+ }
+ assertThat(runnablesCalled).containsExactly(1, 2)
+ assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2, runnable99)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
new file mode 100644
index 000000000000..d8e418a7815c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.systemui.util
+
+import android.app.WallpaperInfo
+import android.app.WallpaperManager
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import android.view.ViewRootImpl
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyFloat
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.doThrow
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+@SmallTest
+class WallpaperControllerTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var wallpaperManager: WallpaperManager
+ @Mock
+ private lateinit var root: View
+ @Mock
+ private lateinit var viewRootImpl: ViewRootImpl
+ @Mock
+ private lateinit var windowToken: IBinder
+
+ @JvmField
+ @Rule
+ val mockitoRule = MockitoJUnit.rule()
+
+ private lateinit var wallaperController: WallpaperController
+
+ @Before
+ fun setup() {
+ `when`(root.viewRootImpl).thenReturn(viewRootImpl)
+ `when`(root.windowToken).thenReturn(windowToken)
+ `when`(root.isAttachedToWindow).thenReturn(true)
+
+ wallaperController = WallpaperController(wallpaperManager)
+
+ wallaperController.rootView = root
+ }
+
+ @Test
+ fun setNotificationShadeZoom_updatesWallpaperManagerZoom() {
+ wallaperController.setNotificationShadeZoom(0.5f)
+
+ verify(wallpaperManager).setWallpaperZoomOut(any(), eq(0.5f))
+ }
+
+ @Test
+ fun setUnfoldTransitionZoom_updatesWallpaperManagerZoom() {
+ wallaperController.setUnfoldTransitionZoom(0.5f)
+
+ verify(wallpaperManager).setWallpaperZoomOut(any(), eq(0.5f))
+ }
+
+ @Test
+ fun setUnfoldTransitionZoom_defaultUnfoldTransitionIsDisabled_doesNotUpdateWallpaperZoom() {
+ wallaperController.onWallpaperInfoUpdated(createWallpaperInfo(
+ useDefaultTransition = false
+ ))
+
+ wallaperController.setUnfoldTransitionZoom(0.5f)
+
+ verify(wallpaperManager, never()).setWallpaperZoomOut(any(), anyFloat())
+ }
+
+ @Test
+ fun setUnfoldTransitionZoomAndNotificationShadeZoom_updatesWithMaximumZoom() {
+ wallaperController.setUnfoldTransitionZoom(0.7f)
+ clearInvocations(wallpaperManager)
+
+ wallaperController.setNotificationShadeZoom(0.5f)
+
+ verify(wallpaperManager).setWallpaperZoomOut(any(), eq(0.7f))
+ }
+
+ @Test
+ fun setNotificationShadeZoomAndThenUnfoldTransition_updatesWithMaximumZoom() {
+ wallaperController.setNotificationShadeZoom(0.7f)
+ clearInvocations(wallpaperManager)
+
+ wallaperController.setUnfoldTransitionZoom(0.5f)
+
+ verify(wallpaperManager).setWallpaperZoomOut(any(), eq(0.7f))
+ }
+
+ @Test
+ fun setNotificationZoom_invalidWindow_doesNotSetZoom() {
+ `when`(root.isAttachedToWindow).thenReturn(false)
+
+ verify(wallpaperManager, times(0)).setWallpaperZoomOut(any(), anyFloat())
+ }
+
+ @Test
+ fun setNotificationZoom_exceptionWhenUpdatingZoom_doesNotFail() {
+ doThrow(IllegalArgumentException("test exception")).`when`(wallpaperManager)
+ .setWallpaperZoomOut(any(), anyFloat())
+
+ wallaperController.setNotificationShadeZoom(0.5f)
+
+ verify(wallpaperManager).setWallpaperZoomOut(any(), anyFloat())
+ }
+
+ private fun createWallpaperInfo(useDefaultTransition: Boolean = true): WallpaperInfo {
+ val info = mock(WallpaperInfo::class.java)
+ whenever(info.shouldUseDefaultUnfoldTransition())
+ .thenReturn(useDefaultTransition)
+ return info
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java
new file mode 100644
index 000000000000..78fc6803ea7e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java
@@ -0,0 +1,371 @@
+/*
+ * 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.systemui.util.concurrency;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MessageRouterImplTest extends SysuiTestCase {
+ private static final int MESSAGE_A = 0;
+ private static final int MESSAGE_B = 1;
+ private static final int MESSAGE_C = 2;
+
+ private static final String METADATA_A = "A";
+ private static final String METADATA_B = "B";
+ private static final String METADATA_C = "C";
+ private static final Foobar METADATA_FOO = new Foobar();
+
+ FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+ @Mock
+ MessageRouter.SimpleMessageListener mNoMdListener;
+ @Mock
+ MessageRouter.DataMessageListener<String> mStringListener;
+ @Mock
+ MessageRouter.DataMessageListener<Foobar> mFoobarListener;
+ private MessageRouterImpl mMR;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mMR = new MessageRouterImpl(mFakeExecutor);
+ }
+
+ @Test
+ public void testSingleMessage_NoMetaData() {
+ mMR.subscribeTo(MESSAGE_A, mNoMdListener);
+
+ mMR.sendMessage(MESSAGE_A);
+ verify(mNoMdListener, never()).onMessage(anyInt());
+
+ mFakeExecutor.runAllReady();
+ verify(mNoMdListener).onMessage(MESSAGE_A);
+ }
+
+ @Test
+ public void testSingleMessage_WithMetaData() {
+ mMR.subscribeTo(String.class, mStringListener);
+
+ mMR.sendMessage(METADATA_A);
+ verify(mStringListener, never()).onMessage(anyString());
+
+ mFakeExecutor.runAllReady();
+ verify(mStringListener).onMessage(METADATA_A);
+ }
+
+ @Test
+ public void testMessages_WithMixedMetaData() {
+ mMR.subscribeTo(String.class, mStringListener);
+ mMR.subscribeTo(Foobar.class, mFoobarListener);
+
+ mMR.sendMessage(METADATA_A);
+ verify(mStringListener, never()).onMessage(anyString());
+ verify(mFoobarListener, never()).onMessage(any(Foobar.class));
+
+ mFakeExecutor.runAllReady();
+ verify(mStringListener).onMessage(METADATA_A);
+ verify(mFoobarListener, never()).onMessage(any(Foobar.class));
+
+ reset(mStringListener);
+ reset(mFoobarListener);
+
+ mMR.sendMessage(METADATA_FOO);
+ verify(mStringListener, never()).onMessage(anyString());
+ verify(mFoobarListener, never()).onMessage(any(Foobar.class));
+
+ mFakeExecutor.runAllReady();
+ verify(mStringListener, never()).onMessage(anyString());
+ verify(mFoobarListener).onMessage(METADATA_FOO);
+ }
+
+ @Test
+ public void testMessages_WithAndWithoutMetaData() {
+ mMR.subscribeTo(MESSAGE_A, mNoMdListener);
+ mMR.subscribeTo(String.class, mStringListener);
+
+ mMR.sendMessage(MESSAGE_A);
+ verify(mNoMdListener, never()).onMessage(anyInt());
+ verify(mStringListener, never()).onMessage(anyString());
+
+ mFakeExecutor.runAllReady();
+ verify(mNoMdListener).onMessage(MESSAGE_A);
+ verify(mStringListener, never()).onMessage(anyString());
+
+ reset(mNoMdListener);
+ reset(mStringListener);
+
+ mMR.sendMessage(METADATA_A);
+ verify(mNoMdListener, never()).onMessage(anyInt());
+ verify(mStringListener, never()).onMessage(anyString());
+
+ mFakeExecutor.runAllReady();
+ verify(mNoMdListener, never()).onMessage(anyInt());
+ verify(mStringListener).onMessage(METADATA_A);
+ }
+
+ @Test
+ public void testRepeatedMessage() {
+ mMR.subscribeTo(String.class, mStringListener);
+
+ mMR.sendMessage(METADATA_A);
+ mMR.sendMessage(METADATA_A);
+ mMR.sendMessage(METADATA_B);
+ verify(mStringListener, never()).onMessage(anyString());
+
+ InOrder ordered = inOrder(mStringListener);
+ mFakeExecutor.runNextReady();
+ ordered.verify(mStringListener).onMessage(METADATA_A);
+ mFakeExecutor.runNextReady();
+ ordered.verify(mStringListener).onMessage(METADATA_A);
+ mFakeExecutor.runNextReady();
+ ordered.verify(mStringListener).onMessage(METADATA_B);
+ }
+
+ @Test
+ public void testCancelMessage() {
+ mMR.subscribeTo(MESSAGE_A, mNoMdListener);
+ mMR.subscribeTo(MESSAGE_B, mNoMdListener);
+ mMR.subscribeTo(MESSAGE_C, mNoMdListener);
+
+ mMR.sendMessage(MESSAGE_A);
+ mMR.sendMessage(MESSAGE_B);
+ mMR.sendMessage(MESSAGE_B);
+ mMR.sendMessage(MESSAGE_B);
+ mMR.sendMessage(MESSAGE_B);
+ mMR.sendMessage(MESSAGE_C);
+ verify(mNoMdListener, never()).onMessage(anyInt());
+
+ mMR.cancelMessages(MESSAGE_B);
+
+ mFakeExecutor.runAllReady();
+
+ InOrder ordered = inOrder(mNoMdListener);
+ ordered.verify(mNoMdListener).onMessage(MESSAGE_A);
+ ordered.verify(mNoMdListener).onMessage(MESSAGE_C);
+ }
+
+ @Test
+ public void testSendMessage_NoSubscriber() {
+ mMR.sendMessage(MESSAGE_A);
+ verify(mNoMdListener, never()).onMessage(anyInt());
+
+ mFakeExecutor.runAllReady();
+ verify(mNoMdListener, never()).onMessage(anyInt());
+
+ mMR.subscribeTo(MESSAGE_A, mNoMdListener);
+
+ mFakeExecutor.runAllReady();
+ verify(mNoMdListener, never()).onMessage(anyInt());
+
+ mMR.sendMessage(MESSAGE_A);
+ verify(mNoMdListener, never()).onMessage(anyInt());
+
+ mFakeExecutor.runAllReady();
+ verify(mNoMdListener).onMessage(MESSAGE_A);
+ }
+
+ @Test
+ public void testUnsubscribe_SpecificMessage_NoMetadata() {
+ mMR.subscribeTo(MESSAGE_A, mNoMdListener);
+ mMR.subscribeTo(MESSAGE_B, mNoMdListener);
+ mMR.sendMessage(MESSAGE_A);
+ mMR.sendMessage(MESSAGE_B);
+
+ mFakeExecutor.runAllReady();
+ InOrder ordered = inOrder(mNoMdListener);
+ ordered.verify(mNoMdListener).onMessage(MESSAGE_A);
+ ordered.verify(mNoMdListener).onMessage(MESSAGE_B);
+
+ reset(mNoMdListener);
+ mMR.unsubscribeFrom(MESSAGE_A, mNoMdListener);
+ mMR.sendMessage(MESSAGE_A);
+ mMR.sendMessage(MESSAGE_B);
+
+ mFakeExecutor.runAllReady();
+ verify(mNoMdListener, never()).onMessage(MESSAGE_A);
+ verify(mNoMdListener).onMessage(MESSAGE_B);
+ }
+
+ @Test
+ public void testUnsubscribe_SpecificMessage_WithMetadata() {
+ mMR.subscribeTo(String.class, mStringListener);
+ mMR.subscribeTo(Foobar.class, mFoobarListener);
+ mMR.sendMessage(METADATA_A);
+ mMR.sendMessage(METADATA_FOO);
+
+ mFakeExecutor.runNextReady();
+ verify(mStringListener).onMessage(METADATA_A);
+ mFakeExecutor.runNextReady();
+ verify(mFoobarListener).onMessage(METADATA_FOO);
+
+ reset(mStringListener);
+ reset(mFoobarListener);
+ mMR.unsubscribeFrom(String.class, mStringListener);
+ mMR.sendMessage(METADATA_A);
+ mMR.sendMessage(METADATA_FOO);
+
+ mFakeExecutor.runAllReady();
+ verify(mStringListener, never()).onMessage(METADATA_A);
+ verify(mFoobarListener).onMessage(METADATA_FOO);
+ }
+
+ @Test
+ public void testUnsubscribe_AllMessages_NoMetadata() {
+ mMR.subscribeTo(MESSAGE_A, mNoMdListener);
+ mMR.subscribeTo(MESSAGE_B, mNoMdListener);
+ mMR.subscribeTo(MESSAGE_C, mNoMdListener);
+
+ mMR.sendMessage(MESSAGE_A);
+ mMR.sendMessage(MESSAGE_B);
+ mMR.sendMessage(MESSAGE_C);
+
+ mFakeExecutor.runAllReady();
+ verify(mNoMdListener).onMessage(MESSAGE_A);
+ verify(mNoMdListener).onMessage(MESSAGE_B);
+ verify(mNoMdListener).onMessage(MESSAGE_C);
+
+ reset(mNoMdListener);
+
+ mMR.unsubscribeFrom(mNoMdListener);
+ mMR.sendMessage(MESSAGE_A);
+ mMR.sendMessage(MESSAGE_B);
+ mMR.sendMessage(MESSAGE_C);
+
+ mFakeExecutor.runAllReady();
+ verify(mNoMdListener, never()).onMessage(MESSAGE_A);
+ verify(mNoMdListener, never()).onMessage(MESSAGE_B);
+ verify(mNoMdListener, never()).onMessage(MESSAGE_C);
+ }
+
+ @Test
+ public void testUnsubscribe_AllMessages_WithMetadata() {
+ mMR.subscribeTo(String.class, mStringListener);
+
+ mMR.sendMessage(METADATA_A);
+ mMR.sendMessage(METADATA_B);
+ mMR.sendMessage(METADATA_C);
+
+ mFakeExecutor.runAllReady();
+ verify(mStringListener).onMessage(METADATA_A);
+ verify(mStringListener).onMessage(METADATA_B);
+ verify(mStringListener).onMessage(METADATA_C);
+
+ reset(mStringListener);
+
+ mMR.unsubscribeFrom(mStringListener);
+ mMR.sendMessage(METADATA_A);
+ mMR.sendMessage(METADATA_B);
+ mMR.sendMessage(METADATA_C);
+
+ mFakeExecutor.runAllReady();
+ verify(mStringListener, never()).onMessage(METADATA_A);
+ verify(mStringListener, never()).onMessage(METADATA_B);
+ verify(mStringListener, never()).onMessage(METADATA_C);
+ }
+
+ @Test
+ public void testSingleDelayedMessage_NoMetaData() {
+ mMR.subscribeTo(MESSAGE_A, mNoMdListener);
+
+ mMR.sendMessageDelayed(MESSAGE_A, 100);
+ verify(mNoMdListener, never()).onMessage(anyInt());
+
+ mFakeExecutor.runAllReady();
+ verify(mNoMdListener, never()).onMessage(anyInt());
+
+ mFakeExecutor.advanceClockToNext();
+ mFakeExecutor.runAllReady();
+ verify(mNoMdListener).onMessage(MESSAGE_A);
+ }
+
+ @Test
+ public void testSingleDelayedMessage_WithMetaData() {
+ mMR.subscribeTo(String.class, mStringListener);
+
+ mMR.sendMessageDelayed(METADATA_C, 1000);
+ verify(mStringListener, never()).onMessage(anyString());
+
+ mFakeExecutor.runAllReady();
+ verify(mStringListener, never()).onMessage(anyString());
+
+ mFakeExecutor.advanceClockToNext();
+ mFakeExecutor.runAllReady();
+ verify(mStringListener).onMessage(METADATA_C);
+ }
+
+ @Test
+ public void testMultipleDelayedMessages() {
+ mMR.subscribeTo(String.class, mStringListener);
+
+ mMR.sendMessageDelayed(METADATA_A, 100);
+ mMR.sendMessageDelayed(METADATA_B, 1000);
+ mMR.sendMessageDelayed(METADATA_C, 500);
+ verify(mStringListener, never()).onMessage(anyString());
+
+ mFakeExecutor.runAllReady();
+ verify(mStringListener, never()).onMessage(anyString());
+
+ mFakeExecutor.advanceClockToLast();
+ mFakeExecutor.runAllReady();
+
+ InOrder ordered = inOrder(mStringListener);
+ ordered.verify(mStringListener).onMessage(METADATA_A);
+ ordered.verify(mStringListener).onMessage(METADATA_C);
+ ordered.verify(mStringListener).onMessage(METADATA_B);
+ }
+
+ @Test
+ public void testCancelDelayedMessages() {
+ mMR.subscribeTo(String.class, mStringListener);
+
+ mMR.sendMessageDelayed(METADATA_A, 100);
+ mMR.sendMessageDelayed(METADATA_B, 1000);
+ mMR.sendMessageDelayed(METADATA_C, 500);
+ verify(mStringListener, never()).onMessage(anyString());
+
+ mFakeExecutor.runAllReady();
+ verify(mStringListener, never()).onMessage(anyString());
+
+ mMR.cancelMessages(String.class);
+ mFakeExecutor.advanceClockToLast();
+ mFakeExecutor.runAllReady();
+
+ verify(mStringListener, never()).onMessage(anyString());
+ }
+
+ private static class Foobar {}
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java
index bcc20c2d1b37..a2b016f22473 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java
@@ -17,124 +17,97 @@
package com.android.systemui.util.leak;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.Context;
-import android.os.Looper;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.concurrency.MessageRouterImpl;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
public class GarbageMonitorTest extends SysuiTestCase {
- private LeakReporter mLeakReporter;
- private TrackedGarbage mTrackedGarbage;
- private TestableGarbageMonitor mGarbageMonitor;
+ @Mock private LeakReporter mLeakReporter;
+ @Mock private TrackedGarbage mTrackedGarbage;
+ @Mock private DumpManager mDumpManager;
+ private GarbageMonitor mGarbageMonitor;
+ private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setup() {
- mTrackedGarbage = mock(TrackedGarbage.class);
- mLeakReporter = mock(LeakReporter.class);
+ MockitoAnnotations.initMocks(this);
mGarbageMonitor =
- new TestableGarbageMonitor(
+ new GarbageMonitor(
mContext,
- TestableLooper.get(this).getLooper(),
- new LeakDetector(null, mTrackedGarbage, null),
- mLeakReporter);
- }
-
- @Test
- public void testCallbacks_getScheduled() {
- mGarbageMonitor.startLeakMonitor();
- mGarbageMonitor.runCallbacksOnce();
- mGarbageMonitor.runCallbacksOnce();
- mGarbageMonitor.runCallbacksOnce();
- }
-
- @Test
- public void testNoGarbage_doesntDump() {
- when(mTrackedGarbage.countOldGarbage()).thenReturn(0);
-
- mGarbageMonitor.startLeakMonitor();
- mGarbageMonitor.runCallbacksOnce();
- mGarbageMonitor.runCallbacksOnce();
- mGarbageMonitor.runCallbacksOnce();
-
- verify(mLeakReporter, never()).dumpLeak(anyInt());
+ mFakeExecutor,
+ new MessageRouterImpl(mFakeExecutor),
+ new LeakDetector(null, mTrackedGarbage, null, mDumpManager),
+ mLeakReporter,
+ mDumpManager);
}
@Test
public void testALittleGarbage_doesntDump() {
- when(mTrackedGarbage.countOldGarbage()).thenReturn(4);
+ when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE);
- mGarbageMonitor.startLeakMonitor();
- mGarbageMonitor.runCallbacksOnce();
- mGarbageMonitor.runCallbacksOnce();
- mGarbageMonitor.runCallbacksOnce();
+ mGarbageMonitor.reinspectGarbageAfterGc();
verify(mLeakReporter, never()).dumpLeak(anyInt());
}
@Test
public void testTransientGarbage_doesntDump() {
- when(mTrackedGarbage.countOldGarbage()).thenReturn(100);
+ when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
+ // Start the leak monitor. Nothing gets reported immediately.
mGarbageMonitor.startLeakMonitor();
- mGarbageMonitor.runInspectCallback();
+ mFakeExecutor.runAllReady();
+ verify(mLeakReporter, never()).dumpLeak(anyInt());
+ // Garbage gets reset to 0 before the leak reporte actually gets called.
when(mTrackedGarbage.countOldGarbage()).thenReturn(0);
+ mFakeExecutor.advanceClockToLast();
+ mFakeExecutor.runAllReady();
- mGarbageMonitor.runReinspectCallback();
-
+ // Therefore nothing gets dumped.
verify(mLeakReporter, never()).dumpLeak(anyInt());
}
@Test
public void testLotsOfPersistentGarbage_dumps() {
- when(mTrackedGarbage.countOldGarbage()).thenReturn(100);
+ when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
- mGarbageMonitor.startLeakMonitor();
- mGarbageMonitor.runCallbacksOnce();
+ mGarbageMonitor.reinspectGarbageAfterGc();
- verify(mLeakReporter).dumpLeak(anyInt());
+ verify(mLeakReporter).dumpLeak(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
}
- private static class TestableGarbageMonitor extends GarbageMonitor {
- public TestableGarbageMonitor(
- Context context,
- Looper looper,
- LeakDetector leakDetector,
- LeakReporter leakReporter) {
- super(context, looper, leakDetector, leakReporter);
- }
-
- void runInspectCallback() {
- startLeakMonitor();
- }
-
- void runReinspectCallback() {
- reinspectGarbageAfterGc();
- }
-
- void runCallbacksOnce() {
- // Note that TestableLooper doesn't currently support delayed messages so we need to run
- // callbacks explicitly.
- runInspectCallback();
- runReinspectCallback();
- }
+ @Test
+ public void testLotsOfPersistentGarbage_dumpsAfterAtime() {
+ when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
+
+ // Start the leak monitor. Nothing gets reported immediately.
+ mGarbageMonitor.startLeakMonitor();
+ mFakeExecutor.runAllReady();
+ verify(mLeakReporter, never()).dumpLeak(anyInt());
+
+ mFakeExecutor.advanceClockToLast();
+ mFakeExecutor.runAllReady();
+
+ verify(mLeakReporter).dumpLeak(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
}
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java
index c68c9206a2d8..6e42f0cc5784 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java
@@ -21,12 +21,14 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.leak.ReferenceTestUtils.CollectionWaiter;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import java.io.FileOutputStream;
import java.io.PrintWriter;
@@ -67,7 +69,7 @@ public class LeakDetectorTest extends SysuiTestCase {
@Before
public void setup() {
- mLeakDetector = LeakDetector.create();
+ mLeakDetector = LeakDetector.create(Mockito.mock(DumpManager.class));
// Note: Do not try to factor out object / collection waiter creation. The optimizer will
// try and cache accesses to fields and thus create a GC root for the duration of the test
@@ -112,7 +114,7 @@ public class LeakDetectorTest extends SysuiTestCase {
@Test
public void testDisabled() throws Exception {
- mLeakDetector = new LeakDetector(null, null, null);
+ mLeakDetector = new LeakDetector(null, null, null, Mockito.mock(DumpManager.class));
Object o1 = new Object();
Object o2 = new Object();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
index bff99bf340d6..483dc9fc42b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
@@ -58,3 +58,24 @@ fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
*/
inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
ArgumentCaptor.forClass(T::class.java)
+
+/**
+ * Helper function for creating new mocks, without the need to pass in a [Class] instance.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+inline fun <reified T : Any> mock(): T = Mockito.mock(T::class.java)
+
+/**
+ * Helper function for creating and using a single-use ArgumentCaptor in kotlin.
+ *
+ * val captor = argumentCaptor<Foo>()
+ * verify(...).someMethod(captor.capture())
+ * val captured = captor.value
+ *
+ * becomes:
+ *
+ * val captured = withArgCaptor<Foo> { verify(...).someMethod(capture()) }
+ */
+inline fun <reified T : Any> withArgCaptor(block: ArgumentCaptor<T>.() -> Unit): T =
+ argumentCaptor<T>().apply { block() }.value \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
index 50947ab0ee86..22cf744c726b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
@@ -19,14 +19,21 @@ package com.android.systemui.util.sensors;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.FakeExecution;
-public class FakeProximitySensor extends ProximitySensor {
+public class FakeProximitySensor extends ProximitySensorImpl {
private boolean mAvailable;
private boolean mRegistered;
- public FakeProximitySensor(ThresholdSensor primary, ThresholdSensor secondary,
- DelayableExecutor delayableExecutor) {
- super(primary, secondary == null ? new FakeThresholdSensor() : secondary,
- delayableExecutor, new FakeExecution());
+ public FakeProximitySensor(
+ ThresholdSensor primary,
+ ThresholdSensor secondary,
+ DelayableExecutor delayableExecutor
+ ) {
+ super(
+ primary,
+ secondary == null ? new FakeThresholdSensor() : secondary,
+ delayableExecutor,
+ new FakeExecution()
+ );
mAvailable = true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
index 6e73827fedfb..197873f15d0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
@@ -55,6 +55,7 @@ public class FakeSensorManager extends SensorManager {
private final FakeProximitySensor mFakeProximitySensor;
private final FakeGenericSensor mFakeLightSensor;
+ private final FakeGenericSensor mFakeLightSensor2;
private final FakeGenericSensor mFakeTapSensor;
private final FakeGenericSensor[] mSensors;
@@ -70,7 +71,8 @@ public class FakeSensorManager extends SensorManager {
mSensors = new FakeGenericSensor[]{
mFakeProximitySensor = new FakeProximitySensor(proxSensor),
mFakeLightSensor = new FakeGenericSensor(createSensor(Sensor.TYPE_LIGHT, null)),
- mFakeTapSensor = new FakeGenericSensor(createSensor(99, TAP_SENSOR_TYPE))
+ mFakeTapSensor = new FakeGenericSensor(createSensor(99, TAP_SENSOR_TYPE)),
+ mFakeLightSensor2 = new FakeGenericSensor(createSensor(Sensor.TYPE_LIGHT, null))
};
}
@@ -82,6 +84,10 @@ public class FakeSensorManager extends SensorManager {
return mFakeLightSensor;
}
+ public FakeGenericSensor getFakeLightSensor2() {
+ return mFakeLightSensor2;
+ }
+
public FakeGenericSensor getFakeTapSensor() {
return mFakeTapSensor;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
index d9f978944cde..0d4a6c7023fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
@@ -59,6 +59,16 @@ public class FakeThresholdSensor implements ThresholdSensor {
mListeners.remove(listener);
}
+ @Override
+ public String getName() {
+ return "FakeThresholdSensorName";
+ }
+
+ @Override
+ public String getType() {
+ return "FakeThresholdSensorType";
+ }
+
public void setLoaded(boolean loaded) {
mIsLoaded = loaded;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java
new file mode 100644
index 000000000000..075f393df15a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.systemui.util.sensors;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.verify;
+
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.util.concurrency.FakeExecution;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class PostureDependentProximitySensorTest extends SysuiTestCase {
+ @Mock private Resources mResources;
+ @Mock private DevicePostureController mDevicePostureController;
+ @Mock private AsyncSensorManager mSensorManager;
+
+ @Captor private ArgumentCaptor<DevicePostureController.Callback> mPostureListenerCaptor =
+ ArgumentCaptor.forClass(DevicePostureController.Callback.class);
+ private DevicePostureController.Callback mPostureListener;
+
+ private PostureDependentProximitySensor mProximitySensor;
+ private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ allowTestableLooperAsMainThread();
+
+ mProximitySensor = new PostureDependentProximitySensor(
+ new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE],
+ new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE],
+ mFakeExecutor,
+ new FakeExecution(),
+ mDevicePostureController
+ );
+ }
+
+ @Test
+ public void testPostureChangeListenerAdded() {
+ capturePostureListener();
+ }
+
+ @Test
+ public void testPostureChangeListenerUpdatesPosture() {
+ // GIVEN posture listener is registered
+ capturePostureListener();
+
+ // WHEN the posture changes to DEVICE_POSTURE_OPENED
+ mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_OPENED);
+
+ // THEN device posture is updated to DEVICE_POSTURE_OPENED
+ assertEquals(DevicePostureController.DEVICE_POSTURE_OPENED,
+ mProximitySensor.mDevicePosture);
+
+ // WHEN the posture changes to DEVICE_POSTURE_CLOSED
+ mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_CLOSED);
+
+ // THEN device posture is updated to DEVICE_POSTURE_CLOSED
+ assertEquals(DevicePostureController.DEVICE_POSTURE_CLOSED,
+ mProximitySensor.mDevicePosture);
+
+ // WHEN the posture changes to DEVICE_POSTURE_FLIPPED
+ mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_FLIPPED);
+
+ // THEN device posture is updated to DEVICE_POSTURE_FLIPPED
+ assertEquals(DevicePostureController.DEVICE_POSTURE_FLIPPED,
+ mProximitySensor.mDevicePosture);
+
+ // WHEN the posture changes to DEVICE_POSTURE_HALF_OPENED
+ mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
+
+ // THEN device posture is updated to DEVICE_POSTURE_HALF_OPENED
+ assertEquals(DevicePostureController.DEVICE_POSTURE_HALF_OPENED,
+ mProximitySensor.mDevicePosture);
+ }
+
+ private void capturePostureListener() {
+ verify(mDevicePostureController).addCallback(mPostureListenerCaptor.capture());
+ mPostureListener = mPostureListenerCaptor.getValue();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
index 242fe9f5fffe..19dbf9aa3c13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
@@ -49,7 +49,7 @@ public class ProximityCheckTest extends SysuiTestCase {
private TestableCallback mTestableCallback = new TestableCallback();
- private ProximitySensor.ProximityCheck mProximityCheck;
+ private ProximityCheck mProximityCheck;
@Before
public void setUp() throws Exception {
@@ -58,7 +58,7 @@ public class ProximityCheckTest extends SysuiTestCase {
thresholdSensor.setLoaded(true);
mFakeProximitySensor = new FakeProximitySensor(thresholdSensor, null, mFakeExecutor);
- mProximityCheck = new ProximitySensor.ProximityCheck(mFakeProximitySensor, mFakeExecutor);
+ mProximityCheck = new ProximityCheck(mFakeProximitySensor, mFakeExecutor);
}
@Test
@@ -67,7 +67,7 @@ public class ProximityCheckTest extends SysuiTestCase {
assertNull(mTestableCallback.mLastResult);
- mFakeProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(true, 0));
+ mFakeProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 0));
mFakeProximitySensor.alertListeners();
assertTrue(mTestableCallback.mLastResult);
@@ -103,7 +103,7 @@ public class ProximityCheckTest extends SysuiTestCase {
mProximityCheck.check(100, mTestableCallback);
- mFakeProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(true, 0));
+ mFakeProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 0));
mFakeProximitySensor.alertListeners();
assertThat(mTestableCallback.mLastResult).isNotNull();
@@ -123,7 +123,7 @@ public class ProximityCheckTest extends SysuiTestCase {
assertNull(mTestableCallback.mLastResult);
- mFakeProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(true, 0));
+ mFakeProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 0));
mFakeProximitySensor.alertListeners();
assertTrue(mTestableCallback.mLastResult);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java
index 0e9d96c61e54..5e7557896145 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java
@@ -42,7 +42,7 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class ProximitySensorDualTest extends SysuiTestCase {
+public class ProximitySensorImplDualTest extends SysuiTestCase {
private ProximitySensor mProximitySensor;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private FakeThresholdSensor mThresholdSensorPrimary;
@@ -57,7 +57,7 @@ public class ProximitySensorDualTest extends SysuiTestCase {
mThresholdSensorSecondary = new FakeThresholdSensor();
mThresholdSensorSecondary.setLoaded(true);
- mProximitySensor = new ProximitySensor(
+ mProximitySensor = new ProximitySensorImpl(
mThresholdSensorPrimary, mThresholdSensorSecondary, mFakeExecutor,
new FakeExecution());
}
@@ -430,11 +430,11 @@ public class ProximitySensorDualTest extends SysuiTestCase {
}
private static class TestableListener implements ThresholdSensor.Listener {
- ThresholdSensor.ThresholdSensorEvent mLastEvent;
+ ThresholdSensorEvent mLastEvent;
int mCallCount = 0;
@Override
- public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent proximityEvent) {
+ public void onThresholdCrossed(ThresholdSensorEvent proximityEvent) {
mLastEvent = proximityEvent;
mCallCount++;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java
index 6c6d355d7866..752cd3211161 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java
@@ -42,7 +42,7 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class ProximitySensorSingleTest extends SysuiTestCase {
+public class ProximitySensorImplSingleTest extends SysuiTestCase {
private ProximitySensor mProximitySensor;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private FakeThresholdSensor mThresholdSensor;
@@ -54,7 +54,7 @@ public class ProximitySensorSingleTest extends SysuiTestCase {
mThresholdSensor = new FakeThresholdSensor();
mThresholdSensor.setLoaded(true);
- mProximitySensor = new ProximitySensor(
+ mProximitySensor = new ProximitySensorImpl(
mThresholdSensor, new FakeThresholdSensor(), mFakeExecutor, new FakeExecution());
}
@@ -215,7 +215,7 @@ public class ProximitySensorSingleTest extends SysuiTestCase {
public void testPreventRecursiveAlert() {
TestableListener listenerA = new TestableListener() {
@Override
- public void onThresholdCrossed(ProximitySensor.ThresholdSensorEvent proximityEvent) {
+ public void onThresholdCrossed(ThresholdSensorEvent proximityEvent) {
super.onThresholdCrossed(proximityEvent);
if (mCallCount < 2) {
mProximitySensor.alertListeners();
@@ -231,11 +231,11 @@ public class ProximitySensorSingleTest extends SysuiTestCase {
}
private static class TestableListener implements ThresholdSensor.Listener {
- ThresholdSensor.ThresholdSensorEvent mLastEvent;
+ ThresholdSensorEvent mLastEvent;
int mCallCount = 0;
@Override
- public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent proximityEvent) {
+ public void onThresholdCrossed(ThresholdSensorEvent proximityEvent) {
mLastEvent = proximityEvent;
mCallCount++;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
index 125063a7adc4..b10f16c963ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
@@ -380,7 +380,7 @@ public class ThresholdSensorImplTest extends SysuiTestCase {
int mCallCount;
@Override
- public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent event) {
+ public void onThresholdCrossed(ThresholdSensorEvent event) {
mBelow = event.getBelow();
mTimestampNs = event.getTimestampNs();
mCallCount++;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
index 7bb26748a9d9..e66491e4cbd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
@@ -123,11 +123,11 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti
Uri uri = getUriFor(name);
for (ContentObserver observer : mContentObservers.getOrDefault(key, new ArrayList<>())) {
- observer.dispatchChange(false, List.of(uri), userHandle);
+ observer.dispatchChange(false, List.of(uri), 0, userHandle);
}
for (ContentObserver observer :
mContentObserversAllUsers.getOrDefault(uri.toString(), new ArrayList<>())) {
- observer.dispatchChange(false, List.of(uri), userHandle);
+ observer.dispatchChange(false, List.of(uri), 0, userHandle);
}
return true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
index 34cae58d30e1..f65caee24e34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -86,7 +87,8 @@ public class FakeSettingsTest extends SysuiTestCase {
mFakeSettings.putString("cat", "hat");
- verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt());
+ verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt(),
+ anyInt());
}
@Test
@@ -96,7 +98,8 @@ public class FakeSettingsTest extends SysuiTestCase {
mFakeSettings.putString("cat", "hat");
- verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt());
+ verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt(),
+ anyInt());
}
@Test
@@ -119,6 +122,18 @@ public class FakeSettingsTest extends SysuiTestCase {
mFakeSettings.putString("cat", "hat");
verify(mContentObserver, never()).dispatchChange(
- anyBoolean(), any(Collection.class), anyInt());
+ anyBoolean(), any(Collection.class), anyInt(), anyInt());
+ }
+
+ @Test
+ public void testContentObserverDispatchCorrectUser() {
+ int user = 10;
+ mFakeSettings.registerContentObserverForUser(
+ mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL
+ );
+
+ mFakeSettings.putStringForUser("cat", "hat", user);
+ verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt(),
+ eq(user));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
index e7acfae24f30..8ea9da6f4d0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
@@ -18,9 +18,9 @@ import android.os.Bundle;
import android.testing.LeakCheck;
import com.android.settingslib.net.DataUsageController;
+import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.NetworkController.SignalCallback;
import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
public class FakeNetworkController extends BaseLeakChecker<SignalCallback>
implements NetworkController {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
index 6d1e6ce9ea33..d245c727dcf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
@@ -22,7 +22,7 @@ import com.android.systemui.shared.plugins.PluginManager;
public class FakePluginManager implements PluginManager {
- private final BaseLeakChecker<PluginListener> mLeakChecker;
+ private final BaseLeakChecker<PluginListener<?>> mLeakChecker;
public FakePluginManager(LeakCheck test) {
mLeakChecker = new BaseLeakChecker<>(test, "Plugin");
@@ -30,24 +30,24 @@ public class FakePluginManager implements PluginManager {
@Override
public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
- Class cls, boolean allowMultiple) {
+ Class<T> cls, boolean allowMultiple) {
mLeakChecker.addCallback(listener);
}
@Override
- public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) {
+ public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls) {
mLeakChecker.addCallback(listener);
}
@Override
- public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,
+ public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<T> cls,
boolean allowMultiple) {
mLeakChecker.addCallback(listener);
}
@Override
public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
- Class<?> cls) {
+ Class<T> cls) {
mLeakChecker.addCallback(listener);
}
@@ -62,17 +62,7 @@ public class FakePluginManager implements PluginManager {
}
@Override
- public String[] getWhitelistedPlugins() {
+ public String[] getPrivilegedPlugins() {
return new String[0];
}
-
- @Override
- public <T extends Plugin> T getOneShotPlugin(Class<T> cls) {
- return null;
- }
-
- @Override
- public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) {
- return null;
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
index 4f9cb35db1a3..be110242a3eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
@@ -51,11 +51,6 @@ public class FakeRotationLockController extends BaseLeakChecker<RotationLockCont
}
@Override
- public boolean isCameraRotationEnabled() {
- return false;
- }
-
- @Override
public void setRotationLockedAtAngle(boolean locked, int rotation) {
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
index fedc08d93bc7..dc6a8fb9a4c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
@@ -19,6 +19,7 @@ import android.util.ArrayMap;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -29,7 +30,6 @@ import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.SecurityController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index dd4830e893af..949345662e79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -41,9 +41,13 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import org.junit.Before;
import org.junit.Test;
@@ -68,23 +72,34 @@ public class VolumeDialogImplTest extends SysuiTestCase {
View mDrawerNormal;
@Mock
- VolumeDialogController mController;
-
+ VolumeDialogController mVolumeDialogController;
@Mock
KeyguardManager mKeyguard;
-
@Mock
AccessibilityManagerWrapper mAccessibilityMgr;
+ @Mock
+ DeviceProvisionedController mDeviceProvisionedController;
+ @Mock
+ ConfigurationController mConfigurationController;
+ @Mock
+ MediaOutputDialogFactory mMediaOutputDialogFactory;
+ @Mock
+ ActivityStarter mActivityStarter;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- mController = mDependency.injectMockDependency(VolumeDialogController.class);
- mAccessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
getContext().addMockSystemService(KeyguardManager.class, mKeyguard);
- mDialog = new VolumeDialogImpl(getContext());
+ mDialog = new VolumeDialogImpl(
+ getContext(),
+ mVolumeDialogController,
+ mAccessibilityMgr,
+ mDeviceProvisionedController,
+ mConfigurationController,
+ mMediaOutputDialogFactory,
+ mActivityStarter);
mDialog.init(0, null);
State state = createShellState();
mDialog.onStateChangedH(state);
@@ -170,7 +185,7 @@ public class VolumeDialogImplTest extends SysuiTestCase {
Mockito.reset(mAccessibilityMgr);
ArgumentCaptor<VolumeDialogController.Callbacks> controllerCallbackCapture =
ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class);
- verify(mController).addCallback(controllerCallbackCapture.capture(), any());
+ verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any());
VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue();
callbacks.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI);
verify(mAccessibilityMgr).getRecommendedTimeoutMillis(
@@ -201,13 +216,13 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mDialog.onStateChangedH(initialSilentState);
// expected: shouldn't call vibrate yet
- verify(mController, never()).vibrate(any());
+ verify(mVolumeDialogController, never()).vibrate(any());
// changed ringer to vibrate
mDialog.onStateChangedH(vibrateState);
// expected: vibrate device
- verify(mController).vibrate(any());
+ verify(mVolumeDialogController).vibrate(any());
}
@Test
@@ -225,7 +240,7 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mDialog.onStateChangedH(vibrateState);
// shouldn't call vibrate
- verify(mController, never()).vibrate(any());
+ verify(mVolumeDialogController, never()).vibrate(any());
}
@Test
@@ -238,7 +253,7 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mDrawerVibrate.performClick();
// Make sure we've actually changed the ringer mode.
- verify(mController, times(1)).setRingerMode(
+ verify(mVolumeDialogController, times(1)).setRingerMode(
AudioManager.RINGER_MODE_VIBRATE, false);
}
@@ -252,7 +267,7 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mDrawerMute.performClick();
// Make sure we've actually changed the ringer mode.
- verify(mController, times(1)).setRingerMode(
+ verify(mVolumeDialogController, times(1)).setRingerMode(
AudioManager.RINGER_MODE_SILENT, false);
}
@@ -266,7 +281,7 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mDrawerNormal.performClick();
// Make sure we've actually changed the ringer mode.
- verify(mController, times(1)).setRingerMode(
+ verify(mVolumeDialogController, times(1)).setRingerMode(
AudioManager.RINGER_MODE_NORMAL, false);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index f243077afc93..1159e0912926 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -24,6 +24,7 @@ import static android.service.notification.NotificationListenerService.REASON_CA
import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NOTIF_CANCEL;
import static com.google.common.truth.Truth.assertThat;
@@ -75,11 +76,11 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
import com.android.systemui.statusbar.RankingBuilder;
@@ -120,6 +121,7 @@ import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.google.common.collect.ImmutableList;
@@ -177,6 +179,7 @@ public class BubblesTest extends SysuiTestCase {
private SysUiState mSysUiState;
private boolean mSysUiStateBubblesExpanded;
+ private boolean mSysUiStateBubblesManageMenuExpanded;
@Captor
private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
@@ -293,9 +296,13 @@ public class BubblesTest extends SysuiTestCase {
when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
mSysUiState = new SysUiState();
- mSysUiState.addCallback(sysUiFlags ->
- mSysUiStateBubblesExpanded =
- (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0);
+ mSysUiState.addCallback(sysUiFlags -> {
+ mSysUiStateBubblesManageMenuExpanded =
+ (sysUiFlags
+ & QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
+ mSysUiStateBubblesExpanded =
+ (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0;
+ });
// TODO: Fix
mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
@@ -331,7 +338,8 @@ public class BubblesTest extends SysuiTestCase {
mPositioner,
mock(DisplayController.class),
syncExecutor,
- mock(Handler.class));
+ mock(Handler.class),
+ mock(SyncTransactionQueue.class));
mBubbleController.setExpandListener(mBubbleExpandListener);
spyOn(mBubbleController);
@@ -369,8 +377,7 @@ public class BubblesTest extends SysuiTestCase {
public void testAddBubble() {
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
-
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -378,7 +385,7 @@ public class BubblesTest extends SysuiTestCase {
assertFalse(mBubbleController.hasBubbles());
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -393,7 +400,7 @@ public class BubblesTest extends SysuiTestCase {
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
verify(mNotificationEntryManager, times(2)).updateNotifications(anyString());
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -439,7 +446,7 @@ public class BubblesTest extends SysuiTestCase {
mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
+ mRow.getKey(), DISMISS_NOTIF_CANCEL);
verify(mNotificationEntryManager, times(1)).performRemoveNotification(
eq(mRow.getSbn()), any(), anyInt());
assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
@@ -456,7 +463,7 @@ public class BubblesTest extends SysuiTestCase {
verify(mNotificationEntryManager, never()).performRemoveNotification(
eq(mRow.getSbn()), any(), anyInt());
assertFalse(mBubbleController.hasBubbles());
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
assertTrue(mRow.isBubble());
}
@@ -475,7 +482,7 @@ public class BubblesTest extends SysuiTestCase {
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -495,8 +502,7 @@ public class BubblesTest extends SysuiTestCase {
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
// Make sure the notif is suppressed
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -505,8 +511,7 @@ public class BubblesTest extends SysuiTestCase {
mBubbleController.collapseStack();
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
assertStackCollapsed();
-
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -529,8 +534,7 @@ public class BubblesTest extends SysuiTestCase {
assertStackExpanded();
verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
true, mRow2.getKey());
-
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
// Last added is the one that is expanded
assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey());
@@ -554,8 +558,7 @@ public class BubblesTest extends SysuiTestCase {
// Collapse
mBubbleController.collapseStack();
assertStackCollapsed();
-
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -575,8 +578,7 @@ public class BubblesTest extends SysuiTestCase {
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
// Notif is suppressed after expansion
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -601,8 +603,7 @@ public class BubblesTest extends SysuiTestCase {
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
// Notif is suppressed after expansion
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -631,7 +632,7 @@ public class BubblesTest extends SysuiTestCase {
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey());
@@ -663,7 +664,7 @@ public class BubblesTest extends SysuiTestCase {
assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY);
verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY);
assertTrue(mBubbleController.hasBubbles());
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -676,7 +677,7 @@ public class BubblesTest extends SysuiTestCase {
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
@@ -691,7 +692,7 @@ public class BubblesTest extends SysuiTestCase {
// We should be collapsed
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
assertFalse(mBubbleController.hasBubbles());
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -708,8 +709,7 @@ public class BubblesTest extends SysuiTestCase {
verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
mRow.getKey());
assertStackCollapsed();
-
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -725,8 +725,7 @@ public class BubblesTest extends SysuiTestCase {
verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
mRow.getKey());
assertStackExpanded();
-
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -743,8 +742,7 @@ public class BubblesTest extends SysuiTestCase {
// Dot + flyout is hidden because notif is suppressed
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
-
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -766,8 +764,7 @@ public class BubblesTest extends SysuiTestCase {
// Dot + flyout is hidden because notif is suppressed
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
-
- assertFalse(mSysUiStateBubblesExpanded);
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -781,7 +778,7 @@ public class BubblesTest extends SysuiTestCase {
mBubbleController.expandStackAndSelectBubble(mBubbleEntry);
- assertTrue(mSysUiStateBubblesExpanded);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -1144,6 +1141,28 @@ public class BubblesTest extends SysuiTestCase {
// Verify these are in the overflow
assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntryUser11.getKey())).isNotNull();
assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2User11.getKey())).isNotNull();
+
+ // Would have loaded bubbles twice because of user switch
+ verify(mDataRepository, times(2)).loadBubbles(anyInt(), any());
+ }
+
+ /**
+ * Verifies we only load the overflow data once.
+ */
+ @Test
+ public void testOverflowLoadedOnce() {
+ mBubbleController.updateBubble(mBubbleEntry);
+ mBubbleController.updateBubble(mBubbleEntry2);
+ mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
+ assertThat(mBubbleData.getOverflowBubbles().isEmpty()).isFalse();
+
+ mBubbleController.updateBubble(mBubbleEntry);
+ mBubbleController.updateBubble(mBubbleEntry2);
+ mBubbleController.removeBubble(mBubbleEntry.getKey(), DISMISS_NOTIF_CANCEL);
+ mBubbleController.removeBubble(mBubbleEntry2.getKey(), DISMISS_NOTIF_CANCEL);
+ assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
+
+ verify(mDataRepository, times(1)).loadBubbles(anyInt(), any());
}
@@ -1181,6 +1200,63 @@ public class BubblesTest extends SysuiTestCase {
assertNotNull(info);
}
+ @Test
+ public void testShowManageMenuChangesSysuiState() {
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Expand the stack
+ BubbleStackView stackView = mBubbleController.getStackView();
+ mBubbleData.setExpanded(true);
+ assertStackExpanded();
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+ // Show the menu
+ stackView.showManageMenu(true);
+ assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+ }
+
+ @Test
+ public void testHideManageMenuChangesSysuiState() {
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Expand the stack
+ BubbleStackView stackView = mBubbleController.getStackView();
+ mBubbleData.setExpanded(true);
+ assertStackExpanded();
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+ // Show the menu
+ stackView.showManageMenu(true);
+ assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+
+ // Hide the menu
+ stackView.showManageMenu(false);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+ }
+
+ @Test
+ public void testCollapseBubbleManageMenuChangesSysuiState() {
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Expand the stack
+ BubbleStackView stackView = mBubbleController.getStackView();
+ mBubbleData.setExpanded(true);
+ assertStackExpanded();
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+ // Show the menu
+ stackView.showManageMenu(true);
+ assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+
+ // Collapse the stack
+ mBubbleData.setExpanded(false);
+
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+ }
+
/** Creates a bubble using the userId and package. */
private Bubble createBubble(int userId, String pkg) {
final UserHandle userHandle = new UserHandle(userId);
@@ -1278,4 +1354,12 @@ public class BubblesTest extends SysuiTestCase {
assertFalse(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
entry.getKey(), entry.getGroupKey()));
}
+
+ /**
+ * Asserts that the system ui states associated to bubbles are in the correct state.
+ */
+ private void assertSysuiStates(boolean stackExpanded, boolean manageMenuExpanded) {
+ assertThat(mSysUiStateBubblesExpanded).isEqualTo(stackExpanded);
+ assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index e4c78009f491..05c4822f288e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.wmshell;
import static android.app.Notification.FLAG_BUBBLE;
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -47,6 +48,7 @@ import android.content.pm.LauncherApps;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.PowerManager;
+import android.os.UserHandle;
import android.service.dreams.IDreamManager;
import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
@@ -62,10 +64,11 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -100,6 +103,7 @@ import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
import org.junit.Before;
@@ -157,7 +161,9 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
@Mock
private AuthController mAuthController;
- private SysUiState mSysUiState = new SysUiState();
+ private SysUiState mSysUiState;
+ private boolean mSysUiStateBubblesExpanded;
+ private boolean mSysUiStateBubblesManageMenuExpanded;
@Captor
private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor;
@@ -171,6 +177,10 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
private ExpandableNotificationRow mNonBubbleNotifRow;
private BubbleEntry mBubbleEntry;
private BubbleEntry mBubbleEntry2;
+
+ private BubbleEntry mBubbleEntryUser11;
+ private BubbleEntry mBubbleEntry2User11;
+
@Mock
private Bubbles.BubbleExpandListener mBubbleExpandListener;
@Mock
@@ -240,9 +250,25 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
mBubbleEntry = BubblesManager.notifToBubbleEntry(mRow);
mBubbleEntry2 = BubblesManager.notifToBubbleEntry(mRow2);
+ UserHandle handle = mock(UserHandle.class);
+ when(handle.getIdentifier()).thenReturn(11);
+ mBubbleEntryUser11 = BubblesManager.notifToBubbleEntry(
+ mNotificationTestHelper.createBubble(handle));
+ mBubbleEntry2User11 = BubblesManager.notifToBubbleEntry(
+ mNotificationTestHelper.createBubble(handle));
+
mZenModeConfig.suppressedVisualEffects = 0;
when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
+ mSysUiState = new SysUiState();
+ mSysUiState.addCallback(sysUiFlags -> {
+ mSysUiStateBubblesManageMenuExpanded =
+ (sysUiFlags
+ & QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
+ mSysUiStateBubblesExpanded =
+ (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0;
+ });
+
mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
mPositioner.setMaxBubbles(5);
mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
@@ -275,7 +301,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
mPositioner,
mock(DisplayController.class),
syncExecutor,
- mock(Handler.class));
+ mock(Handler.class),
+ mock(SyncTransactionQueue.class));
mBubbleController.setExpandListener(mBubbleExpandListener);
spyOn(mBubbleController);
@@ -310,6 +337,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
public void testAddBubble() {
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -317,6 +345,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
assertFalse(mBubbleController.hasBubbles());
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -330,6 +359,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
+
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -392,6 +423,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
verify(mNotifCallback, times(3)).invalidateNotifications(anyString());
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
+
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -410,6 +443,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
// Make sure the notif is suppressed
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -418,6 +452,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
mBubbleController.collapseStack();
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
assertStackCollapsed();
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -440,6 +475,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
assertStackExpanded();
verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
true, mRow2.getKey());
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
// Last added is the one that is expanded
assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey());
@@ -464,6 +500,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
// Collapse
mBubbleController.collapseStack();
assertStackCollapsed();
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -483,6 +520,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
// Notif is suppressed after expansion
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -507,6 +545,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
// Notif is suppressed after expansion
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -535,6 +574,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey());
@@ -565,6 +606,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY);
verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY);
assertTrue(mBubbleController.hasBubbles());
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -577,6 +619,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
@@ -591,6 +634,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
// We should be collapsed
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
assertFalse(mBubbleController.hasBubbles());
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@@ -608,6 +652,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
mRow.getKey());
assertStackCollapsed();
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -623,6 +668,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
mRow.getKey());
assertStackExpanded();
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -639,6 +685,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
// Dot + flyout is hidden because notif is suppressed
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -660,6 +707,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
// Dot + flyout is hidden because notif is suppressed
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
@@ -906,6 +954,122 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
groupSummary.getEntry().getSbn().getGroupKey()));
}
+
+ /**
+ * Verifies that when the user changes, the bubbles in the overflow list is cleared. Doesn't
+ * test the loading from the repository which would be a nice thing to add.
+ */
+ @Test
+ public void testOnUserChanged_overflowState() {
+ int firstUserId = mBubbleEntry.getStatusBarNotification().getUser().getIdentifier();
+ int secondUserId = mBubbleEntryUser11.getStatusBarNotification().getUser().getIdentifier();
+
+ mBubbleController.updateBubble(mBubbleEntry);
+ mBubbleController.updateBubble(mBubbleEntry2);
+ assertTrue(mBubbleController.hasBubbles());
+ mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
+
+ // Verify these are in the overflow
+ assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry.getKey())).isNotNull();
+ assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2.getKey())).isNotNull();
+
+ // Switch users
+ mBubbleController.onUserChanged(secondUserId);
+ assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
+
+ // Give this user some bubbles
+ mBubbleController.updateBubble(mBubbleEntryUser11);
+ mBubbleController.updateBubble(mBubbleEntry2User11);
+ assertTrue(mBubbleController.hasBubbles());
+ mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
+
+ // Verify these are in the overflow
+ assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntryUser11.getKey())).isNotNull();
+ assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2User11.getKey())).isNotNull();
+
+ // Would have loaded bubbles twice because of user switch
+ verify(mDataRepository, times(2)).loadBubbles(anyInt(), any());
+ }
+
+ /**
+ * Verifies we only load the overflow data once.
+ */
+ @Test
+ public void testOverflowLoadedOnce() {
+ when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getKey()))
+ .thenReturn(mRow);
+ when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getKey()))
+ .thenReturn(mRow2);
+
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
+ mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
+ assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty();
+
+ mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL);
+ mEntryListener.onEntryRemoved(mRow2, REASON_APP_CANCEL);
+ assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
+
+ verify(mDataRepository, times(1)).loadBubbles(anyInt(), any());
+ }
+
+ @Test
+ public void testShowManageMenuChangesSysuiState() {
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Expand the stack
+ BubbleStackView stackView = mBubbleController.getStackView();
+ mBubbleData.setExpanded(true);
+ assertStackExpanded();
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+ // Show the menu
+ stackView.showManageMenu(true);
+ assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+ }
+
+ @Test
+ public void testHideManageMenuChangesSysuiState() {
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Expand the stack
+ BubbleStackView stackView = mBubbleController.getStackView();
+ mBubbleData.setExpanded(true);
+ assertStackExpanded();
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+ // Show the menu
+ stackView.showManageMenu(true);
+ assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+
+ // Hide the menu
+ stackView.showManageMenu(false);
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+ }
+
+ @Test
+ public void testCollapseBubbleManageMenuChangesSysuiState() {
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Expand the stack
+ BubbleStackView stackView = mBubbleController.getStackView();
+ mBubbleData.setExpanded(true);
+ assertStackExpanded();
+ assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+
+ // Show the menu
+ stackView.showManageMenu(true);
+ assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+
+ // Collapse the stack
+ mBubbleData.setExpanded(false);
+
+ assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+ }
+
/**
* Sets the bubble metadata flags for this entry. These flags are normally set by
* NotificationManagerService when the notification is sent, however, these tests do not
@@ -960,4 +1124,12 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
assertFalse(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
entry.getKey(), entry.getGroupKey()));
}
+
+ /**
+ * Asserts that the system ui states associated to bubbles are in the correct state.
+ */
+ private void assertSysuiStates(boolean stackExpanded, boolean manageMenuExpanded) {
+ assertThat(mSysUiStateBubblesExpanded).isEqualTo(stackExpanded);
+ assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index cd5aa9a3f9dc..7b77cb0b5b30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -32,6 +32,7 @@ import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
/**
@@ -54,11 +55,12 @@ public class TestableBubbleController extends BubbleController {
BubblePositioner positioner,
DisplayController displayController,
ShellExecutor shellMainExecutor,
- Handler shellMainHandler) {
+ Handler shellMainHandler,
+ SyncTransactionQueue syncQueue) {
super(context, data, Runnable::run, floatingContentCoordinator, dataRepository,
statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
bubbleLogger, taskStackListener, shellTaskOrganizer, positioner, displayController,
- shellMainExecutor, shellMainHandler);
+ shellMainExecutor, shellMainHandler, syncQueue);
setInflateSynchronously(true);
initialize();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 5691660b4882..8480702c57c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -41,6 +41,7 @@ import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.splitscreen.SplitScreen;
import org.junit.Before;
import org.junit.Test;
@@ -69,6 +70,7 @@ public class WMShellTest extends SysuiTestCase {
@Mock SysUiState mSysUiState;
@Mock Pip mPip;
@Mock LegacySplitScreen mLegacySplitScreen;
+ @Mock SplitScreen mSplitScreen;
@Mock OneHanded mOneHanded;
@Mock HideDisplayCutout mHideDisplayCutout;
@Mock WakefulnessLifecycle mWakefulnessLifecycle;
@@ -81,7 +83,7 @@ public class WMShellTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
mWMShell = new WMShell(mContext, Optional.of(mPip), Optional.of(mLegacySplitScreen),
- Optional.of(mOneHanded), Optional.of(mHideDisplayCutout),
+ Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mHideDisplayCutout),
Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController,
mKeyguardUpdateMonitor, mNavigationModeController,
mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle,
@@ -96,8 +98,15 @@ public class WMShellTest extends SysuiTestCase {
}
@Test
+ public void initLegacySplitScreen_registersCallbacks() {
+ mWMShell.initLegacySplitScreen(mLegacySplitScreen);
+
+ verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
+ }
+
+ @Test
public void initSplitScreen_registersCallbacks() {
- mWMShell.initSplitScreen(mLegacySplitScreen);
+ mWMShell.initSplitScreen(mSplitScreen);
verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
}
diff --git a/packages/VpnDialogs/res/values-ar/strings.xml b/packages/VpnDialogs/res/values-ar/strings.xml
index 33be6a3e458a..b99d761e1993 100644
--- a/packages/VpnDialogs/res/values-ar/strings.xml
+++ b/packages/VpnDialogs/res/values-ar/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"طلب الاتصال"</string>
<string name="warning" msgid="809658604548412033">"‏يريد <xliff:g id="APP">%s</xliff:g> إعداد الاتصال بالشبكة الافتراضية الخاصة التي تتيح له مراقبة حركة المرور على الشبكة. فلا توافق إلا إذا كنت تثق في المصدر. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; يظهر في الجزء العلوي من الشاشة عندما تكون الشبكة الافتراضية الخاصة نشطة."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"‏يريد تطبيق <xliff:g id="APP">%s</xliff:g> إعداد اتصال شبكة افتراضية خاصة (VPN) يتيح له مراقبة حركة بيانات الشبكة. لا تقبل السماح بذلك إلا إذا كنت تثق في المصدر. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; يظهر على شاشتك عندما تكون الشبكة الافتراضية الخاصة نشطة."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"‏VPN متصلة"</string>
<string name="session" msgid="6470628549473641030">"الجلسة"</string>
<string name="duration" msgid="3584782459928719435">"المدة:"</string>
diff --git a/packages/VpnDialogs/res/values-as/strings.xml b/packages/VpnDialogs/res/values-as/strings.xml
index 3f2e2347135d..ad404cf6778e 100644
--- a/packages/VpnDialogs/res/values-as/strings.xml
+++ b/packages/VpnDialogs/res/values-as/strings.xml
@@ -17,8 +17,9 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"সংযোগৰ অনুৰোধ"</string>
- <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g>এ নেটৱৰ্ক ট্ৰেফিক নিৰীক্ষণ কৰিবলৈ এটা ভিপিএন সংযোগ ছেট আপ কৰিবলৈ বিচাৰিছে৷ আপুনি কেৱল উৎসটোক বিশ্বাস কৰিলেহে অনুৰোধ স্বীকাৰ কৰিব৷ ভিপিএন সক্ৰিয় থকাৰ সময়ত আপোনাৰ স্ক্ৰীনৰ ওপৰত &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; দৃশ্যমান হয়৷"</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g>এ এটা ভিপিএন সংযোগ ছেট আপ কৰিব বিচাৰে, যিটোৱে ইয়াক নেটৱৰ্ক ট্ৰেফিক নিৰীক্ষণ কৰিবলৈ দিয়ে। আপুনি উৎসটোক বিশ্বাস কৰিলেহে গ্ৰহণ কৰক। ভিপিএনটো সক্ৰিয় হৈ থকাৰ সময়ত আপোনাৰ স্ক্ৰীনত&lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; প্ৰদৰ্শিত হয়।"</string>
+ <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g>এ নেটৱৰ্ক ট্ৰেফিক নিৰীক্ষণ কৰিবলৈ এটা ভিপিএন সংযোগ ছেট আপ কৰিবলৈ বিচাৰিছে৷ আপুনি কেৱল উৎসটোক বিশ্বাস কৰিলেহে অনুৰোধ স্বীকাৰ কৰিব৷ ভিপিএন সক্ৰিয় থকাৰ সময়ত আপোনাৰ স্ক্ৰীণৰ ওপৰত &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; দৃশ্যমান হয়৷"</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"ভিপিএন সংযোগ হৈ আছে"</string>
<string name="session" msgid="6470628549473641030">"ছেশ্বন:"</string>
<string name="duration" msgid="3584782459928719435">"সময়সীমা:"</string>
@@ -29,7 +30,7 @@
<string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g>ক সকলো সময়তে সংযুক্ত হৈ থাকিবলৈ ছেট কৰি থোৱা হৈছে, কিন্তু ই বৰ্তমান সংযোগ কৰিবপৰা নাই। আপোনাৰ ফ\'নটোৱে <xliff:g id="VPN_APP_1">%1$s</xliff:g>ৰ সৈতে সংযোগ কৰিব নোৱাৰালৈকে এটা ৰাজহুৱা নেটৱৰ্ক ব্যৱহাৰ কৰিব।"</string>
<string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g>ক সকলো সময়তে সংযুক্ত হৈ থাকিবলৈ ছেট কৰি থোৱা হৈছে, কিন্তু ই বৰ্তমান সংযোগ কৰিবপৰা নাই। ভিপিএনটোৰ সৈতে পুনৰ সংযুক্ত নোহোৱালৈকে আপোনাৰ কোনো সংযোগ নাথাকিব।"</string>
<string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
- <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"ভিপিএন ছেটিং সলনি কৰক"</string>
+ <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"ভিপিএন ছেটিংসমূহ সলনি কৰক"</string>
<string name="configure" msgid="4905518375574791375">"কনফিগাৰ কৰক"</string>
<string name="disconnect" msgid="971412338304200056">"সংযোগ বিচ্ছিন্ন কৰক"</string>
<string name="open_app" msgid="3717639178595958667">"এপ্ খোলক"</string>
diff --git a/packages/VpnDialogs/res/values-az/strings.xml b/packages/VpnDialogs/res/values-az/strings.xml
index d8788350bb8c..09428b8f174a 100644
--- a/packages/VpnDialogs/res/values-az/strings.xml
+++ b/packages/VpnDialogs/res/values-az/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Bağlantı Sorğusu"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> VPN bağlantı yaratmaq istəyir ki, bu da şəbəkə trafikini izləyə bilər. Yalnız mənbəyə güvəndiyiniz halda qəbul edin. VPN aktiv olan zaman &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; ekranın yuxarısında görünür."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> şəbəkə trafikini izləməyə imkan verən VPN bağlantısı yaratmaq istəyir. Yalnız mənbəyə güvəndiyiniz halda qəbul edin. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; VPN aktiv olan zaman ekranda görünür."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN qoşuludur"</string>
<string name="session" msgid="6470628549473641030">"Sessiya:"</string>
<string name="duration" msgid="3584782459928719435">"Müddət:"</string>
diff --git a/packages/VpnDialogs/res/values-be/strings.xml b/packages/VpnDialogs/res/values-be/strings.xml
index fc3f8787b5d7..88e6a615e225 100644
--- a/packages/VpnDialogs/res/values-be/strings.xml
+++ b/packages/VpnDialogs/res/values-be/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Запыт на падлучэнне"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> спрабуе наладзіць падлучэнне VPN, якое дазваляе сачыць за сеткавым трафікам. Прымайце толькі тады, калі вы давяраеце гэтай крыніцы. Калі VPN актыўны, у верхняй частцы экрана адлюстроўваецца &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt;."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"Праграма \"<xliff:g id="APP">%s</xliff:g>\" запытвае дазвол на падключэнне да сеткі VPN, каб адсочваць сеткавы трафік. Дайце дазвол, толькі калі вы давяраеце крыніцы. Калі адбудзецца падключэнне да VPN, на экране з\'явіцца значок &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt;."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN падключаны"</string>
<string name="session" msgid="6470628549473641030">"Сессія"</string>
<string name="duration" msgid="3584782459928719435">"Працягласць:"</string>
diff --git a/packages/VpnDialogs/res/values-bn/strings.xml b/packages/VpnDialogs/res/values-bn/strings.xml
index 352b786bc009..041e46c5c159 100644
--- a/packages/VpnDialogs/res/values-bn/strings.xml
+++ b/packages/VpnDialogs/res/values-bn/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"সংযোগের অনুরোধ"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> এমন একটি VPN সংযোগ সেট-আপ করতে চাচ্ছে যেটি দিয়ে এটি নেটওয়ার্ক ট্রাফিক নিরীক্ষণ করতে পারবে। আপনি যদি উৎসটিকে বিশ্বাস করেন, তাহলেই কেবল এতে সম্মতি দিন। VPN সক্রিয় থাকলে আপনার স্ক্রীনের উপরে &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; দেখা যাবে।"</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> এমন একটি VPN সংযোগ সেট আপ করতে চাইছে যেটি দিয়ে এটি নেটওয়ার্ক ট্রাফিক নিরীক্ষণ করতে পারবে। আপনি সোর্সটি বিশ্বাস করলে একমাত্র তখনই অ্যাক্সেপ্ট করুন। PN অ্যাক্টিভ থাকলে &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; আপনার স্ক্রিনে দেখা যায়।"</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN সংযুক্ত হয়েছে"</string>
<string name="session" msgid="6470628549473641030">"অধিবেশন:"</string>
<string name="duration" msgid="3584782459928719435">"সময়কাল:"</string>
diff --git a/packages/VpnDialogs/res/values-ca/strings.xml b/packages/VpnDialogs/res/values-ca/strings.xml
index cdb754723c28..7674c2c08837 100644
--- a/packages/VpnDialogs/res/values-ca/strings.xml
+++ b/packages/VpnDialogs/res/values-ca/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Sol·licitud de connexió"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> vol configurar una connexió VPN que li permeti controlar el trànsit de xarxa. Accepta la sol·licitud només si prové d\'una font de confiança. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; es mostra a la part superior de la pantalla quan la VPN està activada."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> vol configurar una connexió VPN que li permeti monitorar el trànsit de xarxa. Accepta la sol·licitud només si prové d\'una font de confiança. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; és la icona que veuràs a la pantalla quan la VPN estigui activa."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"La VPN està connectada"</string>
<string name="session" msgid="6470628549473641030">"Sessió:"</string>
<string name="duration" msgid="3584782459928719435">"Durada:"</string>
diff --git a/packages/VpnDialogs/res/values-et/strings.xml b/packages/VpnDialogs/res/values-et/strings.xml
index 140c18311607..3c7b3f342794 100644
--- a/packages/VpnDialogs/res/values-et/strings.xml
+++ b/packages/VpnDialogs/res/values-et/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Ühendamise taotlus"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> tahab seadistada VPN-i ühenduse, mis võimaldab jälgida võrguliiklust. Nõustuge ainult siis, kui usaldate seda allikat. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; kuvatakse ekraani ülaservas, kui VPN on aktiivne."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> tahab seadistada VPN-i ühenduse, mis võimaldab jälgida võrguliiklust. Nõustuge ainult siis, kui usaldate seda allikat. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; kuvatakse ekraanil, kui VPN on aktiivne."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN on ühendatud"</string>
<string name="session" msgid="6470628549473641030">"Seansid"</string>
<string name="duration" msgid="3584782459928719435">"Kestus:"</string>
diff --git a/packages/VpnDialogs/res/values-eu/strings.xml b/packages/VpnDialogs/res/values-eu/strings.xml
index a27a66a86c9d..74c537880e68 100644
--- a/packages/VpnDialogs/res/values-eu/strings.xml
+++ b/packages/VpnDialogs/res/values-eu/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Konektatzeko eskaera"</string>
- <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> aplikazioak VPN bidezko konexioa ezarri nahi du sareko trafikoa kontrolatzeko. Iturburua fidagarria bada bakarrik baimendu. &lt;br /&gt; &lt;br /&gt; VPN bidezko konexioa aktibo dagoenean, &lt;img src=vpn_icon /&gt; agertuko da pantailaren goialdean."</string>
+ <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> aplikazioak VPN bidezko konexioa ezarri nahi du sareko trafikoa kontrolatzeko. Iturburua fidagarria bada bakarrik baimendu. &lt;br /&gt; &lt;br /&gt; VPN konexioa aktibo dagoenean, &lt;img src=vpn_icon /&gt; agertuko da pantailaren goialdean."</string>
<string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> aplikazioak VPN bidezko konexio bat konfiguratu nahi du sareko trafikoa gainbegiratzeko. Onartu soilik iturburuaz fidatzen bazara. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; agertzen da pantailan, VPNa aktibo dagoenean."</string>
<string name="legacy_title" msgid="192936250066580964">"VPN sarera konektatuta dago"</string>
<string name="session" msgid="6470628549473641030">"Saioa:"</string>
@@ -25,7 +25,7 @@
<string name="data_transmitted" msgid="7988167672982199061">"Bidalita:"</string>
<string name="data_received" msgid="4062776929376067820">"Jasota:"</string>
<string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> byte / <xliff:g id="NUMBER_1">%2$s</xliff:g> pakete"</string>
- <string name="always_on_disconnected_title" msgid="1906740176262776166">"Ezin da konektatu beti aktibatuta dagoen VPNa"</string>
+ <string name="always_on_disconnected_title" msgid="1906740176262776166">"Ezin da konektatu beti aktibatuta dagoen VPN sarea"</string>
<string name="always_on_disconnected_message" msgid="555634519845992917">"Beti aktibatuta egoteko dago konfiguratuta <xliff:g id="VPN_APP_0">%1$s</xliff:g>, baina une honetan ezin da konektatu. <xliff:g id="VPN_APP_1">%1$s</xliff:g> sarera berriro konektatu ahal izan arte, sare publiko bat erabiliko du telefonoak."</string>
<string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"Beti aktibatuta egoteko dago konfiguratuta <xliff:g id="VPN_APP">%1$s</xliff:g>, baina une honetan ezin da konektatu. VPN sarearen konexioa berreskuratu arte, ez duzu izango konexiorik."</string>
<string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
diff --git a/packages/VpnDialogs/res/values-fa/strings.xml b/packages/VpnDialogs/res/values-fa/strings.xml
index 6fb5a001316e..77223cb8c15d 100644
--- a/packages/VpnDialogs/res/values-fa/strings.xml
+++ b/packages/VpnDialogs/res/values-fa/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"درخواست اتصال"</string>
<string name="warning" msgid="809658604548412033">"‏<xliff:g id="APP">%s</xliff:g> می‌خواهد یک اتصال VPN راه‌اندازی کند که به آن امکان نظارت بر ترافیک شبکه را می‌دهد. فقط در صورتی بپذیرید که به منبع آن اطمینان دارید. هنگامی که VPN فعال شد، &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; در بالای صفحه نمایش شما نشان داده می‌شود."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"‏<xliff:g id="APP">%s</xliff:g> می‌خواهد یک اتصال VPN راه‌اندازی کند که به آن امکان نظارت بر ترافیک شبکه را می‌دهد. فقط درصورتی‌که به منبع اعتماد دارید قبول کنید. وقتی VPN فعال باشد، &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; در صفحه‌نمایش نشان داده می‌شود."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"‏VPN متصل است"</string>
<string name="session" msgid="6470628549473641030">"جلسه:"</string>
<string name="duration" msgid="3584782459928719435">"مدت زمان:"</string>
diff --git a/packages/VpnDialogs/res/values-fr/strings.xml b/packages/VpnDialogs/res/values-fr/strings.xml
index 27ebfb01f098..7b6a9507284b 100644
--- a/packages/VpnDialogs/res/values-fr/strings.xml
+++ b/packages/VpnDialogs/res/values-fr/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Demande de connexion"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> souhaite configurer une connexion VPN qui lui permet de surveiller le trafic réseau. N\'acceptez que si vous faites confiance à la source. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; s\'affiche en haut de votre écran lorsqu\'une connexion VPN est active."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> souhaite configurer une connexion VPN qui lui permet de surveiller le trafic réseau. N\'acceptez que si vous faites confiance à la source. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; s\'affiche à l\'écran lorsqu\'un VPN est actif."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN connecté"</string>
<string name="session" msgid="6470628549473641030">"Session :"</string>
<string name="duration" msgid="3584782459928719435">"Durée :"</string>
diff --git a/packages/VpnDialogs/res/values-gu/strings.xml b/packages/VpnDialogs/res/values-gu/strings.xml
index 5ffdcb1d8079..fd6e116aa146 100644
--- a/packages/VpnDialogs/res/values-gu/strings.xml
+++ b/packages/VpnDialogs/res/values-gu/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"કનેક્શન વિનંતી"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> VPN કનેક્શન સેટ કરવા માગે છે જે તેને નેટવર્ક ટ્રાફિક મૉનિટર કરવાની મંજૂરી આપે છે. જો તમને સ્રોત પર વિશ્વાસ હોય તો જ સ્વીકારો. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; તમારી સ્ક્રીનની ટોચ પર ત્યારે દેખાય છે જ્યારે VPN સક્રિય હોય છે."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> એક એવું VPN કનેક્શન સેટ કરવા માગે છે કે જે તેને નેટવર્ક ટ્રાફિકનું નિરીક્ષણ કરવાની મંજૂરી આપતું હોય. જો તમને સૉર્સ પર વિશ્વાસ હોય તો જ સ્વીકારો. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; તમારી સ્ક્રીન પર ત્યારે દેખાય છે, જ્યારે VPN સક્રિય હોય છે."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN કનેક્ટ કરેલું છે"</string>
<string name="session" msgid="6470628549473641030">"સત્ર:"</string>
<string name="duration" msgid="3584782459928719435">"અવધિ:"</string>
@@ -29,7 +30,7 @@
<string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g>ને હંમેશાં જોડાયેલ રહેવા માટે સેટ કરેલ છે, પરંતુ તે હાલમાં કનેક્ટ કરી શકાતું નથી. તમારો ફોન જ્યાં સુધી <xliff:g id="VPN_APP_1">%1$s</xliff:g> સાથે ફરીથી કનેક્ટ ન થાય ત્યાં સુધી તે સાર્વજનિક નેટવર્કનો ઉપયોગ કરશે."</string>
<string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g>ને હંમેશાં જોડાયેલ રહેવા માટે સેટ કરેલ છે, પરંતુ તે હાલમાં કનેક્ટ કરી શકાતું નથી. VPN ફરીથી કનેક્ટ ન થઈ શકે ત્યાં સુધી તમારી પાસે કોઈ કનેક્શન હશે નહીં."</string>
<string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
- <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"VPN સેટિંગ બદલો"</string>
+ <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"VPN સેટિંગ્સ બદલો"</string>
<string name="configure" msgid="4905518375574791375">"ગોઠવો"</string>
<string name="disconnect" msgid="971412338304200056">"ડિસ્કનેક્ટ કરો"</string>
<string name="open_app" msgid="3717639178595958667">"ઍપ ખોલો"</string>
diff --git a/packages/VpnDialogs/res/values-hi/strings.xml b/packages/VpnDialogs/res/values-hi/strings.xml
index c9c65d5ac593..916c25deb190 100644
--- a/packages/VpnDialogs/res/values-hi/strings.xml
+++ b/packages/VpnDialogs/res/values-hi/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"कनेक्शन अनुरोध"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> वीपीएन कनेक्‍शन सेट अप करना चाहता है, जिससे वह नेटवर्क ट्रैफ़िक पर नज़र रख पाएगा. इसकी मंज़ूरी तभी दें जब आपको इस पर भरोसा हो. वीपीएन चालू होने पर &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; आपकी स्क्रीन के सबसे ऊपर दिखाई देता है."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> को वीपीएन कनेक्शन सेट अप करने की अनुमति चाहिए. इससे वह नेटवर्क ट्रैफ़िक पर नज़र रख पाएगा. अनुमति तब दें, जब आपको ऐप्लिकेशन पर भरोसा हो. वीपीएन चालू होने पर, आपकी स्क्रीन पर &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; दिखेगा."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN कनेक्‍ट है"</string>
<string name="session" msgid="6470628549473641030">"सत्र:"</string>
<string name="duration" msgid="3584782459928719435">"अवधि:"</string>
diff --git a/packages/VpnDialogs/res/values-hu/strings.xml b/packages/VpnDialogs/res/values-hu/strings.xml
index 69b999fee1e1..486448422f73 100644
--- a/packages/VpnDialogs/res/values-hu/strings.xml
+++ b/packages/VpnDialogs/res/values-hu/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Kapcsolódási kérés"</string>
<string name="warning" msgid="809658604548412033">"A(z) <xliff:g id="APP">%s</xliff:g> VPN kapcsolatot akar beállítani, amelynek segítségével figyelheti a hálózati forgalmat. Csak akkor fogadja el, ha megbízik a forrásban. &lt;br /&gt; &lt;br /&gt; Amikor a VPN aktív, &lt;img src=vpn_icon /&gt; ikon jelenik meg a képernyő tetején."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"A(z) <xliff:g id="APP">%s</xliff:g> alkalmazás VPN-kapcsolatot szeretne beállítani, amely segítségével figyelheti a hálózati forgalmat. Csak akkor fogadja el, ha megbízik a forrásban. &lt;br /&gt; &lt;br /&gt; Amikor aktív a VPN, a következő ikon látható a képernyőn: &lt;img src=vpn_icon /&gt;."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"A VPN csatlakoztatva van"</string>
<string name="session" msgid="6470628549473641030">"Munkamenet:"</string>
<string name="duration" msgid="3584782459928719435">"Időtartam:"</string>
diff --git a/packages/VpnDialogs/res/values-hy/strings.xml b/packages/VpnDialogs/res/values-hy/strings.xml
index d2a6d421592c..64053a4bf47c 100644
--- a/packages/VpnDialogs/res/values-hy/strings.xml
+++ b/packages/VpnDialogs/res/values-hy/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Միացման հայց"</string>
<string name="warning" msgid="809658604548412033">"«<xliff:g id="APP">%s</xliff:g>» հավելվածը ցանկանում է VPN կապ հաստատել՝ ցանցային երթևեկը հսկելու համար: Թույլատրեք, միայն եթե վստահում եք աղբյուրին։ Երբ VPN-ն ակտիվ լինի, ձեր էկրանի վերին հատվածում կհայտնվի &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; պատկերը:"</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> հավելվածն ուզում է միանալ VPN-ի ցանցին՝ թրաֆիկին հետևելու համար։ Թույլատրեք, միայն եթե վստահում եք աղբյուրին։ Երբ VPN-ն ակտիվացված լինի, &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; պատկերակը կհայտնվի ձեր էկրանին։"</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN-ը կապակցված է"</string>
<string name="session" msgid="6470628549473641030">"Աշխատաշրջան`"</string>
<string name="duration" msgid="3584782459928719435">"Տևողությունը՝"</string>
diff --git a/packages/VpnDialogs/res/values-in/strings.xml b/packages/VpnDialogs/res/values-in/strings.xml
index 88a588c5eddc..059008f3674e 100644
--- a/packages/VpnDialogs/res/values-in/strings.xml
+++ b/packages/VpnDialogs/res/values-in/strings.xml
@@ -19,19 +19,19 @@
<string name="prompt" msgid="3183836924226407828">"Permintaan sambungan"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> ingin menyiapkan sambungan VPN yang memungkinkannya memantau traffic jaringan. Terima hanya jika Anda memercayai sumber. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; muncul di bagian atas layar Anda saat VPN aktif."</string>
<string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> ingin menyiapkan koneksi VPN yang memungkinkannya memantau traffic jaringan. Hanya terima jika Anda memercayai sumbernya. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; muncul di layar bila VPN aktif."</string>
- <string name="legacy_title" msgid="192936250066580964">"VPN terhubung"</string>
+ <string name="legacy_title" msgid="192936250066580964">"VPN tersambung"</string>
<string name="session" msgid="6470628549473641030">"Sesi:"</string>
<string name="duration" msgid="3584782459928719435">"Durasi:"</string>
<string name="data_transmitted" msgid="7988167672982199061">"Terkirim:"</string>
<string name="data_received" msgid="4062776929376067820">"Diterima:"</string>
<string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> bita / <xliff:g id="NUMBER_1">%2$s</xliff:g> paket"</string>
- <string name="always_on_disconnected_title" msgid="1906740176262776166">"Tidak dapat terhubung ke VPN yang selalu aktif"</string>
- <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> disiapkan untuk selalu terhubung, tetapi saat ini tidak dapat terhubung. Ponsel akan menggunakan jaringan publik sampai dapat terhubung ulang ke <xliff:g id="VPN_APP_1">%1$s</xliff:g>."</string>
- <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> disiapkan untuk selalu terhubung, tetapi saat ini tidak dapat terhubung. Anda akan terhubung jika VPN dapat terhubung ulang."</string>
+ <string name="always_on_disconnected_title" msgid="1906740176262776166">"Tidak dapat tersambung ke VPN yang selalu aktif"</string>
+ <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> disiapkan untuk selalu tersambung, tetapi saat ini tidak dapat tersambung. Ponsel akan menggunakan jaringan publik sampai dapat tersambung ulang ke <xliff:g id="VPN_APP_1">%1$s</xliff:g>."</string>
+ <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> disiapkan untuk selalu tersambung, tetapi saat ini tidak dapat tersambung. Anda akan tersambung jika VPN dapat tersambung ulang."</string>
<string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
<string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"Ubah setelan VPN"</string>
<string name="configure" msgid="4905518375574791375">"Konfigurasikan"</string>
- <string name="disconnect" msgid="971412338304200056">"Putuskan koneksi"</string>
+ <string name="disconnect" msgid="971412338304200056">"Putuskan sambungan"</string>
<string name="open_app" msgid="3717639178595958667">"Buka aplikasi"</string>
<string name="dismiss" msgid="6192859333764711227">"Tutup"</string>
</resources>
diff --git a/packages/VpnDialogs/res/values-is/strings.xml b/packages/VpnDialogs/res/values-is/strings.xml
index a75371d77951..53a5c02792a2 100644
--- a/packages/VpnDialogs/res/values-is/strings.xml
+++ b/packages/VpnDialogs/res/values-is/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Beiðni um tengingu"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> vill setja upp VPN-tengingu til þess að geta fylgst með netumferð. Samþykktu þetta aðeins ef þú treystir upprunanum. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; birtist efst á skjánum þegar VPN er virkt."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> vill setja upp VPN-tengingu til að fylgjast með netumferð. Ekki samþykkja þú treystir upprunanum. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; birtist á skjánum hjá þér þegar VPN er virkt."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN er tengt"</string>
<string name="session" msgid="6470628549473641030">"Lota:"</string>
<string name="duration" msgid="3584782459928719435">"Tímalengd:"</string>
diff --git a/packages/VpnDialogs/res/values-iw/strings.xml b/packages/VpnDialogs/res/values-iw/strings.xml
index 81903d2b2442..41569441faa2 100644
--- a/packages/VpnDialogs/res/values-iw/strings.xml
+++ b/packages/VpnDialogs/res/values-iw/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"בקשת חיבור"</string>
<string name="warning" msgid="809658604548412033">"‏<xliff:g id="APP">%s</xliff:g> רוצה להגדיר חיבור VPN שיאפשר לו לפקח על תעבורת הרשת. אשר את הבקשה רק אם אתה נותן אמון במקור. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; מופיע בחלק העליון של המסך כאשר VPN פעיל."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"‏האפליקציה <xliff:g id="APP">%s</xliff:g> מבקשת להגדיר חיבור VPN שבאמצעותו היא תנהל מעקב אחר התנועה ברשת. יש לאשר את הבקשה רק אם המקור נראה לך אמין. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; מופיע על המסך כאשר חיבור ה-VPN פעיל."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"‏VPN מחובר"</string>
<string name="session" msgid="6470628549473641030">"הפעלה"</string>
<string name="duration" msgid="3584782459928719435">"משך:"</string>
diff --git a/packages/VpnDialogs/res/values-kk/strings.xml b/packages/VpnDialogs/res/values-kk/strings.xml
index 9a499d346ef7..494821747dc9 100644
--- a/packages/VpnDialogs/res/values-kk/strings.xml
+++ b/packages/VpnDialogs/res/values-kk/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Байланысты сұрау"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> VPN байланысын орнатқысы келеді, бұл оған желілік трафикті бақылауға мүмкіндік береді. Көзге сенсеңіз ғана қабылдаңыз. VPN белсенді болғанда экранның жоғарғы жағында &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; көрсетіледі."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> қолданбасы VPN байланысын орнатқысы келеді, бұл оған желі трафигін бақылауға мүмкіндік береді. Сұрауды қабылдамас бұрын, дереккөздің сенімді екеніне көз жеткізіңіз. VPN белсенді болған кезде, экранда &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; белгішесі пайда болады."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"ВЖЖ қосылған"</string>
<string name="session" msgid="6470628549473641030">"Сессия:"</string>
<string name="duration" msgid="3584782459928719435">"Ұзақтығы:"</string>
diff --git a/packages/VpnDialogs/res/values-kn/strings.xml b/packages/VpnDialogs/res/values-kn/strings.xml
index 6308f1844bfd..3ebabe381035 100644
--- a/packages/VpnDialogs/res/values-kn/strings.xml
+++ b/packages/VpnDialogs/res/values-kn/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"ಸಂಪರ್ಕ ವಿನಂತಿ"</string>
<string name="warning" msgid="809658604548412033">"ನೆಟ್‌ವರ್ಕ್ ಟ್ರಾಫಿಕ್ ಅನ್ನು ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಲು ಅನುಮತಿಸುವಂತಹ VPN ಸಂಪರ್ಕವನ್ನು ಹೊಂದಿಸಲು <xliff:g id="APP">%s</xliff:g> ಬಯಸುತ್ತದೆ. ನೀವು ಮೂಲವನ್ನು ನಂಬಿದರೆ ಮಾತ್ರ ಸಮ್ಮತಿಸಿ. VPN ಸಕ್ರಿಯವಾಗಿರುವಾಗ ನಿಮ್ಮ ಪರದೆಯ ಮೇಲ್ಭಾಗದಲ್ಲಿ &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; ಗೋರಿಸುತ್ತದೆ."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"ನೆಟ್‌ವರ್ಕ್ ಟ್ರಾಫಿಕ್ ಅನ್ನು ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಲು ಅನುಮತಿಸುವಂತಹ VPN ಸಂಪರ್ಕವನ್ನು ಹೊಂದಿಸಲು <xliff:g id="APP">%s</xliff:g> ಬಯಸುತ್ತದೆ. ನಿಮಗೆ ಮೂಲದ ಮೇಲೆ ನಂಬಿಕೆ ಇದ್ದರೆ ಮಾತ್ರ ಸ್ವೀಕರಿಸಿ. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; VPN ಸಕ್ರಿಯವಾದ ನಂತರ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಗೋಚರಿಸುತ್ತದೆ."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
<string name="session" msgid="6470628549473641030">"ಸೆಷನ್:"</string>
<string name="duration" msgid="3584782459928719435">"ಅವಧಿ:"</string>
diff --git a/packages/VpnDialogs/res/values-ky/strings.xml b/packages/VpnDialogs/res/values-ky/strings.xml
index 31f9e2da11c7..40836651a89b 100644
--- a/packages/VpnDialogs/res/values-ky/strings.xml
+++ b/packages/VpnDialogs/res/values-ky/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Туташуу сурамы"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> тармактык трафикти көзөмөлдөөгө уруксат берген VPN туташуусун орноткусу келет. Аны булакка ишенсеңиз гана кабыл алыңыз. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; VPN иштеп турганда экраныңыздын жогору жагынан көрүнөт."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> тармак трафигин көзөмөлдөөгө уруксат берген VPN байланышын орноткусу келет. Булакка ишенсеңиз гана кабыл алыңыз. VPN иштеп жатканда, экраныңызда &lt;br /&gt; &lt;br /&gt; &amp;It;img src=vpn_icon /&gt; көрүнөт."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN байланышта"</string>
<string name="session" msgid="6470628549473641030">"Сессия:"</string>
<string name="duration" msgid="3584782459928719435">"Узактыгы:"</string>
diff --git a/packages/VpnDialogs/res/values-lt/strings.xml b/packages/VpnDialogs/res/values-lt/strings.xml
index 97abd0d66eb3..1f86180db070 100644
--- a/packages/VpnDialogs/res/values-lt/strings.xml
+++ b/packages/VpnDialogs/res/values-lt/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Ryšio užklausa"</string>
<string name="warning" msgid="809658604548412033">"„<xliff:g id="APP">%s</xliff:g>“ nori nustatyti VPN ryšį, kad galėtų stebėti tinklo srautą. Sutikite, tik jei pasitikite šaltiniu. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; rodoma ekrano viršuje, kai VPN aktyvus."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"Programa „<xliff:g id="APP">%s</xliff:g>“ nori nustatyti VPN ryšį, kad galėtų stebėti tinklo srautą. Sutikite, tik jei pasitikite šaltiniu. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; piktograma rodoma ekrane, kai VPN aktyvus."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN prijungtas"</string>
<string name="session" msgid="6470628549473641030">"Sesija"</string>
<string name="duration" msgid="3584782459928719435">"Trukmė:"</string>
diff --git a/packages/VpnDialogs/res/values-lv/strings.xml b/packages/VpnDialogs/res/values-lv/strings.xml
index 6341fbdf0158..71da87062506 100644
--- a/packages/VpnDialogs/res/values-lv/strings.xml
+++ b/packages/VpnDialogs/res/values-lv/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Savienojuma pieprasījums"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> vēlas izveidot VPN savienojumu, kas ļaus pārraudzīt tīkla datplūsmu. Piekrītiet tikai tad, ja uzticaties avotam. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; tiek rādīta ekrāna augšdaļā, kad darbojas VPN."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"Lietotne <xliff:g id="APP">%s</xliff:g> vēlas izveidot VPN savienojumu, kas ļaus pārraudzīt tīkla datplūsmu. Piekrītiet tikai tad, ja uzticaties avotam. &lt;br /&gt; &lt;br /&gt; Ekrānā tiek rādīta ikona &lt;img src=vpn_icon /&gt;, kad darbojas VPN."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"Ir izveidots savienojums ar VPN"</string>
<string name="session" msgid="6470628549473641030">"Sesija:"</string>
<string name="duration" msgid="3584782459928719435">"Ilgums:"</string>
diff --git a/packages/VpnDialogs/res/values-ml/strings.xml b/packages/VpnDialogs/res/values-ml/strings.xml
index 8284a78c26f8..a298caad2d6f 100644
--- a/packages/VpnDialogs/res/values-ml/strings.xml
+++ b/packages/VpnDialogs/res/values-ml/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"കണക്ഷൻ അഭ്യർത്ഥന"</string>
<string name="warning" msgid="809658604548412033">"നെറ്റ്‌വർക്ക് ട്രാഫിക്ക് നിരീക്ഷിക്കാൻ അനുവദിക്കുന്ന ഒരു VPN കണക്ഷൻ <xliff:g id="APP">%s</xliff:g> സജ്ജീകരിക്കേണ്ടതുണ്ട്. ഉറവിടം പരിചിതമാണെങ്കിൽ മാത്രം അംഗീകരിക്കുക. VPN സജീവമാകുമ്പോൾ &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; നിങ്ങളുടെ സ്ക്രീനിന്റെ മുകളിൽ ദൃശ്യമാകുന്നു."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"നെറ്റ്‌വർക്ക് ട്രാഫിക് നിരീക്ഷിക്കാൻ അനുവദിക്കുന്ന ഒരു VPN കണക്ഷൻ സജ്ജീകരിക്കാൻ <xliff:g id="APP">%s</xliff:g> താൽപ്പര്യപ്പെടുന്നു. നിങ്ങൾ ഉറവിടം വിശ്വസിക്കുന്നുണ്ടെങ്കിൽ മാത്രം അംഗീകരിക്കുക. VPN സജീവമാകുമ്പോൾ &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; നിങ്ങളുടെ സ്‌ക്രീനിൽ ദൃശ്യമാകും."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN കണക്‌റ്റുചെയ്‌തു"</string>
<string name="session" msgid="6470628549473641030">"സെഷൻ:"</string>
<string name="duration" msgid="3584782459928719435">"സമയദൈര്‍ഘ്യം:"</string>
diff --git a/packages/VpnDialogs/res/values-mr/strings.xml b/packages/VpnDialogs/res/values-mr/strings.xml
index 22fb502129a5..6caccf781b82 100644
--- a/packages/VpnDialogs/res/values-mr/strings.xml
+++ b/packages/VpnDialogs/res/values-mr/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"कनेक्‍शन विनंती"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> नेटवर्क रहदारीचे परीक्षण करण्‍यासाठी त्यास अनुमती देणारे VPN कनेक्‍शन सेट करू इच्‍छितो. तुम्हाला स्रोत विश्वसनीय वाटत असेल तरच स्वीकार करा. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; VPN सक्रिय असताना आपल्‍या स्क्रीनच्या शीर्षावर दिसते."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> ला नेटवर्क ट्रॅफिकवर लक्ष ठेवण्याची अनुमती देणारे VPN कनेक्‍शन सेट करायचे आहे. तुमचा स्रोतावर विश्वास असेल तरच स्वीकारा. VPN अ‍ॅक्टिव्ह असल्यास, तुमच्या स्क्रीनवर &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; दिसते."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN कनेक्‍ट केले"</string>
<string name="session" msgid="6470628549473641030">"सत्र:"</string>
<string name="duration" msgid="3584782459928719435">"कालावधी:"</string>
diff --git a/packages/VpnDialogs/res/values-my/strings.xml b/packages/VpnDialogs/res/values-my/strings.xml
index 36348c8b5c8c..bda925f90f65 100644
--- a/packages/VpnDialogs/res/values-my/strings.xml
+++ b/packages/VpnDialogs/res/values-my/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"ချိတ်ဆက်ရန် တောင်းဆိုချက်"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> က ကွန်ရက် လုပ်ငန်းကို စောင့်ကြည့်ခွင့် ပြုမည့် VPN ချိတ်ဆက်မှုကို ထူထောင်လိုသည်။ ရင်းမြစ်ကို သင်က ယုံကြည်မှသာ လက်ခံပါ။ &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; မှာ VPN အလုပ်လုပ်နေလျှင် သင်၏ မျက်နှာပြင် ထိပ်မှာ ပေါ်လာမည်။"</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> က ကွန်ရက်ဒေတာ စီးဆင်းမှုကို စောင့်ကြည့်ရန် ခွင့်ပြုသည့် VPN ချိတ်ဆက်မှုကို စနစ်ထည့်သွင်းလိုသည်။ ဤရင်းမြစ်ကို သင်ယုံကြည်မှသာ လက်ခံပါ။ &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; သည် VPN ဖွင့်ထားသောအခါ သင့်ဖန်သားပြင်တွင် ပေါ်ပါသည်။"</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPNနှင့်ချိတ်ဆက်ထားသည်"</string>
<string name="session" msgid="6470628549473641030">"သတ်မှတ်ပေးထားသည့်အချိန်:"</string>
<string name="duration" msgid="3584782459928719435">"အချိန်ကာလ-"</string>
diff --git a/packages/VpnDialogs/res/values-nb/strings.xml b/packages/VpnDialogs/res/values-nb/strings.xml
index 14c84d702712..eaa2c17d5a6e 100644
--- a/packages/VpnDialogs/res/values-nb/strings.xml
+++ b/packages/VpnDialogs/res/values-nb/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Tilkoblingsforespørsel"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> ønsker å bruke en VPN-tilkobling som tillater at appen overvåker nettverkstrafikken. Du bør bare godta dette hvis du stoler på kilden. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; vises øverst på skjermen din når VPN er aktivert."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> vil konfigurere en VPN-tilkobling som lar appen overvåke nettverkstrafikk. Du bør bare godta dette hvis du stoler på kilden. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; vises på skjermen når VPN er aktivert."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN er tilkoblet"</string>
<string name="session" msgid="6470628549473641030">"Økt:"</string>
<string name="duration" msgid="3584782459928719435">"Varighet:"</string>
diff --git a/packages/VpnDialogs/res/values-ne/strings.xml b/packages/VpnDialogs/res/values-ne/strings.xml
index 2a5648d147c1..a248d6d8ccbc 100644
--- a/packages/VpnDialogs/res/values-ne/strings.xml
+++ b/packages/VpnDialogs/res/values-ne/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"जडान अनुरोध"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> ले नेटवर्क यातायात अनुगमन गर्न अनुमति दिने VPN जडान स्थापना गर्न चाहन्छ। तपाईँले स्रोत भरोसा छ भने मात्र स्वीकार गर्नुहोस्। &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; जब VPN सक्रिय हुन्छ आफ्नो स्क्रिनको माथि देखा पर्छन्।"</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> ले कुनै VPN कनेक्सन सेटअप गर्न चाहन्छ। यसको सहायताले यो एप नेटवर्क ट्राफिकको निगरानी राख्न सक्छ। तपाईं यो एपमाथि विश्वास गर्नुहुन्छ भने मात्र स्वीकार गर्नुहोस्। VPN सक्रिय हुँदा तपाईंको स्क्रिनमा &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; देखा पर्छ।"</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN जोडिएको छ"</string>
<string name="session" msgid="6470628549473641030">"सत्र:"</string>
<string name="duration" msgid="3584782459928719435">"अवधि:"</string>
diff --git a/packages/VpnDialogs/res/values-or/strings.xml b/packages/VpnDialogs/res/values-or/strings.xml
index 0604b47cdb50..f37814e0f4ab 100644
--- a/packages/VpnDialogs/res/values-or/strings.xml
+++ b/packages/VpnDialogs/res/values-or/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"ସଂଯୋଗ ଅନୁରୋଧ"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> ଏକ VPN ସଂଯୋଗ ସେଟ୍ ଅପ୍ କରିବାକୁ ଚାହେଁ, ଯାହା ଏହି ନେଟ୍‌ୱର୍କର ଟ୍ରାଫିକକୁ ମନିଟର୍ କରିବାକୁ ଅନୁମତି ଦିଏ। ଆପଣ ସୋର୍ସ ଉପରେ ବିଶ୍ୱାସ କରିବା ବଦଳରେ କେବଳ ସ୍ୱୀକାର କରନ୍ତୁ। &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; VPN ସକ୍ରିୟ ଥିବାବେଳେ ଏହା ଆପଣଙ୍କ ସ୍କ୍ରୀନ୍‍ର ଉପରେ ଦେଖାଯାଏ।"</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> ଏକ VPN ସଂଯୋଗ ସେଟ୍ ଅପ୍ କରିବାକୁ ଚାହେଁ, ଯାହା ଏହାକୁ ନେଟୱାର୍କ ଟ୍ରାଫିକ ମନିଟର୍ କରିବାକୁ ଅନୁମତି ଦେଇଥାଏ। ଯଦି ଆପଣ ସୋର୍ସରେ ବିଶ୍ୱାସ କରୁଛନ୍ତି, ତେବେ ହିଁ କେବଳ ସ୍ୱୀକାର କରନ୍ତୁ। &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; VPN ସକ୍ରିୟ ଥିବା ସମୟରେ ଆପଣଙ୍କ ସ୍କ୍ରିନ୍ ଉପରେ ଦେଖାଯାଏ।"</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN ସଂଯୋଗ ହେଲା"</string>
<string name="session" msgid="6470628549473641030">"ସେସନ୍‍:"</string>
<string name="duration" msgid="3584782459928719435">"ଅବଧି:"</string>
diff --git a/packages/VpnDialogs/res/values-pl/strings.xml b/packages/VpnDialogs/res/values-pl/strings.xml
index 82161d389368..bbcbaaff3ed9 100644
--- a/packages/VpnDialogs/res/values-pl/strings.xml
+++ b/packages/VpnDialogs/res/values-pl/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Żądanie połączenia"</string>
<string name="warning" msgid="809658604548412033">"Aplikacja <xliff:g id="APP">%s</xliff:g> chce utworzyć połączenie VPN, które pozwoli jej na monitorowanie ruchu sieciowego. Zaakceptuj, tylko jeśli masz zaufanie do źródła. &lt;br /&gt; &lt;br /&gt;Gdy sieć VPN jest aktywna, u góry ekranu pojawia się &lt;img src=vpn_icon /&gt;."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"Aplikacja <xliff:g id="APP">%s</xliff:g> chce utworzyć połączenie VPN, które pozwoli jej na monitorowanie ruchu w sieci. Zaakceptuj, jeśli masz zaufanie do źródła. &lt;br /&gt; &lt;br Gdy sieć VPN jest aktywna, na ekranie pojawia się ikona /&gt; &lt;img src=vpn_icon /&gt;."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"Połączono z VPN"</string>
<string name="session" msgid="6470628549473641030">"Sesja:"</string>
<string name="duration" msgid="3584782459928719435">"Czas trwania:"</string>
diff --git a/packages/VpnDialogs/res/values-sl/strings.xml b/packages/VpnDialogs/res/values-sl/strings.xml
index 361a5fa2f1fa..0b3fd931d386 100644
--- a/packages/VpnDialogs/res/values-sl/strings.xml
+++ b/packages/VpnDialogs/res/values-sl/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Zahteva za povezavo"</string>
<string name="warning" msgid="809658604548412033">"Aplikacija <xliff:g id="APP">%s</xliff:g> želi nastaviti povezavo VPN, ki omogoča nadzor omrežnega prometa. To sprejmite samo, če zaupate viru. Ko je povezava VPN aktivna, se na vrhu zaslona prikaže ikona &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt;."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"Aplikacija <xliff:g id="APP">%s</xliff:g> želi nastaviti povezavo VPN, ki ji omogoča nadzor omrežnega prometa. To sprejmite samo, če zaupate viru. Ko je povezava VPN aktivna, je na zaslonu prikazana ikona &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt;."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"Povezava z navideznim zasebnim omrežjem je vzpostavljena"</string>
<string name="session" msgid="6470628549473641030">"Seja:"</string>
<string name="duration" msgid="3584782459928719435">"Trajanje:"</string>
diff --git a/packages/VpnDialogs/res/values-sq/strings.xml b/packages/VpnDialogs/res/values-sq/strings.xml
index 0b4ce4df9514..f2cccbc5d0d0 100644
--- a/packages/VpnDialogs/res/values-sq/strings.xml
+++ b/packages/VpnDialogs/res/values-sq/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Kërkesë për lidhje"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> kërkon të vendosë një lidhje VPN-je që e lejon të monitorojë trafikun e rrjetit. Prano vetëm nëse i beson burimit. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; shfaqet në krye të ekranit kur VPN-ja është aktive."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> kërkon të vendosë një lidhje VPN që i lejon të monitorojë trafikun e rrjetit. Pranoje vetëm nëse i beson burimit. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; shfaqet në ekranin tënd kur është aktive VPN."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN-ja është e lidhur"</string>
<string name="session" msgid="6470628549473641030">"Sesioni:"</string>
<string name="duration" msgid="3584782459928719435">"Kohëzgjatja:"</string>
diff --git a/packages/VpnDialogs/res/values-sv/strings.xml b/packages/VpnDialogs/res/values-sv/strings.xml
index 60ed75250856..6c608a5f573c 100644
--- a/packages/VpnDialogs/res/values-sv/strings.xml
+++ b/packages/VpnDialogs/res/values-sv/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Anslutningsförfrågan"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> vill starta en VPN-anslutning som tillåter att appen övervakar nätverkstrafiken. Godkänn endast detta om du litar på källan. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; visas längst upp på skärmen när VPN-anslutningen är aktiv."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> vill skapa en VPN-anslutning så att den kan övervaka nätverkstrafik. Godkänn bara om du litar på källan. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; visas på skärmen när VPN-anslutningen är aktiv."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN är anslutet"</string>
<string name="session" msgid="6470628549473641030">"Session:"</string>
<string name="duration" msgid="3584782459928719435">"Längd:"</string>
diff --git a/packages/VpnDialogs/res/values-ta/strings.xml b/packages/VpnDialogs/res/values-ta/strings.xml
index 1385bdc401c3..73eae206f4f8 100644
--- a/packages/VpnDialogs/res/values-ta/strings.xml
+++ b/packages/VpnDialogs/res/values-ta/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"இணைப்புக் கோரிக்கை"</string>
<string name="warning" msgid="809658604548412033">"நெட்வொர்க் டிராஃபிக்கைக் கண்காணிக்க வசதியாக VPN இணைப்பை அமைக்க <xliff:g id="APP">%s</xliff:g> கோருகிறது. நம்பகமான மூலத்தை மட்டுமே ஏற்கவும். &lt;br /&gt; &lt;br /&gt; VPN இயக்கத்தில் உள்ளபோது திரையின் மேல் பகுதியில் &lt;img src=vpn_icon /&gt; தோன்றும்."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"நெட்வொர்க் டிராஃபிக்கைக் கண்காணிக்க அனுமதிக்கும் VPN இணைப்பை அமைக்க <xliff:g id="APP">%s</xliff:g> விரும்புகிறது. நம்பகமான VPN ஆப்ஸாக இருந்தால் மட்டுமே ஏற்கவும். &lt;br /&gt; &lt;br /&gt; VPN இயங்கும்போது உங்கள் திரையில் &lt;img src=vpn_icon /&gt; தோன்றும்."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN இணைக்கப்பட்டது"</string>
<string name="session" msgid="6470628549473641030">"அமர்வு:"</string>
<string name="duration" msgid="3584782459928719435">"காலஅளவு:"</string>
diff --git a/packages/VpnDialogs/res/values-te/strings.xml b/packages/VpnDialogs/res/values-te/strings.xml
index 8f8ff0778d06..f6d19ffc7fce 100644
--- a/packages/VpnDialogs/res/values-te/strings.xml
+++ b/packages/VpnDialogs/res/values-te/strings.xml
@@ -16,9 +16,10 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="prompt" msgid="3183836924226407828">"కనెక్షన్ రిక్వెస్ట్‌"</string>
+ <string name="prompt" msgid="3183836924226407828">"కనెక్షన్ అభ్యర్థన"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> నెట్‌వర్క్ ట్రాఫిక్‌ని పర్యవేక్షించగలగడానికి VPN కనెక్షన్‌ను సెటప్ చేయాలనుకుంటోంది. మీరు మూలాన్ని విశ్వసిస్తే మాత్రమే ఆమోదించండి. VPN సక్రియంగా ఉన్నప్పుడు మీ స్క్రీన్ ఎగువన &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; కనిపిస్తుంది."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"నెట్‌వర్క్ ట్రాఫిక్‌ను పర్యవేక్షించగలగడానికి, <xliff:g id="APP">%s</xliff:g> VPN కనెక్షన్‌ను సెటప్ చేయాలనుకుంటోంది. మీరు సోర్స్‌ను విశ్వసిస్తే మాత్రమే ఆమోదించండి. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; VPN యాక్టివ్‌గా ఉన్నప్పుడు మీ స్క్రీన్ పై కనిపిస్తుంది."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN కనెక్ట్ చేయబడింది"</string>
<string name="session" msgid="6470628549473641030">"సెషన్:"</string>
<string name="duration" msgid="3584782459928719435">"వ్యవధి:"</string>
diff --git a/packages/VpnDialogs/res/values-ur/strings.xml b/packages/VpnDialogs/res/values-ur/strings.xml
index 3a23e940d9e9..10dc56ce18ab 100644
--- a/packages/VpnDialogs/res/values-ur/strings.xml
+++ b/packages/VpnDialogs/res/values-ur/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"کنکشن کی درخواست"</string>
<string name="warning" msgid="809658604548412033">"‏<xliff:g id="APP">%s</xliff:g> ایک ایسا VPN کنکشن ترتیب دینا چاہتی ہے جو اسے نیٹ ورک ٹریفک کو مانیٹر کرنے کی اجازت دیتا ہے۔ اگر آپ کو ماخذ پر بھروسہ ہے تبھی قبول کریں۔ &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; آپ کی اسکرین کے اوپر اس وقت ظاہر ہوتا ہے جب VPN فعال ہوتا ہے۔"</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"‏<xliff:g id="APP">%s</xliff:g> ایک ایسا VPN کنکشن سیٹ اپ کرنا چاہتی ہے جو اسے نیٹ ورک ٹریفک کو مانیٹر کرنے کی اجازت دیتا ہو۔ آپ کو ماخذ پر اعتماد ہونے پر ہی قبول کریں۔ ‎&lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; ‏VPN کے فعال ہونے پر آپ کی اسکرین پر ظاہر ہوتا ہے۔"</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"‏VPN مربوط ہے"</string>
<string name="session" msgid="6470628549473641030">"سیشن:"</string>
<string name="duration" msgid="3584782459928719435">"دورانیہ:"</string>
diff --git a/packages/VpnDialogs/res/values-vi/strings.xml b/packages/VpnDialogs/res/values-vi/strings.xml
index 184d08d7d665..ed338aac1228 100644
--- a/packages/VpnDialogs/res/values-vi/strings.xml
+++ b/packages/VpnDialogs/res/values-vi/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Yêu cầu kết nối"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> muốn thiết lập kết nối VPN cho phép ứng dụng giám sát lưu lượng truy cập mạng. Chỉ chấp nhận nếu bạn tin tưởng nguồn. &lt;br /&gt; &lt;br /&gt; Biểu tượng &lt;img src=vpn_icon /&gt; xuất hiện ở đầu màn hình của bạn khi VPN đang hoạt động."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> muốn thiết lập kết nối VPN cho phép ứng dụng giám sát lưu lượng truy cập mạng. Bạn chỉ nên chấp nhận nếu tin tưởng nguồn đó. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; sẽ xuất hiện trên màn hình khi VPN đang hoạt động."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN được kết nối"</string>
<string name="session" msgid="6470628549473641030">"Phiên"</string>
<string name="duration" msgid="3584782459928719435">"Thời lượng:"</string>
diff --git a/packages/VpnDialogs/res/values-zh-rCN/strings.xml b/packages/VpnDialogs/res/values-zh-rCN/strings.xml
index a7262bedce96..6992d2e85539 100644
--- a/packages/VpnDialogs/res/values-zh-rCN/strings.xml
+++ b/packages/VpnDialogs/res/values-zh-rCN/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"网络连接请求"</string>
<string name="warning" msgid="809658604548412033">"“<xliff:g id="APP">%s</xliff:g>”想要设置一个 VPN 连接,以便监控网络流量。除非您信任该来源,否则请勿接受此请求。&lt;br /&gt; &lt;br /&gt;启用 VPN 后,屏幕顶部会出现一个 &lt;img src=vpn_icon /&gt; 图标。"</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"“<xliff:g id="APP">%s</xliff:g>”想要建立一个 VPN 连接,以便监控网络流量。除非您信任该来源,否则请不要接受。VPN 处于启用状态时,屏幕上会显示 &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt;。"</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"已连接VPN"</string>
<string name="session" msgid="6470628549473641030">"会话:"</string>
<string name="duration" msgid="3584782459928719435">"时长:"</string>
diff --git a/packages/VpnDialogs/res/values-zh-rTW/strings.xml b/packages/VpnDialogs/res/values-zh-rTW/strings.xml
index f54ca4a7a576..e704e03aabb9 100644
--- a/packages/VpnDialogs/res/values-zh-rTW/strings.xml
+++ b/packages/VpnDialogs/res/values-zh-rTW/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"連線要求"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> 要求設定 VPN 連線,允許此要求即開放該來源監控網路流量。除非你信任該來源,否則請勿任意接受要求。&lt;br /&gt; &lt;br /&gt;VPN 啟用時,畫面頂端會顯示 &lt;img src=vpn_icon /&gt;。"</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"「<xliff:g id="APP">%s</xliff:g>」要求設定 VPN 連線,以便監控網路流量。除非你信任該來源,否則請勿接受要求。&lt;br /&gt; &lt;br /&gt; VPN 啟用時,畫面上會顯示 &lt;img src=vpn_icon /&gt;。"</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"VPN 已連線"</string>
<string name="session" msgid="6470628549473641030">"工作階段:"</string>
<string name="duration" msgid="3584782459928719435">"持續時間:"</string>
diff --git a/packages/VpnDialogs/res/values-zu/strings.xml b/packages/VpnDialogs/res/values-zu/strings.xml
index c224b13b06da..ddfed40016fb 100644
--- a/packages/VpnDialogs/res/values-zu/strings.xml
+++ b/packages/VpnDialogs/res/values-zu/strings.xml
@@ -18,7 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="prompt" msgid="3183836924226407828">"Isicelo soxhumo"</string>
<string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> ifuna ukusetha uxhumo lwe-VPN eyivumela ukwengamela ithrafikhi yenethiwekhi. Yamukela kuphela uma wethemba umthombo. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; ibonakala phezu kwesikrini sakho uma i-VPN isebenza."</string>
- <string name="warning" product="tv" msgid="5188957997628124947">"I-<xliff:g id="APP">%s</xliff:g> ifuna ukusetha uxhumano lwe-VPN oluyivumela ukuthi igade ithrafikhi yenethiwekhi. Yamukela kuphela uma wethemba umthombo. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; ivela kusikrini sakho lapho i-VPN isebenza."</string>
+ <!-- no translation found for warning (5188957997628124947) -->
+ <skip />
<string name="legacy_title" msgid="192936250066580964">"I-VPN ixhunyiwe"</string>
<string name="session" msgid="6470628549473641030">"Iseshini:"</string>
<string name="duration" msgid="3584782459928719435">"Ubude besikhathi:"</string>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-af/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-af/strings.xml
index adc308600ebb..8c47bcc9cb26 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-af/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-af/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Lewer programme onder uitsnede-area"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Versteek (vermy programme in uitsnede-area)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-am/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-am/strings.xml
index 648e1d4cf383..0f1edf682d0a 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-am/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-am/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"ከተቆረጠው አከባቢ በታች የመተግበሪያዎች ምስልን ስራ"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"ደብቅ (በተቆራረጠ ክልል ውስጥ መተግበሪያዎችን ያስወግዱ)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-b+sr+Latn/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-b+sr+Latn/strings.xml
index f80fa8d14054..c21fcda7e112 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-b+sr+Latn/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-b+sr+Latn/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Prikazuj aplikacije ispod oblasti izreza"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Sakrij (izbegavaj aplikacije u izrezanoj oblasti)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-bg/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-bg/strings.xml
index e97bb57068ca..8b81d6ad9fe7 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-bg/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-bg/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Изобразяване на приложенията под областта на прореза"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Скриване (избягване на приложенията в областта на прореза на екрана)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-bn/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-bn/strings.xml
deleted file mode 100644
index d13c777fe468..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-bn/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"কাটআউট এরিয়ার নিচে অ্যাপ রেন্ডার করুন"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-bs/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-bs/strings.xml
index 9c9f43779b66..eb2b8d258bc6 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-bs/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-bs/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Renderovanje aplikacija ispod izrezanog područja"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Sakrij (izbjegavaj aplikacije u izrezanoj oblasti)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-cs/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-cs/strings.xml
index 0f64473c7260..67ed6aff8661 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-cs/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-cs/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Vykreslovat aplikace pod oblastí výseče"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Skrýt (nezobrazovat aplikace v oblasti výřezu)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-da/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-da/strings.xml
index d0cc43e8025f..dcf70bf22125 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-da/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-da/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Gengiv apps under skærmhakkets område"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Skjul (undgå apps i cutout-område)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-de/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-de/strings.xml
index a7759ea6175a..86e373263776 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-de/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-de/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Apps unterhalb des Aussparungs-Bereichs darstellen"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Ausblenden (Apps im Bereich der Display-Aussparung vermeiden)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-el/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-el/strings.xml
index b71679a1912f..9806966ffec7 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-el/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-el/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Απόδοση εφαρμογών κάτω από την περιοχή εγκοπής"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Απόκρυψη (αποφυγή εφαρμογών στην περιοχή εγκοπής)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rAU/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rAU/strings.xml
index 8c85cbdebb92..a7700b9abd34 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rAU/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rAU/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Render apps below cutout area"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Hide (avoid apps in cutout region)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rCA/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rCA/strings.xml
index 8c85cbdebb92..a7700b9abd34 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rCA/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rCA/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Render apps below cutout area"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Hide (avoid apps in cutout region)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rGB/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rGB/strings.xml
index 8c85cbdebb92..a7700b9abd34 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rGB/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rGB/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Render apps below cutout area"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Hide (avoid apps in cutout region)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rIN/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rIN/strings.xml
index 8c85cbdebb92..a7700b9abd34 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rIN/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rIN/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Render apps below cutout area"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Hide (avoid apps in cutout region)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rXC/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rXC/strings.xml
index 8b72d9f77c49..e9b76fb8eb02 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rXC/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-en-rXC/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‎‏‏‏‏‏‏‎‎‏‏‎‏‏‏‏‎‏‏‏‎‏‏‎‎‏‏‏‎‎‎‏‏‎‎‎‏‎‏‏‏‎‏‎‎‎‏‎‎‏‎‎‏‎Render apps below cutout area‎‏‎‎‏‎"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‎‎‎‏‎‏‎‏‎‎‎‏‎‎‏‏‏‎‏‏‎‏‏‎‏‏‎‏‎‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‎‏‎‏‎‎‎‏‎Hide (avoid apps in cutout region)‎‏‎‎‏‎"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-es-rUS/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-es-rUS/strings.xml
index 359cdd0eab52..ee5f8eabb343 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-es-rUS/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-es-rUS/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Renderizar apps debajo del área de recorte"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Ocultar (evitar apps en la región excluida)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-es/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-es/strings.xml
index 47f525ec1d28..c244e49f3160 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-es/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-es/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Renderizar aplicaciones por debajo de la zona de recorte"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Ocultar (evitar aplicaciones en la zona recortada)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-et/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-et/strings.xml
deleted file mode 100644
index 0cc5a25868c2..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-et/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Väljalõikeala all olevate rakenduste renderdamine"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-eu/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-eu/strings.xml
index 15d7d6045361..f1a6fda82f97 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-eu/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-eu/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Errendatu mozketa-eremutik kanpo geratzen diren aplikazioak"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Ezkutatu (saihestu aplikazioak agertzea pantailaren mozketa-eremuan)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-fa/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-fa/strings.xml
deleted file mode 100644
index 0865f7559ef9..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-fa/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"پرداز زدن برنامه‌ها در زیر ناحیه بریده‌شده"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-fi/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-fi/strings.xml
index 1a6bf7a16839..5e626eea210c 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-fi/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-fi/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Renderöi sovellukset lovialueen alle"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Piilota (vältä lovialueella olevia sovelluksia)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-fr-rCA/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-fr-rCA/strings.xml
index ea0a27b069da..5c9194e8e694 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-fr-rCA/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-fr-rCA/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Rendre les applications sous la zone de découpe"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Masquer (éviter les applications dans la forme découpée)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-fr/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-fr/strings.xml
deleted file mode 100644
index 6d91a9d603f4..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-fr/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Afficher les applis sous la zone d\'encoche"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-gl/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-gl/strings.xml
index 382497b1caf0..a05a5fdc08d1 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-gl/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-gl/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Renderizar aplicacións que aparezan na zona recortada"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Ocultar (non mostrar as aplicacións que aparezan na zona recortada)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-gu/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-gu/strings.xml
deleted file mode 100644
index d578d9286d4c..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-gu/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"ઍપને કટઆઉટ ક્ષેત્રની નીચે રેન્ડર કરો"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-hi/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-hi/strings.xml
deleted file mode 100644
index e1f09f249bda..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-hi/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"ऐप्लिकेशन को कटआउट एरिया के नीचे दिखाएं"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-hr/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-hr/strings.xml
index db734e8254e5..a2c1feb26c25 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-hr/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-hr/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Renderiraj aplikacije ispod područja ureza"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Sakrij (izbjegavaj aplikacije u području ureza)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-hu/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-hu/strings.xml
deleted file mode 100644
index 264095b6f4e2..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-hu/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Alkalmazások megjelenítése a kivágási terület alatt"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-hy/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-hy/strings.xml
deleted file mode 100644
index 72e67ec1c424..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-hy/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Արտապատկերել հավելվածները էկրանի կտրված հատվածի ներքևում"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-in/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-in/strings.xml
index c49bf0ca939a..d40d73b72454 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-in/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-in/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Render aplikasi di bawah area potongan"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Sembunyikan (hindari aplikasi di wilayah cutout)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-is/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-is/strings.xml
deleted file mode 100644
index 0b90991b0b1e..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-is/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Birta forrit fyrir neðan útklippta svæðið"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-it/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-it/strings.xml
index 2a0f026b39a6..90b98100de50 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-it/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-it/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Visualizza le app sotto l\'area di ritaglio"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Nascondi (evita le app nell\'area di ritaglio)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-iw/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-iw/strings.xml
deleted file mode 100644
index cc7a0a486c54..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-iw/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"עיבוד האפליקציות שמתחת לאזור המגרעת"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ja/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ja/strings.xml
index 9e99482e5e2a..69b9f24d1d08 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ja/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ja/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"カットアウト領域の下でアプリをレンダリング"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"非表示(カットアウト領域にアプリを表示しない)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ka/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ka/strings.xml
index 5464a5699449..1ee2faef0bc6 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ka/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ka/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"აპების ასახვა ჭრილის ქვემოთ"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"დამალვა (აპების არდაშვება ჭრილის უბანში)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-kk/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-kk/strings.xml
deleted file mode 100644
index 6a2623f8696f..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-kk/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Экран ойығының астындағы қолданбаларды көрсету"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-km/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-km/strings.xml
index 4b4d169cc738..ea0a9d0dbc5b 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-km/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-km/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"បំប្លែងកម្មវិធីខាងក្រោមផ្នែកឆក"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"លាក់ (ជៀសវាងបង្ហាញកម្មវិធីនៅផ្នែកឆក)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-kn/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-kn/strings.xml
deleted file mode 100644
index 7a929d13d83f..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-kn/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"ಕಟೌಟ್ ಪ್ರದೇಶದ ಕೆಳಗಿನ ಆ್ಯಪ್‌ಗಳನ್ನು ರೆಂಡರ್ ಮಾಡಿ"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ko/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ko/strings.xml
index 4b9e64020eee..97856d35618c 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ko/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ko/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"컷아웃 영역 아래에 앱 렌더링"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"숨기기(컷아웃 영역에 앱을 표시하지 않음)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ky/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ky/strings.xml
deleted file mode 100644
index 1ac6a8bb9c1f..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ky/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Колдонмолорду кесилген аймактын ылдый жагында көрсөтүү"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-lo/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-lo/strings.xml
index 4c38580169af..29ec22453e93 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-lo/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-lo/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"ສະແດງພາບແອັບຢູ່ທາງລຸ່ມພື້ນທີ່ຮອຍເສັ້ນ"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"ເຊື່ອງ (ຫຼີກເວັ້ນແອັບໃນພື້ນທີ່ຕັດອອກ)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-lt/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-lt/strings.xml
deleted file mode 100644
index c43736d006dd..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-lt/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Pateikti programas po išpjovos sritimi"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-lv/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-lv/strings.xml
deleted file mode 100644
index f95abb69abf5..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-lv/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Atveidot lietotnes zem izgriezuma apgabala"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-mk/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-mk/strings.xml
index ff236be46b14..67b45d3020a3 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-mk/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-mk/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Прикажувај апликации под отсечената област"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Сокриј (избегнувај апликации во отсечен регион)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ml/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ml/strings.xml
deleted file mode 100644
index ef728ab64ab5..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ml/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"കട്ടൗട്ട് ഭാഗത്തിന് താഴെ ആപ്പുകൾ റെൻഡർ ചെയ്യുക"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-mn/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-mn/strings.xml
index 23dbe0c822f0..9bda419a1e17 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-mn/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-mn/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Аппуудыг тасалж авсан хэсгийн доор буулгах"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Нуух (тусгаарласан бүс дэх аппуудаас зайлсхийнэ)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-mr/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-mr/strings.xml
deleted file mode 100644
index 42f09cb1a1f0..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-mr/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"कटआउट क्षेत्राच्या खाली असलेली ॲप्स रेंडर करा"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ms/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ms/strings.xml
index e348630e0447..5864ff8689dd 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ms/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ms/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Serahkan apl di bawah kawasan potongan"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Sembunyikan (elakkan apl dalam kawasan potongan)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-my/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-my/strings.xml
deleted file mode 100644
index 90cb0a5f56a4..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-my/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"ဖြတ်ထုတ်ထားသော နေရာအောက်ရှိ အက်ပ်များ ပြသရန်"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-nb/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-nb/strings.xml
deleted file mode 100644
index b8b4e7526ab2..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-nb/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Gjengi apper under utklippsområdet"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ne/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ne/strings.xml
deleted file mode 100644
index bd213bb64a0d..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ne/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"कटआउट गरिएको क्षेत्रभन्दा तल पर्ने एपहरू रेन्डर गर्नुहोस्"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-nl/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-nl/strings.xml
index 68f5c0701beb..0e515147c0ab 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-nl/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-nl/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Apps renderen onder display-cutout"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Verbergen (apps in cutout-regio vermijden)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-or/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-or/strings.xml
deleted file mode 100644
index 162a29e8968c..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-or/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"ଆପଗୁଡ଼ିକୁ କଟଆଉଟ୍ ଏରିଆ ନିମ୍ନରେ ରେଣ୍ଡର୍ କରନ୍ତୁ"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pa/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pa/strings.xml
index 908393b1abb0..803a69d40213 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pa/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pa/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"ਕੱਟਆਊਟ ਖੇਤਰ ਹੇਠ ਐਪਾਂ ਨੂੰ ਰੈਂਡਰ ਕਰੋ"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"ਲੁਕਾਓ (ਕੱਟਆਊਟ ਖੇਤਰ ਵਿਚਲੀਆਂ ਐਪਾਂ ਨੂੰ ਨਾ ਛੇੜੋ)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pl/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pl/strings.xml
deleted file mode 100644
index c027d5266928..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pl/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Renderuj aplikacje pod obszarem wycięcia"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pt-rBR/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pt-rBR/strings.xml
index d09ed97121fa..b5364e996b91 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pt-rBR/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pt-rBR/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Renderizar apps abaixo da área de corte"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Ocultar (evitar apps na região recortada)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pt-rPT/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pt-rPT/strings.xml
index d38ce43204d2..e9467a2879c4 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pt-rPT/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pt-rPT/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Renderizar apps abaixo da área de recorte"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Ocultar (evitar apps na área de recorte)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pt/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pt/strings.xml
index d09ed97121fa..b5364e996b91 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pt/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-pt/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Renderizar apps abaixo da área de corte"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Ocultar (evitar apps na região recortada)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ro/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ro/strings.xml
index 6e5947c0d753..9227cebb7bd3 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ro/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ro/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Redați aplicațiile sub zona de decupaj"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Ascundeți (evitați aplicațiile din regiunea decupată)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ru/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ru/strings.xml
index c7f54bbff6a7..8335c77fc86d 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ru/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ru/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Отображать приложения под вырезом"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Скрыть (не показывать приложения в вырезе на экране)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-si/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-si/strings.xml
index 4a14a360e857..d21b02c04bdf 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-si/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-si/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"කටවුට් ප්‍රදේශයට පහළින් යෙදුම් විදහන්න"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"සඟවන්න (කටවුට් කලාපයෙහි යෙදුම් වළක්වන්න)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sk/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sk/strings.xml
index 98b82e636392..dfd01afb4c4b 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sk/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sk/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Vykresľovať aplikácie pod oblasťou výrezu"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Skryť (nezobrazovať aplikácie v oblasti výrezu)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sl/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sl/strings.xml
deleted file mode 100644
index dcf0c842cd36..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sl/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Upodobitev aplikacij pod predelom zareze zaslona"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sq/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sq/strings.xml
deleted file mode 100644
index d7b0676970b8..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sq/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Paraqiti aplikacionet poshtë zonës së prerjes"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sr/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sr/strings.xml
index c2b611e70da9..c835b3582e25 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sr/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sr/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Приказуј апликације испод области изреза"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Сакриј (избегавај апликације у изрезаној области)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sv/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sv/strings.xml
deleted file mode 100644
index 3007ffb99e10..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sv/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Visa appar under skärmutskärningens område"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sw/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sw/strings.xml
index b605554b46f3..57ef68437013 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sw/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-sw/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Usionyeshe programu chini ya eneo lenye pengo"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Ficha (epuka programu katika eneo lenye pengo)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ta/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ta/strings.xml
deleted file mode 100644
index c4d06fb68564..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ta/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"கட் அவுட் பகுதிக்குள்ளாக ஆப்ஸை ரெண்டர் செய்"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-te/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-te/strings.xml
deleted file mode 100644
index 08fa4ae7669b..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-te/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"కట్అవుట్ ఏరియా కింద యాప్‌లను రెండర్ చేయండి"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-th/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-th/strings.xml
index 9a302507411a..09d597dd456d 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-th/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-th/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"แสดงผลแอปใต้บริเวณรอยบาก"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"ซ่อน (หลีกเลี่ยงการแสดงแอปในภูมิภาคที่ถูกตัดออก)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-tl/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-tl/strings.xml
index a3d4a3afe376..6b1c3720ce8b 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-tl/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-tl/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"I-render ang mga app sa ibaba ng lugar ng cutout"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Itago (iwasan ang mga app sa rehiyon ng cutout)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-tr/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-tr/strings.xml
index 12e0f3019814..991a8406808e 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-tr/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-tr/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Uygulamaları kesme alanının altında oluşturun"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Gizle (kesim bölgesindeki uygulamalardan kaçının)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-uk/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-uk/strings.xml
index 08b1521a12a0..7d6c068fd7e0 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-uk/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-uk/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Відображати додатки під областю вирізу екрана"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Сховати (не показувати додатки з вирізаних регіонів)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ur/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ur/strings.xml
deleted file mode 100644
index 711b5389b26c..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-ur/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"کٹ آؤٹ ایریا کے نیچے رینڈر ایپس"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-uz/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-uz/strings.xml
index 7f6f2b45fbd2..13a56acbab51 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-uz/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-uz/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Ekran kesimi quyidagi ilovalarni renderlash"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"Berkitish (qirqilgan hudud ilovalariga diqqat qiling)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-vi/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-vi/strings.xml
deleted file mode 100644
index a7d54fbae9f5..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-vi/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Hiển thị các ứng dụng bên dưới khu vực có vết cắt"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-zh-rCN/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-zh-rCN/strings.xml
deleted file mode 100644
index f596520fad30..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"在刘海区域下方呈现应用"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-zh-rHK/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-zh-rHK/strings.xml
index ddb1df77b00c..7f37f3bfb488 100644
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-zh-rHK/strings.xml
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-zh-rHK/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"在凹口區域下方輸出應用程式"</string>
+ <string name="display_cutout_emulation_overlay" msgid="1946296620328354129">"隱藏 (避免將應用程式置於凹口區域)"</string>
</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-zh-rTW/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-zh-rTW/strings.xml
deleted file mode 100644
index 7aad79c11bf2..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-zh-rTW/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"在螢幕凹口底下顯示應用程式畫面"</string>
-</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-zu/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values-zu/strings.xml
deleted file mode 100644
index d861c5e60708..000000000000
--- a/packages/overlays/AvoidAppsInCutoutOverlay/res/values-zu/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="3814493834951357513">"Nikezela ngama-app angaphansi kwendawo yokukhipha"</string>
-</resources>
diff --git a/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-land/config.xml
deleted file mode 100644
index bd52901cef95..000000000000
--- a/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-land/config.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
- ~ Copyright (C) 2018 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<resources>
- <!-- Can't link to other dimensions here, but this should be status_bar_height_landscape -->
- <dimen name="quick_qs_offset_height">28dp</dimen>
- <!-- Total height of QQS in landscape; quick_qs_offset_height + 128 -->
- <dimen name="quick_qs_total_height">156dp</dimen>
-</resources> \ No newline at end of file
diff --git a/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values/config.xml
index 9254b4d65b50..c340432b9b8c 100644
--- a/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values/config.xml
+++ b/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values/config.xml
@@ -44,14 +44,6 @@
-->
<bool name="config_fillMainBuiltInDisplayCutout">true</bool>
- <!-- Height of the status bar -->
- <dimen name="status_bar_height_portrait">48dp</dimen>
- <dimen name="status_bar_height_landscape">28dp</dimen>
- <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) -->
- <dimen name="quick_qs_offset_height">48dp</dimen>
- <!-- Total height of QQS (quick_qs_offset_height + 128) -->
- <dimen name="quick_qs_total_height">176dp</dimen>
-
</resources>
diff --git a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-land/config.xml
deleted file mode 100644
index bd52901cef95..000000000000
--- a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-land/config.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
- ~ Copyright (C) 2018 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<resources>
- <!-- Can't link to other dimensions here, but this should be status_bar_height_landscape -->
- <dimen name="quick_qs_offset_height">28dp</dimen>
- <!-- Total height of QQS in landscape; quick_qs_offset_height + 128 -->
- <dimen name="quick_qs_total_height">156dp</dimen>
-</resources> \ No newline at end of file
diff --git a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values/config.xml
index 80c997a46264..928d9dfa3ce1 100644
--- a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values/config.xml
+++ b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values/config.xml
@@ -56,14 +56,6 @@
-->
<bool name="config_fillMainBuiltInDisplayCutout">true</bool>
- <!-- Height of the status bar -->
- <dimen name="status_bar_height_portrait">48dp</dimen>
- <dimen name="status_bar_height_landscape">28dp</dimen>
- <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) -->
- <dimen name="quick_qs_offset_height">48dp</dimen>
- <!-- Total height of QQS (quick_qs_offset_height + 128) -->
- <dimen name="quick_qs_total_height">176dp</dimen>
-
</resources>
diff --git a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-land/config.xml
deleted file mode 100644
index 2e971ded2256..000000000000
--- a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-land/config.xml
+++ /dev/null
@@ -1,22 +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
- -->
-
-<resources>
- <!-- Can't link to other dimensions here, but this should be status_bar_height_landscape -->
- <dimen name="quick_qs_offset_height">28dp</dimen>
- <!-- Total height of QQS in landscape; quick_qs_offset_height + 128 -->
- <dimen name="quick_qs_total_height">156dp</dimen>
-</resources>
diff --git a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values/config.xml
index 9f558d0e2bd5..62f0535a1746 100644
--- a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values/config.xml
+++ b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values/config.xml
@@ -48,14 +48,6 @@
-->
<bool name="config_fillMainBuiltInDisplayCutout">true</bool>
- <!-- Height of the status bar -->
- <dimen name="status_bar_height_portrait">136px</dimen>
- <dimen name="status_bar_height_landscape">28dp</dimen>
- <!-- Height of area above QQS where battery/time go (equal to status bar) -->
- <dimen name="quick_qs_offset_height">136px</dimen>
- <!-- Total height of QQS (quick_qs_offset_height + 128) -->
- <dimen name="quick_qs_total_height">488px</dimen>
-
</resources>
diff --git a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values-land/config.xml
deleted file mode 100644
index bd52901cef95..000000000000
--- a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values-land/config.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
- ~ Copyright (C) 2018 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<resources>
- <!-- Can't link to other dimensions here, but this should be status_bar_height_landscape -->
- <dimen name="quick_qs_offset_height">28dp</dimen>
- <!-- Total height of QQS in landscape; quick_qs_offset_height + 128 -->
- <dimen name="quick_qs_total_height">156dp</dimen>
-</resources> \ No newline at end of file
diff --git a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values/config.xml
index 6fb3c7f51e26..a9f8b4bc6329 100644
--- a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values/config.xml
+++ b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/res/values/config.xml
@@ -47,14 +47,6 @@
-->
<bool name="config_fillMainBuiltInDisplayCutout">true</bool>
- <!-- Height of the status bar -->
- <dimen name="status_bar_height_portrait">48dp</dimen>
- <dimen name="status_bar_height_landscape">28dp</dimen>
- <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) -->
- <dimen name="quick_qs_offset_height">48dp</dimen>
- <!-- Total height of QQS (quick_qs_offset_height + 128) -->
- <dimen name="quick_qs_total_height">176dp</dimen>
-
</resources>
diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-land/config.xml
deleted file mode 100644
index bd52901cef95..000000000000
--- a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-land/config.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
- ~ Copyright (C) 2018 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<resources>
- <!-- Can't link to other dimensions here, but this should be status_bar_height_landscape -->
- <dimen name="quick_qs_offset_height">28dp</dimen>
- <!-- Total height of QQS in landscape; quick_qs_offset_height + 128 -->
- <dimen name="quick_qs_total_height">156dp</dimen>
-</resources> \ No newline at end of file
diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values/config.xml
index 7c29ffb92f4e..be7d0e48fa3f 100644
--- a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values/config.xml
+++ b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values/config.xml
@@ -47,14 +47,6 @@
-->
<bool name="config_fillMainBuiltInDisplayCutout">true</bool>
- <!-- Height of the status bar -->
- <dimen name="status_bar_height_portrait">48dp</dimen>
- <dimen name="status_bar_height_landscape">28dp</dimen>
- <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) -->
- <dimen name="quick_qs_offset_height">48dp</dimen>
- <!-- Total height of QQS (quick_qs_offset_height + 128) -->
- <dimen name="quick_qs_total_height">176dp</dimen>
-
</resources>
diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-land/config.xml
deleted file mode 100644
index df2f3d19626e..000000000000
--- a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values-land/config.xml
+++ /dev/null
@@ -1,22 +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.
- -->
-
-<resources>
- <!-- Can't link to other dimensions here, but this should be status_bar_height_landscape -->
- <dimen name="quick_qs_offset_height">48dp</dimen>
- <!-- Total height of QQS in landscape; quick_qs_offset_height + 128 -->
- <dimen name="quick_qs_total_height">176dp</dimen>
-</resources> \ No newline at end of file
diff --git a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml
index 8d0227eed875..cc51ebee270c 100644
--- a/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml
+++ b/packages/overlays/DisplayCutoutEmulationWaterfallOverlay/res/values/config.xml
@@ -19,16 +19,6 @@
<string translatable="false" name="config_mainBuiltInDisplayCutout"></string>
<string translatable="false" name="config_mainBuiltInDisplayCutoutRectApproximation"></string>
- <!-- Height of the status bar in portrait. The height should be
- Max((status bar content height + waterfall top size), top cutout size) -->
- <dimen name="status_bar_height_portrait">28dp</dimen>
- <!-- Max((28 + 20), 0) = 48 -->
- <dimen name="status_bar_height_landscape">48dp</dimen>
- <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) -->
- <dimen name="quick_qs_offset_height">48dp</dimen>
- <!-- Total height of QQS (quick_qs_offset_height + 128) -->
- <dimen name="quick_qs_total_height">176dp</dimen>
-
<dimen name="waterfall_display_left_edge_size">20dp</dimen>
<dimen name="waterfall_display_top_edge_size">0dp</dimen>
<dimen name="waterfall_display_right_edge_size">20dp</dimen>
diff --git a/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values-land/config.xml b/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values-land/config.xml
deleted file mode 100644
index bd52901cef95..000000000000
--- a/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values-land/config.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
- ~ Copyright (C) 2018 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<resources>
- <!-- Can't link to other dimensions here, but this should be status_bar_height_landscape -->
- <dimen name="quick_qs_offset_height">28dp</dimen>
- <!-- Total height of QQS in landscape; quick_qs_offset_height + 128 -->
- <dimen name="quick_qs_total_height">156dp</dimen>
-</resources> \ No newline at end of file
diff --git a/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values/config.xml
index 5fb8b9e241b8..78cc7e04c7a0 100644
--- a/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values/config.xml
+++ b/packages/overlays/DisplayCutoutEmulationWideOverlay/res/values/config.xml
@@ -47,14 +47,6 @@
-->
<bool name="config_fillMainBuiltInDisplayCutout">true</bool>
- <!-- Height of the status bar -->
- <dimen name="status_bar_height_portrait">48dp</dimen>
- <dimen name="status_bar_height_landscape">28dp</dimen>
- <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) -->
- <dimen name="quick_qs_offset_height">48dp</dimen>
- <!-- Total height of QQS (quick_qs_offset_height + 128) -->
- <dimen name="quick_qs_total_height">176dp</dimen>
-
</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-ar/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-ar/strings.xml
deleted file mode 100644
index 7e41cb1935b9..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-ar/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"إخفاء"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-as/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-as/strings.xml
deleted file mode 100644
index d2399fffc8c3..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-as/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"লুকুৱাওক"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-az/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-az/strings.xml
deleted file mode 100644
index 420c5133cbd1..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-az/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Gizlədin"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-be/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-be/strings.xml
deleted file mode 100644
index ce75c4584dac..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-be/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Схаваць"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-bn/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-bn/strings.xml
deleted file mode 100644
index 1e627256d943..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-bn/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"লুকান"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-fa/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-fa/strings.xml
deleted file mode 100644
index 8de756085a84..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-fa/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"پنهان کردن"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-fr/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-fr/strings.xml
deleted file mode 100644
index 31fa56766ebc..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-fr/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Masquer"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-gu/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-gu/strings.xml
deleted file mode 100644
index 7e4b33a8749c..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-gu/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"છુપાવો"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-hi/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-hi/strings.xml
deleted file mode 100644
index 1513f2a8ffe5..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-hi/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"छिपाएं"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-hu/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-hu/strings.xml
deleted file mode 100644
index 2b9717fca1cf..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-hu/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Elrejtés"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-hy/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-hy/strings.xml
deleted file mode 100644
index 3407bb499645..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-hy/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Թաքցնել"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-iw/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-iw/strings.xml
deleted file mode 100644
index 221a129317f9..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-iw/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"הסתרה"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-kk/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-kk/strings.xml
deleted file mode 100644
index 23d02b81c62e..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-kk/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Жасыру"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-kn/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-kn/strings.xml
deleted file mode 100644
index 9cd6da7fb314..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-kn/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"ಮರೆಮಾಡಿ"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-ky/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-ky/strings.xml
deleted file mode 100644
index 419132513fad..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-ky/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Жашыруу"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-lt/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-lt/strings.xml
deleted file mode 100644
index 6364b96096e0..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-lt/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Slėpti"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-lv/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-lv/strings.xml
deleted file mode 100644
index 61f2ad3d51fb..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-lv/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Paslēpt"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-ml/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-ml/strings.xml
deleted file mode 100644
index 1c30dec6bc83..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-ml/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"മറയ്ക്കുക"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-mr/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-mr/strings.xml
deleted file mode 100644
index 46f0ab889226..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-mr/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"लपवा"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-my/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-my/strings.xml
deleted file mode 100644
index 84be3f9a0986..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-my/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"ဝှက်ရန်"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-ne/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-ne/strings.xml
deleted file mode 100644
index ff920b290dbb..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-ne/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"लुकाइयोस्"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-or/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-or/strings.xml
deleted file mode 100644
index fcfd7252a8bf..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-or/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"ଲୁଚାନ୍ତୁ"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-pl/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-pl/strings.xml
deleted file mode 100644
index 3d65546bf794..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-pl/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Ukryj"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-sl/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-sl/strings.xml
deleted file mode 100644
index e8adb9810303..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-sl/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Skrij"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-sq/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-sq/strings.xml
deleted file mode 100644
index 85fb95a1f283..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-sq/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Fshih"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-sv/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-sv/strings.xml
deleted file mode 100644
index 193c1798fc87..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-sv/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Dölj"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-ta/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-ta/strings.xml
deleted file mode 100644
index b743c660f183..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-ta/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"மறை"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-te/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-te/strings.xml
deleted file mode 100644
index de04152a4e20..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-te/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"దాచండి"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-ur/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-ur/strings.xml
deleted file mode 100644
index 0f081705f6f1..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-ur/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"چھپائیں"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-zh-rCN/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-zh-rCN/strings.xml
deleted file mode 100644
index acdfd1c2e4a5..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"隐藏"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-zh-rTW/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-zh-rTW/strings.xml
deleted file mode 100644
index abb8e81fbca4..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-zh-rTW/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"隱藏"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values-zu/strings.xml b/packages/overlays/NoCutoutOverlay/res/values-zu/strings.xml
deleted file mode 100644
index 11842d91a53a..000000000000
--- a/packages/overlays/NoCutoutOverlay/res/values-zu/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="9031691255599853162">"Fihla"</string>
-</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values/config.xml b/packages/overlays/NoCutoutOverlay/res/values/config.xml
index 91576998cc54..84b91b85350d 100644
--- a/packages/overlays/NoCutoutOverlay/res/values/config.xml
+++ b/packages/overlays/NoCutoutOverlay/res/values/config.xml
@@ -25,12 +25,4 @@
by shrinking the display such that it does not overlap the cutout area. -->
<bool name="config_maskMainBuiltInDisplayCutout">true</bool>
- <!-- Height of the status bar -->
- <dimen name="status_bar_height_portrait">28dp</dimen>
- <dimen name="status_bar_height_landscape">28dp</dimen>
-
- <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) -->
- <dimen name="quick_qs_offset_height">48dp</dimen>
- <!-- Total height of QQS (quick_qs_offset_height + 128) -->
- <dimen name="quick_qs_total_height">176dp</dimen>
</resources>
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index b0893cc360b7..ed37d7e2e6e6 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -19,6 +19,7 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.graphics.Camera;
import android.graphics.GraphicBuffer;
import android.graphics.Rect;
import android.hardware.camera2.CameraAccessException;
@@ -135,6 +136,7 @@ public class CameraExtensionsProxyService extends Service {
private HashMap<String, CameraCharacteristics> mCharacteristicsHashMap = new HashMap<>();
private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>();
+ private CameraManager mCameraManager;
private static boolean checkForAdvancedAPI() {
if (EXTENSIONS_PRESENT && EXTENSIONS_VERSION.startsWith(ADVANCED_VERSION_PREFIX)) {
@@ -460,12 +462,12 @@ public class CameraExtensionsProxyService extends Service {
// This will setup the camera vendor tag descriptor in the service process
// along with all camera characteristics.
try {
- CameraManager manager = getSystemService(CameraManager.class);
+ mCameraManager = getSystemService(CameraManager.class);
- String [] cameraIds = manager.getCameraIdListNoLazy();
+ String [] cameraIds = mCameraManager.getCameraIdListNoLazy();
if (cameraIds != null) {
for (String cameraId : cameraIds) {
- CameraCharacteristics chars = manager.getCameraCharacteristics(cameraId);
+ CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId);
mCharacteristicsHashMap.put(cameraId, chars);
Object thisClass = CameraCharacteristics.Key.class;
Class<CameraCharacteristics.Key<?>> keyClass =
@@ -1174,8 +1176,9 @@ public class CameraExtensionsProxyService extends Service {
@Override
public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) {
mCameraId = cameraId;
- mPreviewExtender.onInit(cameraId, new CameraCharacteristics(cameraCharacteristics),
- CameraExtensionsProxyService.this);
+ CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics);
+ mCameraManager.registerDeviceStateListener(chars);
+ mPreviewExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this);
}
@Override
@@ -1200,13 +1203,16 @@ public class CameraExtensionsProxyService extends Service {
@Override
public void init(String cameraId, CameraMetadataNative chars) {
- mPreviewExtender.init(cameraId, new CameraCharacteristics(chars));
+ CameraCharacteristics c = new CameraCharacteristics(chars);
+ mCameraManager.registerDeviceStateListener(c);
+ mPreviewExtender.init(cameraId, c);
}
@Override
public boolean isExtensionAvailable(String cameraId, CameraMetadataNative chars) {
- return mPreviewExtender.isExtensionAvailable(cameraId,
- new CameraCharacteristics(chars));
+ CameraCharacteristics c = new CameraCharacteristics(chars);
+ mCameraManager.registerDeviceStateListener(c);
+ return mPreviewExtender.isExtensionAvailable(cameraId, c);
}
@Override
@@ -1283,8 +1289,9 @@ public class CameraExtensionsProxyService extends Service {
@Override
public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) {
- mImageExtender.onInit(cameraId, new CameraCharacteristics(cameraCharacteristics),
- CameraExtensionsProxyService.this);
+ CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics);
+ mCameraManager.registerDeviceStateListener(chars);
+ mImageExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this);
mCameraId = cameraId;
}
@@ -1310,13 +1317,16 @@ public class CameraExtensionsProxyService extends Service {
@Override
public void init(String cameraId, CameraMetadataNative chars) {
- mImageExtender.init(cameraId, new CameraCharacteristics(chars));
+ CameraCharacteristics c = new CameraCharacteristics(chars);
+ mCameraManager.registerDeviceStateListener(c);
+ mImageExtender.init(cameraId, c);
}
@Override
public boolean isExtensionAvailable(String cameraId, CameraMetadataNative chars) {
- return mImageExtender.isExtensionAvailable(cameraId,
- new CameraCharacteristics(chars));
+ CameraCharacteristics c = new CameraCharacteristics(chars);
+ mCameraManager.registerDeviceStateListener(c);
+ return mImageExtender.isExtensionAvailable(cameraId, c);
}
@Override
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
index 46bda06d0e04..27d4ea71dfc0 100644
--- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
+++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
@@ -21,6 +21,7 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.Log;
import android.webkit.PacProcessor;
@@ -33,16 +34,44 @@ import java.net.URL;
public class PacService extends Service {
private static final String TAG = "PacService";
- private Object mLock = new Object();
+ private final Object mLock = new Object();
+ // Webkit PacProcessor cannot be instantiated before the user is unlocked, so this field is
+ // initialized lazily.
@GuardedBy("mLock")
- private final PacProcessor mPacProcessor = PacProcessor.getInstance();
+ private PacProcessor mPacProcessor;
+
+ // Stores PAC script when setPacFile is called before mPacProcessor is available. In case the
+ // script was already fed to the PacProcessor, it should be null.
+ @GuardedBy("mLock")
+ private String mPendingScript;
private ProxyServiceStub mStub = new ProxyServiceStub();
@Override
public void onCreate() {
super.onCreate();
+
+ synchronized (mLock) {
+ checkPacProcessorLocked();
+ }
+ }
+
+ /**
+ * Initializes PacProcessor if it hasn't been initialized yet and if the system user is
+ * unlocked, e.g. after the user has entered their PIN after a reboot.
+ * Returns whether PacProcessor is available.
+ */
+ private boolean checkPacProcessorLocked() {
+ if (mPacProcessor != null) {
+ return true;
+ }
+ UserManager um = getSystemService(UserManager.class);
+ if (um.isUserUnlocked()) {
+ mPacProcessor = PacProcessor.getInstance();
+ return true;
+ }
+ return false;
}
@Override
@@ -74,7 +103,20 @@ public class PacService extends Service {
}
synchronized (mLock) {
- return mPacProcessor.findProxyForUrl(url);
+ if (checkPacProcessorLocked()) {
+ // Apply pending script in case it was set before processor was ready.
+ if (mPendingScript != null) {
+ if (!mPacProcessor.setProxyScript(mPendingScript)) {
+ Log.e(TAG, "Unable to parse proxy script.");
+ }
+ mPendingScript = null;
+ }
+ return mPacProcessor.findProxyForUrl(url);
+ } else {
+ Log.e(TAG, "PacProcessor isn't ready during early boot,"
+ + " request will be direct");
+ return null;
+ }
}
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Invalid URL was passed");
@@ -88,8 +130,13 @@ public class PacService extends Service {
throw new SecurityException();
}
synchronized (mLock) {
- if (!mPacProcessor.setProxyScript(script)) {
- Log.e(TAG, "Unable to parse proxy script.");
+ if (checkPacProcessorLocked()) {
+ if (!mPacProcessor.setProxyScript(script)) {
+ Log.e(TAG, "Unable to parse proxy script.");
+ }
+ } else {
+ Log.d(TAG, "PAC processor isn't ready, saving script for later.");
+ mPendingScript = script;
}
}
}
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/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/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 8a42ddfdc19d..71749e787115 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 55b982b40611..e5716eee467e 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -149,7 +149,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/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index b4413a4447b7..63301ac49573 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1025,7 +1025,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
return;
}
- int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
// register
IBinder b = callback.asBinder();
@@ -1048,21 +1047,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 +1080,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 +1094,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 +1103,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 +1111,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 +1123,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 +1164,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 +1174,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 +1188,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 +1212,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 +1229,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 +1288,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 +1319,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 +1329,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 +1337,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 +1581,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 +1643,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 +1654,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 +1696,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 +1710,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 +1757,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 +1789,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 +1823,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 +1850,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 +1889,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 +1941,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 +1970,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) {
@@ -2014,7 +2018,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_DATA_CONNECTION_STATE_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
if (DBG) {
log("Notify data connection state changed on sub: " + subId);
@@ -2039,7 +2043,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) {
@@ -2086,7 +2090,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 +2144,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 +2153,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 +2178,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 +2203,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 +2235,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 +2264,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 +2344,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) {
@@ -2369,7 +2373,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) {
@@ -2456,7 +2460,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) {
@@ -2487,7 +2491,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)
@@ -2530,7 +2534,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="
@@ -2575,7 +2579,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="
@@ -2642,7 +2646,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) {
@@ -2677,7 +2681,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
@@ -2719,7 +2723,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) {
@@ -3169,33 +3173,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 fcd049f1c494..d10ab8e2d0dd 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -142,6 +142,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/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c78d5d538f8d..18ed9586d263 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1550,6 +1550,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);
@@ -1886,6 +1893,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 =
@@ -2737,6 +2752,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();
+ }
}
/**
@@ -16357,6 +16377,21 @@ 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);
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 36c0de919279..ad0485b6df28 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1033,6 +1033,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 {
@@ -1075,24 +1076,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) {
@@ -1119,28 +1102,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);
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/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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 8d596b62cbf7..655278657b83 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";
@@ -238,7 +244,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;
@@ -312,12 +318,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
@@ -867,6 +877,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();
@@ -1028,9 +1040,13 @@ 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 */);
+ queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_SPATIALIZER,
+ 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
}
/**
@@ -1220,6 +1236,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 {
@@ -1404,6 +1439,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");
@@ -7537,6 +7577,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(/*init*/ msg.arg1 == 1);
+ break;
+
case MSG_CHECK_MUSIC_ACTIVE:
onCheckMusicActive((String) msg.obj);
break;
@@ -7669,6 +7722,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;
}
}
}
@@ -8237,6 +8298,240 @@ 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() {
+ enforceModifyAudioRoutingPermission();
+ 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
+ * @param init initialization if true, release if false
+ */
+ void postInitSpatializerHeadTrackingSensors(boolean init) {
+ sendMsg(mAudioHandler,
+ MSG_INIT_HEADTRACKING_SENSORS,
+ SENDMSG_REPLACE,
+ /*arg1*/ init ? 1 : 0,
+ 0, TAG, /*delay*/ 0);
+ }
+
+ //==========================================================================================
private boolean readCameraSoundForced() {
return SystemProperties.getBoolean("audio.camerasound.force", false) ||
mContext.getResources().getBoolean(
@@ -8816,6 +9111,12 @@ 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());
}
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..ac212eee21e6 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;
+ }
}
/**
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..7cd027c7550f
--- /dev/null
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -0,0 +1,957 @@
+/*
+ * 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;
+
+ //------------------------------------------------------------
+ // 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;
+
+
+ // 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
+ if (level == SpatializationLevel.NONE) {
+ initSensors(/*init*/false);
+ } else {
+ postInitSensors(true);
+ }
+ }
+
+ 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);
+ }
+ };
+
+ //------------------------------------------------------
+ // 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 =
+ // going through adapter to take advantage of routing cache
+ (AudioDeviceAttributes[]) mASA.getDevicesForAttributes(attributes).toArray();
+ 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 initSensors(boolean init) {
+ if (mSensorManager == null) {
+ mSensorManager = (SensorManager)
+ mAudioService.mContext.getSystemService(Context.SENSOR_SERVICE);
+ }
+ final int headHandle;
+ final int screenHandle;
+ if (init) {
+ if (mSensorManager == null) {
+ Log.e(TAG, "Null SensorManager, can't init sensors");
+ return;
+ }
+ // TODO replace with dynamic association of sensor for headtracker
+ Sensor headSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR);
+ headHandle = headSensor.getHandle();
+ //Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
+ //screenHandle = deviceSensor.getHandle();
+ screenHandle = -1;
+ } else {
+ // -1 is disable value
+ screenHandle = -1;
+ headHandle = -1;
+ }
+ 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);
+ }
+ }
+
+ private void postInitSensors(boolean init) {
+ mAudioService.postInitSpatializerHeadTrackingSensors(init);
+ }
+
+ synchronized void onInitSensors(boolean init) {
+ 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;
+ }
+ initSensors(init);
+ }
+
+ //------------------------------------------------------
+ // 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/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 996f0fd3a55f..4f7c6b012c23 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -50,7 +50,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 +61,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;
@@ -541,14 +539,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..031f6eeeca5f 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;
@@ -359,6 +360,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 +495,14 @@ 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()) {
+ return BiometricOverlayConstants.REASON_AUTH_BP;
+ } else {
+ return BiometricOverlayConstants.REASON_AUTH_OTHER;
+ }
+ }
}
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..97d791b7e1c9 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
@@ -225,6 +225,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 +240,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/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/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 b2d35f45ab44..9d2cff9901e2 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -15,14 +15,20 @@
*/
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.compat.CompatChanges;
import android.app.TaskStackListener;
+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;
@@ -34,18 +40,24 @@ 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;
@@ -94,6 +106,95 @@ 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. 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 potential mismatches between the orientation of the camera and the fixed orientation of
+ * the activity. You can check the table below for further details on the possible override
+ * combinations.
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_CAMERA_RESIZABLE_AND_SDK_CHECK = 191513214L; // buganizer id
+
+ /**
+ * This change id forces the packages it is applied to override the default camera rotate & crop
+ * behavior. Enabling it will set the crop & rotate parameter to
+ * {@link android.hardware.camera2.CaptureRequest#SCALER_ROTATE_AND_CROP_90} and disabling it
+ * will reset the parameter to
+ * {@link android.hardware.camera2.CaptureRequest#SCALER_ROTATE_AND_CROP_NONE} as long as camera
+ * clients include {@link android.hardware.camera2.CaptureRequest#SCALER_ROTATE_AND_CROP_AUTO}
+ * in their capture requests.
+ *
+ * This treatment only takes effect if OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS is also enabled.
+ * The table below includes further information about the possible override combinations.
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_CAMERA_ROTATE_AND_CROP = 190069291L; //buganizer id
+
+ /**
+ * Possible override combinations
+ *
+ * |OVERRIDE | |OVERRIDE_
+ * |CAMERA_ |OVERRIDE |CAMERA_
+ * |ROTATE_ |CAMERA_ |RESIZEABLE_
+ * |AND_CROP_ |ROTATE_ |AND_SDK_
+ * |DEFAULTS |AND_CROP |CHECK
+ * ______________________________________________
+ * Default | | |
+ * Behavior | D |D |D
+ * ______________________________________________
+ * Ignore | | |
+ * SDK&Resize | D |D |E
+ * ______________________________________________
+ * Default | | |
+ * Behavior | D |E |D
+ * ______________________________________________
+ * Ignore | | |
+ * SDK&Resize | D |E |E
+ * ______________________________________________
+ * Rotate&Crop| | |
+ * disabled | E |D |D
+ * ______________________________________________
+ * Rotate&Crop| | |
+ * disabled | E |D |E
+ * ______________________________________________
+ * Rotate&Crop| | |
+ * enabled | E |E |D
+ * ______________________________________________
+ * Rotate&Crop| | |
+ * enabled | E |E |E
+ * ______________________________________________
+ * Where:
+ * E -> Override enabled
+ * D -> Override disabled
+ * Default behavior -> Rotate&crop will be enabled only in cases
+ * where the fixed app orientation mismatches
+ * with the orientation of the camera.
+ * Additionally the app must either target M (or below)
+ * or is declared as non-resizeable.
+ * Ignore SDK&Resize -> Rotate&crop will be enabled only in cases
+ * where the fixed app orientation mismatches
+ * with the orientation of the camera.
+ */
+
// 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;
@@ -248,12 +349,13 @@ public class CameraServiceProxy extends SystemService
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 {
@@ -268,8 +370,10 @@ public class CameraServiceProxy extends SystemService
synchronized (mMapLock) {
TaskInfo info = new TaskInfo();
info.frontTaskId = taskInfo.taskId;
- info.isResizeable = taskInfo.isResizeable;
+ info.isResizeable =
+ (taskInfo.topActivityInfo.resizeMode != RESIZE_MODE_UNRESIZEABLE);
info.displayId = taskInfo.displayId;
+ info.userId = taskInfo.userId;
info.isFixedOrientationLandscape = ActivityInfo.isFixedOrientationLandscape(
taskInfo.topActivityInfo.screenOrientation);
info.isFixedOrientationPortrait = ActivityInfo.isFixedOrientationPortrait(
@@ -300,7 +404,7 @@ public class CameraServiceProxy extends SystemService
Log.e(TAG, "Top task with package name: " + packageName + " not found!");
return null;
}
- };
+ }
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
@@ -327,93 +431,152 @@ 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) {
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;
}
// 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);
+ TaskInfo taskInfo = mTaskStackListener.getFrontTaskInfo(packageName);
+ if ((taskInfo != null) && (CompatChanges.isChangeEnabled(
+ OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS, packageName,
+ UserHandle.getUserHandleForUid(taskInfo.userId)))) {
+ if (CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_ROTATE_AND_CROP, packageName,
+ UserHandle.getUserHandleForUid(taskInfo.userId))) {
+ Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP enabled!");
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ } else {
+ Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP disabled!");
+ 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 +610,8 @@ public class CameraServiceProxy extends SystemService
}
};
+ private final FoldStateListener mFoldStateListener;
+
public CameraServiceProxy(Context context) {
super(context);
mContext = context;
@@ -459,6 +624,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 +644,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 +664,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;
@@ -550,11 +723,17 @@ public class CameraServiceProxy extends SystemService
}
try {
- WindowManagerGlobal.getWindowManagerService().registerDisplayWindowListener(
- mDisplayWindowListener);
+ int[] displayIds = WindowManagerGlobal.getWindowManagerService()
+ .registerDisplayWindowListener(mDisplayWindowListener);
+ for (int i = 0; i < displayIds.length; i++) {
+ mDisplayWindowListener.onDisplayAdded(displayIds[i]);
+ }
} 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..2bf1ccd51939 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,12 +219,11 @@ 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.
*/
- void addOverrides(CompatibilityOverrideConfig overrides, String packageName) {
+ synchronized void addOverrides(CompatibilityOverrideConfig overrides, String packageName) {
for (Long changeId : overrides.overrides.keySet()) {
addOverrideUnsafe(changeId, packageName, overrides.overrides.get(changeId));
}
@@ -265,36 +233,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 +258,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 +299,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 +314,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,13 +330,7 @@ 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);
}
/**
@@ -414,19 +341,16 @@ final class CompatConfig {
*
* @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();
}
/**
@@ -439,34 +363,31 @@ final class CompatConfig {
* @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);
+ 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 +400,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 +420,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 +458,7 @@ final class CompatConfig {
@VisibleForTesting
void clearChanges() {
- mReadWriteLock.writeLock().lock();
- try {
- mChanges.clear();
- } finally {
- mReadWriteLock.writeLock().unlock();
- }
+ mChanges.clear();
}
/**
@@ -551,18 +467,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 +484,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 +500,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 +525,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 +542,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 +596,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 +633,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/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/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..792feea01e27 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -19,23 +19,27 @@ 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;
import android.content.Context;
import android.hardware.devicestate.DeviceStateInfo;
import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateManagerInternal;
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 +47,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 +92,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 +129,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,14 +147,27 @@ 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
public void onStart() {
publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService);
+ publishLocalService(DeviceStateManagerInternal.class, new LocalService());
+ }
+
+ @VisibleForTesting
+ Handler getHandler() {
+ return mHandler;
}
/**
@@ -191,12 +223,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();
}
}
@@ -212,13 +242,6 @@ public final class DeviceStateManagerService extends SystemService {
}
/** Returns the list of currently supported device state identifiers. */
- private int[] getSupportedStateIdentifiers() {
- synchronized (mLock) {
- return getSupportedStateIdentifiersLocked();
- }
- }
-
- /** Returns the list of currently supported device state identifiers. */
private int[] getSupportedStateIdentifiersLocked() {
int[] supportedStates = new int[mDeviceStates.size()];
for (int i = 0; i < supportedStates.length; i++) {
@@ -247,43 +270,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 +332,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 +345,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 +377,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 +444,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 +551,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 +577,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 +588,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 +601,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 +622,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 +646,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 +776,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 +801,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);
@@ -901,4 +843,14 @@ public final class DeviceStateManagerService extends SystemService {
}
}
}
+
+ /** Implementation of {@link DeviceStateManagerInternal} published as a local service. */
+ private final class LocalService extends DeviceStateManagerInternal {
+ @Override
+ public int[] getSupportedStateIdentifiers() {
+ synchronized (mLock) {
+ return getSupportedStateIdentifiersLocked();
+ }
+ }
+ }
}
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 c2200430e53b..a3b5e79cc8c7 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -53,6 +53,7 @@ import android.graphics.Point;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateManagerInternal;
import android.hardware.display.AmbientBrightnessDayStats;
import android.hardware.display.BrightnessChangeEvent;
import android.hardware.display.BrightnessConfiguration;
@@ -63,8 +64,6 @@ import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayGroupListener;
import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
-import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
-import android.hardware.display.DisplayManagerInternal.RefreshRateRange;
import android.hardware.display.DisplayViewport;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
@@ -133,6 +132,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
@@ -201,7 +201,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;
@@ -212,6 +212,7 @@ public final class DisplayManagerService extends SystemService {
private WindowManagerInternal mWindowManagerInternal;
private InputManagerInternal mInputManagerInternal;
private IMediaProjectionManager mProjectionService;
+ private DeviceStateManagerInternal mDeviceStateManager;
private int[] mUserDisabledHdrTypes = {};
private boolean mAreUserDisabledHdrTypesAllowed = true;
@@ -527,16 +528,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();
}
}
@@ -546,10 +560,9 @@ public final class DisplayManagerService extends SystemService {
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
- DeviceStateManager deviceStateManager =
- mContext.getSystemService(DeviceStateManager.class);
- deviceStateManager.registerCallback(new HandlerExecutor(mHandler),
- new DeviceStateListener());
+ mDeviceStateManager = LocalServices.getService(DeviceStateManagerInternal.class);
+ mContext.getSystemService(DeviceStateManager.class).registerCallback(
+ new HandlerExecutor(mHandler), new DeviceStateListener());
scheduleTraversalLocked(false);
}
@@ -638,6 +651,9 @@ public final class DisplayManagerService extends SystemService {
synchronized (mSyncRoot) {
final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display != null) {
+ // Do not let constrain be overwritten by override from WindowManager.
+ info.shouldConstrainMetricsForLauncher =
+ display.getDisplayInfoLocked().shouldConstrainMetricsForLauncher;
if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) {
handleLogicalDisplayChangedLocked(display);
scheduleTraversalLocked(false);
@@ -1317,6 +1333,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);
}
@@ -1425,24 +1448,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) {
@@ -1465,13 +1506,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);
+ }
+ }
+ });
}
}
@@ -1682,9 +1732,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) {
@@ -1725,6 +1783,21 @@ public final class DisplayManagerService extends SystemService {
}
}
+ void setShouldConstrainMetricsForLauncher(boolean constrain) {
+ // Apply constrain for every display.
+ synchronized (mSyncRoot) {
+ int[] displayIds = mLogicalDisplayMapper.getDisplayIdsLocked(Process.myUid());
+ for (int i : displayIds) {
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(i);
+ if (display == null) {
+ return;
+ }
+ display.getDisplayInfoLocked().shouldConstrainMetricsForLauncher = constrain;
+ setDisplayInfoOverrideFromWindowManagerInternal(i, display.getDisplayInfoLocked());
+ }
+ }
+ }
+
private void clearViewportsLocked() {
mViewports.clear();
}
@@ -1751,10 +1824,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.
@@ -1986,9 +2062,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);
@@ -2011,6 +2084,8 @@ public final class DisplayManagerService extends SystemService {
pw.println();
mPersistentDataStore.dump(pw);
}
+ pw.println();
+ mDisplayModeDirector.dump(pw);
}
private static float[] getFloatArray(TypedArray array) {
@@ -2115,6 +2190,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*/);
@@ -2156,8 +2243,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:
@@ -2166,6 +2253,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();
}
@@ -2772,6 +2862,19 @@ public final class DisplayManagerService extends SystemService {
@Override // Binder call
public void setBrightnessConfigurationForUser(
BrightnessConfiguration c, @UserIdInt int userId, String packageName) {
+ mLogicalDisplayMapper.forEachLocked(logicalDisplay -> {
+ if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) {
+ return;
+ }
+ final DisplayDevice displayDevice = logicalDisplay.getPrimaryDisplayDeviceLocked();
+ setBrightnessConfigurationForDisplay(c, displayDevice.getUniqueId(), userId,
+ packageName);
+ });
+ }
+
+ @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");
@@ -2779,21 +2882,19 @@ public final class DisplayManagerService extends SystemService {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS,
"Permission required to change the display brightness"
- + " configuration of another user");
- }
- if (packageName != null && !validatePackageName(getCallingUid(), packageName)) {
- packageName = null;
+ + " 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");
@@ -2804,14 +2905,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;
}
@@ -2820,6 +2926,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(
@@ -3088,7 +3209,7 @@ public final class DisplayManagerService extends SystemService {
initializeDisplayPowerControllersLocked();
}
- mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATION);
+ mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATIONS);
}
@Override
@@ -3154,6 +3275,53 @@ public final class DisplayManagerService extends SystemService {
}
@Override
+ public Set<DisplayInfo> getPossibleDisplayInfo(int displayId) {
+ synchronized (mSyncRoot) {
+ // Retrieve the group associated with this display id.
+ final int displayGroupId =
+ mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(displayId);
+ if (displayGroupId == Display.INVALID_DISPLAY_GROUP) {
+ Slog.w(TAG,
+ "Can't get possible display info since display group for " + displayId
+ + " does not exist");
+ return new ArraySet<>();
+ }
+
+ // Assume any display in this group can be swapped out for the given display id.
+ Set<DisplayInfo> possibleInfo = new ArraySet<>();
+ final DisplayGroup group = mLogicalDisplayMapper.getDisplayGroupLocked(
+ displayGroupId);
+ for (int i = 0; i < group.getSizeLocked(); i++) {
+ final int id = group.getIdLocked(i);
+ final LogicalDisplay logical = mLogicalDisplayMapper.getDisplayLocked(id);
+ if (logical == null) {
+ Slog.w(TAG,
+ "Can't get possible display info since logical display for "
+ + "display id " + id + " does not exist, as part of group "
+ + displayGroupId);
+ } else {
+ possibleInfo.add(logical.getDisplayInfoLocked());
+ }
+ }
+
+ // For the supported device states, retrieve the DisplayInfos for the logical
+ // display layout.
+ if (mDeviceStateManager == null) {
+ Slog.w(TAG, "Can't get supported states since DeviceStateManager not ready");
+ } else {
+ final int[] supportedStates =
+ mDeviceStateManager.getSupportedStateIdentifiers();
+ for (int state : supportedStates) {
+ possibleInfo.addAll(
+ mLogicalDisplayMapper.getDisplayInfoForStateLocked(state, displayId,
+ displayGroupId));
+ }
+ }
+ return possibleInfo;
+ }
+ }
+
+ @Override
public Point getDisplayPosition(int displayId) {
synchronized (mSyncRoot) {
final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
@@ -3316,6 +3484,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..9412c938f934 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -58,6 +58,8 @@ class DisplayManagerShellCommand extends ShellCommand {
return setDisplayModeDirectorLoggingEnabled(false);
case "dwb-set-cct":
return setAmbientColorTemperatureOverride();
+ case "constrain-launcher-metrics":
+ return setConstrainLauncherMetrics();
default:
return handleDefaultCommands(cmd);
}
@@ -88,6 +90,9 @@ class DisplayManagerShellCommand extends ShellCommand {
pw.println(" Disable display mode director logging.");
pw.println(" dwb-set-cct CCT");
pw.println(" Sets the ambient color temperature override to CCT (use -1 to disable).");
+ pw.println(" constrain-launcher-metrics [true|false]");
+ pw.println(" Sets if Display#getRealSize and getRealMetrics should be constrained for ");
+ pw.println(" Launcher.");
pw.println();
Intent.printIntentArgsHelp(pw , "");
}
@@ -115,7 +120,7 @@ class DisplayManagerShellCommand extends ShellCommand {
}
private int resetBrightnessConfiguration() {
- mService.resetBrightnessConfiguration();
+ mService.resetBrightnessConfigurations();
return 0;
}
@@ -150,4 +155,15 @@ class DisplayManagerShellCommand extends ShellCommand {
mService.setAmbientColorTemperatureOverride(cct);
return 0;
}
+
+ private int setConstrainLauncherMetrics() {
+ String constrainText = getNextArg();
+ if (constrainText == null) {
+ getErrPrintWriter().println("Error: no value specified");
+ return 1;
+ }
+ boolean constrain = Boolean.parseBoolean(constrainText);
+ mService.setShouldConstrainMetricsForLauncher(constrain);
+ return 0;
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 1224902aa7be..768587a6a2b8 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -739,13 +739,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 +1052,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 +1102,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.
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index f953cc8c8a27..5a9efd707f5d 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -601,14 +601,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 (!res.getBoolean(
com.android.internal.R.bool.config_localDisplaysMirrorContent)) {
@@ -620,6 +612,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..86c9ca937482 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;
@@ -231,6 +233,8 @@ final class LogicalDisplay {
info.physicalXDpi = mOverrideDisplayInfo.physicalXDpi;
info.physicalYDpi = mOverrideDisplayInfo.physicalYDpi;
info.roundedCorners = mOverrideDisplayInfo.roundedCorners;
+ info.shouldConstrainMetricsForLauncher =
+ mOverrideDisplayInfo.shouldConstrainMetricsForLauncher;
}
mInfo.set(info);
}
@@ -512,6 +516,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 4c9a2d702114..0fbc3e8fb89a 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -22,8 +22,11 @@ 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.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
@@ -38,6 +41,7 @@ import com.android.server.display.layout.Layout;
import java.io.PrintWriter;
import java.util.Arrays;
+import java.util.Set;
import java.util.function.Consumer;
/**
@@ -67,7 +71,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;
@@ -96,6 +100,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?
@@ -111,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.
@@ -148,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();
}
@@ -195,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);
@@ -251,6 +267,61 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
return mDisplayGroups.get(groupId);
}
+ /**
+ * Returns the set of {@link DisplayInfo} for this device state, only fetching the info that is
+ * part of the same display group as the provided display id. The DisplayInfo represent the
+ * logical display layouts possible for the given device state.
+ *
+ * @param deviceState the state to query possible layouts for
+ * @param displayId the display id to apply to all displays within the group
+ * @param groupId the display group to filter display info for. Must be the same group as
+ * the display with the provided display id.
+ */
+ public Set<DisplayInfo> getDisplayInfoForStateLocked(int deviceState, int displayId,
+ int groupId) {
+ Set<DisplayInfo> displayInfos = new ArraySet<>();
+ final Layout layout = mDeviceStateToLayoutMap.get(deviceState);
+ final int layoutSize = layout.size();
+ for (int i = 0; i < layoutSize; i++) {
+ Layout.Display displayLayout = layout.getAt(i);
+ if (displayLayout == null) {
+ continue;
+ }
+
+ // If the underlying display-device we want to use for this display
+ // doesn't exist, then skip it. This can happen at startup as display-devices
+ // trickle in one at a time. When the new display finally shows up, the layout is
+ // recalculated so that the display is properly added to the current layout.
+ final DisplayAddress address = displayLayout.getAddress();
+ final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address);
+ if (device == null) {
+ Slog.w(TAG, "The display device (" + address + "), is not available"
+ + " for the display state " + deviceState);
+ continue;
+ }
+
+ // Find or create the LogicalDisplay to map the DisplayDevice to.
+ final int logicalDisplayId = displayLayout.getLogicalDisplayId();
+ final LogicalDisplay logicalDisplay = getDisplayLocked(logicalDisplayId);
+ if (logicalDisplay == null) {
+ Slog.w(TAG, "The logical display (" + address + "), is not available"
+ + " for the display state " + deviceState);
+ continue;
+ }
+ final DisplayInfo temp = logicalDisplay.getDisplayInfoLocked();
+ DisplayInfo displayInfo = new DisplayInfo(temp);
+ if (displayInfo.displayGroupId != groupId) {
+ // Ignore any displays not in the provided group.
+ continue;
+ }
+ // A display in the same group can be swapped out at any point, so set the display id
+ // for all results to the provided display id.
+ displayInfo.displayId = displayId;
+ displayInfos.add(displayInfo);
+ }
+ return displayInfos;
+ }
+
public void dumpLocked(PrintWriter pw) {
pw.println("LogicalDisplayMapper:");
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
@@ -258,6 +329,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();
@@ -275,7 +347,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
@@ -284,8 +358,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;
}
@@ -296,6 +375,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);
}
@@ -422,6 +509,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);
@@ -612,14 +700,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..9d6678053533 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2356,6 +2356,8 @@ 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;
@@ -2528,9 +2530,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);
@@ -4121,6 +4122,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 +4252,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 +4268,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 +4289,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/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 345dc217110b..72ab8c1e2d1a 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -1996,6 +1996,11 @@ public class LocationProviderManager extends
+ TimeUtils.formatDuration(delayMs));
}
+ if (mDelayedRegister != null) {
+ mAlarmHelper.cancel(mDelayedRegister);
+ mDelayedRegister = null;
+ }
+
mDelayedRegister = new OnAlarmListener() {
@Override
public void onAlarm() {
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 7502afc8cd82..9be618cb7add 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -922,8 +922,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/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..20687c6764db 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) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7ba0f04a435f..2f550c6e9338 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7076,7 +7076,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);
@@ -9424,7 +9423,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/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
index f47aa487744a..0a69aec76306 100644
--- a/services/core/java/com/android/server/notification/VibratorHelper.java
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -39,6 +39,9 @@ public final class VibratorHelper {
private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
+ private static final int CHIRP_LEVEL_DURATION_MILLIS = 100;
+ private static final int DEFAULT_CHIRP_RAMP_DURATION_MILLIS = 100;
+ private static final int FALLBACK_CHIRP_RAMP_DURATION_MILLIS = 50;
private final Vibrator mVibrator;
private final long[] mDefaultPattern;
@@ -102,6 +105,9 @@ 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()) {
+ return createChirpVibration(FALLBACK_CHIRP_RAMP_DURATION_MILLIS, insistent);
+ }
return createWaveformVibration(mFallbackPattern, insistent);
}
@@ -111,9 +117,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()) {
+ return createChirpVibration(DEFAULT_CHIRP_RAMP_DURATION_MILLIS, insistent);
+ }
return createWaveformVibration(mDefaultPattern, insistent);
}
+ private static VibrationEffect createChirpVibration(int rampDuration, boolean insistent) {
+ VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform()
+ .addStep(/* amplitude= */ 0, /* frequency= */ -0.85f, /* duration= */ 0)
+ .addRamp(/* amplitude= */ 1, /* frequency= */ -0.25f, rampDuration)
+ .addStep(/* amplitude= */ 1, /* frequency= */ -0.25f, CHIRP_LEVEL_DURATION_MILLIS)
+ .addRamp(/* amplitude= */ 0, /* frequency= */ -0.85f, rampDuration);
+
+ if (insistent) {
+ return waveformBuilder
+ .addStep(/* amplitude= */ 0, CHIRP_LEVEL_DURATION_MILLIS)
+ .build(/* repeat= */ 0);
+ }
+
+ VibrationEffect singleBeat = waveformBuilder.build();
+ return VibrationEffect.startComposition()
+ .addEffect(singleBeat)
+ .addEffect(singleBeat, /* delay= */ CHIRP_LEVEL_DURATION_MILLIS)
+ .compose();
+ }
+
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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6da70ad77d67..1debaf3ea9b4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -10574,7 +10574,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);
@@ -10590,7 +10590,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,
@@ -10632,7 +10632,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;
}
@@ -10778,17 +10778,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()
@@ -23599,7 +23599,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;
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 301914615562..7096f6f419b7 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -913,6 +913,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/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 8b4690629ec5..18c45e494c9b 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -196,8 +196,9 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
}
private static boolean isHotwordDetectionServiceRequired(PackageManager pm) {
- // Usage of the HotwordDetectionService won't be enforced until a later release.
- return false;
+ // The HotwordDetectionService APIs aren't ready yet for Auto or TV.
+ return !(pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK));
}
@Override
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/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7767fe611f87..b7d4d22d3966 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;
@@ -1815,14 +1812,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 */);
}
});
@@ -2399,7 +2404,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;
}
}
@@ -3040,31 +3045,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);
}
@@ -3214,7 +3222,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() {
@@ -3261,46 +3269,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} */
@@ -4270,6 +4264,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pmWakeReason)) + ")");
}
+ mActivityTaskManagerInternal.notifyWakingUp();
mDefaultDisplayPolicy.setAwake(true);
// Since goToSleep performs these functions synchronously, we must
@@ -4594,7 +4589,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
public boolean isKeyguardShowingAndNotOccluded() {
if (mKeyguardDelegate == null) return false;
- return mKeyguardDelegate.isShowing() && !mKeyguardOccluded;
+ return mKeyguardDelegate.isShowing() && !isKeyguardOccluded();
}
@Override
@@ -4620,7 +4615,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
public boolean isKeyguardOccluded() {
if (mKeyguardDelegate == null) return false;
- return mKeyguardOccluded;
+ return mKeyguardDelegate.isOccluded();
}
/** {@inheritDoc} */
@@ -5312,7 +5307,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) {
@@ -5397,7 +5392,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/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index 6366280e1762..7ffff935128f 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -429,15 +429,7 @@ class ConversionUtil {
private static @NonNull
HidlMemory parcelFileDescriptorToHidlMemory(@Nullable ParcelFileDescriptor data, int dataSize) {
if (dataSize > 0) {
- // Extract a dup of the underlying FileDescriptor out of data.
- FileDescriptor fd = new FileDescriptor();
- try {
- ParcelFileDescriptor dup = data.dup();
- fd.setInt$(dup.detachFd());
- return HidlMemoryUtil.fileDescriptorToHidlMemory(fd, dataSize);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ return HidlMemoryUtil.fileDescriptorToHidlMemory(data.getFileDescriptor(), dataSize);
} else {
return HidlMemoryUtil.fileDescriptorToHidlMemory(null, 0);
}
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..9999aff3aa91 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
@@ -125,25 +125,16 @@ public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddleware
* originator temporarily doesn't have the right permissions to use this service.
*/
private void enforcePermissionsForPreflight(@NonNull Identity identity) {
- enforcePermissionForPreflight(mContext, identity, RECORD_AUDIO,
- /* allowSoftDenial= */ true);
- enforcePermissionForPreflight(mContext, identity, CAPTURE_AUDIO_HOTWORD,
- /* allowSoftDenial= */ true);
+ enforcePermissionForPreflight(mContext, identity, RECORD_AUDIO);
+ enforcePermissionForPreflight(mContext, identity, CAPTURE_AUDIO_HOTWORD);
}
/**
* Throws a {@link SecurityException} iff the originator has permission to receive data.
*/
void enforcePermissionsForDataDelivery(@NonNull Identity identity, @NonNull String reason) {
- // 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.
- enforcePermissionForPreflight(mContext, identity, RECORD_AUDIO,
- /* allowSoftDenial= */ false);
- int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
- mContext.getSystemService(AppOpsManager.class).noteOpNoThrow(hotwordOp, identity.uid,
- identity.packageName, identity.attributionTag, reason);
-
+ enforcePermissionForDataDelivery(mContext, identity, RECORD_AUDIO,
+ reason);
enforcePermissionForDataDelivery(mContext, identity, CAPTURE_AUDIO_HOTWORD,
reason);
}
@@ -172,25 +163,20 @@ public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddleware
/**
* Throws a {@link SecurityException} if originator permanently doesn't have the given
* permission.
+ * Soft (temporary) denials are considered OK for preflight purposes.
*
- * @param context A {@link Context}, used for permission checks.
- * @param identity The identity to check.
- * @param permission The identifier of the permission we want to check.
- * @param allowSoftDenial If true, the operation succeeds even for soft (temporary) denials.
+ * @param context A {@link Context}, used for permission checks.
+ * @param identity The identity to check.
+ * @param permission The identifier of the permission we want to check.
*/
- // TODO: Consider splitting up this method instead of using `allowSoftDenial`, to make it
- // clearer when soft denials are not allowed.
private static void enforcePermissionForPreflight(@NonNull Context context,
- @NonNull Identity identity, @NonNull String permission, boolean allowSoftDenial) {
+ @NonNull Identity identity, @NonNull String permission) {
final int status = PermissionUtil.checkPermissionForPreflight(context, identity,
permission);
switch (status) {
case PermissionChecker.PERMISSION_GRANTED:
- return;
case PermissionChecker.PERMISSION_SOFT_DENIED:
- if (allowSoftDenial) {
- return;
- } // else fall through
+ return;
case PermissionChecker.PERMISSION_HARD_DENIED:
throw new SecurityException(
String.format("Failed to obtain permission %s for identity %s", permission,
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..2be29d4a5ab4 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;
@@ -67,6 +68,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 +84,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 +737,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 +937,8 @@ public class StatsPullAtomService extends SystemService {
registerKeystoreKeyOperationWithGeneralInfo();
registerRkpErrorStats();
registerKeystoreCrashStats();
+ registerAccessibilityShortcutStats();
+ registerAccessibilityFloatingMenuStats();
}
private void initAndRegisterNetworkStatsPullers() {
@@ -1340,7 +1349,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 +1389,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 +4160,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 +4371,144 @@ 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 = 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);
+ }
+
// 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..cdab91bbaef8 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,7 +133,7 @@ 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);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index abe81e139b43..b58ca1f4ef80 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;
@@ -526,13 +527,14 @@ 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) { }
}
}
@@ -1104,13 +1106,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;
@@ -1120,12 +1123,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) {
@@ -1245,8 +1250,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/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/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index a24319f7a98c..cf9783fb9241 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -16,6 +16,8 @@
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_IS_ROUNDED_CORNERS_OVERLAY;
@@ -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;
@@ -1052,9 +1119,10 @@ final class AccessibilityController {
|| 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 +1180,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 +1229,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 +1300,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 +1439,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 +1471,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 +1555,7 @@ final class AccessibilityController {
private final Handler mHandler;
- private final AccessibilityTracing mAccessibilityTracing;
+ private final AccessibilityControllerInternalImpl mAccessibilityTracing;
private final WindowsForAccessibilityCallback mCallback;
@@ -1502,24 +1575,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 +1617,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 +1676,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 +1727,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 +1749,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 +1808,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 +1819,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 +1847,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 +1878,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 +2029,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 +2040,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 +2064,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 +2110,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 +2140,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 +2170,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 +2342,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 +2354,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 494f49663b67..d1374362505f 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -59,6 +59,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;
@@ -89,6 +91,7 @@ import android.util.ArrayMap;
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;
@@ -157,6 +160,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();
@@ -184,7 +190,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) {
@@ -214,8 +224,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;
@@ -300,15 +310,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. */
@@ -319,28 +329,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;
}
/**
@@ -363,7 +352,7 @@ class ActivityMetricsLogger {
@Override
public String toString() {
return "TransitionInfo{" + Integer.toHexString(System.identityHashCode(this))
- + " a=" + mLastLaunchedActivity + " ua=" + mPendingDrawActivities + "}";
+ + " a=" + mLastLaunchedActivity + " d=" + mIsDrawn + "}";
}
}
@@ -444,6 +433,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;
@@ -630,6 +628,7 @@ class ActivityMetricsLogger {
if (crossPackage) {
startLaunchTrace(info);
}
+ scheduleCheckActivityToBeDrawnIfSleeping(launchedActivity);
return;
}
@@ -651,13 +650,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
@@ -665,13 +658,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.
*
@@ -683,16 +685,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(
@@ -741,10 +743,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);
}
}
}
@@ -759,6 +759,21 @@ 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;
+ }
+ if (compatStateInfo.mVisibleActivities.isEmpty()) {
+ // No need to keep the entry if there are no visible activities.
+ mPackageUidToCompatStateInfo.remove(packageUid);
+ }
}
/**
@@ -775,19 +790,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 */);
}
}
@@ -806,17 +818,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
@@ -899,7 +906,6 @@ class ActivityMetricsLogger {
}
logAppTransitionFinished(info, isHibernating != null ? isHibernating : false);
}
- info.mPendingDrawActivities.clear();
mTransitionInfoList.remove(info);
}
@@ -1076,7 +1082,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 = () -> {
@@ -1255,6 +1261,115 @@ 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 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} 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 (activity == lastLoggedActivity) {
+ // Make sure a new visible state is logged if needed.
+ findAppCompatStateToLog(compatStateInfo, packageUid);
+ }
+ return;
+ }
+
+ if (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}.
+ */
+ private void findAppCompatStateToLog(PackageCompatStateInfo compatStateInfo, int packageUid) {
+ final ArrayList<ActivityRecord> visibleActivities = compatStateInfo.mVisibleActivities;
+
+ 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 == 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 d13c8ba96d48..8ace5e4aff87 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,6 +154,7 @@ 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;
@@ -191,18 +206,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;
@@ -218,7 +222,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;
@@ -273,13 +276,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;
@@ -303,6 +306,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;
@@ -310,24 +314,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;
@@ -338,7 +342,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;
@@ -347,6 +350,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;
@@ -396,10 +400,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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
@@ -484,13 +484,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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
@@ -540,11 +540,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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;
@@ -552,6 +547,21 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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.
*/
@@ -648,6 +658,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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;
@@ -705,7 +723,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// TODO: rename to mNoDisplay
@VisibleForTesting
boolean noDisplay;
- boolean mShowForAllUsers;
+ final boolean mShowForAllUsers;
// TODO: Make this final
int mTargetSdk;
@@ -728,6 +746,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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;
@@ -797,6 +822,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
*/
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();
@@ -808,6 +834,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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;
@@ -862,19 +891,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
};
- 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();
@@ -999,7 +1015,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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);
@@ -1018,6 +1035,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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);
@@ -1026,9 +1048,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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);
@@ -1073,6 +1093,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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);
@@ -1123,10 +1146,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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());
@@ -1146,6 +1170,76 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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;
}
@@ -1255,15 +1349,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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 */);
}
}
}
@@ -1282,33 +1369,48 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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 (mWmService.mDisableTransitionAnimation
+ || mDisplayContent == null || newParent == null || oldParent == null
+ || getSurfaceControl() == null || !isVisible() || !isVisibleRequested()) {
+ 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()) {
@@ -1364,11 +1466,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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;
}
@@ -1417,7 +1527,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
// 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);
@@ -1709,6 +1819,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null;
mHandoverLaunchDisplayId = options.getLaunchDisplayId();
mLaunchCookie = options.getLaunchCookie();
+ mLaunchRootTask = options.getLaunchRootTask();
}
mPersistentState = persistentState;
@@ -1790,6 +1901,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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() {
@@ -1919,9 +2037,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
+ @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.
@@ -1980,7 +2099,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
applyStartingWindowTheme(pkg, resolvedTheme);
- if (transferStartingWindow(transferFrom)) {
+ if (from != null && transferStartingWindow(from)) {
return true;
}
@@ -2005,6 +2124,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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;
}
@@ -2198,7 +2322,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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();
}
@@ -2207,13 +2332,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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;
}
@@ -2223,14 +2352,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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 */);
@@ -2253,15 +2377,44 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
- 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.
@@ -2318,38 +2471,25 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
- 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) {
@@ -2470,6 +2610,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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() {
@@ -2561,7 +2714,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
boolean isResizeable() {
return mAtmService.mForceResizableActivities
|| ActivityInfo.isResizeableMode(info.resizeMode)
- || info.supportsPictureInPicture();
+ || info.supportsPictureInPicture()
+ // 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. */
@@ -2741,7 +2896,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
/**
* @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;
}
@@ -2881,7 +3036,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@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,
@@ -2915,7 +3070,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
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();
@@ -2926,9 +3081,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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
@@ -2960,10 +3112,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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(
@@ -2994,12 +3143,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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");
}
@@ -3121,6 +3270,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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
@@ -3130,12 +3293,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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 */,
@@ -3318,8 +3481,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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);
@@ -3343,7 +3506,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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();
@@ -3361,10 +3526,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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);
}
/**
@@ -3398,7 +3571,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* 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.
@@ -3458,6 +3631,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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();
@@ -3506,7 +3683,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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
@@ -3542,20 +3719,36 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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();
}
@@ -3582,8 +3775,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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);
@@ -3591,8 +3782,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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
@@ -3604,10 +3793,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
} 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(),
@@ -3769,17 +3968,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
- 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);
@@ -3795,6 +3991,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Transfer the starting window over to the new token.
mStartingData = fromActivity.mStartingData;
+ mSharedStartingData = fromActivity.mSharedStartingData;
mStartingSurface = fromActivity.mStartingSurface;
startingDisplayed = fromActivity.startingDisplayed;
fromActivity.startingDisplayed = false;
@@ -3809,7 +4006,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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,
@@ -3835,7 +4032,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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;
}
@@ -3857,6 +4054,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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();
@@ -3875,16 +4073,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* 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() {
@@ -3953,19 +4145,40 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* 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.
@@ -4040,20 +4253,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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;
@@ -4230,6 +4429,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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(
@@ -4239,11 +4441,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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(),
@@ -4255,6 +4465,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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(),
@@ -4270,6 +4483,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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(),
@@ -4309,6 +4525,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
case ANIM_OPEN_CROSS_PROFILE_APPS:
displayContent.mAppTransition
.overridePendingAppTransitionStartCrossProfileApps();
+ options = AnimationOptions.makeCrossProfileAnimOptions();
break;
case ANIM_NONE:
case ANIM_UNDEFINED:
@@ -4317,6 +4534,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
Slog.e(TAG_WM, "applyOptionsLocked: Unknown animationType=" + animationType);
break;
}
+
+ if (options != null) {
+ mTransitionController.setOverrideAnimation(options, startCallback, finishCallback);
+ }
}
void clearAllDrawn() {
@@ -4396,8 +4617,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return opts;
}
- IRemoteTransition takeRemoteTransition() {
- IRemoteTransition out = mPendingRemoteTransition;
+ RemoteTransition takeRemoteTransition() {
+ RemoteTransition out = mPendingRemoteTransition;
mPendingRemoteTransition = null;
return out;
}
@@ -4449,6 +4670,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
+ 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
@@ -4484,6 +4709,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (app != null) {
mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
}
+ logAppCompatState();
}
/**
@@ -4541,7 +4767,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
Debug.getCallers(6));
// Before setting mVisibleRequested so we can track changes.
- mAtmService.getTransitionController().collect(this);
+ mTransitionController.collect(this);
onChildVisibilityRequested(visible);
@@ -4613,7 +4839,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
// 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
@@ -4677,8 +4903,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* @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;
@@ -4698,7 +4925,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
} 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;
}
@@ -4706,6 +4934,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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);
@@ -4719,7 +4955,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
mUseTransferredAnimation = false;
- postApplyAnimation(visible);
+ postApplyAnimation(visible, fromTransition);
+ }
+
+ void commitVisibility(boolean visible, boolean performLayout) {
+ commitVisibility(visible, performLayout, false /* fromTransition */);
}
/**
@@ -4730,12 +4970,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
*
* @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.
@@ -4754,8 +4998,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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);
}
@@ -4771,7 +5015,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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);
@@ -4865,7 +5110,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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);
@@ -4877,8 +5122,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mState = state;
- if (task != null) {
- task.onActivityStateChanged(this, state, reason);
+ if (getTaskFragment() != null) {
+ getTaskFragment().onActivityStateChanged(this, state, reason);
}
// The WindowManager interprets the app stopping signal as
@@ -4938,44 +5183,42 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
- 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;
}
@@ -4983,8 +5226,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
/**
* 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;
}
@@ -5041,6 +5284,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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.
@@ -5112,7 +5356,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
void updateVisibilityIgnoringKeyguard(boolean behindFullscreenActivity) {
visibleIgnoringKeyguard = (!behindFullscreenActivity || mLaunchTaskBehind)
- && okToShowLocked();
+ && showToCurrentUser();
}
boolean shouldBeVisible() {
@@ -5182,7 +5426,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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;
@@ -5198,13 +5443,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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:
@@ -5301,7 +5539,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
*/
private boolean shouldBeResumed(ActivityRecord activeActivity) {
return shouldMakeActive(activeActivity) && isFocusable()
- && getTask().getVisibility(activeActivity) == TASK_VISIBILITY_VISIBLE
+ && getTaskFragment().getVisibility(activeActivity)
+ == TASK_FRAGMENT_VISIBILITY_VISIBLE
&& canResumeByCompat();
}
@@ -5355,7 +5594,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (!task.hasChild(this)) {
throw new IllegalStateException("Activity not found in its task");
}
- return task.topRunningActivity() == this;
+ return getTaskFragment().topRunningActivity() == this;
}
void handleAlreadyVisible() {
@@ -5444,16 +5683,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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();
}
@@ -5708,6 +5948,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
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,
@@ -5808,7 +6051,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
- void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
+ void onFirstWindowDrawn(WindowState win) {
firstWindowDrawn = true;
// stop tracking
mSplashScreenStyleEmpty = true;
@@ -5824,7 +6067,22 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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();
}
@@ -5868,6 +6126,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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. */
@@ -6001,11 +6262,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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();
}
/**
@@ -6124,9 +6381,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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;
@@ -6135,22 +6392,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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();
}
/**
@@ -6187,6 +6430,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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.
@@ -6204,7 +6453,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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;
@@ -6391,13 +6641,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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);
}
}
@@ -6409,14 +6658,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* 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);
@@ -6461,17 +6708,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
- 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);
@@ -6555,12 +6791,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return candidate;
}
- SurfaceControl getAppAnimationLayer() {
- return getAppAnimationLayer(isActivityTypeHome() ? ANIMATION_LAYER_HOME
- : needsZBoost() ? ANIMATION_LAYER_BOOSTED
- : ANIMATION_LAYER_STANDARD);
- }
-
@Override
boolean needsZBoost() {
return mNeedsZBoost || super.needsZBoost();
@@ -6621,29 +6851,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|| 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
@@ -6672,7 +6882,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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);
@@ -6681,7 +6891,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@Override
void prepareSurfaces() {
- final boolean show = isVisible() || isAnimating(PARENTS,
+ final boolean show = isVisible() || isAnimating(TRANSITION | PARENTS,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
if (mSurfaceControl != null) {
@@ -6765,7 +6975,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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 {
@@ -6774,8 +6984,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
final Configuration displayConfig = mDisplayContent.getConfiguration();
return getDisplayContent().mAppTransition.createThumbnailAspectScaleAnimationLocked(
- appRect, insets, thumbnailHeader, task, displayConfig.uiMode,
- displayConfig.orientation);
+ appRect, insets, thumbnailHeader, task, displayConfig.orientation);
}
@Override
@@ -6922,7 +7131,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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,
@@ -7078,7 +7287,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return false;
}
}
- return !isResizeable() && (info.isFixedOrientation() || info.hasFixedAspectRatio())
+ 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
@@ -7200,7 +7409,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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.
@@ -7251,6 +7461,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
}
+
+ logAppCompatState();
}
/**
@@ -7260,24 +7472,61 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* 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();
@@ -7297,11 +7546,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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);
}
@@ -7315,7 +7560,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
// 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.
}
/**
@@ -7331,8 +7585,60 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
/**
- * 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.
@@ -7340,9 +7646,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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()) {
@@ -7362,7 +7674,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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;
}
@@ -7374,48 +7688,58 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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);
}
@@ -7427,14 +7751,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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);
}
@@ -7462,7 +7787,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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());
}
}
@@ -7522,10 +7847,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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);
@@ -7538,7 +7863,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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.
// ------------------------------
@@ -7581,12 +7906,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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;
+ final int screenPosY = mSizeCompatBounds == null
+ ? (containerBounds.height() - resolvedBounds.height()) / 2
+ : (containerBounds.height() - mSizeCompatBounds.height()) / 2;
if (screenPosX != 0 || screenPosY != 0) {
if (mSizeCompatBounds != null) {
mSizeCompatBounds.offset(screenPosX, screenPosY);
@@ -7630,13 +7958,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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;
}
@@ -7669,6 +7998,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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()) {
return false;
@@ -7693,13 +8026,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@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
@@ -7846,11 +8176,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
Rect containingBounds, float desiredAspectRatio, boolean fixedOrientationLetterboxed) {
final float maxAspectRatio = info.getMaxAspectRatio();
final Task rootTask = getRootTask();
- final float minAspectRatio = info.getMinAspectRatio();
-
+ final float minAspectRatio = getMinAspectRatio();
if (task == null || rootTask == null
|| (inMultiWindowMode() && !shouldCreateCompatDisplayInsets()
- && !fixedOrientationLetterboxed)
+ && !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
@@ -7943,7 +8272,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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;
}
@@ -7951,6 +8280,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
/**
+ * 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) {
@@ -8101,7 +8444,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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();
@@ -8677,6 +9022,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
proto.write(PIP_AUTO_ENTER_ENABLED, pictureInPictureArgs.isAutoEnterEnabled());
proto.write(IN_SIZE_COMPAT_MODE, inSizeCompatMode());
+ proto.write(MIN_ASPECT_RATIO, getMinAspectRatio());
}
@Override
@@ -8715,6 +9061,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* 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. */
@@ -8741,6 +9089,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
/** 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();
@@ -8887,7 +9236,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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]);
}
}
}
@@ -8910,17 +9260,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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
@@ -8956,6 +9308,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
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..6ad2f7cad3c2 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,30 @@ 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
+ * @return the start result.
+ */
+ int startActivityInTaskFragment(@NonNull TaskFragment taskFragment,
+ @NonNull Intent activityIntent, @Nullable Bundle activityOptions,
+ @Nullable IBinder resultTo) {
+ final ActivityRecord caller =
+ resultTo != null ? ActivityRecord.forTokenLocked(resultTo) : null;
+ return obtainStarter(activityIntent, "startActivityInTaskFragment")
+ .setActivityOptions(activityOptions)
+ .setInTaskFragment(taskFragment)
+ .setResultTo(resultTo)
+ .setRequestCode(-1)
+ .setCallingUid(Binder.getCallingUid())
+ .setCallingPid(Binder.getCallingPid())
+ .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 a9a25fc2d272..dc5126dbf916 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;
@@ -74,7 +77,6 @@ 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;
@@ -112,7 +114,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;
@@ -177,6 +179,7 @@ class ActivityStarter {
private int mPreferredWindowingMode;
private Task mInTask;
+ private TaskFragment mInTaskFragment;
@VisibleForTesting
boolean mAddingToTask;
private Task mReuseTask;
@@ -190,7 +193,6 @@ class ActivityStarter {
private Task mTargetTask;
private boolean mMovedToFront;
private boolean mNoAnimation;
- private boolean mKeepCurTransition;
private boolean mAvoidMoveToFront;
private boolean mFrozeTaskList;
private boolean mTransientLaunch;
@@ -342,6 +344,7 @@ class ActivityStarter {
boolean avoidMoveToFront;
ActivityRecord[] outActivity;
Task inTask;
+ TaskFragment inTaskFragment;
String reason;
ProfilerInfo profilerInfo;
Configuration globalConfig;
@@ -392,6 +395,7 @@ class ActivityStarter {
componentSpecified = false;
outActivity = null;
inTask = null;
+ inTaskFragment = null;
reason = null;
profilerInfo = null;
globalConfig = null;
@@ -407,7 +411,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;
@@ -432,6 +436,7 @@ class ActivityStarter {
componentSpecified = request.componentSpecified;
outActivity = request.outActivity;
inTask = request.inTask;
+ inTaskFragment = request.inTaskFragment;
reason = request.reason;
profilerInfo = request.profilerInfo;
globalConfig = request.globalConfig;
@@ -574,6 +579,7 @@ class ActivityStarter {
mPreferredWindowingMode = starter.mPreferredWindowingMode;
mInTask = starter.mInTask;
+ mInTaskFragment = starter.mInTaskFragment;
mAddingToTask = starter.mAddingToTask;
mReuseTask = starter.mReuseTask;
@@ -585,7 +591,6 @@ class ActivityStarter {
mTargetRootTask = starter.mTargetRootTask;
mMovedToFront = starter.mMovedToFront;
mNoAnimation = starter.mNoAnimation;
- mKeepCurTransition = starter.mKeepCurTransition;
mAvoidMoveToFront = starter.mAvoidMoveToFront;
mFrozeTaskList = starter.mFrozeTaskList;
@@ -737,7 +742,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;
}
}
@@ -835,6 +840,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.
@@ -850,7 +856,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;
}
}
@@ -864,7 +870,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);
}
@@ -1175,8 +1181,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;
@@ -1502,7 +1508,7 @@ class ActivityStarter {
final Task targetTask = r.getTask() != null
? r.getTask()
: mTargetTask;
- if (startedActivityRootTask == null || targetTask == null) {
+ if (startedActivityRootTask == null || targetTask == null || !targetTask.isAttached()) {
return;
}
@@ -1546,29 +1552,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);
@@ -1576,7 +1596,7 @@ class ActivityStarter {
mSupervisor.mUserLeaving = false;
// Transition housekeeping
- if (!ActivityManager.isStartResultSuccessful(result)) {
+ if (!startResultSuccessful) {
if (newTransition != null) {
newTransition.abort();
}
@@ -1595,17 +1615,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);
}
}
}
@@ -1665,15 +1695,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();
@@ -1681,6 +1711,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
@@ -1739,11 +1771,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.
@@ -1765,24 +1792,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)) {
@@ -1816,8 +1842,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;
@@ -1931,10 +1957,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
@@ -2023,7 +2085,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;
}
@@ -2049,7 +2111,7 @@ class ActivityStarter {
}
// For paranoia, make sure we have correctly resumed the top activity.
- topRootTask.mLastPausedActivity = null;
+ top.getTaskFragment().clearLastPausedActivity();
if (mDoResume) {
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
@@ -2145,7 +2207,7 @@ class ActivityStarter {
task.moveActivityToFrontLocked(act);
act.updateOptionsLocked(mOptions);
deliverNewIntent(act, intentGrants);
- mTargetRootTask.mLastPausedActivity = null;
+ act.getTaskFragment().clearLastPausedActivity();
} else {
mAddingToTask = true;
}
@@ -2213,6 +2275,7 @@ class ActivityStarter {
mPreferredWindowingMode = WINDOWING_MODE_UNDEFINED;
mInTask = null;
+ mInTaskFragment = null;
mAddingToTask = false;
mReuseTask = null;
@@ -2224,7 +2287,6 @@ class ActivityStarter {
mTargetTask = null;
mMovedToFront = false;
mNoAnimation = false;
- mKeepCurTransition = false;
mAvoidMoveToFront = false;
mFrozeTaskList = false;
mTransientLaunch = false;
@@ -2240,9 +2302,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;
@@ -2305,7 +2367,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;
}
@@ -2332,6 +2394,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;
@@ -2345,6 +2412,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
@@ -2568,13 +2636,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
@@ -2602,16 +2685,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
@@ -2627,8 +2708,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;
}
@@ -2651,6 +2736,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, 0 /** launchFlags */);
+ // 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() {
@@ -2678,7 +2776,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",
@@ -2700,14 +2798,51 @@ class ActivityStarter {
mIntentDelivered = true;
}
- private void addOrReparentStartingActivity(Task parent, String reason) {
- if (mStartActivity.getTask() == null || mStartActivity.getTask() == parent) {
- parent.addChild(mStartActivity);
+ private void addOrReparentStartingActivity(@NonNull Task task, String reason) {
+ 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 &&
@@ -2936,6 +3071,11 @@ class ActivityStarter {
return this;
}
+ ActivityStarter setInTaskFragment(TaskFragment taskFragment) {
+ mRequest.inTaskFragment = taskFragment;
+ return this;
+ }
+
ActivityStarter setWaitResult(WaitResult result) {
mRequest.waitResult = result;
return this;
@@ -3019,5 +3159,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 08f3b1a8c6e0..60a514e4d612 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;
@@ -652,16 +657,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 +746,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
WindowOrganizerController mWindowOrganizerController;
TaskOrganizerController mTaskOrganizerController;
+ TaskFragmentOrganizerController mTaskFragmentOrganizerController;
@Nullable
private BackgroundActivityStartCallback mBackgroundActivityStartCallback;
@@ -805,6 +820,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 +960,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 +991,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 +1120,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return mBackgroundActivityStartCallback;
}
+ SparseArray<ActivityInterceptorCallback> getActivityInterceptorCallbacks() {
+ return mActivityInterceptorCallbacks;
+ }
+
private void start() {
LocalServices.addService(ActivityTaskManagerInternal.class, mInternal);
}
@@ -1222,8 +1244,8 @@ 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;
}
@@ -1537,7 +1559,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 +1741,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 +1901,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()");
@@ -3431,7 +3468,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public IWindowOrganizerController getWindowOrganizerController() {
- enforceTaskPermission("getWindowOrganizerController()");
return mWindowOrganizerController;
}
@@ -3775,6 +3811,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 +4088,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 +4270,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 +4753,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 +5138,38 @@ 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 FIRST_ACTIVITY_TASK_MSG = 100;
static final int FIRST_SUPERVISOR_TASK_MSG = 200;
@@ -5087,6 +5193,20 @@ 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;
}
}
}
@@ -5405,6 +5525,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 +6567,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 +6591,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 efa67e934543..7c5f059fb89a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -44,11 +44,13 @@ 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.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,8 +75,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.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;
@@ -140,7 +140,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,7 +150,6 @@ import com.android.server.am.UserState;
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;
@@ -353,6 +351,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. */
@@ -836,6 +840,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(
@@ -1376,8 +1386,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)) {
@@ -1550,13 +1561,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);
@@ -1856,12 +1861,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.
@@ -1875,7 +1874,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) {
@@ -1930,6 +1929,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,
@@ -1967,6 +1967,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
mWaitingActivityLaunched.get(i).dump(pw, prefix + " ");
}
}
+ pw.println(prefix + "mNoHistoryActivities=" + mNoHistoryActivities);
pw.println();
}
@@ -1991,76 +1992,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;
}
@@ -2087,7 +2026,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.
@@ -2109,7 +2048,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
// Update the current top activity.
- mTopResumedActivity = topRootTask.getResumedActivity();
+ mTopResumedActivity = topRootTask.getTopResumedActivity();
scheduleTopResumedActivityStateIfNeeded();
mService.updateTopApp(mTopResumedActivity);
@@ -2236,7 +2175,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.
@@ -2349,6 +2288,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.
*
@@ -2416,8 +2363,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;
}
@@ -2517,102 +2463,126 @@ 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();
- }
- 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;
+ synchronized (mService.mGlobalLock) {
+ int activityType = ACTIVITY_TYPE_UNDEFINED;
+ if (activityOptions != null) {
+ activityType = activityOptions.getLaunchActivityType();
+ final int windowingMode = activityOptions.getLaunchWindowingMode();
+ if (activityOptions.freezeRecentTasksReordering()
+ && mRecentTasks.isCallerRecents(callingUid)) {
+ 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 (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 (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 (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");
- }
+ 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.");
+ }
- // 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);
+ 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");
}
- mService.getActivityStartController().postStartActivityProcessingForLastStarter(
- task.getTopNonFinishingActivity(), ActivityManager.START_TASK_TO_FRONT,
- task.getRootTask());
+ // 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);
+ }
- // As it doesn't go to ActivityStarter.executeRequest() path, we need to resume
- // app switching here also.
- mService.resumeAppSwitches();
+ mService.getActivityStartController().postStartActivityProcessingForLastStarter(
+ task.getTopNonFinishingActivity(), ActivityManager.START_TASK_TO_FRONT,
+ task.getRootTask());
- return ActivityManager.START_TASK_TO_FRONT;
+ // 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();
+ }
}
}
@@ -2635,7 +2605,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..b9353e1f9a0c 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) {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index c869ec67776d..535a061ee4ab 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;
@@ -68,6 +71,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACT
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 +86,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 +109,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,8 +166,8 @@ 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");
@@ -185,8 +207,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 +235,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 +295,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 +304,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 +338,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 +426,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 +474,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 +514,151 @@ 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.
+ // @see NonAppWindowAnimationAdapter#startNonAppWindowAnimations
+ if (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
+ || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER
+ || transit == TRANSIT_OLD_TASK_OPEN || transit == TRANSIT_OLD_TASK_TO_FRONT
+ || transit == TRANSIT_OLD_WALLPAPER_CLOSE) {
+ return true;
+ }
+
+ // Check if the wallpaper is going to participate in the transition. We don't want to have
+ // the client to animate the wallpaper windows.
+ // @see WallpaperAnimationAdapter#startWallpaperAnimations
+ return mDisplayContent.mWallpaperController.isWallpaperVisible();
+ }
+
+ /**
+ * 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() != rootActivity.getUid() && !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 +671,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 +705,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 +848,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.
@@ -727,7 +944,7 @@ public class AppTransitionController {
final AccessibilityController accessibilityController =
mDisplayContent.mWmService.mAccessibilityController;
- if (accessibilityController != null) {
+ if (accessibilityController.hasCallbacks()) {
accessibilityController.onAppWindowTransition(mDisplayContent.getDisplayId(), transit);
}
}
@@ -840,71 +1057,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/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 9335846e7805..cb376520e000 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -61,7 +61,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.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
@@ -77,6 +76,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;
@@ -88,13 +88,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;
@@ -111,13 +115,14 @@ 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_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;
@@ -126,7 +131,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;
@@ -134,7 +138,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;
@@ -202,6 +205,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;
@@ -296,7 +300,29 @@ 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;
+
+ /**
+ * The last state of the display.
+ */
+ private int mLastDisplayState;
// 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
@@ -306,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;
@@ -362,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)
@@ -426,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.
@@ -498,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.
@@ -553,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;
@@ -561,7 +591,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** Caches the value whether told display manager that we have content. */
private boolean mLastHasContent;
- private DisplayRotationUtil mRotationUtil = new DisplayRotationUtil();
+ private static DisplayRotationUtil sRotationUtil = new DisplayRotationUtil();
/**
* The input method window for this display.
@@ -690,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;
@@ -771,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);
@@ -959,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) {
@@ -1020,6 +1067,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
+ mTransitionController.registerLegacyListener(
+ mWmService.mActivityManagerAppTransitionNotifier);
mAppTransition.registerListenerLocked(mFixedRotationTransitionListener);
mAppTransitionController = new AppTransitionController(mWmService, this);
mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
@@ -1058,52 +1107,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() {
@@ -1234,17 +1322,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());
@@ -1350,11 +1432,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();
}
@@ -1470,7 +1557,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.
@@ -1621,7 +1708,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;
@@ -1869,8 +1956,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 =
@@ -1919,10 +2005,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
}
-
- if (mWmService.mAccessibilityController != null) {
- mWmService.mAccessibilityController.onRotationChanged(this);
- }
}
void configureDisplayPolicy() {
@@ -2013,29 +2095,35 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return mDisplayCutoutCache.getOrCompute(mInitialDisplayCutout, rotation);
}
- private WmDisplayCutout calculateDisplayCutoutForRotationUncached(
- DisplayCutout cutout, int rotation) {
+ static WmDisplayCutout calculateDisplayCutoutForRotationAndDisplaySizeUncached(
+ DisplayCutout cutout, int rotation, int displayWidth, int displayHeight) {
if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
return WmDisplayCutout.NO_CUTOUT;
}
if (rotation == ROTATION_0) {
return WmDisplayCutout.computeSafeInsets(
- cutout, mInitialDisplayWidth, mInitialDisplayHeight);
+ cutout, displayWidth, displayHeight);
}
final Insets waterfallInsets =
RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- final Rect[] newBounds = mRotationUtil.getRotatedBounds(
+ final Rect[] newBounds = sRotationUtil.getRotatedBounds(
cutout.getBoundingRectsAll(),
- rotation, mInitialDisplayWidth, mInitialDisplayHeight);
+ rotation, displayWidth, displayHeight);
final CutoutPathParserInfo info = cutout.getCutoutPathParserInfo();
final CutoutPathParserInfo newInfo = new CutoutPathParserInfo(
info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
info.getCutoutSpec(), rotation, info.getScale());
return WmDisplayCutout.computeSafeInsets(
DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo),
- rotated ? mInitialDisplayHeight : mInitialDisplayWidth,
- rotated ? mInitialDisplayWidth : mInitialDisplayHeight);
+ rotated ? displayHeight : displayWidth,
+ rotated ? displayWidth : displayHeight);
+ }
+
+ private WmDisplayCutout calculateDisplayCutoutForRotationUncached(
+ DisplayCutout cutout, int rotation) {
+ return calculateDisplayCutoutForRotationAndDisplaySizeUncached(cutout, rotation,
+ mInitialDisplayWidth, mInitialDisplayHeight);
}
RoundedCorners calculateRoundedCornersForRotation(int rotation) {
@@ -2470,6 +2558,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)
@@ -2490,7 +2620,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Override
boolean isVisibleRequested() {
- return isVisible();
+ return isVisible() && !mRemoved && !mRemoving;
}
@Override
@@ -2984,7 +3114,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;
}
@@ -3014,6 +3147,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mOverlayLayer.release();
mInputMonitor.onDisplayRemoved();
mWmService.mDisplayNotificationController.dispatchDisplayRemoved(this);
+ mWmService.mAccessibilityController.onDisplayRemoved(mDisplayId);
} finally {
mDisplayReady = false;
}
@@ -3085,6 +3219,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) {
@@ -3105,7 +3268,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);
}
@@ -3128,6 +3295,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);
}
@@ -3193,9 +3365,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);
@@ -3287,7 +3456,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() {
@@ -3426,15 +3597,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.
@@ -3442,16 +3610,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);
@@ -3472,13 +3630,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;
}
@@ -3486,20 +3643,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.
*
@@ -3523,7 +3666,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;
@@ -3664,6 +3814,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;
}
@@ -3714,7 +3865,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
@@ -3796,6 +3951,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;
@@ -3975,6 +4147,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false;
}
setImeInputTarget(target);
+ mInsetsStateController.updateAboveInsetsState(mInputMethodWindow, mInsetsStateController
+ .getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
updateImeControlTarget();
}
}
@@ -4036,14 +4210,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;
@@ -4224,7 +4394,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) {
@@ -4349,6 +4518,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) {
@@ -4514,6 +4685,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;
@@ -4521,16 +4694,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
@@ -4601,7 +4782,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;
}
@@ -4896,6 +5079,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++;
}
@@ -4909,7 +5095,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
mDeferUpdateImeTargetCount--;
- if (mDeferUpdateImeTargetCount == 0) {
+ if (mDeferUpdateImeTargetCount == 0 && mUpdateImeRequestedWhileDeferred) {
computeImeTarget(true /* updateImeTarget */);
}
}
@@ -4954,10 +5140,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);
@@ -4970,26 +5164,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",
@@ -4999,6 +5194,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.
*/
@@ -5018,9 +5218,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
@@ -5032,6 +5230,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);
}
@@ -5073,7 +5277,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();
@@ -5415,6 +5619,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
} 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 (mLastDisplayState != 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();
+ }
+ mLastDisplayState = displayState;
}
mWmService.requestTraversal();
}
@@ -5595,6 +5808,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
mWmService.mDisplayNotificationController.dispatchDisplayChanged(
this, getConfiguration());
+ if (isReady() && mTransitionController.isShellTransitionsEnabled()) {
+ requestChangeTransitionIfNeeded(changes);
+ }
}
return changes;
}
@@ -5604,7 +5820,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);
@@ -5615,6 +5832,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
@@ -5695,6 +5920,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) {
@@ -5857,6 +6089,197 @@ 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);
+ // 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).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 {
@@ -6001,7 +6424,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;
@@ -6063,15 +6486,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 d0457b08aa34..eab1aaf0d9fb 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -16,12 +16,9 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-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.content.res.Configuration.UI_MODE_TYPE_CAR;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.util.RotationUtils.deltaRotation;
@@ -39,6 +36,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;
@@ -124,6 +122,7 @@ import android.graphics.Insets;
import android.graphics.PixelFormat;
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;
@@ -142,6 +141,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;
@@ -157,6 +157,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;
@@ -173,6 +174,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;
/**
@@ -313,17 +317,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;
+
+ /** 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 system UI about whether the focused window is fullscreen/immersive.
+ // 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.
@@ -333,19 +361,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;
@@ -432,8 +456,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);
@@ -458,7 +484,8 @@ public class DisplayPolicy {
if (mStatusBar != null) {
requestTransientBars(mStatusBar);
}
- checkAltBarSwipeForTransientBars(ALT_BAR_TOP);
+ checkAltBarSwipeForTransientBars(ALT_BAR_TOP,
+ false /* allowForAllPositions */);
}
}
@@ -469,7 +496,8 @@ public class DisplayPolicy {
&& mNavigationBarPosition == NAV_BAR_BOTTOM) {
requestTransientBars(mNavigationBar);
}
- checkAltBarSwipeForTransientBars(ALT_BAR_BOTTOM);
+ checkAltBarSwipeForTransientBars(ALT_BAR_BOTTOM,
+ false /* allowForAllPositions */);
}
}
@@ -479,13 +507,13 @@ public class DisplayPolicy {
synchronized (mLock) {
mDisplayContent.calculateSystemGestureExclusion(
excludedRegion, null /* outUnrestricted */);
- final boolean excluded =
- mSystemGestures.currentGestureStartedInRegion(excludedRegion);
+ final boolean allowSideSwipe = mNavigationBarAlwaysShowOnSideGesture &&
+ !mSystemGestures.currentGestureStartedInRegion(excludedRegion);
if (mNavigationBar != null && (mNavigationBarPosition == NAV_BAR_RIGHT
- || !excluded && mNavigationBarAlwaysShowOnSideGesture)) {
+ || allowSideSwipe)) {
requestTransientBars(mNavigationBar);
}
- checkAltBarSwipeForTransientBars(ALT_BAR_RIGHT);
+ checkAltBarSwipeForTransientBars(ALT_BAR_RIGHT, allowSideSwipe);
}
excludedRegion.recycle();
}
@@ -496,13 +524,13 @@ public class DisplayPolicy {
synchronized (mLock) {
mDisplayContent.calculateSystemGestureExclusion(
excludedRegion, null /* outUnrestricted */);
- final boolean excluded =
- mSystemGestures.currentGestureStartedInRegion(excludedRegion);
+ final boolean allowSideSwipe = mNavigationBarAlwaysShowOnSideGesture &&
+ !mSystemGestures.currentGestureStartedInRegion(excludedRegion);
if (mNavigationBar != null && (mNavigationBarPosition == NAV_BAR_LEFT
- || !excluded && mNavigationBarAlwaysShowOnSideGesture)) {
+ || allowSideSwipe)) {
requestTransientBars(mNavigationBar);
}
- checkAltBarSwipeForTransientBars(ALT_BAR_LEFT);
+ checkAltBarSwipeForTransientBars(ALT_BAR_LEFT, allowSideSwipe);
}
excludedRegion.recycle();
}
@@ -592,7 +620,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();
@@ -615,6 +644,7 @@ public class DisplayPolicy {
}
};
displayContent.mAppTransition.registerListenerLocked(mAppTransitionListener);
+ displayContent.mTransitionController.registerLegacyListener(mAppTransitionListener);
mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext, looper,
mService.mVrModeEnabled);
@@ -654,17 +684,19 @@ public class DisplayPolicy {
mHandler.post(mGestureNavigationSettingsObserver::register);
}
- private void checkAltBarSwipeForTransientBars(@WindowManagerPolicy.AltBarPosition int pos) {
- if (mStatusBarAlt != null && mStatusBarAltPosition == pos) {
+ private void checkAltBarSwipeForTransientBars(@WindowManagerPolicy.AltBarPosition int pos,
+ boolean allowForAllPositions) {
+ if (mStatusBarAlt != null && (mStatusBarAltPosition == pos || allowForAllPositions)) {
requestTransientBars(mStatusBarAlt);
}
- if (mNavigationBarAlt != null && mNavigationBarAltPosition == pos) {
+ if (mNavigationBarAlt != null
+ && (mNavigationBarAltPosition == pos || allowForAllPositions)) {
requestTransientBars(mNavigationBarAlt);
}
- if (mClimateBarAlt != null && mClimateBarAltPosition == pos) {
+ if (mClimateBarAlt != null && (mClimateBarAltPosition == pos || allowForAllPositions)) {
requestTransientBars(mClimateBarAlt);
}
- if (mExtraNavBarAlt != null && mExtraNavBarAltPosition == pos) {
+ if (mExtraNavBarAlt != null && (mExtraNavBarAltPosition == pos || allowForAllPositions)) {
requestTransientBars(mExtraNavBarAlt);
}
}
@@ -869,15 +901,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
@@ -930,6 +953,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:
@@ -1073,15 +1110,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) -> {
@@ -1104,18 +1140,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);
+ }
}
},
@@ -1158,6 +1198,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;
@@ -1176,7 +1222,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;
@@ -1251,9 +1305,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);
@@ -1267,8 +1318,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() {
@@ -1403,7 +1468,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;
}
@@ -1438,13 +1503,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);
}
@@ -1455,15 +1537,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));
+ }
}
}
@@ -1482,13 +1600,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
@@ -1533,18 +1649,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);
@@ -1553,6 +1669,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;
@@ -1574,12 +1700,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;
}
@@ -1587,7 +1713,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;
@@ -1595,7 +1721,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;
@@ -1606,7 +1732,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) {
@@ -1706,7 +1838,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() {
@@ -1722,12 +1864,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;
@@ -1746,20 +1889,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, mNavigationBar);
+ 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;
}
@@ -1772,68 +1917,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;
}
}
@@ -1897,11 +2034,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;
@@ -2004,10 +2137,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 {
@@ -2138,18 +2272,47 @@ 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];
+ }
}
}
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) {
@@ -2169,7 +2332,7 @@ public class DisplayPolicy {
if (hasNavigationBar()) {
final int navBarPosition = navigationBarPosition(fullWidth, fullHeight, rotation);
if (navBarPosition == NAV_BAR_LEFT || navBarPosition == NAV_BAR_RIGHT) {
- width -= getNavigationBarWidth(rotation, uiMode);
+ width -= getNavigationBarWidth(rotation, uiMode, navBarPosition);
}
}
if (displayCutout != null) {
@@ -2179,10 +2342,21 @@ public class DisplayPolicy {
}
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];
+ }
}
}
@@ -2199,10 +2373,17 @@ 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];
+ }
}
}
@@ -2267,7 +2448,7 @@ public class DisplayPolicy {
*/
float getWindowCornerRadius() {
return mDisplayContent.getDisplay().getType() == TYPE_INTERNAL
- ? ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources()) : 0f;
+ ? ScreenDecorationsUtils.getWindowCornerRadius(mContext) : 0f;
}
boolean isShowingDreamLw() {
@@ -2323,9 +2504,9 @@ public class DisplayPolicy {
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);
}
}
@@ -2351,6 +2532,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;
@@ -2385,18 +2577,13 @@ 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) {
@@ -2472,21 +2659,18 @@ 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() {
// 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
@@ -2502,86 +2686,63 @@ 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) {
@@ -2592,10 +2753,18 @@ public class DisplayPolicy {
: 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.
@@ -2604,71 +2773,59 @@ 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 || navColorWin.isDimming()
+ || !isLightBarAllowed(navColorWin, ITYPE_NAVIGATION_BAR)) {
+ // 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();
@@ -2714,7 +2871,24 @@ public class DisplayPolicy {
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;
}
/**
@@ -2749,17 +2923,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;
}
@@ -2770,53 +2946,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;
}
@@ -2856,7 +3014,7 @@ public class DisplayPolicy {
return;
}
mPendingPanicGestureUptime = SystemClock.uptimeMillis();
- updateSystemUiVisibilityLw();
+ updateSystemBarAttributes();
}
}
};
@@ -2917,6 +3075,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=");
@@ -2984,14 +3143,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);
@@ -3030,7 +3202,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 |=
@@ -3074,13 +3246,17 @@ public class DisplayPolicy {
}
@VisibleForTesting
- static boolean isOverlappingWithNavBar(WindowState targetWindow, WindowState navBarWindow) {
+ static boolean isOverlappingWithNavBar(@NonNull WindowState targetWindow,
+ WindowState navBarWindow) {
if (navBarWindow == null || !navBarWindow.isVisible()
|| targetWindow.mActivityRecord == null || !targetWindow.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 navigation bar by its container's bounds.
+ return Rect.intersects(targetWindow.isDimming()
+ ? targetWindow.getBounds() : targetWindow.getFrame(), navBarWindow.getFrame());
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index c9db14de507c..34e81498b1c3 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;
@@ -414,7 +413,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) {
@@ -443,7 +442,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 */)) {
@@ -451,6 +452,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;
}
}
@@ -492,12 +494,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);
@@ -511,11 +507,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.
@@ -591,17 +586,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();
diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
index b627b33c036e..4141090f7fa0 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import android.content.res.Configuration;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.util.IntArray;
import android.view.IDisplayWindowListener;
/**
@@ -28,23 +29,20 @@ import android.view.IDisplayWindowListener;
class DisplayWindowListenerController {
RemoteCallbackList<IDisplayWindowListener> mDisplayListeners = new RemoteCallbackList<>();
-// private final ArrayList<DisplayContainerListener> mDisplayListeners = new ArrayList<>();
private final WindowManagerService mService;
DisplayWindowListenerController(WindowManagerService service) {
mService = service;
}
- void registerListener(IDisplayWindowListener listener) {
+ int[] registerListener(IDisplayWindowListener listener) {
synchronized (mService.mGlobalLock) {
mDisplayListeners.register(listener);
- try {
- for (int i = 0; i < mService.mAtmService.mRootWindowContainer.getChildCount();
- ++i) {
- DisplayContent d = mService.mAtmService.mRootWindowContainer.getChildAt(i);
- listener.onDisplayAdded(d.mDisplayId);
- }
- } catch (RemoteException e) { }
+ final IntArray displayIds = new IntArray();
+ mService.mAtmService.mRootWindowContainer.forAllDisplays((displayContent) -> {
+ displayIds.add(displayContent.mDisplayId);
+ });
+ return displayIds.toArray();
}
}
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..cddb1e7edb3b 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,55 @@ 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)) {
+ // 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 +148,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 +158,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 +211,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 +258,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..1e7b676fbfe4 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -48,6 +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 static java.lang.Integer.MAX_VALUE;
+
+import android.annotation.Nullable;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Handler;
@@ -67,6 +70,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 +105,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 +292,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());
@@ -308,7 +322,10 @@ final class InputMonitor {
boolean useSurfaceCrop = false;
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
@@ -393,6 +410,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 +434,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 +537,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 +579,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 +600,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..3d19f54ad132 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);
}
@@ -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());
+ }
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..d202587bd306 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;
@@ -163,8 +163,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 +268,7 @@ class InsetsSourceProvider {
&& mWin.okToDisplay()) {
mWin.applyWithNextDraw(mSetLeashPositionConsumer);
} else {
- mSetLeashPositionConsumer.accept(mWin.getPendingTransaction());
+ mSetLeashPositionConsumer.accept(mWin.getSyncTransaction());
}
}
if (mServerVisible && !mLastSourceFrame.equals(mSource.getFrame())) {
@@ -307,11 +309,6 @@ class InsetsSourceProvider {
// 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 +332,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 +345,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 +378,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 +394,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,14 +535,14 @@ 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);
@@ -557,7 +558,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 cf0f973fa7db..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(
@@ -190,8 +194,7 @@ class KeyguardController {
if (keyguardChanged) {
// Irrelevant to AOD.
- dismissMultiWindowModeForTaskIfNeeded(null /* currentTaskControllsingOcclusion */,
- false /* turningScreenOn */);
+ dismissMultiWindowModeForTaskIfNeeded(null /* currentTaskControllsingOcclusion */);
mKeyguardGoingAway = false;
if (keyguardShowing) {
mDismissalRequested = false;
@@ -203,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());
+ }
}
/**
@@ -224,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)
@@ -265,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;
}
@@ -334,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;
@@ -355,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;
@@ -366,25 +385,18 @@ 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 {
mService.continueWindowLayout();
}
}
+ dismissMultiWindowModeForTaskIfNeeded(topActivity != null
+ ? topActivity.getRootTask() : null);
}
/**
@@ -410,21 +422,6 @@ class KeyguardController {
}
}
- /**
- * Called when somebody wants to turn screen on.
- */
- private void handleTurnScreenOn(int displayId) {
- if (displayId != DEFAULT_DISPLAY) {
- return;
- }
-
- mTaskSupervisor.wakeUp("handleTurnScreenOn");
- if (mKeyguardShowing && canDismissKeyguard()) {
- mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
- mDismissalRequested = true;
- }
- }
-
boolean isDisplayOccluded(int displayId) {
return getDisplayState(displayId).mOccluded;
}
@@ -438,11 +435,9 @@ class KeyguardController {
}
private void dismissMultiWindowModeForTaskIfNeeded(
- @Nullable Task currentTaskControllingOcclusion, boolean turningScreenOn) {
- // If turningScreenOn is true, it means that the visibility state has changed from
- // currentTaskControllingOcclusion and we should update windowing mode.
+ @Nullable Task currentTaskControllingOcclusion) {
// TODO(b/113840485): Handle docked stack for individual display.
- if (!turningScreenOn && (!mKeyguardShowing || !isDisplayOccluded(DEFAULT_DISPLAY))) {
+ if (!mKeyguardShowing || !isDisplayOccluded(DEFAULT_DISPLAY)) {
return;
}
@@ -581,26 +576,17 @@ class KeyguardController {
&& controller.mWindowManager.isKeyguardSecure(
controller.mService.getCurrentUserId());
- boolean occludingChange = false;
- boolean turningScreenOn = false;
if (mTopTurnScreenOnActivity != lastTurnScreenOnActivity
&& mTopTurnScreenOnActivity != null
&& !mService.mWindowManager.mPowerManager.isInteractive()
- && (mRequestDismissKeyguard || occludedByActivity
- || controller.canDismissKeyguard())) {
- turningScreenOn = true;
- controller.handleTurnScreenOn(mDisplayId);
+ && (mRequestDismissKeyguard || occludedByActivity)) {
+ controller.mTaskSupervisor.wakeUp("handleTurnScreenOn");
mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
}
if (lastOccluded != mOccluded) {
- occludingChange = true;
controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
}
-
- if (occludingChange || turningScreenOn) {
- controller.dismissMultiWindowModeForTaskIfNeeded(task, turningScreenOn);
- }
}
/**
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..cbb473c10c6d 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;
@@ -54,6 +54,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 +85,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 +106,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 +151,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 +172,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
+ * 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,12 +205,48 @@ 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;
}
/**
@@ -149,6 +258,19 @@ final class LetterboxConfiguration {
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 +300,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 +328,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}.
*/
@@ -197,12 +362,12 @@ final class LetterboxConfiguration {
* 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).
*/
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 +376,129 @@ 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} or via an ADB
+ * command.
+ */
+ @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..7d073573835f 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.*/
@@ -138,7 +154,8 @@ final class LetterboxUiController {
this::getLetterboxBackgroundColor,
this::hasWallpaperBackgroudForLetterbox,
this::getLetterboxWallpaperBlurRadius,
- this::getLetterboxWallpaperDarkScrimAlpha);
+ this::getLetterboxWallpaperDarkScrimAlpha,
+ this::handleDoubleTap);
mLetterbox.attachInput(w);
}
mActivityRecord.getPosition(mTmpPoint);
@@ -158,6 +175,85 @@ final class LetterboxUiController {
}
}
+ 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()
@@ -219,21 +315,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 +388,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 +431,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 +445,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 +456,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/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..9f28509c56bd 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;
@@ -138,14 +139,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..081a53e7bd05 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -21,6 +21,7 @@ import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
import android.annotation.NonNull;
import android.os.Environment;
+import android.os.LocaleList;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
@@ -54,12 +55,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 +75,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 +104,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 +125,9 @@ public class PackageConfigPersister {
case ATTR_NIGHT_MODE:
nightMode = Integer.parseInt(attrValue);
break;
+ case ATTR_LOCALES:
+ locales = LocaleList.forLanguageTags(attrValue);
+ break;
}
}
}
@@ -130,6 +138,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 +164,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 +196,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 +209,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 +244,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 +272,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;
@@ -256,7 +309,7 @@ public class PackageConfigPersister {
@Override
public String toString() {
return "PackageConfigRecord package name: " + mName + " userId " + mUserId
- + " nightMode " + mNightMode;
+ + " nightMode " + mNightMode + " locales " + mLocales;
}
}
@@ -369,7 +422,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..b4963c5b9f1c 100644
--- a/services/core/java/com/android/server/wm/PinnedTaskController.java
+++ b/services/core/java/com/android/server/wm/PinnedTaskController.java
@@ -211,7 +211,9 @@ class PinnedTaskController {
}
mFreezingTaskConfig = true;
mDestRotatedBounds = new Rect(bounds);
- continueOrientationChange();
+ if (!mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
+ continueOrientationChange();
+ }
}
/**
@@ -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/PossibleDisplayInfoMapper.java b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
new file mode 100644
index 000000000000..11a27c593d9e
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
@@ -0,0 +1,138 @@
+/*
+ * 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.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_270;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.DisplayInfo;
+import android.view.Surface;
+
+import java.util.Set;
+
+/**
+ * Maintains a map of possible {@link DisplayInfo} for displays and states that may be encountered
+ * on a device. This is not guaranteed to include all possible device states for all displays.
+ *
+ * By 'possible', this class only handles device states for displays and display groups it is
+ * currently aware of. It can not handle all eventual states the system may enter, for example, if
+ * an external display is added, or a new display is added to the group.
+ */
+public class PossibleDisplayInfoMapper {
+ private static final String TAG = "PossibleDisplayInfoMapper";
+ private static final boolean DEBUG = false;
+
+ private final DisplayManagerInternal mDisplayManagerInternal;
+
+ /**
+ * Map of all logical displays, indexed by logical display id.
+ * Each logical display has multiple entries, one for each possible rotation and device
+ * state.
+ *
+ * Emptied and re-calculated when a display is added, removed, or changed.
+ */
+ private final SparseArray<Set<DisplayInfo>> mDisplayInfos = new SparseArray<>();
+
+ PossibleDisplayInfoMapper(DisplayManagerInternal displayManagerInternal) {
+ mDisplayManagerInternal = displayManagerInternal;
+ }
+
+
+ /**
+ * Returns, for the given displayId, a set of display infos. Set contains the possible rotations
+ * for each supported device state.
+ */
+ public Set<DisplayInfo> getPossibleDisplayInfos(int displayId) {
+ // Update display infos before returning, since any cached values would have been removed
+ // in response to any display event. This model avoids re-computing the cache for every
+ // display change event (which occurs extremely frequently in the normal usage of the
+ // device).
+ updatePossibleDisplayInfos(displayId);
+ if (!mDisplayInfos.contains(displayId)) {
+ return new ArraySet<>();
+ }
+ return Set.copyOf(mDisplayInfos.get(displayId));
+ }
+
+ /**
+ * Updates the possible {@link DisplayInfo}s for the given display, by calculating the
+ * DisplayInfo for each rotation across supported device states.
+ */
+ public void updatePossibleDisplayInfos(int displayId) {
+ Set<DisplayInfo> displayInfos = mDisplayManagerInternal.getPossibleDisplayInfo(displayId);
+ if (DEBUG) {
+ Slog.v(TAG, "updatePossibleDisplayInfos, calculate rotations for given DisplayInfo "
+ + displayInfos.size() + " on display " + displayId);
+ }
+ updateDisplayInfos(displayInfos);
+ }
+
+ /**
+ * For the given displayId, removes all possible {@link DisplayInfo}.
+ */
+ public void removePossibleDisplayInfos(int displayId) {
+ if (DEBUG && mDisplayInfos.get(displayId) != null) {
+ Slog.v(TAG, "onDisplayRemoved, remove all DisplayInfo (" + mDisplayInfos.get(
+ displayId).size() + ") with id " + displayId);
+ }
+ mDisplayInfos.remove(displayId);
+ }
+
+ private void updateDisplayInfos(Set<DisplayInfo> displayInfos) {
+ // Empty out cache before re-computing.
+ mDisplayInfos.clear();
+ DisplayInfo[] originalDisplayInfos = new DisplayInfo[displayInfos.size()];
+ displayInfos.toArray(originalDisplayInfos);
+ // Iterate over each logical display layout for the current state.
+ Set<DisplayInfo> rotatedDisplayInfos;
+ for (DisplayInfo di : originalDisplayInfos) {
+ rotatedDisplayInfos = new ArraySet<>();
+ // Calculate all possible rotations for each logical display.
+ for (int rotation = ROTATION_0; rotation <= ROTATION_270; rotation++) {
+ rotatedDisplayInfos.add(applyRotation(di, rotation));
+ }
+ // Combine all results under the logical display id.
+ Set<DisplayInfo> priorDisplayInfos = mDisplayInfos.get(di.displayId, new ArraySet<>());
+ priorDisplayInfos.addAll(rotatedDisplayInfos);
+ mDisplayInfos.put(di.displayId, priorDisplayInfos);
+ }
+ }
+
+ private static DisplayInfo applyRotation(DisplayInfo displayInfo,
+ @Surface.Rotation int rotation) {
+ DisplayInfo updatedDisplayInfo = new DisplayInfo();
+ updatedDisplayInfo.copyFrom(displayInfo);
+ // Apply rotations before updating width and height
+ updatedDisplayInfo.roundedCorners = updatedDisplayInfo.roundedCorners.rotate(rotation,
+ updatedDisplayInfo.logicalWidth, updatedDisplayInfo.logicalHeight);
+ updatedDisplayInfo.displayCutout =
+ DisplayContent.calculateDisplayCutoutForRotationAndDisplaySizeUncached(
+ updatedDisplayInfo.displayCutout, rotation, updatedDisplayInfo.logicalWidth,
+ updatedDisplayInfo.logicalHeight).getDisplayCutout();
+
+ updatedDisplayInfo.rotation = rotation;
+ final int naturalWidth = updatedDisplayInfo.getNaturalWidth();
+ final int naturalHeight = updatedDisplayInfo.getNaturalHeight();
+ updatedDisplayInfo.logicalWidth = naturalWidth;
+ updatedDisplayInfo.logicalHeight = naturalHeight;
+ return updatedDisplayInfo;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 455f568d7523..dca0bbda78cf 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1108,13 +1108,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) {
@@ -1344,7 +1346,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()) {
@@ -1390,6 +1393,11 @@ class RecentTasks {
return false;
}
+ // Ignore the task if it is a embedded task
+ if (task.isEmbedded()) {
+ return false;
+ }
+
return true;
}
@@ -1482,14 +1490,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
@@ -1511,6 +1519,7 @@ class RecentTasks {
}
}
notifyTaskPersisterLocked(removedTask, false /* flush */);
+ return removeIndex;
}
/**
@@ -1518,11 +1527,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 b1bdc11ecee4..ee05523b3f2a 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
@@ -164,6 +170,13 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "startRecentsActivity(): intent=%s", mTargetIntent);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RecentsAnimation#startRecentsActivity");
+ // Cancel any existing recents animation running synchronously (do not hold the
+ // WM lock) before starting the newly requested recents animation as they can not coexist
+ if (mWindowManager.getRecentsAnimationController() != null) {
+ mWindowManager.getRecentsAnimationController().forceCancelAnimation(
+ REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity");
+ }
+
// If the activity is associated with the root recents task, then try and get that first
Task targetRootTask = mDefaultTaskDisplayArea.getRootTask(WINDOWING_MODE_UNDEFINED,
mTargetActivityType);
@@ -237,12 +250,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
targetActivity.intent.replaceExtras(mTargetIntent);
// Fetch all the surface controls and pass them to the client to get the animation
- // started. Cancel any existing recents animation running synchronously (do not hold the
- // WM lock)
- if (mWindowManager.getRecentsAnimationController() != null) {
- mWindowManager.getRecentsAnimationController().forceCancelAnimation(
- REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity");
- }
+ // started
mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
this, mDefaultTaskDisplayArea.getDisplayId(),
mTaskSupervisor.mRecentTasks.getRecentTaskIds(), targetActivity);
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index e346e3ec7db9..67b3ec8c2b90 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -123,6 +123,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;
@@ -168,8 +169,9 @@ public class RecentsAnimationController implements DeathRecipient {
*/
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;
}
@@ -581,7 +583,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);
@@ -792,7 +794,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
@@ -921,6 +923,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.
*/
@@ -1007,6 +1027,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 +1126,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) {
@@ -1192,7 +1223,7 @@ 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
@@ -1201,7 +1232,8 @@ public class RecentsAnimationController implements DeathRecipient {
!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 +1321,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 52da4b80a75d..b1eca9d5d4e4 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,17 @@ 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.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,19 +73,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.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;
@@ -365,8 +366,26 @@ 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);
@@ -486,6 +505,7 @@ 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;
@@ -509,6 +529,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
mTaskSupervisor.updateTopResumedActivityIfNeeded();
}
+ @Override
+ boolean isAttached() {
+ return true;
+ }
+
/**
* Called when DisplayWindowSettings values may change.
*/
@@ -817,8 +842,7 @@ 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);
}
@@ -855,6 +879,7 @@ 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();
@@ -867,10 +892,10 @@ 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",
@@ -929,12 +954,12 @@ 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();
@@ -1230,6 +1255,11 @@ 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;
@@ -1276,7 +1306,7 @@ 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);
}
@@ -1865,7 +1895,7 @@ 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;
}
@@ -1887,11 +1917,11 @@ 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;
@@ -1917,7 +1947,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
return;
}
- if (rootTask.getVisibility(null /*starting*/) == TASK_VISIBILITY_INVISIBLE) {
+ if (rootTask.getVisibility(null /* starting */)
+ == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
return;
}
@@ -1941,7 +1972,7 @@ 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;
}
@@ -1974,7 +2005,8 @@ 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;
}
@@ -2189,7 +2221,7 @@ 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
@@ -2394,7 +2426,9 @@ 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)) {
@@ -2553,6 +2587,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
if (mService.isBooted() || mService.isBooting()) {
startSystemDecorations(display.mDisplayContent);
}
+ // Drop any cached DisplayInfos associated with this display id - the values are now
+ // out of date given this display added event.
+ mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId);
}
}
@@ -2573,8 +2610,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
if (displayContent == null) {
return;
}
-
displayContent.remove();
+ mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId);
}
}
@@ -2586,6 +2623,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
if (displayContent != null) {
displayContent.onDisplayChanged();
}
+ // Drop any cached DisplayInfos associated with this display id - the values are now
+ // out of date given this display changed event.
+ mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId);
}
}
@@ -2660,6 +2700,7 @@ 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);
}
@@ -2678,6 +2719,8 @@ 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();
@@ -2771,8 +2814,8 @@ 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);
}
@@ -2806,7 +2849,6 @@ 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");
@@ -3359,7 +3401,7 @@ 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);
@@ -3374,7 +3416,7 @@ 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;
@@ -3392,7 +3434,7 @@ 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());
@@ -3435,14 +3477,6 @@ 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);
}
@@ -3522,11 +3556,6 @@ 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) {
@@ -3535,14 +3564,7 @@ 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};
@@ -3558,15 +3580,32 @@ 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(
@@ -3693,6 +3732,10 @@ 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/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 58363f2316ea..d1460f41cad6 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;
@@ -91,13 +90,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 final Context mContext;
private final DisplayContent mDisplayContent;
private final float[] mTmpFloats = new float[9];
@@ -423,7 +415,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);
}
@@ -737,7 +729,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..8c056b2a43b3 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.
*/
@@ -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..d712bbf0fdef 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,16 @@ class SurfaceAnimator {
return;
}
mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
+ if (snapshotAnim != null) {
+ mSnapshot = freezer.takeSnapshotForAnimation();
+ 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 +337,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 +356,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 +376,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 +398,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 +633,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..c667db86e410 100644
--- a/services/core/java/com/android/server/wm/SurfaceFreezer.java
+++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java
@@ -17,22 +17,20 @@
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.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.
@@ -53,7 +51,8 @@ class SurfaceFreezer {
private final Freezable mAnimatable;
private final WindowManagerService mWmService;
- private SurfaceControl mLeash;
+ @VisibleForTesting
+ SurfaceControl mLeash;
Snapshot mSnapshot = null;
final Rect mFreezeBounds = new Rect();
@@ -70,17 +69,21 @@ 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) {
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(
freezeTarget, startBounds);
@@ -89,7 +92,7 @@ class SurfaceFreezer {
if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
return;
}
- mSnapshot = new Snapshot(mWmService.mSurfaceFactory, t, screenshotBuffer, mLeash);
+ mSnapshot = new Snapshot(t, screenshotBuffer, mLeash);
}
}
@@ -104,12 +107,25 @@ 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) {
if (mSnapshot != null) {
mSnapshot.cancelAnimation(t, false /* restarting */);
+ mSnapshot = null;
}
if (mLeash == null) {
return;
@@ -117,12 +133,24 @@ class SurfaceFreezer {
SurfaceControl leash = mLeash;
mLeash = null;
final boolean scheduleAnim = SurfaceAnimator.removeLeash(t, mAnimatable, leash,
- false /* destroy */);
+ true /* destroy */);
if (scheduleAnim) {
mWmService.scheduleAnimationLocked();
}
}
+ 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;
}
@@ -146,13 +174,12 @@ class SurfaceFreezer {
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
@@ -194,19 +221,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 +241,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);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index c068fd1a5f6e..ba2da06efab4 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;
@@ -194,6 +164,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,7 +179,6 @@ import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -244,23 +214,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;
@@ -303,10 +271,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;
@@ -315,7 +279,6 @@ class Task extends WindowContainer<WindowContainer> {
// code.
static final int PERSIST_TASK_VERSION = 1;
- static final int INVALID_MIN_SIZE = -1;
private float mShadowRadius = 0;
/**
@@ -335,36 +298,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
- }
-
// The topmost Activity passed to convertToTranslucent(). When non-null it means we are
// waiting for all Activities in mUndrawnActivitiesBelowTopTranslucent to be removed as they
// are drawn. When the last member of mUndrawnActivitiesBelowTopTranslucent is removed the
@@ -446,7 +379,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.
@@ -458,21 +390,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
@@ -497,10 +420,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. */
@@ -561,7 +480,6 @@ class Task extends WindowContainer<WindowContainer> {
private Dimmer mDimmer = new Dimmer(this);
private final Rect mTmpDimBoundsRect = new Rect();
- private final Point mLastSurfaceSize = new Point();
/** @see #setCanAffectSystemUiFlags */
private boolean mCanAffectSystemUiFlags = true;
@@ -571,29 +489,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. */
@@ -641,121 +536,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 {
@@ -789,7 +569,9 @@ class Task extends WindowContainer<WindowContainer> {
mRoot = r;
// Only end search if we are ignore relinquishing identity or we are not relinquishing.
- return ignoreRelinquishIdentity || (r.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0;
+ return ignoreRelinquishIdentity
+ || mNeverRelinquishIdentity
+ || (r.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0;
}
}
@@ -809,28 +591,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;
@@ -858,11 +618,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;
@@ -875,7 +632,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;
@@ -913,7 +669,6 @@ class Task extends WindowContainer<WindowContainer> {
mHandler = new ActivityTaskHandler(mTaskSupervisor.mLooper);
mCurrentUser = mAtmService.mAmInternal.getCurrentUserId();
- mCreatedByOrganizer = _createdByOrganizer;
mLaunchCookie = _launchCookie;
mDeferTaskAppear = _deferTaskAppear;
mRemoveWithTaskOrganizer = _removeWithTaskOrganizer;
@@ -942,13 +697,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;
@@ -958,7 +713,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);
@@ -1246,7 +1001,6 @@ class Task extends WindowContainer<WindowContainer> {
mCallingPackage = r.launchedFromPackage;
mCallingFeatureId = r.launchedFromFeatureId;
setIntent(intent != null ? intent : r.intent, info != null ? info : r.info);
- return;
}
setLockTaskAuth(r);
}
@@ -1255,7 +1009,14 @@ class Task extends WindowContainer<WindowContainer> {
private void setIntent(Intent _intent, ActivityInfo info) {
if (!isLeafTask()) return;
- mNeverRelinquishIdentity = (info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0;
+ if (info.applicationInfo.uid == Process.SYSTEM_UID
+ || info.applicationInfo.isSystemApp()) {
+ // Only allow the apps that pre-installed on the system image to apply
+ // relinquishTaskIdentity
+ mNeverRelinquishIdentity = (info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0;
+ } else {
+ mNeverRelinquishIdentity = true;
+ }
affinity = info.taskAffinity;
if (intent == null) {
// If this task already has an intent associated with it, don't set the root
@@ -1379,11 +1140,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;
@@ -1424,7 +1185,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,
@@ -1442,6 +1203,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) {
@@ -1466,64 +1233,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) {
@@ -1562,11 +1327,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;
@@ -1612,14 +1372,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;
@@ -1646,14 +1398,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.
@@ -1675,22 +1419,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);
@@ -1706,10 +1435,17 @@ 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 hadChild, int activityType, ActivityRecord r) {
+ warnForNonLeafTask("onDescendantActivityAdded");
// Only set this based on the first activity
if (!hadChild) {
@@ -1736,10 +1472,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");
@@ -1759,7 +1491,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
@@ -1789,7 +1521,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);
@@ -1821,11 +1556,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() {
@@ -1849,9 +1585,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);
+ }
});
}
}
@@ -1985,32 +1727,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.
*
@@ -2146,60 +1862,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);
@@ -2208,32 +1870,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.
@@ -2281,19 +1917,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();
@@ -2352,14 +1986,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();
@@ -2378,14 +2005,154 @@ 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) {
@@ -2526,400 +2293,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;
@@ -2968,24 +2341,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() {
@@ -3098,12 +2458,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;
}
@@ -3152,17 +2512,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;
}
}
@@ -3173,6 +2532,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);
}
@@ -3193,25 +2558,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) {
@@ -3554,18 +2906,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
@@ -3576,70 +2916,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
@@ -3647,21 +2972,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.
@@ -3691,36 +3001,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);
@@ -3775,16 +3060,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);
}
@@ -3896,6 +3171,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;
@@ -4015,19 +3325,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--) {
@@ -4066,6 +3366,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.
@@ -4125,6 +3428,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;
}
@@ -4135,9 +3439,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() {
@@ -4177,6 +3481,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.
@@ -4204,184 +3509,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;
@@ -4492,9 +3619,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
@@ -4511,6 +3635,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=");
@@ -4564,7 +3690,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
@@ -4968,10 +4094,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;
@@ -5124,9 +4246,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;
}
@@ -5144,11 +4265,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;
}
/**
@@ -5202,13 +4323,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() {
@@ -5307,9 +4427,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;
}
@@ -5454,7 +4572,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();
}
@@ -5462,8 +4581,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() {
@@ -5595,19 +4716,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 */);
@@ -5626,306 +4734,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;
- }
-
- 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;
+ final int[] sleepInProgress = {0};
+ forAllLeafTasksAndLeafTaskFragments(taskFragment -> {
+ if (!taskFragment.sleepIfPossible(shuttingDown)) {
+ sleepInProgress[0]++;
}
- // 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();
- }
- }
- }
-
- 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() {
@@ -5948,10 +4763,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,
@@ -5967,25 +4782,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()) {
@@ -6056,25 +4868,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.
*
@@ -6115,7 +4908,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;
}
@@ -6162,383 +4956,26 @@ 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 (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming " + next);
-
- 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 (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 {
- 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 {
- 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.isFocusableAndVisible()) {
+ // No need to resume activity in TaskFragment that is not visible.
+ 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];
}
/**
@@ -6572,7 +5009,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();
@@ -6618,7 +5055,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);
// The transition animation and starting window are not needed if {@code allowMoveToFront}
// is false, because the activity won't be visible.
@@ -6679,21 +5115,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 {
@@ -6727,10 +5160,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
@@ -6788,7 +5217,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 */);
@@ -6846,18 +5274,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();
@@ -7080,7 +5496,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());
@@ -7168,7 +5584,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);
@@ -7194,13 +5609,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) {
@@ -7254,114 +5662,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) {
@@ -7747,15 +6073,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();
}
@@ -7764,6 +6081,7 @@ class Task extends WindowContainer<WindowContainer> {
return mAnimatingActivityRegistry;
}
+ @Override
void executeAppTransition(ActivityOptions options) {
mDisplayContent.executeAppTransition();
ActivityOptions.abort(options);
@@ -7786,10 +6104,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();
}
@@ -7808,14 +6122,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());
@@ -7823,11 +6135,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);
@@ -7844,6 +6152,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 ee4c629189dc..f5e7967de410 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -32,13 +32,13 @@ 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.RESUMED;
-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.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -99,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
@@ -132,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<>();
@@ -488,7 +479,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");
}
@@ -800,10 +791,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;
}
}
@@ -870,36 +863,10 @@ 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);
+ adjustRootTaskLayer(t, mTmpAlwaysOnTopChildren, layer);
+ t.setLayer(mSplitScreenDividerAnchor, SPLIT_DIVIDER_LAYER);
}
/**
@@ -908,38 +875,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;
}
@@ -953,19 +927,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();
@@ -985,42 +946,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;
}
}
@@ -1061,15 +1001,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();
@@ -1158,29 +1095,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)
@@ -1292,7 +1227,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);
@@ -1330,8 +1265,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;
}
@@ -1340,16 +1275,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;
}
@@ -1434,11 +1375,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.
@@ -1465,7 +1406,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;
@@ -1486,7 +1427,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;
}
@@ -1512,18 +1453,30 @@ 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.getVisibility(resuming) != TASK_FRAGMENT_VISIBILITY_VISIBLE
+ || !taskFrag.isTopActivityFocusable())) {
+ if (taskFrag.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) {
+ someActivityPaused[0]++;
+ }
+ }
+ }, true /* traverseTopToBottom */);
}, true /* traverseTopToBottom */);
return someActivityPaused[0] > 0;
}
@@ -2150,7 +2103,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..99f6f341e977
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -0,0 +1,2422 @@
+/*
+ * 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.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.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;
+
+ /** 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;
+
+ /**
+ * 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;
+
+ /** 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 other actions or animations.
+ */
+ private boolean mDelayLastActivityRemoval;
+
+ /**
+ * The PID of the organizer that created this TaskFragment. It should be the same as the PID
+ * of {@link android.window.TaskFragmentCreationParams#getOwnerToken()}.
+ * {@link ActivityRecord#INVALID_PID} if this is not an organizer-created TaskFragment.
+ */
+ private int mTaskFragmentOrganizerPid = ActivityRecord.INVALID_PID;
+
+ 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) {
+ if (mAdjacentTaskFragment == taskFragment) {
+ return;
+ }
+ resetAdjacentTaskFragment();
+ if (taskFragment != null) {
+ mAdjacentTaskFragment = taskFragment;
+ taskFragment.setAdjacentTaskFragment(this);
+ }
+ }
+
+ 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 = null;
+ mDelayLastActivityRemoval = false;
+ }
+
+ void setTaskFragmentOrganizer(TaskFragmentOrganizerToken organizer, int pid) {
+ mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder());
+ mTaskFragmentOrganizerPid = pid;
+ }
+
+ /** 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 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) {
+ // 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;
+ 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 (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming " + next);
+
+ 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;
+ 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 {
+ 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 {
+ 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;
+ }
+
+ 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;
+ }
+
+ 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.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.
+ 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 child before we added this one.
+ boolean taskHadChild = task != null && task.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 = task != null ? task.getActivityType() : ACTIVITY_TYPE_UNDEFINED;
+
+ super.addChild(child, index);
+
+ if (isAddingActivity && task != null) {
+ child.asActivityRecord().inHistory = true;
+ task.onDescendantActivityAdded(taskHadChild, 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;
+ }
+
+ return tda.supportsActivityMinWidthHeightMultiWindow(mMinWidth, mMinHeight);
+ }
+
+ 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) {
+ 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);
+ }
+ }
+
+ /**
+ * 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 (mWmService.mDisableTransitionAnimation
+ || mDisplayContent == null
+ || mTaskFragmentOrganizer == null
+ || getSurfaceControl() == null
+ // The change transition will be covered by display.
+ || mDisplayContent.inTransition()
+ || !isVisible()) {
+ 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++) {
+ WindowContainer wc = getChildAt(i);
+ if (mTaskFragmentOrganizerPid != ActivityRecord.INVALID_PID
+ && wc.asActivityRecord() != null
+ && wc.asActivityRecord().getPid() == mTaskFragmentOrganizerPid) {
+ // Only includes Activities that belong to the organizer process for security.
+ childActivities.add(wc.asActivityRecord().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
+ boolean canBeAnimationTarget() {
+ return true;
+ }
+
+ 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..d91165685c63
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -0,0 +1,550 @@
+/*
+ * 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.Nullable;
+import android.content.res.Configuration;
+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.view.SurfaceControl;
+import android.window.ITaskFragmentOrganizer;
+import android.window.ITaskFragmentOrganizerController;
+import android.window.TaskFragmentAppearedInfo;
+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();
+ final SurfaceControl outSurfaceControl = new SurfaceControl(tf.getSurfaceControl(),
+ "TaskFragmentOrganizerController.onTaskFragmentInfoAppeared");
+ try {
+ organizer.onTaskFragmentAppeared(
+ new TaskFragmentAppearedInfo(info, outSurfaceControl));
+ 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;
+
+ 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;
+ }
+
+ void dispatchPendingEvents() {
+ if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()
+ || mPendingTaskFragmentEvents.isEmpty()) {
+ return;
+ }
+ for (int i = 0, n = mPendingTaskFragmentEvents.size(); i < n; i++) {
+ PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i);
+ dispatchEvent(event);
+ }
+ mPendingTaskFragmentEvents.clear();
+ }
+
+ 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);
+ }
+ }
+}
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..ce93f2495c22 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -168,6 +168,9 @@ class TaskSnapshotController {
*/
@VisibleForTesting
void addSkipClosingAppSnapshotTasks(ArraySet<Task> tasks) {
+ if (shouldDisableSnapshots()) {
+ return;
+ }
mSkipClosingAppSnapshotTasks.addAll(tasks);
}
@@ -175,46 +178,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!
@@ -683,7 +689,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/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..a21e4f2fee0d 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -16,24 +16,38 @@
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.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 +55,24 @@ 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();
+
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 +80,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 +118,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,19 +126,28 @@ 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) {
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;
} catch (RemoteException e) {
throw new RuntimeException("Unable to set transition player");
@@ -155,21 +206,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 +252,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 +287,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 +305,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 +333,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);
@@ -261,6 +363,7 @@ class TransitionController {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
mPlayingTransitions.remove(record);
record.finishTransition();
+ mRunningLock.doNotifyLocked();
}
void moveToPlaying(Transition transition) {
@@ -279,4 +382,133 @@ 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 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..26527233aef0 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 (!displayContent.mWallpaperController.isWallpaperVisible()) {
+ 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);
@@ -93,7 +93,7 @@ class WallpaperAnimationAdapter implements AnimationAdapter {
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 +134,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..a68b09e2e307 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,7 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -125,26 +125,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 +175,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 +313,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 +347,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);
@@ -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);
@@ -1389,6 +1383,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
}
+ /** Returns whether the window should be shown for current user. */
boolean showToCurrentUser() {
return true;
}
@@ -1685,6 +1680,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 +1744,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 +1779,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 +2293,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 +2317,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 +2333,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 +2411,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 +2563,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 +2578,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 +2603,47 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
mSurfaceFreezer.unfreeze(getPendingTransaction());
}
+ /**
+ * 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
@@ -2567,17 +2656,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) {
@@ -2668,6 +2746,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 +2822,32 @@ 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());
}
- 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 +2858,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,6 +2922,14 @@ 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();
@@ -2885,6 +2969,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 +2979,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 +3139,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 +3214,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 +3261,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 +3431,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 +3499,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 +3535,53 @@ 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 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/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 e3ff9e75dbd8..d17c9dd8de3c 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;
@@ -84,8 +86,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;
@@ -149,6 +153,7 @@ import android.app.IActivityTaskManager;
import android.app.IAssistDataReceiver;
import android.app.WindowConfiguration;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -158,6 +163,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.TestUtilityService;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -167,8 +173,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;
@@ -220,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;
@@ -248,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;
@@ -257,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;
@@ -337,27 +348,6 @@ public class WindowManagerService extends IWindowManager.Stub
static final boolean PROFILE_ORIENTATION = false;
- /** 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.
*/
@@ -445,19 +435,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
@@ -465,6 +455,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)
@@ -646,7 +638,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;
@@ -765,6 +757,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;
@@ -1065,6 +1062,10 @@ public class WindowManagerService extends IWindowManager.Stub
final HighRefreshRateDenylist mHighRefreshRateDenylist;
+ // Maintainer of a collection of all possible DisplayInfo for all configurations of the
+ // logical displays.
+ final PossibleDisplayInfoMapper mPossibleDisplayInfoMapper;
+
// If true, only the core apps and services are being launched because the device
// is in a special boot mode, such as being encrypted or waiting for a decryption password.
// For example, when this flag is true, there will be no wallpaper service.
@@ -1234,10 +1235,13 @@ 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);
+ mPossibleDisplayInfoMapper = new PossibleDisplayInfoMapper(mDisplayManagerInternal);
mSurfaceControlFactory = surfaceControlFactory;
mTransactionFactory = transactionFactory;
@@ -1390,6 +1394,7 @@ public class WindowManagerService extends IWindowManager.Stub
mStartingSurfaceController = new StartingSurfaceController(this);
mBlurController = new BlurController(mContext, mPowerManager);
+ mAccessibilityController = new AccessibilityController(this);
}
DisplayAreaPolicy.Provider getDisplayAreaPolicyProvider() {
@@ -1455,7 +1460,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);
@@ -1677,7 +1682,7 @@ public class WindowManagerService extends IWindowManager.Stub
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
- win.updateRequestedVisibility(requestedVisibility);
+ win.setRequestedVisibilities(requestedVisibilities);
res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
if (res != ADD_OKAY) {
@@ -1728,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!
@@ -1767,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) {
@@ -1804,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
@@ -2163,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());
}
@@ -2176,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(
@@ -2266,7 +2276,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());
@@ -2461,16 +2471,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 */);
@@ -2478,7 +2494,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()) {
@@ -2524,7 +2540,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);
@@ -2562,7 +2579,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)) {
@@ -2574,20 +2591,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);
}
@@ -3055,7 +3076,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);
}
}
@@ -3784,6 +3805,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
@@ -4049,7 +4078,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) {
@@ -4169,7 +4198,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);
@@ -4181,7 +4210,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 {
@@ -4353,13 +4382,18 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- /** Registers a hierarchy listener that gets callbacks when the hierarchy changes. */
+ /**
+ * Registers a hierarchy listener that gets callbacks when the hierarchy changes. The listener's
+ * onDisplayAdded() will not be called for the displays returned.
+ *
+ * @return the displayIds for the existing displays
+ */
@Override
- public void registerDisplayWindowListener(IDisplayWindowListener listener) {
+ public int[] registerDisplayWindowListener(IDisplayWindowListener listener) {
mAtmService.enforceTaskPermission("registerDisplayWindowListener");
final long ident = Binder.clearCallingIdentity();
try {
- mDisplayNotificationController.registerListener(listener);
+ return mDisplayNotificationController.registerListener(listener);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -4875,6 +4909,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));
@@ -4894,6 +4931,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();
@@ -4934,23 +4974,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);
}
}
@@ -5302,6 +5401,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;
@@ -5309,6 +5409,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;
@@ -5406,6 +5507,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;
@@ -5730,7 +5850,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;
@@ -6324,6 +6446,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);
@@ -6357,7 +6480,7 @@ public class WindowManagerService extends IWindowManager.Stub
mInputManagerCallback.dump(pw, " ");
mTaskSnapshotController.dump(pw, " ");
- if (mAccessibilityController != null) {
+ if (mAccessibilityController.hasCallbacks()) {
mAccessibilityController.dump(pw, " ");
}
@@ -7377,7 +7500,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!");
@@ -7388,7 +7511,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!");
@@ -7399,7 +7522,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!");
@@ -7415,7 +7538,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) {
@@ -7434,16 +7557,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);
}
}
@@ -7451,17 +7565,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);
}
}
@@ -7473,11 +7578,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();
}
}
@@ -7570,6 +7671,7 @@ public class WindowManagerService extends IWindowManager.Stub
public void registerAppTransitionListener(AppTransitionListener listener) {
synchronized (mGlobalLock) {
getDefaultDisplayContentLocked().mAppTransition.registerListenerLocked(listener);
+ mAtmService.getTransitionController().registerLegacyListener(listener);
}
}
@@ -7633,13 +7735,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
@@ -7713,7 +7809,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;
@@ -7872,30 +7968,37 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public String getImeControlTargetNameForLogging(int displayId) {
- synchronized (mGlobalLock) {
- 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) {
+ 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 || 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
@@ -8093,11 +8196,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 {
@@ -8111,13 +8223,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);
}
}
}
@@ -8159,11 +8272,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;
}
@@ -8182,7 +8295,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- mAtmService.setFocusedTask(task.mTaskId);
+ mAtmService.setFocusedTask(task.mTaskId, touchedActivity);
}
/**
@@ -8206,11 +8319,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);
}
@@ -8218,9 +8331,10 @@ 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;
final int sanitizedFlags = flags & (LayoutParams.FLAG_NOT_TOUCHABLE
@@ -8271,12 +8385,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 */
@@ -8419,6 +8533,52 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ @Override
+ public List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ if (packageName == null || !isRecentsComponent(packageName, callingUid)) {
+ Slog.e(TAG, "Unable to verify uid for package " + packageName
+ + " for getPossibleMaximumWindowMetrics");
+ return new ArrayList<>();
+ }
+
+ // Retrieve the DisplayInfo for all possible rotations across all possible display
+ // layouts.
+ return List.copyOf(mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ /**
+ * Returns {@code true} when the calling package is the recents component.
+ */
+ boolean isRecentsComponent(@NonNull String callingPackageName, int callingUid) {
+ String recentsPackage;
+ try {
+ String recentsComponent = mContext.getResources().getString(
+ R.string.config_recentsComponentName);
+ if (recentsComponent == null) {
+ return false;
+ }
+ recentsPackage = ComponentName.unflattenFromString(recentsComponent).getPackageName();
+ } catch (Resources.NotFoundException e) {
+ Slog.e(TAG, "Unable to verify if recents component", e);
+ return false;
+ }
+ try {
+ return callingUid == mContext.getPackageManager().getPackageUid(callingPackageName, 0)
+ && callingPackageName.equals(recentsPackage);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Unable to verify if recents component", e);
+ return false;
+ }
+ }
+
void grantEmbeddedWindowFocus(Session session, IBinder inputToken, boolean grantFocus) {
synchronized (mGlobalLock) {
final EmbeddedWindowController.EmbeddedWindow embeddedWindow =
@@ -8434,10 +8594,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);
@@ -8446,18 +8605,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);
}
}
@@ -8486,24 +8645,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);
}
}
@@ -8555,7 +8714,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;
@@ -8611,4 +8770,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/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index a94fd074ff2e..0f8587c99958 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -19,6 +19,16 @@ package com.android.server.wm;
import static android.os.Build.IS_USER;
import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_REACHABILITY_POSITION_RIGHT;
+
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.ParcelFileDescriptor;
@@ -36,6 +46,8 @@ import com.android.internal.os.ByteTransferPipe;
import com.android.internal.protolog.ProtoLogImpl;
import com.android.server.LocalServices;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
+import com.android.server.wm.LetterboxConfiguration.LetterboxReachabilityPosition;
import java.io.IOException;
import java.io.PrintWriter;
@@ -58,10 +70,12 @@ public class WindowManagerShellCommand extends ShellCommand {
// Internal service impl -- must perform security checks before touching.
private final WindowManagerService mInternal;
+ private final LetterboxConfiguration mLetterboxConfiguration;
public WindowManagerShellCommand(WindowManagerService service) {
mInterface = service;
mInternal = service;
+ mLetterboxConfiguration = service.mLetterboxConfiguration;
}
@Override
@@ -113,6 +127,14 @@ public class WindowManagerShellCommand extends ShellCommand {
return runGetIgnoreOrientationRequest(pw);
case "dump-visible-window-views":
return runDumpVisibleWindowViews(pw);
+ case "set-letterbox-style":
+ return runSetLetterboxStyle(pw);
+ case "get-letterbox-style":
+ return runGetLetterboxStyle(pw);
+ case "reset-letterbox-style":
+ return runResetLetterboxStyle(pw);
+ case "set-sandbox-display-apis":
+ return runSandboxDisplayApis(pw);
case "set-multi-window-config":
return runSetMultiWindowConfig();
case "get-multi-window-config":
@@ -331,6 +353,37 @@ public class WindowManagerShellCommand extends ShellCommand {
return 0;
}
+ /**
+ * Override display size and metrics to reflect the DisplayArea of the calling activity.
+ */
+ private int runSandboxDisplayApis(PrintWriter pw) throws RemoteException {
+ int displayId = Display.DEFAULT_DISPLAY;
+ String arg = getNextArgRequired();
+ if ("-d".equals(arg)) {
+ displayId = Integer.parseInt(getNextArgRequired());
+ arg = getNextArgRequired();
+ }
+
+ final boolean sandboxDisplayApis;
+ switch (arg) {
+ case "true":
+ case "1":
+ sandboxDisplayApis = true;
+ break;
+ case "false":
+ case "0":
+ sandboxDisplayApis = false;
+ break;
+ default:
+ getErrPrintWriter().println("Error: expecting true, 1, false, 0, but we "
+ + "get " + arg);
+ return -1;
+ }
+
+ mInternal.setSandboxDisplayApis(displayId, sandboxDisplayApis);
+ return 0;
+ }
+
private int runDismissKeyguard(PrintWriter pw) throws RemoteException {
mInterface.dismissKeyguard(null /* callback */, null /* message */);
return 0;
@@ -548,6 +601,318 @@ public class WindowManagerShellCommand extends ShellCommand {
return 0;
}
+ private int runSetFixedOrientationLetterboxAspectRatio(PrintWriter pw) throws RemoteException {
+ final float aspectRatio;
+ try {
+ String arg = getNextArgRequired();
+ aspectRatio = Float.parseFloat(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: bad aspect ratio format " + e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: aspect ratio should be provided as an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(aspectRatio);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxActivityCornersRadius(PrintWriter pw) throws RemoteException {
+ final int cornersRadius;
+ try {
+ String arg = getNextArgRequired();
+ cornersRadius = Integer.parseInt(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: bad corners radius format " + e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: corners radius should be provided as an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setLetterboxActivityCornersRadius(cornersRadius);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxBackgroundType(PrintWriter pw) throws RemoteException {
+ @LetterboxBackgroundType final int backgroundType;
+ try {
+ String arg = getNextArgRequired();
+ switch (arg) {
+ case "solid_color":
+ backgroundType = LETTERBOX_BACKGROUND_SOLID_COLOR;
+ break;
+ case "app_color_background":
+ backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+ break;
+ case "app_color_background_floating":
+ backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+ break;
+ case "wallpaper":
+ backgroundType = LETTERBOX_BACKGROUND_WALLPAPER;
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: 'solid_color', 'app_color_background' or "
+ + "'wallpaper' should be provided as an argument");
+ return -1;
+ }
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: 'solid_color', 'app_color_background' or "
+ + "'wallpaper' should be provided as an argument" + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setLetterboxBackgroundType(backgroundType);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxBackgroundColorResource(PrintWriter pw) throws RemoteException {
+ final int colorId;
+ try {
+ String arg = getNextArgRequired();
+ colorId = mInternal.mContext.getResources()
+ .getIdentifier(arg, "color", "com.android.internal");
+ } catch (NotFoundException e) {
+ getErrPrintWriter().println(
+ "Error: color in '@android:color/resource_name' format should be provided as "
+ + "an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setLetterboxBackgroundColorResourceId(colorId);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxBackgroundColor(PrintWriter pw) throws RemoteException {
+ final Color color;
+ try {
+ String arg = getNextArgRequired();
+ color = Color.valueOf(Color.parseColor(arg));
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: color in #RRGGBB format should be provided as "
+ + "an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setLetterboxBackgroundColor(color);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxBackgroundWallpaperBlurRadius(PrintWriter pw)
+ throws RemoteException {
+ final int radius;
+ try {
+ String arg = getNextArgRequired();
+ radius = Integer.parseInt(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: blur radius format " + e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: blur radius should be provided as an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setLetterboxBackgroundWallpaperBlurRadius(radius);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxBackgroundWallpaperDarkScrimAlpha(PrintWriter pw)
+ throws RemoteException {
+ final float alpha;
+ try {
+ String arg = getNextArgRequired();
+ alpha = Float.parseFloat(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: bad alpha format " + e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: alpha should be provided as an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setLetterboxBackgroundWallpaperDarkScrimAlpha(alpha);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxHorizontalPositionMultiplier(PrintWriter pw) throws RemoteException {
+ final float multiplier;
+ try {
+ String arg = getNextArgRequired();
+ multiplier = Float.parseFloat(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: bad multiplier format " + e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: multiplier should be provided as an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxIsReachabilityEnabled(PrintWriter pw) throws RemoteException {
+ String arg = getNextArg();
+ final boolean enabled;
+ switch (arg) {
+ case "true":
+ case "1":
+ enabled = true;
+ break;
+ case "false":
+ case "0":
+ enabled = false;
+ break;
+ default:
+ getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
+ return -1;
+ }
+
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setIsReachabilityEnabled(enabled);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxDefaultPositionForReachability(PrintWriter pw)
+ throws RemoteException {
+ @LetterboxReachabilityPosition final int position;
+ try {
+ String arg = getNextArgRequired();
+ switch (arg) {
+ case "left":
+ position = LETTERBOX_REACHABILITY_POSITION_LEFT;
+ break;
+ case "center":
+ position = LETTERBOX_REACHABILITY_POSITION_CENTER;
+ break;
+ case "right":
+ position = LETTERBOX_REACHABILITY_POSITION_RIGHT;
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: 'left', 'center' or 'right' are expected as an argument");
+ return -1;
+ }
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: 'left', 'center' or 'right' are expected as an argument" + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setDefaultPositionForReachability(position);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException {
+ if (peekNextArg() == null) {
+ getErrPrintWriter().println("Error: No arguments provided.");
+ }
+ while (peekNextArg() != null) {
+ String arg = getNextArg();
+ switch (arg) {
+ case "--aspectRatio":
+ runSetFixedOrientationLetterboxAspectRatio(pw);
+ break;
+ case "--cornerRadius":
+ runSetLetterboxActivityCornersRadius(pw);
+ break;
+ case "--backgroundType":
+ runSetLetterboxBackgroundType(pw);
+ break;
+ case "--backgroundColor":
+ runSetLetterboxBackgroundColor(pw);
+ break;
+ case "--backgroundColorResource":
+ runSetLetterboxBackgroundColorResource(pw);
+ break;
+ case "--wallpaperBlurRadius":
+ runSetLetterboxBackgroundWallpaperBlurRadius(pw);
+ break;
+ case "--wallpaperDarkScrimAlpha":
+ runSetLetterboxBackgroundWallpaperDarkScrimAlpha(pw);
+ break;
+ case "--horizontalPositionMultiplier":
+ runSetLetterboxHorizontalPositionMultiplier(pw);
+ break;
+ case "--isReachabilityEnabled":
+ runSetLetterboxIsReachabilityEnabled(pw);
+ break;
+ case "--defaultPositionForReachability":
+ runSetLetterboxDefaultPositionForReachability(pw);
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: Unrecognized letterbox style option: " + arg);
+ return -1;
+ }
+ }
+ return 0;
+ }
+
+ private int runResetLetterboxStyle(PrintWriter pw) throws RemoteException {
+ if (peekNextArg() == null) {
+ resetLetterboxStyle();
+ }
+ synchronized (mInternal.mGlobalLock) {
+ while (peekNextArg() != null) {
+ String arg = getNextArg();
+ switch (arg) {
+ case "aspectRatio":
+ mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
+ break;
+ case "cornerRadius":
+ mLetterboxConfiguration.resetLetterboxActivityCornersRadius();
+ break;
+ case "backgroundType":
+ mLetterboxConfiguration.resetLetterboxBackgroundType();
+ break;
+ case "backgroundColor":
+ mLetterboxConfiguration.resetLetterboxBackgroundColor();
+ break;
+ case "wallpaperBlurRadius":
+ mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius();
+ break;
+ case "wallpaperDarkScrimAlpha":
+ mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
+ break;
+ case "horizontalPositionMultiplier":
+ mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
+ break;
+ case "isReachabilityEnabled":
+ mLetterboxConfiguration.getIsReachabilityEnabled();
+ break;
+ case "defaultPositionForReachability":
+ mLetterboxConfiguration.getDefaultPositionForReachability();
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: Unrecognized letterbox style option: " + arg);
+ return -1;
+ }
+ }
+ }
+ return 0;
+ }
+
private int runSetMultiWindowConfig() {
if (peekNextArg() == null) {
getErrPrintWriter().println("Error: No arguments provided.");
@@ -622,6 +987,47 @@ public class WindowManagerShellCommand extends ShellCommand {
return 0;
}
+ private void resetLetterboxStyle() {
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
+ mLetterboxConfiguration.resetLetterboxActivityCornersRadius();
+ mLetterboxConfiguration.resetLetterboxBackgroundType();
+ mLetterboxConfiguration.resetLetterboxBackgroundColor();
+ mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius();
+ mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
+ mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
+ mLetterboxConfiguration.resetIsReachabilityEnabled();
+ mLetterboxConfiguration.resetDefaultPositionForReachability();
+ }
+ }
+
+ private int runGetLetterboxStyle(PrintWriter pw) throws RemoteException {
+ synchronized (mInternal.mGlobalLock) {
+ pw.println("Corner radius: "
+ + mLetterboxConfiguration.getLetterboxActivityCornersRadius());
+ pw.println("Horizontal position multiplier: "
+ + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier());
+ pw.println("Aspect ratio: "
+ + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
+ pw.println("Is reachability enabled: "
+ + mLetterboxConfiguration.getIsReachabilityEnabled());
+ pw.println("Default position for reachability: "
+ + LetterboxConfiguration.letterboxReachabilityPositionToString(
+ mLetterboxConfiguration.getDefaultPositionForReachability()));
+
+ pw.println("Background type: "
+ + LetterboxConfiguration.letterboxBackgroundTypeToString(
+ mLetterboxConfiguration.getLetterboxBackgroundType()));
+ pw.println(" Background color: " + Integer.toHexString(
+ mLetterboxConfiguration.getLetterboxBackgroundColor().toArgb()));
+ pw.println(" Wallpaper blur radius: "
+ + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius());
+ pw.println(" Wallpaper dark scrim alpha: "
+ + mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha());
+ }
+ return 0;
+ }
+
private int runReset(PrintWriter pw) throws RemoteException {
int displayId = getDisplayId(getNextArg());
@@ -646,6 +1052,12 @@ public class WindowManagerShellCommand extends ShellCommand {
// set-ignore-orientation-request
mInterface.setIgnoreOrientationRequest(displayId, false /* ignoreOrientationRequest */);
+ // set-letterbox-style
+ resetLetterboxStyle();
+
+ // set-sandbox-display-apis
+ mInternal.setSandboxDisplayApis(displayId, /* sandboxDisplayApis= */ true);
+
// set-multi-window-config
runResetMultiWindowConfig();
@@ -680,7 +1092,12 @@ public class WindowManagerShellCommand extends ShellCommand {
pw.println(" set-ignore-orientation-request [-d DISPLAY_ID] [true|1|false|0]");
pw.println(" get-ignore-orientation-request [-d DISPLAY_ID] ");
pw.println(" If app requested orientation should be ignored.");
+ pw.println(" set-sandbox-display-apis [true|1|false|0]");
+ pw.println(" Sets override of Display APIs getRealSize / getRealMetrics to reflect ");
+ pw.println(" DisplayArea of the activity, or the window bounds if in letterbox or");
+ pw.println(" Size Compat Mode.");
+ printLetterboxHelp(pw);
printMultiWindowConfigHelp(pw);
pw.println(" reset [-d DISPLAY_ID]");
@@ -693,6 +1110,61 @@ public class WindowManagerShellCommand extends ShellCommand {
}
}
+ private void printLetterboxHelp(PrintWriter pw) {
+ pw.println(" set-letterbox-style");
+ pw.println(" Sets letterbox style using the following options:");
+ pw.println(" --aspectRatio aspectRatio");
+ pw.println(" Aspect ratio of letterbox for fixed orientation. If aspectRatio <= "
+ + LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
+ pw.println(" both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will");
+ pw.println(" be ignored and framework implementation will determine aspect ratio.");
+ pw.println(" --cornerRadius radius");
+ pw.println(" Corners radius for activities in the letterbox mode. If radius < 0,");
+ pw.println(" both it and R.integer.config_letterboxActivityCornersRadius will be");
+ pw.println(" ignored and corners of the activity won't be rounded.");
+ pw.println(" --backgroundType [reset|solid_color|app_color_background");
+ pw.println(" |app_color_background_floating|wallpaper]");
+ pw.println(" Type of background used in the letterbox mode.");
+ pw.println(" --backgroundColor color");
+ pw.println(" Color of letterbox which is be used when letterbox background type");
+ pw.println(" is 'solid-color'. Use (set)get-letterbox-style to check and control");
+ pw.println(" letterbox background type. See Color#parseColor for allowed color");
+ pw.println(" formats (#RRGGBB and some colors by name, e.g. magenta or olive).");
+ pw.println(" --backgroundColorResource resource_name");
+ pw.println(" Color resource name of letterbox background which is used when");
+ pw.println(" background type is 'solid-color'. Use (set)get-letterbox-style to");
+ pw.println(" check and control background type. Parameter is a color resource");
+ pw.println(" name, for example, @android:color/system_accent2_50.");
+ pw.println(" --wallpaperBlurRadius radius");
+ pw.println(" Blur radius for 'wallpaper' letterbox background. If radius <= 0");
+ pw.println(" both it and R.dimen.config_letterboxBackgroundWallpaperBlurRadius");
+ pw.println(" are ignored and 0 is used.");
+ pw.println(" --wallpaperDarkScrimAlpha alpha");
+ pw.println(" Alpha of a black translucent scrim shown over 'wallpaper'");
+ pw.println(" letterbox background. If alpha < 0 or >= 1 both it and");
+ pw.println(" R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha are ignored");
+ pw.println(" and 0.0 (transparent) is used instead.");
+ pw.println(" --horizontalPositionMultiplier multiplier");
+ pw.println(" Horizontal position of app window center. If multiplier < 0 or > 1,");
+ pw.println(" both it and R.dimen.config_letterboxHorizontalPositionMultiplier");
+ pw.println(" are ignored and central position (0.5) is used.");
+ pw.println(" --isReachabilityEnabled [true|1|false|0]");
+ pw.println(" Whether reachability repositioning is allowed for letterboxed");
+ pw.println(" fullscreen apps in landscape device orientation.");
+ pw.println(" --defaultPositionForReachability [left|center|right]");
+ pw.println(" Default horizontal position of app window when reachability is.");
+ pw.println(" enabled.");
+ pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
+ pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
+ pw.println(" |horizontalPositionMultiplier|isReachabilityEnabled");
+ pw.println(" |defaultPositionMultiplierForReachability]");
+ pw.println(" Resets overrides to default values for specified properties separated");
+ pw.println(" by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
+ pw.println(" If no arguments provided, all values will be reset.");
+ pw.println(" get-letterbox-style");
+ pw.println(" Prints letterbox style configuration.");
+ }
+
private void printMultiWindowConfigHelp(PrintWriter pw) {
pw.println(" set-multi-window-config");
pw.println(" Sets options to determine if activity should be shown in multi window:");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a82a478f2a4f..f25d065f979b 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,11 @@ 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.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 +56,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 +84,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 +116,26 @@ 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);
}
TransitionController getTransitionController() {
@@ -122,14 +154,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 +172,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 +196,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 +211,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 +231,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 +240,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 +252,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 +303,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 +319,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 +377,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 +416,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 +438,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
mService.addWindowLayoutReasons(LAYOUT_REASON_CONFIG_CHANGED);
}
} finally {
+ mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
mService.continueWindowLayout();
}
}
@@ -389,6 +466,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 +483,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 +547,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: {
@@ -480,7 +571,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
} 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) {
throw new UnsupportedOperationException(
"Cannot set non-adjacent task as adjacent flag root: " + wc);
}
@@ -501,13 +592,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return effects;
}
+ final WindowContainer wc;
+ final IBinder fragmentToken;
switch (type) {
case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId);
break;
case HIERARCHY_OP_TYPE_REORDER:
case HIERARCHY_OP_TYPE_REPARENT:
- final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+ wc = WindowContainer.fromBinder(hop.getContainer());
if (wc == null || !wc.isAttached()) {
Slog.e(TAG, "Attempt to operate on detached container: " + wc);
break;
@@ -537,11 +630,151 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
effects |= sanitizeAndApplyHierarchyOp(wc, hop);
break;
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_CREATE_TASK_FRAGMENT:
+ final TaskFragmentCreationParams taskFragmentCreationOptions =
+ hop.getTaskFragmentCreationOptions();
+ createTaskFragment(taskFragmentCreationOptions, errorCallbackToken);
+ break;
+ case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT:
+ 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.");
+ }
+ effects |= deleteTaskFragment(taskFragment, errorCallbackToken);
+ break;
+ case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
+ 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());
+ if (!isStartResultSuccessful(result)) {
+ sendTaskFragmentOperationFailure(tf.getTaskFragmentOrganizer(),
+ errorCallbackToken,
+ convertStartFailureToThrowable(result, activityIntent));
+ }
+ break;
+ case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT:
+ 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_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;
+ case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS:
+ 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);
+ 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;
}
return effects;
@@ -661,24 +894,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 +944,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);
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 +988,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);
@@ -795,7 +1041,241 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
}
+ @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) {
+ 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 root Activity of the target Task.
+ final ActivityRecord rootActivity = ownerActivity.getTask().getRootActivity();
+ if (rootActivity.getUid() != ownerActivity.getUid()) {
+ 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.getPid());
+ 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..81878e3ef229 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) {
@@ -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;
@@ -1215,12 +1227,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 +1559,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 +1610,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..ef0cba973e72 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;
@@ -105,15 +106,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 +154,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 +192,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 +202,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 +212,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 +237,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 +273,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 +308,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 +372,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 +758,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 +881,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 +1175,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 +1272,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 +1284,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 +1397,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 +1446,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 +1489,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 +1727,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 +1736,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 +1886,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 +1895,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 +1953,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.
@@ -1935,7 +2000,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final ActivityRecord atoken = mActivityRecord;
return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
&& isVisibleByPolicy() && !isParentWindowHidden()
- && (atoken == null || atoken.mVisibleRequested)
+ && (atoken == null || atoken.isVisible())
&& !mAnimatingExit && !mDestroying;
}
@@ -2167,7 +2232,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 +2253,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 +2303,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 +2341,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 +2427,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 +2474,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
disposeInputChannel();
- mWinAnimator.destroySurfaceLocked(mTmpTransaction);
- mTmpTransaction.apply();
mSession.windowRemovedLocked();
try {
mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
@@ -2520,13 +2589,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,6 +2725,12 @@ 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()) {
@@ -2880,9 +2954,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);
}
@@ -3535,7 +3614,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (mAttrs.type >= FIRST_SYSTEM_WINDOW && mAttrs.type != TYPE_TOAST) {
mWmService.mAtmService.mActiveUids.onNonAppSurfaceVisibilityChanged(mOwnerUid, shown);
}
- if (mIsImWindow && mWmService.mAccessibilityController != null) {
+ if (mIsImWindow && mWmService.mAccessibilityController.hasCallbacks()) {
mWmService.mAccessibilityController.onImeSurfaceShownChanged(this, shown);
}
}
@@ -3865,7 +3944,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 +3958,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 +4010,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 +4022,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 +4443,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 +4478,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 +4494,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 +4568,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 +4769,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 +4857,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 +4967,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,
@@ -4999,7 +5091,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 +5508,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 +5661,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 +5700,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 +5748,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 +5765,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 +6022,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 +6044,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 +6085,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 +6141,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/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 d48c9ea4831c..a3ff6da9782b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -327,7 +327,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;
@@ -1259,17 +1258,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;
});
}
@@ -8337,7 +8356,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 +8420,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 +9149,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);
@@ -10613,19 +10632,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 +10685,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();
@@ -14337,6 +14361,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
maybePauseDeviceWideLoggingLocked();
maybeResumeDeviceWideLoggingLocked();
maybeClearLockTaskPolicyLocked();
+ updateAdminCanGrantSensorsPermissionCache(callingUserId);
}
}
@@ -17000,6 +17025,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) {
@@ -17446,7 +17484,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;
}
@@ -17501,7 +17540,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);
@@ -17515,10 +17557,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 33e32393090e..f5f1d497878a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -377,6 +377,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 =
@@ -1583,6 +1585,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;
@@ -2646,6 +2651,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/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/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/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/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/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/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/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/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/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/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index b51918e24b13..8b7c90d985b5 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,10 +19,13 @@ 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.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.SensorLocation;
@@ -56,6 +59,8 @@ public class FingerprintProviderTest {
@Mock
private Context mContext;
@Mock
+ private Resources mResources;
+ @Mock
private UserManager mUserManager;
@Mock
private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@@ -74,19 +79,21 @@ public class FingerprintProviderTest {
public void setUp() {
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<>());
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);
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/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index ff8fbda6c83e..03eba9bfc35c 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,10 @@ package com.android.server.devicestate;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static com.google.common.truth.Truth.assertThat;
+
+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 +37,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 +63,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 +82,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 +112,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 +125,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 +133,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 +141,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));
@@ -147,17 +183,21 @@ public final class DeviceStateManagerServiceTest {
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
+ OTHER_DEVICE_STATE);
- mProvider.notifySupportedDeviceStates(new DeviceState[]{ 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.
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE);
assertArrayEquals(callback.getLastNotifiedInfo().supportedStates,
- new int[] { DEFAULT_DEVICE_STATE.getIdentifier() });
+ new int[]{DEFAULT_DEVICE_STATE.getIdentifier()});
}
@Test
@@ -166,20 +206,26 @@ 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));
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
+ OTHER_DEVICE_STATE);
- mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE,
- OTHER_DEVICE_STATE });
+ 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.
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
+ OTHER_DEVICE_STATE);
// The callback wasn't notified about a change in supported states as the states have not
// changed.
@@ -203,12 +249,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 +264,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 +273,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 +286,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 +298,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 +306,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 +327,7 @@ public final class DeviceStateManagerServiceTest {
OTHER_DEVICE_STATE.getIdentifier());
mService.getBinderService().cancelRequest(token);
+ flushHandler();
assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_CANCELED);
@@ -287,10 +344,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 +362,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 +375,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 +393,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 +403,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 +421,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 +429,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 +439,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 +447,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 +463,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 +476,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 +489,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 +501,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/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 8279624f6b97..fbc1952b0faf 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -16,12 +16,17 @@
package com.android.server.display;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.DEFAULT_DISPLAY_GROUP;
+
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.eq;
@@ -55,6 +60,7 @@ import org.mockito.MockitoAnnotations;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
+import java.util.Set;
@SmallTest
@Presubmit
@@ -123,14 +129,14 @@ public class LogicalDisplayMapperTest {
// add
LogicalDisplay displayAdded = add(device);
assertEquals(info(displayAdded).address, info(device).address);
- assertEquals(Display.DEFAULT_DISPLAY, id(displayAdded));
+ assertEquals(DEFAULT_DISPLAY, id(displayAdded));
// remove
mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
verify(mListenerMock).onLogicalDisplayEventLocked(
mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
- assertEquals(Display.DEFAULT_DISPLAY, id(displayRemoved));
+ assertEquals(DEFAULT_DISPLAY, id(displayRemoved));
assertEquals(displayAdded, displayRemoved);
}
@@ -155,11 +161,11 @@ public class LogicalDisplayMapperTest {
LogicalDisplay display1 = add(device1);
assertEquals(info(display1).address, info(device1).address);
- assertNotEquals(Display.DEFAULT_DISPLAY, id(display1));
+ assertNotEquals(DEFAULT_DISPLAY, id(display1));
LogicalDisplay display2 = add(device2);
assertEquals(info(display2).address, info(device2).address);
- assertEquals(Display.DEFAULT_DISPLAY, id(display2));
+ assertEquals(DEFAULT_DISPLAY, id(display2));
}
@Test
@@ -171,12 +177,12 @@ public class LogicalDisplayMapperTest {
LogicalDisplay display1 = add(device1);
assertEquals(info(display1).address, info(device1).address);
- assertEquals(Display.DEFAULT_DISPLAY, id(display1));
+ assertEquals(DEFAULT_DISPLAY, id(display1));
LogicalDisplay display2 = add(device2);
assertEquals(info(display2).address, info(device2).address);
// Despite the flags, we can only have one default display
- assertNotEquals(Display.DEFAULT_DISPLAY, id(display2));
+ assertNotEquals(DEFAULT_DISPLAY, id(display2));
}
@Test
@@ -189,7 +195,67 @@ public class LogicalDisplayMapperTest {
int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID);
assertEquals(3, ids.length);
Arrays.sort(ids);
- assertEquals(Display.DEFAULT_DISPLAY, ids[0]);
+ assertEquals(DEFAULT_DISPLAY, ids[0]);
+ }
+
+ @Test
+ public void testGetDisplayInfoForStateLocked_oneDisplayGroup_internalType() {
+ add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+ add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+ add(createDisplayDevice(Display.TYPE_INTERNAL, 700, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+
+ Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
+ assertThat(displayInfos.size()).isEqualTo(3);
+ for (DisplayInfo displayInfo : displayInfos) {
+ assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
+ assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
+ assertThat(displayInfo.logicalWidth).isAnyOf(600, 200, 700);
+ assertThat(displayInfo.logicalHeight).isEqualTo(800);
+ }
+ }
+
+ @Test
+ public void testGetDisplayInfoForStateLocked_oneDisplayGroup_differentTypes() {
+ add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+ add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+ add(createDisplayDevice(Display.TYPE_EXTERNAL, 700, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+
+ Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
+ assertThat(displayInfos.size()).isEqualTo(2);
+ for (DisplayInfo displayInfo : displayInfos) {
+ assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
+ assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
+ assertThat(displayInfo.logicalWidth).isAnyOf(600, 200);
+ assertThat(displayInfo.logicalHeight).isEqualTo(800);
+ }
+ }
+
+ @Test
+ public void testGetDisplayInfoForStateLocked_multipleDisplayGroups_defaultGroup() {
+ add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+ add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
+ DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY));
+ add(createDisplayDevice(Display.TYPE_VIRTUAL, 700, 800,
+ DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP));
+
+ Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
+ assertThat(displayInfos.size()).isEqualTo(2);
+ for (DisplayInfo displayInfo : displayInfos) {
+ assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
+ assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
+ assertThat(displayInfo.logicalWidth).isAnyOf(600, 200);
+ assertThat(displayInfo.logicalHeight).isEqualTo(800);
+ }
}
@Test
@@ -199,11 +265,11 @@ public class LogicalDisplayMapperTest {
LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0));
LogicalDisplay display3 = add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
- assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ assertEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
- assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ assertEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2)));
- assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ assertEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
}
@@ -218,11 +284,11 @@ public class LogicalDisplayMapperTest {
DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
LogicalDisplay display3 = add(device3);
- assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ assertEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
- assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ assertEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2)));
- assertNotEquals(Display.DEFAULT_DISPLAY_GROUP,
+ assertNotEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
// Now switch it back to the default group by removing the flag and issuing an update
@@ -231,7 +297,7 @@ public class LogicalDisplayMapperTest {
mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
// Verify the new group is correct.
- assertEquals(Display.DEFAULT_DISPLAY_GROUP,
+ assertEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
}
@@ -287,14 +353,14 @@ public class LogicalDisplayMapperTest {
// add
LogicalDisplay displayAdded = add(device);
assertEquals(info(displayAdded).address, info(device).address);
- assertNotEquals(Display.DEFAULT_DISPLAY, id(displayAdded));
+ assertNotEquals(DEFAULT_DISPLAY, id(displayAdded));
// remove
mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
verify(mListenerMock).onLogicalDisplayEventLocked(
mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
- assertNotEquals(Display.DEFAULT_DISPLAY, id(displayRemoved));
+ assertNotEquals(DEFAULT_DISPLAY, id(displayRemoved));
}
/**
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/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..8e2c1f051279 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());
@@ -187,8 +188,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 +266,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 +355,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/uwb/UwbServiceImplTest.java b/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java
index 6334e9d3cdc5..11554c7a7dc5 100644
--- a/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java
@@ -82,8 +82,6 @@ public class UwbServiceImplTest {
MockitoAnnotations.initMocks(this);
when(mUwbInjector.getVendorService()).thenReturn(mVendorService);
when(mUwbInjector.checkUwbRangingPermissionForDataDelivery(any(), any())).thenReturn(true);
- when(mUwbInjector.isPersistedUwbStateEnabled()).thenReturn(true);
- when(mUwbInjector.isAirplaneModeOn()).thenReturn(false);
when(mVendorService.asBinder()).thenReturn(mVendorServiceBinder);
mUwbServiceImpl = new UwbServiceImpl(mContext, mUwbInjector);
}
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/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/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..c493639fc6b1 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3872,6 +3872,27 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@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);
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..128bfa8a28f3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -18,7 +18,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.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
@@ -67,22 +67,22 @@ 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.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+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;
@@ -137,7 +137,7 @@ 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;
@@ -146,6 +146,8 @@ import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import java.util.ArrayList;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
/**
@@ -164,6 +166,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 +176,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 +220,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 +349,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 +374,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 +401,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 +422,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 +470,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()));
@@ -629,7 +633,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 +649,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 +658,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 +675,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 +694,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 +737,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 +1021,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 +1085,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 +1096,7 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.finishIfPossible("test", false /* oomAdj */);
verify(activity.mDisplayContent, never()).prepareAppTransition(eq(TRANSIT_CLOSE));
+ assertFalse(activity.inTransition());
}
/**
@@ -1098,11 +1105,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 +1117,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 +1178,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 +1424,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 +1476,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 +1499,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 +1627,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
@@ -1740,6 +1775,11 @@ public class ActivityRecordTests extends WindowTestsBase {
anyInt() /* orientation */, anyInt() /* lastRotation */);
// Set to visible so the activity can freeze the screen.
activity.setVisibility(true);
+ // Update the display policy to make the screen fully turned on so the freeze is allowed
+ display.getDisplayPolicy().screenTurnedOn(null);
+ display.getDisplayPolicy().finishKeyguardDrawn();
+ display.getDisplayPolicy().finishWindowsDrawn();
+ display.getDisplayPolicy().finishScreenTurningOn();
display.rotateInDifferentOrientationIfNeeded(activity);
display.setFixedRotationLaunchingAppUnchecked(activity);
@@ -1852,7 +1892,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 +1948,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 +1980,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 +2190,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 +2537,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 +2564,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 +2585,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 +2631,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 +2654,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 +2692,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 +2724,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 +2810,36 @@ public class ActivityRecordTests extends WindowTestsBase {
assertEquals(taskBounds, activity.getAnimationBounds(ROOT_TASK_CLIP_AFTER_ANIM));
assertEquals(new Point(0, 0), animationPosition);
+ }
- // 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));
+ @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);
+
+ // 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 +2859,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 +3020,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 +3039,28 @@ public class ActivityRecordTests extends WindowTestsBase {
assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
}
+ @Test
+ public void testInClosingAnimation_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);
+
+ // Update visibility and call to remove window
+ app.mActivityRecord.commitVisibility(false, false);
+ app.mActivityRecord.prepareSurfaces();
+
+ // Because the app is waiting for transition, it should not hide the surface.
+ assertTrue(app.mActivityRecord.isSurfaceShowing());
+
+ // Ensure onAnimationFinished will callback when the closing animation is finished.
+ verify(app.mActivityRecord).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
+ eq(null));
+ }
+
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 d0588a30ca67..3e8a2e9b7b17 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;
@@ -477,7 +478,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();
@@ -755,12 +756,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)
@@ -777,13 +778,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());
}
/**
@@ -852,7 +856,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();
@@ -916,7 +920,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)
@@ -1052,8 +1056,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();
@@ -1136,6 +1140,7 @@ public class ActivityStarterTests extends WindowTestsBase {
/* doResume */true,
/* options */null,
/* inTask */null,
+ /* inTaskFragment */ null,
/* restrictedBgActivity */false,
/* intentGrants */null);
@@ -1146,6 +1151,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 741f33f8aca7..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);
@@ -169,7 +186,10 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
@Override
public void onFixedRotationFinished(int displayId) {}
};
- mAtm.mWindowManager.registerDisplayWindowListener(listener);
+ int[] displayIds = mAtm.mWindowManager.registerDisplayWindowListener(listener);
+ for (int i = 0; i < displayIds.length; i++) {
+ added.add(displayIds[i]);
+ }
// Check that existing displays call added
assertEquals(mRootWindowContainer.getChildCount(), added.size());
assertEquals(0, changed.size());
@@ -244,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());
@@ -254,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());
}
@@ -271,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());
@@ -287,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();
@@ -473,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..a34586bb2e8a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -52,6 +52,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 +112,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]);
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..5d0e34a80f3f 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
@@ -375,7 +399,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 +544,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 +765,252 @@ 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;
+ 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;
+ // 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..fb8bc7be38ce 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,16 @@ 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_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 +35,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;
@@ -82,8 +97,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
@@ -97,8 +112,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 +127,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 +142,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 +157,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 +398,42 @@ 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);
+
+ 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/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..70aa2a2f32bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -37,7 +37,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 +106,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 +114,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 +186,32 @@ 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));
+ displayPolicy.updateLightNavigationBarLw(APPEARANCE_LIGHT_NAVIGATION_BARS, null));
// Dimming window clears APPEARANCE_LIGHT_NAVIGATION_BARS.
+ assertEquals(0, displayPolicy.updateLightNavigationBarLw(0, dimming));
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));
+ APPEARANCE_LIGHT_NAVIGATION_BARS, dimming));
- // IME window clears APPEARANCE_LIGHT_NAVIGATION_BARS
+ // Control window overrides APPEARANCE_LIGHT_NAVIGATION_BARS flag.
+ assertEquals(0, displayPolicy.updateLightNavigationBarLw(0, opaqueDarkNavBar));
assertEquals(0, displayPolicy.updateLightNavigationBarLw(
- APPEARANCE_LIGHT_NAVIGATION_BARS, null, null, imeDrawDarkNavBar,
- imeDrawDarkNavBar));
-
- // Even if the top fullscreen has APPEARANCE_LIGHT_NAVIGATION_BARS, IME window wins.
- 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 */);
@@ -313,7 +290,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..a1e8ca4aa84b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -58,6 +58,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 +71,7 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
@@ -97,14 +99,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 +179,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 +192,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);
}
@@ -436,4 +454,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/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index bf3ed692dc8e..07d467bc07d5 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()
@@ -371,7 +374,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();
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..f8c84df53749 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.
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/PossibleDisplayInfoMapperTests.java b/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java
new file mode 100644
index 000000000000..6e0056821aab
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java
@@ -0,0 +1,182 @@
+/*
+ * 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.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_PRESENTATION;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
+import android.view.DisplayInfo;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+
+/**
+ * Tests for {@link PossibleDisplayInfoMapper}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:PossibleDisplayInfoMapperTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class PossibleDisplayInfoMapperTests extends WindowTestsBase {
+
+ private PossibleDisplayInfoMapper mDisplayInfoMapper;
+ private final Set<DisplayInfo> mPossibleDisplayInfo = new ArraySet<>();
+ private DisplayInfo mDefaultDisplayInfo;
+ private DisplayInfo mSecondDisplayInfo;
+
+ @Before
+ public void setUp() throws Exception {
+ mDisplayInfoMapper = mWm.mPossibleDisplayInfoMapper;
+ final DisplayInfo baseDisplayInfo = mWm.mRoot.getDisplayContent(
+ DEFAULT_DISPLAY).getDisplayInfo();
+ when(mWm.mDisplayManagerInternal.getPossibleDisplayInfo(anyInt())).thenReturn(
+ mPossibleDisplayInfo);
+
+ mDefaultDisplayInfo = new DisplayInfo(baseDisplayInfo);
+ initializeDisplayInfo(mDefaultDisplayInfo, DEFAULT_DISPLAY, new Rect(0, 0, 500, 800));
+ mSecondDisplayInfo = new DisplayInfo(baseDisplayInfo);
+ // Use the same display id for any display in the same group, due to the assumption that
+ // any display in the same grouped can be swapped out for each other (while maintaining the
+ // display id).
+ initializeDisplayInfo(mSecondDisplayInfo, DEFAULT_DISPLAY, new Rect(0, 0, 600, 1600));
+ mSecondDisplayInfo.flags |= FLAG_PRESENTATION;
+ }
+
+ @Test
+ public void testInitialization_isEmpty() {
+ // Empty after initializing.
+ assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY)).isEmpty();
+
+ // Still empty after updating.
+ mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY);
+ assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY)).isEmpty();
+ }
+
+ @Test
+ public void testUpdatePossibleDisplayInfos_singleDisplay() {
+ mPossibleDisplayInfo.add(mDefaultDisplayInfo);
+ mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY);
+
+ Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY);
+ // An entry for each possible rotation, for a display that can be in a single state.
+ assertThat(displayInfos.size()).isEqualTo(4);
+ assertPossibleDisplayInfoEntries(displayInfos, mDefaultDisplayInfo);
+ }
+
+ @Test
+ public void testUpdatePossibleDisplayInfos_secondDisplayAdded_sameGroup() {
+ mPossibleDisplayInfo.add(mDefaultDisplayInfo);
+ mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY);
+
+ assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY).size()).isEqualTo(4);
+
+ // Add another display layout to the set of supported states.
+ mPossibleDisplayInfo.add(mSecondDisplayInfo);
+ mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY);
+
+ Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY);
+ Set<DisplayInfo> defaultDisplayInfos = new ArraySet<>();
+ Set<DisplayInfo> secondDisplayInfos = new ArraySet<>();
+ for (DisplayInfo di : displayInfos) {
+ if ((di.flags & FLAG_PRESENTATION) != 0) {
+ secondDisplayInfos.add(di);
+ } else {
+ defaultDisplayInfos.add(di);
+ }
+ }
+ // An entry for each possible rotation, for the default display.
+ assertThat(defaultDisplayInfos).hasSize(4);
+ assertPossibleDisplayInfoEntries(defaultDisplayInfos, mDefaultDisplayInfo);
+
+ // An entry for each possible rotation, for the second display.
+ assertThat(secondDisplayInfos).hasSize(4);
+ assertPossibleDisplayInfoEntries(secondDisplayInfos, mSecondDisplayInfo);
+ }
+
+ @Test
+ public void testUpdatePossibleDisplayInfos_secondDisplayAdded_differentGroup() {
+ mPossibleDisplayInfo.add(mDefaultDisplayInfo);
+ mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY);
+
+ assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY).size()).isEqualTo(4);
+
+ // Add another display to a different group.
+ mSecondDisplayInfo.displayId = DEFAULT_DISPLAY + 1;
+ mSecondDisplayInfo.displayGroupId = mDefaultDisplayInfo.displayGroupId + 1;
+ mPossibleDisplayInfo.add(mSecondDisplayInfo);
+ mDisplayInfoMapper.updatePossibleDisplayInfos(mSecondDisplayInfo.displayId);
+
+ Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY);
+ // An entry for each possible rotation, for the default display.
+ assertThat(displayInfos).hasSize(4);
+ assertPossibleDisplayInfoEntries(displayInfos, mDefaultDisplayInfo);
+
+ Set<DisplayInfo> secondStateEntries =
+ mDisplayInfoMapper.getPossibleDisplayInfos(mSecondDisplayInfo.displayId);
+ // An entry for each possible rotation, for the second display.
+ assertThat(secondStateEntries).hasSize(4);
+ assertPossibleDisplayInfoEntries(secondStateEntries, mSecondDisplayInfo);
+ }
+
+ private static void initializeDisplayInfo(DisplayInfo outDisplayInfo, int displayId,
+ Rect logicalBounds) {
+ outDisplayInfo.displayId = displayId;
+ outDisplayInfo.rotation = ROTATION_0;
+ outDisplayInfo.logicalWidth = logicalBounds.width();
+ outDisplayInfo.logicalHeight = logicalBounds.height();
+ }
+
+ private static void assertPossibleDisplayInfoEntries(Set<DisplayInfo> displayInfos,
+ DisplayInfo expectedDisplayInfo) {
+ boolean[] seenEveryRotation = new boolean[4];
+ for (DisplayInfo displayInfo : displayInfos) {
+ final int rotation = displayInfo.rotation;
+ seenEveryRotation[rotation] = true;
+ assertThat(displayInfo.displayId).isEqualTo(expectedDisplayInfo.displayId);
+ assertEqualsRotatedDisplayInfo(displayInfo, expectedDisplayInfo);
+ }
+ assertThat(seenEveryRotation).isEqualTo(new boolean[]{true, true, true, true});
+ }
+
+ private static void assertEqualsRotatedDisplayInfo(DisplayInfo actual, DisplayInfo expected) {
+ if (actual.rotation == ROTATION_0 || actual.rotation == ROTATION_180) {
+ assertThat(actual.logicalWidth).isEqualTo(expected.logicalWidth);
+ assertThat(actual.logicalHeight).isEqualTo(expected.logicalHeight);
+ } else {
+ assertThat(actual.logicalWidth).isEqualTo(expected.logicalHeight);
+ assertThat(actual.logicalHeight).isEqualTo(expected.logicalWidth);
+ }
+ }
+}
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..22e687a0bb15 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")
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..9d2a69197013 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;
@@ -257,7 +257,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
verify(mMockRunner).onAnimationCanceled(null /* taskSnapshot */);
// 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);
}
@@ -439,6 +439,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);
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..bed0a94f3b8f 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,7 +153,7 @@ 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());
}
@@ -161,7 +176,9 @@ public class SizeCompatTests extends WindowTestsBase {
// 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);
+ final int dy = bounds.top + bounds.height() / 2;
+ mTask.setBounds(bounds.left - dx, bounds.top - dy, bounds.right - dx, bounds.bottom - dy);
+ // expected:<Rect(-150, 100 - 150, 600)> but was:<Rect(-150, 0 - 150, 500)>
assertEquals(mTask.getBounds(), mActivity.getBounds());
final int density = mActivity.getConfiguration().densityDpi;
@@ -318,6 +335,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 +607,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 +626,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 +640,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 +659,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);
@@ -1101,6 +1123,98 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ 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_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_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_MEDIUM})
public void testOverrideMinAspectRatioWithoutGlobalOverride() {
// In this test, only OVERRIDE_MIN_ASPECT_RATIO_1_5 is set, which has no effect without
@@ -1553,6 +1667,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 +1946,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 +1959,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 +1974,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 +1984,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 +1997,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 +2046,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 +2068,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 +2088,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 +2106,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 +2189,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 +2222,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);
@@ -2111,6 +2327,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..d68edbafb592 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);
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);
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);
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..d475c46eed0c
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -0,0 +1,421 @@
+/*
+ * 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.assertNull;
+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.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 /* pid */);
+
+ 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 /* pid */);
+
+ 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 /* pid */);
+ 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);
+
+ 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 /* pid */);
+ taskFragment2.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */);
+ clearInvocations(mAtm.mRootWindowContainer);
+
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+
+ verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities();
+ }
+
+ @Test
+ public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() {
+ mOrganizer.applyTransaction(mTransaction);
+
+ // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
+ final TaskFragmentCreationParams mockParams = mock(TaskFragmentCreationParams.class);
+ doReturn(mOrganizerToken).when(mockParams).getOrganizer();
+ mTransaction.createTaskFragment(mockParams);
+ mTransaction.startActivityInTaskFragment(
+ mFragmentToken, null /* callerToken */, new Intent(), null /* activityOptions */);
+ mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class));
+ mTransaction.setAdjacentTaskFragments(mFragmentToken, mock(IBinder.class),
+ null /* options */);
+
+ // It is expected to fail for the mock TaskFragmentCreationParams. It is ok as we are
+ // testing the security check here.
+ assertThrows(IllegalArgumentException.class, () -> {
+ try {
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+ } catch (RemoteException e) {
+ fail();
+ }
+ });
+ }
+
+ @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 /* pid */);
+ 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();
+ }
+}
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..cb209abf6aa9
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.Mockito.clearInvocations;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
+import android.window.ITaskFragmentOrganizer;
+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)
+ .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() {
+ 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();
+
+ 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);
+ }
+}
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/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 0ebff1d253ef..ce568f152a5f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -126,8 +126,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 +427,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 +757,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;
@@ -891,7 +898,8 @@ public class TaskTests extends WindowTestsBase {
/**
* Test that root activity index is reported correctly when looking for the 'effective root' in
- * case when bottom activities are relinquishing task identity or finishing.
+ * case when bottom activity is finishing. Ignore the relinquishing task identity if it's not a
+ * system activity even with the FLAG_RELINQUISH_TASK_IDENTITY.
*/
@Test
public void testFindRootIndex_effectiveRoot_finishingAndRelinquishing() {
@@ -905,7 +913,7 @@ public class TaskTests extends WindowTestsBase {
new ActivityBuilder(mAtm).setTask(task).build();
assertEquals("The first non-finishing activity and non-relinquishing task identity "
- + "must be reported.", task.getChildAt(2), task.getRootActivity(
+ + "must be reported.", task.getChildAt(0), task.getRootActivity(
false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/));
}
@@ -925,8 +933,8 @@ public class TaskTests extends WindowTestsBase {
}
/**
- * Test that the topmost activity index is reported correctly when looking for the
- * 'effective root' for the case when all activities have relinquishTaskIdentity set.
+ * Test that the root activity index is reported correctly when looking for the
+ * 'effective root' for the case when all non-system activities have relinquishTaskIdentity set.
*/
@Test
public void testFindRootIndex_effectiveRoot_relinquishingMultipleActivities() {
@@ -937,9 +945,9 @@ public class TaskTests extends WindowTestsBase {
final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
- assertEquals("The topmost activity in the task must be reported.",
- task.getChildAt(task.getChildCount() - 1), task.getRootActivity(
- false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/));
+ assertEquals("The topmost activity in the task must be reported.", task.getChildAt(0),
+ task.getRootActivity(false /*ignoreRelinquishIdentity*/,
+ true /*setToBottomIfNone*/));
}
/** Test that bottom-most activity is reported in {@link Task#getRootActivity()}. */
@@ -1077,8 +1085,8 @@ public class TaskTests extends WindowTestsBase {
}
/**
- * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} with activity that
- * relinquishes task identity.
+ * Test {@link ActivityRecord#getTaskForActivityLocked(IBinder, boolean)} with non-system
+ * activity that relinquishes task identity.
*/
@Test
public void testGetTaskForActivity_onlyRoot_relinquishTaskIdentity() {
@@ -1093,7 +1101,7 @@ public class TaskTests extends WindowTestsBase {
assertEquals(task.mTaskId,
ActivityRecord.getTaskForActivityLocked(activity0.appToken, true /* onlyRoot */));
- assertEquals(task.mTaskId,
+ assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID,
ActivityRecord.getTaskForActivityLocked(activity1.appToken, true /* onlyRoot */));
assertEquals("No task must be reported for activity that is above root", INVALID_TASK_ID,
ActivityRecord.getTaskForActivityLocked(activity2.appToken, true /* onlyRoot */));
@@ -1257,7 +1265,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);
@@ -1367,7 +1376,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..0e504d326280 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,14 @@ class TestDisplayContent extends DisplayContent {
doReturn(false).when(displayPolicy).hasStatusBar();
doReturn(false).when(newDisplay).supportsSystemDecorations();
}
+ 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..4e77fa73fd09 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);
+ 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);
+ 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/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..bbeb980353ed 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,106 @@ 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);
+ spyOn(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));
+ // Can't really take a snapshot, manually set one.
+ surfaceFreezer.mSnapshot = mock(SurfaceFreezer.Snapshot.class);
+
+ assertNotNull(surfaceFreezer.mLeash);
+ 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));
+ surfaceFreezer.mSnapshot = mock(SurfaceFreezer.Snapshot.class);
+
+ assertNotNull(surfaceFreezer.mLeash);
+ 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);
+ }
+
/* 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/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..a482bdacfc82 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);
}
@@ -779,8 +780,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 +1259,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());
- verify(mWm.mAtmService, times(1)).startActivityFromRecents(eq(2), bundleCaptor.capture());
- assertEquals(20, bundleCaptor.getValue().getInt("test"));
+ 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();
+
+ 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..81b00eae0021 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,6 @@ 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.display.DisplayManager;
import android.os.Build;
import android.os.Bundle;
@@ -83,10 +87,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 +100,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;
@@ -132,6 +140,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.
@@ -212,6 +223,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 +234,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 +280,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 +295,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 +331,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 +593,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 +679,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 +789,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);
+ 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).
@@ -992,7 +1098,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 +1158,81 @@ 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(), 10000 /* pid */);
+ }
+ return taskFragment;
+ }
+ }
+
/**
* Builder for creating new tasks.
*/
@@ -1072,7 +1253,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
private IVoiceInteractionSession mVoiceSession;
private boolean mCreateParentTask = false;
- private Task mParentTask;
+ private TaskFragment mParentTaskFragment;
private boolean mCreateActivity = false;
@@ -1156,8 +1337,8 @@ class WindowTestsBase extends SystemServiceTestsBase {
return this;
}
- TaskBuilder setParentTask(Task parentTask) {
- mParentTask = parentTask;
+ TaskBuilder setParentTaskFragment(TaskFragment parentTaskFragment) {
+ mParentTaskFragment = parentTaskFragment;
return this;
}
@@ -1170,12 +1351,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 +1385,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 +1474,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 +1614,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..72b05c08661b 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,9 @@ 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_ABOVE_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
@@ -37,6 +37,7 @@ 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.WindowStateAnimator.PRESERVED_SURFACE_LAYER;
import static com.google.common.truth.Truth.assertThat;
@@ -440,24 +441,42 @@ 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);
+ splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1);
+
+ 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(splitWindow1, belowTaskWindow);
+ assertWindowHigher(splitWindow2, belowTaskWindow);
+ assertWindowHigher(splitWindow2, belowTaskWindow);
+ assertWindowHigher(mDockedDividerWindow, splitWindow1);
+ assertWindowHigher(mDockedDividerWindow, splitWindow2);
+ assertWindowHigher(aboveTaskWindow, mDockedDividerWindow);
+ assertWindowHigher(pinnedWindow, aboveTaskWindow);
}
@Test
@@ -493,4 +512,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/uwb/java/com/android/server/uwb/UwbInjector.java b/services/uwb/java/com/android/server/uwb/UwbInjector.java
index a7a0500483c2..64f1da1c8e16 100644
--- a/services/uwb/java/com/android/server/uwb/UwbInjector.java
+++ b/services/uwb/java/com/android/server/uwb/UwbInjector.java
@@ -21,13 +21,10 @@ import static android.content.PermissionChecker.PERMISSION_GRANTED;
import android.annotation.NonNull;
import android.content.AttributionSource;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.PermissionChecker;
import android.os.IBinder;
import android.os.ServiceManager;
-import android.provider.Settings;
-import android.uwb.AdapterState;
import android.uwb.IUwbAdapter;
@@ -83,23 +80,4 @@ public class UwbInjector {
mContext, UWB_RANGING, -1, attributionSource, message);
return permissionCheckResult == PERMISSION_GRANTED;
}
-
- /** Returns true if UWB state saved in Settings is enabled. */
- public boolean isPersistedUwbStateEnabled() {
- final ContentResolver cr = mContext.getContentResolver();
- try {
- return Settings.Global.getInt(cr, Settings.Global.UWB_ENABLED)
- == AdapterState.STATE_ENABLED_ACTIVE;
- } catch (Settings.SettingNotFoundException e) {
- Settings.Global.putInt(cr, Settings.Global.UWB_ENABLED,
- AdapterState.STATE_ENABLED_ACTIVE);
- return true;
- }
- }
-
- /** Returns true if airplane mode is turned on. */
- public boolean isAirplaneModeOn() {
- return Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
- }
}
diff --git a/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java b/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
index 889e182af46b..4dd26a66cf0e 100644
--- a/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
+++ b/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
@@ -18,19 +18,13 @@ package com.android.server.uwb;
import android.annotation.NonNull;
import android.content.AttributionSource;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.os.Binder;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteException;
-import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Log;
-import android.uwb.AdapterState;
import android.uwb.IUwbAdapter;
import android.uwb.IUwbAdapterStateCallbacks;
import android.uwb.IUwbRangingCallbacks;
@@ -231,20 +225,13 @@ public class UwbServiceImpl extends IUwbAdapter.Stub implements IBinder.DeathRec
mVendorUwbAdapter = null;
}
- private synchronized IUwbAdapter getVendorUwbAdapter()
- throws IllegalStateException, RemoteException {
+ private synchronized IUwbAdapter getVendorUwbAdapter() throws IllegalStateException {
if (mVendorUwbAdapter != null) return mVendorUwbAdapter;
mVendorUwbAdapter = mUwbInjector.getVendorService();
if (mVendorUwbAdapter == null) {
throw new IllegalStateException("No vendor service found!");
}
Log.i(TAG, "Retrieved vendor service");
- long token = Binder.clearCallingIdentity();
- try {
- mVendorUwbAdapter.setEnabled(isEnabled());
- } finally {
- Binder.restoreCallingIdentity(token);
- }
linkToVendorServiceDeath();
return mVendorUwbAdapter;
}
@@ -252,7 +239,6 @@ public class UwbServiceImpl extends IUwbAdapter.Stub implements IBinder.DeathRec
UwbServiceImpl(@NonNull Context context, @NonNull UwbInjector uwbInjector) {
mContext = context;
mUwbInjector = uwbInjector;
- registerAirplaneModeReceiver();
}
private void enforceUwbPrivilegedPermission() {
@@ -334,34 +320,6 @@ public class UwbServiceImpl extends IUwbAdapter.Stub implements IBinder.DeathRec
@Override
public synchronized void setEnabled(boolean enabled) throws RemoteException {
- persistUwbState(enabled);
- getVendorUwbAdapter().setEnabled(isEnabled());
- }
-
- private void persistUwbState(boolean enabled) {
- final ContentResolver cr = mContext.getContentResolver();
- int state = enabled ? AdapterState.STATE_ENABLED_ACTIVE : AdapterState.STATE_DISABLED;
- Settings.Global.putInt(cr, Settings.Global.UWB_ENABLED, state);
- }
-
- private void registerAirplaneModeReceiver() {
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- handleAirplaneModeEvent();
- }
- }, new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
- }
-
- private void handleAirplaneModeEvent() {
- try {
- getVendorUwbAdapter().setEnabled(isEnabled());
- } catch (RemoteException | IllegalStateException e) {
- Log.e(TAG, "Unable to set UWB Adapter state.", e);
- }
- }
-
- private boolean isEnabled() {
- return mUwbInjector.isPersistedUwbStateEnabled() && !mUwbInjector.isAirplaneModeOn();
+ getVendorUwbAdapter().setEnabled(enabled);
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 4dc83ae98d89..f05dd636aa22 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -23,8 +23,11 @@ import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPH
import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN;
import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS;
+import static com.android.server.voiceinteraction.SoundTriggerSessionPermissionsDecorator.enforcePermissionForPreflight;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
import android.content.Context;
@@ -930,12 +933,11 @@ final class HotwordDetectionConnection {
// TODO: Share this code with SoundTriggerMiddlewarePermission.
private void enforcePermissionsForDataDelivery() {
Binder.withCleanCallingIdentity(() -> {
- // Hack to make sure we show the mic privacy-indicator since the Trusted Hotword
- // requirement isn't being enforced for now. Normally, we would note the HOTWORD op here
- // instead.
- enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
- RECORD_AUDIO, OP_MESSAGE);
-
+ enforcePermissionForPreflight(mContext, mVoiceInteractorIdentity, RECORD_AUDIO);
+ int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
+ mContext.getSystemService(AppOpsManager.class).noteOpNoThrow(hotwordOp,
+ mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+ mVoiceInteractorIdentity.attributionTag, OP_MESSAGE);
enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
CAPTURE_AUDIO_HOTWORD, OP_MESSAGE);
});
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) {
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 4d81b5e54470..7a424c87d1d6 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -308,6 +308,12 @@ public final class TelephonyPermissions {
return checkPrivilegedReadPermissionOrCarrierPrivilegePermission(
context, subId, callingPackage, callingFeatureId, message, false, reportFailure);
}
+
+ private static void throwSecurityExceptionAsUidDoesNotHaveAccess(String message, int uid) {
+ throw new SecurityException(message + ": The uid " + uid
+ + " does not meet the requirements to access device identifiers.");
+ }
+
/**
* Checks whether the app with the given pid/uid can read device identifiers.
*
@@ -343,9 +349,14 @@ public final class TelephonyPermissions {
LegacyPermissionManager permissionManager = (LegacyPermissionManager)
context.getSystemService(Context.LEGACY_PERMISSION_SERVICE);
- if (permissionManager.checkDeviceIdentifierAccess(callingPackage, message, callingFeatureId,
- pid, uid) == PackageManager.PERMISSION_GRANTED) {
- return true;
+ try {
+ if (permissionManager.checkDeviceIdentifierAccess(callingPackage, message,
+ callingFeatureId,
+ pid, uid) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ } catch (SecurityException se) {
+ throwSecurityExceptionAsUidDoesNotHaveAccess(message, uid);
}
if (reportFailure) {
@@ -410,8 +421,8 @@ public final class TelephonyPermissions {
return false;
}
}
- throw new SecurityException(message + ": The user " + uid
- + " does not meet the requirements to access device identifiers.");
+ throwSecurityExceptionAsUidDoesNotHaveAccess(message, uid);
+ return true;
}
/**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 45fd7a3467c4..54d3af520d3c 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1889,6 +1889,20 @@ public class CarrierConfigManager {
"lte_plus_threshold_bandwidth_khz_int";
/**
+ * The combined channel bandwidth threshold (non-inclusive) in KHz required to display the
+ * NR advanced (i.e. 5G+) data icon. It is 0 by default, meaning minimum bandwidth check is
+ * not enabled. Other factors like bands or frequency can also determine whether the NR
+ * advanced data icon is shown or not.
+ *
+ * @see #KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY
+ * @see #KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT
+ *
+ * @hide
+ */
+ public static final String KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT =
+ "nr_advanced_threshold_bandwidth_khz_int";
+
+ /**
* The string is used to filter redundant string from PLMN Network Name that's supplied by
* specific carrier.
*
@@ -3553,6 +3567,17 @@ public class CarrierConfigManager {
"nr_advanced_capable_pco_id_int";
/**
+ * Enabled NR advanced (i.e. 5G+) icon while roaming. The default value is {@code true}, meaming
+ * the same NR advanced logic used for home network will be used for roaming network as well.
+ * Set this to {@code false} will disable NR advanced icon while the device is roaming,
+ * regardless meeting NR advanced criteria or not.
+ *
+ * @hide
+ */
+ public static final String KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL =
+ "enable_nr_advanced_for_roaming_bool";
+
+ /**
* This configuration allows the framework to use user data communication to detect Idle state,
* and this is used on the 5G icon.
*
@@ -5186,6 +5211,16 @@ public class CarrierConfigManager {
public static final String KEY_VONR_SETTING_VISIBILITY_BOOL = "vonr_setting_visibility_bool";
/**
+ * Flag specifying whether VoNR should be enabled for carrier.
+ * If true, VoNr will be enabled. If false, hard disabled.
+ *
+ * Disabled by default.
+ *
+ * @hide
+ */
+ public static final String KEY_VONR_ENABLED_BOOL = "vonr_enabled_bool";
+
+ /**
* Determine whether unthrottle data retry when tracking area code (TAC/LAC) from cell changes
*
* @hide
@@ -5581,6 +5616,7 @@ public class CarrierConfigManager {
sDefaults.putString(KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING, "");
sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true);
sDefaults.putInt(KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
+ sDefaults.putInt(KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 0);
sDefaults.putIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
new int[]{CARRIER_NR_AVAILABILITY_NSA, CARRIER_NR_AVAILABILITY_SA});
sDefaults.putBoolean(KEY_LTE_ENABLED_BOOL, true);
@@ -5676,6 +5712,7 @@ public class CarrierConfigManager {
sDefaults.putLong(KEY_5G_WATCHDOG_TIME_MS_LONG, 3600000);
sDefaults.putIntArray(KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY, new int[0]);
sDefaults.putInt(KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT, 0);
+ sDefaults.putBoolean(KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL, true);
sDefaults.putBoolean(KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL, false);
sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_BOOL, false);
sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_MMWAVE_BOOL, false);
@@ -5801,6 +5838,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_DISPLAY_NO_DATA_NOTIFICATION_ON_PERMANENT_FAILURE_BOOL, false);
sDefaults.putBoolean(KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false);
sDefaults.putBoolean(KEY_VONR_SETTING_VISIBILITY_BOOL, false);
+ sDefaults.putBoolean(KEY_VONR_ENABLED_BOOL, false);
}
/**
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 114c90bbd87a..1be1f2f8de30 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -949,6 +949,15 @@ public class SubscriptionManager {
public static final String VOIMS_OPT_IN_STATUS = SimInfo.COLUMN_VOIMS_OPT_IN_STATUS;
/**
+ * TelephonyProvider column name for NR Advanced calling
+ * Determines if the user has enabled VoNR settings for this subscription.
+ *
+ * @hide
+ */
+ public static final String NR_ADVANCED_CALLING_ENABLED =
+ SimInfo.COLUMN_NR_ADVANCED_CALLING_ENABLED;
+
+ /**
* Profile class of the subscription
* @hide
*/
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index e890acb36b48..9572154ec773 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -36,6 +36,7 @@ import com.android.telephony.Rlog;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -152,16 +153,9 @@ public final class TelephonyScanManager {
throw new RuntimeException(
"Failed to find NetworkScanInfo with id " + message.arg2);
}
- NetworkScanCallback callback = nsi.mCallback;
- Executor executor = nsi.mExecutor;
- if (callback == null) {
- throw new RuntimeException(
- "Failed to find NetworkScanCallback with id " + message.arg2);
- }
- if (executor == null) {
- throw new RuntimeException(
- "Failed to find Executor with id " + message.arg2);
- }
+
+ final NetworkScanCallback callback = nsi.mCallback;
+ final Executor executor = nsi.mExecutor;
switch (message.what) {
case CALLBACK_RESTRICTED_SCAN_RESULTS:
@@ -246,17 +240,24 @@ public final class TelephonyScanManager {
NetworkScanRequest request, Executor executor, NetworkScanCallback callback,
String callingPackage, @Nullable String callingFeatureId) {
try {
+ Objects.requireNonNull(request, "Request was null");
+ Objects.requireNonNull(callback, "Callback was null");
+ Objects.requireNonNull(executor, "Executor was null");
final ITelephony telephony = getITelephony();
if (telephony == null) return null;
- int scanId = telephony.requestNetworkScan(
- subId, request, mMessenger, new Binder(), callingPackage,
- callingFeatureId);
- if (scanId == INVALID_SCAN_ID) {
- Rlog.e(TAG, "Failed to initiate network scan");
- return null;
- }
+ // The lock must be taken before calling requestNetworkScan because the resulting
+ // scanId can be invoked asynchronously on another thread at any time after
+ // requestNetworkScan invoked, leaving a critical section between that call and adding
+ // the record to the ScanInfo cache.
synchronized (mScanInfo) {
+ int scanId = telephony.requestNetworkScan(
+ subId, request, mMessenger, new Binder(), callingPackage,
+ callingFeatureId);
+ if (scanId == INVALID_SCAN_ID) {
+ Rlog.e(TAG, "Failed to initiate network scan");
+ return null;
+ }
// We link to death whenever a scan is started to ensure that we are linked
// at the point that phone process death might matter.
// We never unlink because:
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 217a72b90fd4..7731e098d9f5 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -25,11 +25,17 @@ package {
android_test {
name: "FlickerTests",
- srcs: ["src/**/*.java", "src/**/*.kt"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
manifest: "AndroidManifest.xml",
test_config: "AndroidTest.xml",
platform_apis: true,
certificate: "platform",
+ optimize: {
+ enabled: false,
+ },
test_suites: ["device-tests"],
libs: ["android.test.runner"],
static_libs: [
@@ -46,6 +52,9 @@ android_test {
java_library {
name: "wm-flicker-common-assertions",
platform_apis: true,
+ optimize: {
+ enabled: false,
+ },
srcs: [
"src/**/*Assertions.java",
"src/**/*Assertions.kt",
@@ -56,20 +65,23 @@ java_library {
static_libs: [
"flickerlib",
"truth-prebuilt",
- "app-helpers-core"
+ "app-helpers-core",
],
}
java_library {
name: "wm-flicker-common-app-helpers",
platform_apis: true,
+ optimize: {
+ enabled: false,
+ },
srcs: [
- "**/helpers/*"
+ "**/helpers/*",
],
static_libs: [
"flickerlib",
"flickertestapplib",
"truth-prebuilt",
- "app-helpers-core"
+ "app-helpers-core",
],
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index a540dffb3c9c..64cb790d324b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -14,210 +14,157 @@
* limitations under the License.
*/
+@file:JvmName("CommonAssertions")
package com.android.server.wm.flicker
-import android.platform.helpers.IAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.NAV_BAR_LAYER_NAME
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.NAV_BAR_WINDOW_NAME
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_LAYER_NAME
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_WINDOW_NAME
+import com.android.server.wm.traces.common.FlickerComponentName
-val HOME_WINDOW_TITLE = arrayOf("Wallpaper", "Launcher")
+val LAUNCHER_COMPONENT = FlickerComponentName("com.google.android.apps.nexuslauncher",
+ "com.google.android.apps.nexuslauncher.NexusLauncherActivity")
-fun FlickerTestParameter.statusBarWindowIsAlwaysVisible() {
- assertWm {
- this.showsAboveAppWindow(STATUS_BAR_WINDOW_NAME)
- }
-}
-
-fun FlickerTestParameter.navBarWindowIsAlwaysVisible() {
- assertWm {
- this.showsAboveAppWindow(NAV_BAR_WINDOW_NAME)
- }
-}
-
-fun FlickerTestParameter.launcherReplacesAppWindowAsTopWindow(testApp: IAppHelper) {
- assertWm {
- this.showsAppWindowOnTop(testApp.getPackage())
- .then()
- .showsAppWindowOnTop(*HOME_WINDOW_TITLE)
- }
-}
-
-fun FlickerTestParameter.launcherWindowBecomesVisible() {
- assertWm {
- this.hidesBelowAppWindow(*HOME_WINDOW_TITLE)
- .then()
- .showsBelowAppWindow(*HOME_WINDOW_TITLE)
- }
-}
-
-fun FlickerTestParameter.launcherWindowBecomesInvisible() {
- assertWm {
- this.showsBelowAppWindow(*HOME_WINDOW_TITLE)
- .then()
- .hidesBelowAppWindow(*HOME_WINDOW_TITLE)
- }
-}
-
-fun FlickerTestParameter.appWindowAlwaysVisibleOnTop(packageName: String) {
- assertWm {
- this.showsAppWindowOnTop(packageName)
- }
-}
-
-fun FlickerTestParameter.appWindowBecomesVisible(appName: String) {
+/**
+ * Checks that [FlickerComponentName.STATUS_BAR] window is visible and above the app windows in
+ * all WM trace entries
+ */
+fun FlickerTestParameter.statusBarWindowIsVisible() {
assertWm {
- this.hidesAppWindow(appName)
- .then()
- .showsAppWindow(appName)
+ this.isAboveAppWindowVisible(FlickerComponentName.STATUS_BAR)
}
}
-fun FlickerTestParameter.appWindowBecomesInVisible(appName: String) {
+/**
+ * Checks that [FlickerComponentName.NAV_BAR] window is visible and above the app windows in
+ * all WM trace entries
+ */
+fun FlickerTestParameter.navBarWindowIsVisible() {
assertWm {
- this.showsAppWindow(appName)
- .then()
- .hidesAppWindow(appName)
+ this.isAboveAppWindowVisible(FlickerComponentName.NAV_BAR)
}
}
+/**
+ * If [allStates] is true, checks if the stack space of all displays is fully covered
+ * by any visible layer, during the whole transitions
+ *
+ * Otherwise, checks if the stack space of all displays is fully covered
+ * by any visible layer, at the start and end of the transition
+ *
+ * @param allStates if all states should be checked, othersie, just initial and final
+ */
@JvmOverloads
-fun FlickerTestParameter.noUncoveredRegions(
- beginRotation: Int,
- endRotation: Int = beginRotation,
- allStates: Boolean = true
-) {
- val startingBounds = WindowUtils.getDisplayBounds(beginRotation)
- val endingBounds = WindowUtils.getDisplayBounds(endRotation)
+fun FlickerTestParameter.entireScreenCovered(allStates: Boolean = true) {
if (allStates) {
assertLayers {
- if (startingBounds == endingBounds) {
- this.coversAtLeast(startingBounds)
- } else {
- this.coversAtLeast(startingBounds)
- .then()
- .coversAtLeast(endingBounds)
+ this.invoke("entireScreenCovered") { entry ->
+ entry.entry.displays.forEach { display ->
+ entry.visibleRegion().coversAtLeast(display.layerStackSpace)
+ }
}
}
} else {
assertLayersStart {
- this.visibleRegion().coversAtLeast(startingBounds)
+ this.entry.displays.forEach { display ->
+ this.visibleRegion().coversAtLeast(display.layerStackSpace)
+ }
}
assertLayersEnd {
- this.visibleRegion().coversAtLeast(endingBounds)
+ this.entry.displays.forEach { display ->
+ this.visibleRegion().coversAtLeast(display.layerStackSpace)
+ }
}
}
}
-@JvmOverloads
-fun FlickerTestParameter.navBarLayerIsAlwaysVisible(rotatesScreen: Boolean = false) {
- if (rotatesScreen) {
- assertLayers {
- this.isVisible(NAV_BAR_LAYER_NAME)
- .then()
- .isInvisible(NAV_BAR_LAYER_NAME)
- .then()
- .isVisible(NAV_BAR_LAYER_NAME)
- }
- } else {
- assertLayers {
- this.isVisible(NAV_BAR_LAYER_NAME)
- }
+/**
+ * Checks that [FlickerComponentName.NAV_BAR] layer is visible at the start and end of the SF
+ * trace
+ */
+fun FlickerTestParameter.navBarLayerIsVisible() {
+ assertLayersStart {
+ this.isVisible(FlickerComponentName.NAV_BAR)
}
-}
-
-@JvmOverloads
-fun FlickerTestParameter.statusBarLayerIsAlwaysVisible(rotatesScreen: Boolean = false) {
- if (rotatesScreen) {
- assertLayers {
- this.isVisible(STATUS_BAR_LAYER_NAME)
- .then()
- .isInvisible(STATUS_BAR_LAYER_NAME)
- .then()
- .isVisible(STATUS_BAR_LAYER_NAME)
- }
- } else {
- assertLayers {
- this.isVisible(STATUS_BAR_LAYER_NAME)
- }
+ assertLayersEnd {
+ this.isVisible(FlickerComponentName.NAV_BAR)
}
}
-@JvmOverloads
-fun FlickerTestParameter.navBarLayerRotatesAndScales(
- beginRotation: Int,
- endRotation: Int = beginRotation
-) {
- val startingPos = WindowUtils.getNavigationBarPosition(beginRotation)
- val endingPos = WindowUtils.getNavigationBarPosition(endRotation)
-
+/**
+ * Checks that [FlickerComponentName.STATUS_BAR] layer is visible at the start and end of the SF
+ * trace
+ */
+fun FlickerTestParameter.statusBarLayerIsVisible() {
assertLayersStart {
- this.visibleRegion(NAV_BAR_LAYER_NAME).coversExactly(startingPos)
+ this.isVisible(FlickerComponentName.STATUS_BAR)
}
assertLayersEnd {
- this.visibleRegion(NAV_BAR_LAYER_NAME).coversExactly(endingPos)
+ this.isVisible(FlickerComponentName.STATUS_BAR)
}
}
-@JvmOverloads
-fun FlickerTestParameter.statusBarLayerRotatesScales(
- beginRotation: Int,
- endRotation: Int = beginRotation
-) {
- val startingPos = WindowUtils.getStatusBarPosition(beginRotation)
- val endingPos = WindowUtils.getStatusBarPosition(endRotation)
-
+fun FlickerTestParameter.navBarLayerRotatesAndScales() {
assertLayersStart {
- this.visibleRegion(STATUS_BAR_LAYER_NAME).coversExactly(startingPos)
+ val display = this.entry.displays.minByOrNull { it.id }
+ ?: throw RuntimeException("There is no display!")
+ this.visibleRegion(FlickerComponentName.NAV_BAR)
+ .coversExactly(WindowUtils.getNavigationBarPosition(display))
}
assertLayersEnd {
- this.visibleRegion(STATUS_BAR_LAYER_NAME).coversExactly(endingPos)
+ val display = this.entry.displays.minByOrNull { it.id }
+ ?: throw RuntimeException("There is no display!")
+ this.visibleRegion(FlickerComponentName.NAV_BAR)
+ .coversExactly(WindowUtils.getNavigationBarPosition(display))
}
}
-fun FlickerTestParameter.appLayerReplacesLauncher(appName: String) {
- assertLayers {
- this.isVisible(*HOME_WINDOW_TITLE)
- .then()
- .isVisible(appName)
+fun FlickerTestParameter.statusBarLayerRotatesScales() {
+ assertLayersStart {
+ val display = this.entry.displays.minByOrNull { it.id }
+ ?: throw RuntimeException("There is no display!")
+ this.visibleRegion(FlickerComponentName.STATUS_BAR)
+ .coversExactly(WindowUtils.getStatusBarPosition(display))
}
-}
-
-fun FlickerTestParameter.launcherLayerReplacesApp(testApp: IAppHelper) {
- assertLayers {
- this.isVisible(testApp.getPackage())
- .then()
- .isInvisible(testApp.getPackage())
- .isVisible(*HOME_WINDOW_TITLE)
+ assertLayersEnd {
+ val display = this.entry.displays.minByOrNull { it.id }
+ ?: throw RuntimeException("There is no display!")
+ this.visibleRegion(FlickerComponentName.STATUS_BAR)
+ .coversExactly(WindowUtils.getStatusBarPosition(display))
}
}
-fun FlickerTestParameter.layerBecomesVisible(packageName: String) {
+/**
+ * Asserts that:
+ * [originalLayer] is visible at the start of the trace
+ * [originalLayer] becomes invisible during the trace and (in the same entry) [newLayer]
+ * becomes visible
+ * [newLayer] remains visible until the end of the trace
+ *
+ * @param originalLayer Layer that should be visible at the start
+ * @param newLayer Layer that should be visible at the end
+ * @param ignoreSnapshot If the snapshot layer should be ignored during the transition
+ * (useful mostly for app launch)
+ */
+fun FlickerTestParameter.replacesLayer(
+ originalLayer: FlickerComponentName,
+ newLayer: FlickerComponentName,
+ ignoreSnapshot: Boolean = false
+) {
assertLayers {
- this.isInvisible(packageName)
- .then()
- .isVisible(packageName)
+ val assertion = this.isVisible(originalLayer)
+ if (ignoreSnapshot) {
+ assertion.then()
+ .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
+ }
+ assertion.then().isVisible(newLayer)
}
-}
-fun FlickerTestParameter.layerBecomesInvisible(packageName: String) {
- assertLayers {
- this.isVisible(packageName)
- .then()
- .isInvisible(packageName)
+ assertLayersStart {
+ this.isVisible(originalLayer)
+ .isInvisible(newLayer)
}
-}
-fun FlickerTestParameter.focusChanges(vararg windows: String) {
- assertEventLog {
- this.focusChanges(windows)
+ assertLayersEnd {
+ this.isInvisible(originalLayer)
+ .isVisible(newLayer)
}
}
-
-fun FlickerTestParameter.focusDoesNotChange() {
- assertEventLog {
- this.focusDoesNotChange()
- }
-} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index 71184c2e0aa2..9f26c31a6d63 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -1,3 +1,4 @@
+
/*
* Copyright (C) 2020 The Android Open Source Project
*
@@ -16,26 +17,53 @@
package com.android.server.wm.flicker.close
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.FixMethodOrder
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
* Test app closes by pressing back button
+ *
* To run this test: `atest FlickerTests:CloseAppBackButtonTest`
+ *
+ * Actions:
+ * Make sure no apps are running on the device
+ * Launch an app [testApp] and wait animation to complete
+ * Press back button
+ *
+ * To run only the presubmit assertions add: `--
+ * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
+ * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
+ *
+ * To run only the postsubmit assertions add: `--
+ * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
+ * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
+ *
+ * To run only the flaky assertions add: `--
+ * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [CloseAppTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
+@Group4
class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) {
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = {
@@ -46,7 +74,18 @@ class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio
}
}
+ /** {@inheritDoc} */
+ @FlakyTest
+ @Test
+ override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
+
companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): List<FlickerTestParameter> {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index 6786279ae107..795766fccfbd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -16,26 +16,53 @@
package com.android.server.wm.flicker.close
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.FixMethodOrder
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test app closes by pressing home button.
+ * Test app closes by pressing home button
+ *
* To run this test: `atest FlickerTests:CloseAppHomeButtonTest`
+ *
+ * Actions:
+ * Make sure no apps are running on the device
+ * Launch an app [testApp] and wait animation to complete
+ * Press home button
+ *
+ * To run only the presubmit assertions add: `--
+ * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
+ * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
+ *
+ * To run only the postsubmit assertions add: `--
+ * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
+ * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
+ *
+ * To run only the flaky assertions add: `--
+ * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [CloseAppTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
+@Group4
class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) {
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = {
@@ -46,7 +73,18 @@ class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio
}
}
+ /** {@inheritDoc} */
+ @FlakyTest
+ @Test
+ override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
+
companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index f7f977d7bd0a..511fc26fdb38 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -18,31 +18,35 @@ package com.android.server.wm.flicker.close
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.launcherReplacesAppWindowAsTopWindow
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.launcherLayerReplacesApp
-import com.android.server.wm.flicker.launcherWindowBecomesVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.flicker.replacesLayer
import org.junit.Test
+/**
+ * Base test class for transitions that close an app back to the launcher screen
+ */
abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) {
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
+
+ /**
+ * Specification of the test transition to execute
+ */
protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = {
setup {
eachRun {
@@ -57,6 +61,10 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter)
}
}
+ /**
+ * Entry point for the test runner. It will use this method to initialize and cache
+ * flicker executions
+ */
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
@@ -64,42 +72,60 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter)
}
}
+ /**
+ * Checks that the navigation bar window is visible during the whole transition
+ */
@Presubmit
@Test
- open fun navBarWindowIsAlwaysVisible() {
- testSpec.navBarWindowIsAlwaysVisible()
+ open fun navBarWindowIsVisible() {
+ testSpec.navBarWindowIsVisible()
}
+ /**
+ * Checks that the status bar window is visible during the whole transition
+ */
@Presubmit
@Test
- open fun statusBarWindowIsAlwaysVisible() {
- testSpec.statusBarWindowIsAlwaysVisible()
+ open fun statusBarWindowIsVisible() {
+ testSpec.statusBarWindowIsVisible()
}
- @FlakyTest
+ /**
+ * Checks that the navigation bar layer is visible during the whole transition
+ */
+ @Presubmit
@Test
- open fun navBarLayerIsAlwaysVisible() {
- testSpec.navBarLayerIsAlwaysVisible(rotatesScreen = testSpec.isRotated)
+ open fun navBarLayerIsVisible() {
+ testSpec.navBarLayerIsVisible()
}
+ /**
+ * Checks that the status bar layer is visible during the whole transition
+ */
@Presubmit
@Test
- open fun statusBarLayerIsAlwaysVisible() {
- testSpec.statusBarLayerIsAlwaysVisible(rotatesScreen = testSpec.isRotated)
+ open fun statusBarLayerIsVisible() {
+ testSpec.statusBarLayerIsVisible()
}
- @FlakyTest
+ /**
+ * Checks the position of the navigation bar at the start and end of the transition
+ */
+ @Presubmit
@Test
- open fun navBarLayerRotatesAndScales() {
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
- }
+ open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
+ /**
+ * Checks the position of the status bar at the start and end of the transition
+ */
@Presubmit
@Test
- open fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
- }
+ open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
+ /**
+ * Checks that all windows that are visible on the trace, are visible for at least 2
+ * consecutive entries.
+ */
@Presubmit
@Test
open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
@@ -108,6 +134,10 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter)
}
}
+ /**
+ * Checks that all layers that are visible on the trace, are visible for at least 2
+ * consecutive entries.
+ */
@Presubmit
@Test
open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
@@ -116,27 +146,47 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter)
}
}
+ /**
+ * Checks that all parts of the screen are covered during the transition
+ */
@Presubmit
@Test
- open fun noUncoveredRegions() {
- testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0)
- }
+ open fun entireScreenCovered() = testSpec.entireScreenCovered()
+ /**
+ * Checks that [testApp] is the top visible app window at the start of the transition and
+ * that it is replaced by [LAUNCHER_COMPONENT] during the transition
+ */
@Presubmit
@Test
open fun launcherReplacesAppWindowAsTopWindow() {
- testSpec.launcherReplacesAppWindowAsTopWindow(testApp)
+ testSpec.assertWm {
+ this.isAppWindowOnTop(testApp.component)
+ .then()
+ .isAppWindowOnTop(LAUNCHER_COMPONENT)
+ }
}
+ /**
+ * Checks that [LAUNCHER_COMPONENT] is invisible at the start of the transition and that
+ * it becomes visible during the transition
+ */
@Presubmit
@Test
open fun launcherWindowBecomesVisible() {
- testSpec.launcherWindowBecomesVisible()
+ testSpec.assertWm {
+ this.isAppWindowNotOnTop(LAUNCHER_COMPONENT)
+ .then()
+ .isAppWindowOnTop(LAUNCHER_COMPONENT)
+ }
}
+ /**
+ * Checks that [LAUNCHER_COMPONENT] layer becomes visible when [testApp] becomes invisible
+ */
@Presubmit
@Test
open fun launcherLayerReplacesApp() {
- testSpec.launcherLayerReplacesApp(testApp)
+ testSpec.replacesLayer(testApp.component, LAUNCHER_COMPONENT)
}
} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
index fad25b4fa0b9..75900df978df 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+@file:JvmName("FlickerExtensions")
package com.android.server.wm.flicker.helpers
import com.android.server.wm.flicker.Flicker
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
index bd7c1855fea9..0b1748a6bda4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
@@ -17,9 +17,10 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.content.ComponentName
import androidx.test.uiautomator.UiDevice
import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
class ImeAppAutoFocusHelper @JvmOverloads constructor(
@@ -27,7 +28,8 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor(
private val rotation: Int,
private val imePackageName: String = IME_PACKAGE,
launcherName: String = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_LAUNCHER_NAME,
- component: ComponentName = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME
+ component: FlickerComponentName =
+ ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
) : ImeAppHelper(instr, launcherName, component) {
override fun openIME(
device: UiDevice,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
index 83fddae5b1a7..1c2164a70a55 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
@@ -17,19 +17,21 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.content.ComponentName
import android.support.test.launcherhelper.ILauncherStrategy
import android.support.test.launcherhelper.LauncherStrategyFactory
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
open class ImeAppHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.IME_ACTIVITY_LAUNCHER_NAME,
- component: ComponentName = ActivityOptions.IME_ACTIVITY_COMPONENT_NAME,
+ component: FlickerComponentName =
+ ActivityOptions.IME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
@@ -61,7 +63,8 @@ open class ImeAppHelper @JvmOverloads constructor(
if (wmHelper == null) {
device.waitForIdle()
} else {
- wmHelper.waitImeWindowShown()
+ wmHelper.waitImeShown()
+ wmHelper.waitForAppTransitionIdle()
}
}
@@ -78,7 +81,7 @@ open class ImeAppHelper @JvmOverloads constructor(
if (wmHelper == null) {
device.waitForIdle()
} else {
- wmHelper.waitImeWindowGone()
+ wmHelper.waitImeGone()
}
}
} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
new file mode 100644
index 000000000000..be68704fc32d
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.flicker.helpers
+
+import android.app.Instrumentation
+import android.support.test.launcherhelper.ILauncherStrategy
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+
+class NewTasksAppHelper @JvmOverloads constructor(
+ instr: Instrumentation,
+ launcherName: String = ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME,
+ component: FlickerComponentName =
+ ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+ .getInstance(instr)
+ .launcherStrategy
+) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ fun openNewTask(device: UiDevice, wmHelper: WindowManagerStateHelper) {
+ val button = device.wait(
+ Until.findObject(By.res(getPackage(), "launch_new_task")),
+ FIND_TIMEOUT)
+
+ require(button != null) {
+ "Button not found, this usually happens when the device " +
+ "was left in an unknown state (e.g. in split screen)"
+ }
+ button.click()
+ wmHelper.waitForAppTransitionIdle()
+ wmHelper.waitForFullScreenApp(component)
+ }
+} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
new file mode 100644
index 000000000000..f7ca5ce1c001
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.flicker.helpers
+
+import android.app.Instrumentation
+import android.support.test.launcherhelper.ILauncherStrategy
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
+
+class NonResizeableAppHelper @JvmOverloads constructor(
+ instr: Instrumentation,
+ launcherName: String = ActivityOptions.NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME,
+ component: FlickerComponentName =
+ ActivityOptions.NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+ .getInstance(instr)
+ .launcherStrategy
+) : StandardAppHelper(instr, launcherName, component, launcherStrategy) \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
index 02be3cf0a8a3..7bab981ce231 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
@@ -17,15 +17,17 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.content.ComponentName
import android.support.test.launcherhelper.ILauncherStrategy
import android.support.test.launcherhelper.LauncherStrategyFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
class SeamlessRotationAppHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.SEAMLESS_ACTIVITY_LAUNCHER_NAME,
- component: ComponentName = ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME,
+ component: FlickerComponentName =
+ ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
index d7cbaaee2627..f6a8817e5b08 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
@@ -17,15 +17,17 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.content.ComponentName
import android.support.test.launcherhelper.ILauncherStrategy
import android.support.test.launcherhelper.LauncherStrategyFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
class SimpleAppHelper @JvmOverloads constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.SIMPLE_ACTIVITY_LAUNCHER_NAME,
- component: ComponentName = ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME,
+ component: FlickerComponentName =
+ ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent(),
launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
.getInstance(instr)
.launcherStrategy
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
new file mode 100644
index 000000000000..59e8dc826007
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.flicker.helpers
+
+import android.app.Instrumentation
+import android.support.test.launcherhelper.ILauncherStrategy
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+
+class TwoActivitiesAppHelper @JvmOverloads constructor(
+ instr: Instrumentation,
+ launcherName: String = ActivityOptions.BUTTON_ACTIVITY_LAUNCHER_NAME,
+ component: FlickerComponentName =
+ ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+ .getInstance(instr)
+ .launcherStrategy
+) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ fun openSecondActivity(device: UiDevice, wmHelper: WindowManagerStateHelper) {
+ val button = device.wait(
+ Until.findObject(By.res(getPackage(), "launch_second_activity")),
+ FIND_TIMEOUT)
+
+ require(button != null) {
+ "Button not found, this usually happens when the device " +
+ "was left in an unknown state (e.g. in split screen)"
+ }
+ button.click()
+ wmHelper.waitForAppTransitionIdle()
+ wmHelper.waitForFullScreenApp(component)
+ }
+} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index b5757fd21ee0..5e21aff94769 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -18,8 +18,8 @@ package com.android.server.wm.flicker.ime
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
+import android.view.Surface
import android.view.WindowManagerPolicyConstants
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -28,16 +28,16 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -46,6 +46,14 @@ import org.junit.runners.Parameterized
/**
* Test IME window closing back to app window transitions.
+ *
+ * This test doesn't work on 90 degrees. According to the InputMethodService documentation:
+ *
+ * Don't show if this is not explicitly requested by the user and the input method
+ * is fullscreen. That would be too disruptive.
+ *
+ * More details on b/190352379
+ *
* To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToAppTest`
*/
@RequiresDevice
@@ -79,60 +87,78 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
testSpec.assertWm {
- this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE,
- WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME))
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry()
}
}
@Presubmit
@Test
- fun imeAppWindowIsAlwaysVisible() = testSpec.imeAppWindowIsAlwaysVisible(testApp)
+ fun imeAppWindowIsAlwaysVisible() {
+ testSpec.assertWm {
+ this.isAppWindowOnTop(testApp.component)
+ }
+ }
@Presubmit
@Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
+ fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
@Presubmit
@Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
+ fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
- @FlakyTest
+ @Presubmit
@Test
- fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation)
+ fun entireScreenCovered() = testSpec.entireScreenCovered()
@Presubmit
@Test
- fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+ fun imeLayerVisibleStart() {
+ testSpec.assertLayersStart {
+ this.isVisible(FlickerComponentName.IME)
+ }
+ }
@Presubmit
@Test
- fun imeAppLayerIsAlwaysVisible() = testSpec.imeAppLayerIsAlwaysVisible(testApp)
+ fun imeLayerInvisibleEnd() {
+ testSpec.assertLayersEnd {
+ this.isInvisible(FlickerComponentName.IME)
+ }
+ }
- @FlakyTest
+ @Presubmit
@Test
- fun navBarLayerRotatesAndScales() {
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
- }
+ fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
- @FlakyTest
+ @Presubmit
@Test
- fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation)
+ fun imeAppLayerIsAlwaysVisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ }
}
@Presubmit
@Test
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
+
+ @Presubmit
+ @Test
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
+
+ @Presubmit
+ @Test
fun visibleLayersShownMoreThanOneConsecutiveEntry() {
testSpec.assertLayers {
this.visibleLayersShownMoreThanOneConsecutiveEntry()
@@ -145,8 +171,11 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(repetitions = 5,
+ // b/190352379 (IME doesn't show on app launch in 90 degrees)
+ supportedRotations = listOf(Surface.ROTATION_0),
supportedNavigationModes = listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY)
+ WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+ WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index 549e44c511b9..0582685f2c54 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -30,15 +30,15 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,6 +47,14 @@ import org.junit.runners.Parameterized
/**
* Test IME window closing back to app window transitions.
+ *
+ * This test doesn't work on 90 degrees. According to the InputMethodService documentation:
+ *
+ * Don't show if this is not explicitly requested by the user and the input method
+ * is fullscreen. That would be too disruptive.
+ *
+ * More details on b/190352379
+ *
* To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToHomeTest`
*/
@RequiresDevice
@@ -75,76 +83,94 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete
transitions {
device.pressHome()
wmHelper.waitForHomeActivityVisible()
- wmHelper.waitImeWindowGone()
+ wmHelper.waitImeGone()
}
}
}
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
testSpec.assertWm {
- this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE,
- WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME))
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry()
}
}
- @FlakyTest
+ @FlakyTest(bugId = 190189685)
@Test
- fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible()
+ fun imeAppWindowBecomesInvisible() {
+ testSpec.assertWm {
+ this.isAppWindowOnTop(testApp.component)
+ .then()
+ .isAppWindowNotOnTop(testApp.component)
+ }
+ }
- @FlakyTest
+ @Presubmit
@Test
- fun imeAppWindowBecomesInvisible() = testSpec.imeAppWindowBecomesInvisible(testApp)
+ fun entireScreenCovered() = testSpec.entireScreenCovered()
@Presubmit
@Test
- fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation,
- Surface.ROTATION_0)
+ fun imeLayerVisibleStart() {
+ testSpec.assertLayersStart {
+ this.isVisible(FlickerComponentName.IME)
+ }
+ }
- @FlakyTest
+ @Presubmit
@Test
- fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+ fun imeLayerInvisibleEnd() {
+ testSpec.assertLayersEnd {
+ this.isInvisible(FlickerComponentName.IME)
+ }
+ }
@Presubmit
@Test
- fun imeAppLayerBecomesInvisible() = testSpec.imeAppLayerBecomesInvisible(testApp)
+ fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
- @FlakyTest
+ @Presubmit
@Test
- fun navBarLayerRotatesAndScales() {
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
+ fun imeAppLayerBecomesInvisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ .then()
+ .isInvisible(testApp.component)
+ }
}
@Presubmit
@Test
- fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
- }
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
- @FlakyTest
+ @Presubmit
+ @Test
+ fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
+
+ @Presubmit
@Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
+ fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
@Presubmit
@Test
fun visibleLayersShownMoreThanOneConsecutiveEntry() {
testSpec.assertLayers {
- this.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(IME_WINDOW_TITLE, WindowManagerStateHelper.SPLASH_SCREEN_NAME))
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(listOf(
+ FlickerComponentName.IME,
+ FlickerComponentName.SPLASH_SCREEN))
}
}
@@ -154,8 +180,11 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(repetitions = 1,
+ // b/190352379 (IME doesn't show on app launch in 90 degrees)
+ supportedRotations = listOf(Surface.ROTATION_0),
supportedNavigationModes = listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY)
+ WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+ WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index 82ca074b5ef2..91b3d3dae3cd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -28,15 +28,14 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.ImeAppHelper
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.Assume
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -61,7 +60,7 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) {
return FlickerBuilder(instrumentation).apply {
setup {
test {
- testApp.launchViaIntent()
+ testApp.launchViaIntent(wmHelper)
}
eachRun {
testApp.openIME(device, wmHelper)
@@ -80,57 +79,60 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) {
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
testSpec.assertWm {
- this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE,
- WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME))
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(
+ FlickerComponentName.IME,
+ FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT))
}
}
@Presubmit
@Test
- fun imeAppWindowIsAlwaysVisible() = testSpec.imeAppWindowIsAlwaysVisible(testApp)
+ fun imeAppWindowIsAlwaysVisible() {
+ testSpec.assertWm {
+ this.isAppWindowOnTop(testApp.component)
+ }
+ }
@Presubmit
@Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
+ fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
@Presubmit
@Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
+ fun statusBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
@Presubmit
@Test
- fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation)
+ fun entireScreenCovered() = testSpec.entireScreenCovered()
@Presubmit
@Test
fun navBarLayerRotatesAndScales() {
Assume.assumeFalse(testSpec.isRotated)
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
+ testSpec.navBarLayerRotatesAndScales()
}
@FlakyTest
@Test
fun navBarLayerRotatesAndScales_Flaky() {
Assume.assumeTrue(testSpec.isRotated)
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
+ testSpec.navBarLayerRotatesAndScales()
}
@Presubmit
@Test
- fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation)
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
@@ -146,7 +148,11 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) {
@Presubmit
@Test
- fun imeAppLayerIsAlwaysVisible() = testSpec.imeAppLayerIsAlwaysVisible(testApp)
+ fun imeAppLayerIsAlwaysVisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ }
+ }
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 703e4a125440..b589969dee14 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -30,14 +30,13 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.ImeAppHelper
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -68,7 +67,7 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) {
transitions {
device.pressHome()
wmHelper.waitForHomeActivityVisible()
- wmHelper.waitImeWindowGone()
+ wmHelper.waitImeGone()
}
teardown {
eachRun {
@@ -84,19 +83,20 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) {
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
testSpec.assertWm {
- this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE,
- WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME))
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(
+ FlickerComponentName.IME,
+ FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT))
}
}
@@ -106,20 +106,25 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) {
@FlakyTest
@Test
- fun imeAppWindowBecomesInvisible() = testSpec.imeAppWindowBecomesInvisible(testApp)
+ fun imeAppWindowBecomesInvisible() {
+ testSpec.assertWm {
+ this.isAppWindowVisible(testApp.component)
+ .then()
+ .isAppWindowInvisible(testApp.component)
+ }
+ }
@Presubmit
@Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
+ fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
@Presubmit
@Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
+ fun statusBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
@Presubmit
@Test
- fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation,
- Surface.ROTATION_0)
+ fun entireScreenCovered() = testSpec.entireScreenCovered()
@Presubmit
@Test
@@ -127,25 +132,29 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) {
@Presubmit
@Test
- fun imeAppLayerBecomesInvisible() = testSpec.imeAppLayerBecomesInvisible(testApp)
+ fun imeAppLayerBecomesInvisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp.component)
+ .then()
+ .isInvisible(testApp.component)
+ }
+ }
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() =
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
fun visibleLayersShownMoreThanOneConsecutiveEntry() {
testSpec.assertLayers {
- this.visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(IME_WINDOW_TITLE, WindowManagerStateHelper.SPLASH_SCREEN_NAME))
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(listOf(
+ FlickerComponentName.IME,
+ FlickerComponentName.SPLASH_SCREEN))
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
index 7e34469b8188..ba78e25580ec 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
@@ -14,128 +14,56 @@
* limitations under the License.
*/
+@file:JvmName("CommonAssertions")
package com.android.server.wm.flicker.ime
-import android.platform.helpers.IAppHelper
import com.android.server.wm.flicker.FlickerTestParameter
-
-const val IME_WINDOW_TITLE = "InputMethod"
-
-fun FlickerTestParameter.imeLayerIsAlwaysVisible(rotatesScreen: Boolean = false) {
- if (rotatesScreen) {
- assertLayers {
- this.isVisible(IME_WINDOW_TITLE)
- .then()
- .isInvisible(IME_WINDOW_TITLE)
- .then()
- .isVisible(IME_WINDOW_TITLE)
- }
- } else {
- assertLayers {
- this.isVisible(IME_WINDOW_TITLE)
- }
- }
-}
+import com.android.server.wm.traces.common.FlickerComponentName
fun FlickerTestParameter.imeLayerBecomesVisible() {
assertLayers {
- this.isInvisible(IME_WINDOW_TITLE)
+ this.isInvisible(FlickerComponentName.IME)
.then()
- .isVisible(IME_WINDOW_TITLE)
+ .isVisible(FlickerComponentName.IME)
}
}
fun FlickerTestParameter.imeLayerBecomesInvisible() {
assertLayers {
- this.isVisible(IME_WINDOW_TITLE)
+ this.isVisible(FlickerComponentName.IME)
.then()
- .isInvisible(IME_WINDOW_TITLE)
- }
-}
-
-fun FlickerTestParameter.imeAppLayerIsAlwaysVisible(testApp: IAppHelper) {
- assertLayers {
- this.isVisible(testApp.getPackage())
- }
-}
-
-fun FlickerTestParameter.imeAppWindowIsAlwaysVisible(testApp: IAppHelper) {
- assertWm {
- this.showsAppWindowOnTop(testApp.getPackage())
+ .isInvisible(FlickerComponentName.IME)
}
}
fun FlickerTestParameter.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) {
if (rotatesScreen) {
assertWm {
- this.showsNonAppWindow(IME_WINDOW_TITLE)
+ this.isNonAppWindowVisible(FlickerComponentName.IME)
.then()
- .hidesNonAppWindow(IME_WINDOW_TITLE)
+ .isNonAppWindowInvisible(FlickerComponentName.IME)
.then()
- .showsNonAppWindow(IME_WINDOW_TITLE)
+ .isNonAppWindowVisible(FlickerComponentName.IME)
}
} else {
assertWm {
- this.showsNonAppWindow(IME_WINDOW_TITLE)
+ this.isNonAppWindowVisible(FlickerComponentName.IME)
}
}
}
fun FlickerTestParameter.imeWindowBecomesVisible() {
assertWm {
- this.hidesNonAppWindow(IME_WINDOW_TITLE)
+ this.isNonAppWindowInvisible(FlickerComponentName.IME)
.then()
- .showsNonAppWindow(IME_WINDOW_TITLE)
+ .isNonAppWindowVisible(FlickerComponentName.IME)
}
}
fun FlickerTestParameter.imeWindowBecomesInvisible() {
assertWm {
- this.showsNonAppWindow(IME_WINDOW_TITLE)
+ this.isNonAppWindowVisible(FlickerComponentName.IME)
.then()
- .hidesNonAppWindow(IME_WINDOW_TITLE)
+ .isNonAppWindowInvisible(FlickerComponentName.IME)
}
}
-
-fun FlickerTestParameter.imeAppWindowIsAlwaysVisible(
- testApp: IAppHelper,
- rotatesScreen: Boolean = false
-) {
- if (rotatesScreen) {
- assertWm {
- this.showsAppWindow(testApp.getPackage())
- .then()
- .hidesAppWindow(testApp.getPackage())
- .then()
- .showsAppWindow(testApp.getPackage())
- }
- } else {
- assertWm {
- this.showsAppWindow(testApp.getPackage())
- }
- }
-}
-
-fun FlickerTestParameter.imeAppWindowBecomesVisible(windowName: String) {
- assertWm {
- this.hidesAppWindow(windowName)
- .then()
- .showsAppWindow(windowName)
- }
-}
-
-fun FlickerTestParameter.imeAppWindowBecomesInvisible(testApp: IAppHelper) {
- assertWm {
- this.showsAppWindowOnTop(testApp.getPackage())
- .then()
- .appWindowNotOnTop(testApp.getPackage())
- }
-}
-
-fun FlickerTestParameter.imeAppLayerBecomesInvisible(testApp: IAppHelper) {
- assertLayers {
- this.isVisible(testApp.getPackage())
- .then()
- .isInvisible(testApp.getPackage())
- }
-} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
new file mode 100644
index 000000000000..a9568b325af2
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.flicker.ime
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Launch an app that automatically displays the IME
+ *
+ * To run this test: `atest FlickerTests:LaunchAppShowImeOnStartTest`
+ *
+ * Actions:
+ * Make sure no apps are running on the device
+ * Launch an app [testApp] that automatically displays IME and wait animation to complete
+ *
+ * To run only the presubmit assertions add: `--
+ * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
+ * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
+ *
+ * To run only the postsubmit assertions add: `--
+ * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
+ * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
+ *
+ * To run only the flaky assertions add: `--
+ * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [CloseAppTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation)
+
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ setup {
+ eachRun {
+ this.setRotation(testSpec.config.startRotation)
+ }
+ }
+ teardown {
+ eachRun {
+ testApp.exit()
+ }
+ }
+ transitions {
+ testApp.launchViaIntent(wmHelper)
+ wmHelper.waitImeShown()
+ }
+ }
+ }
+
+ /**
+ * Checks that [FlickerComponentName.IME] window becomes visible during the transition
+ */
+ @Presubmit
+ @Test
+ fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
+
+ /**
+ * Checks that [FlickerComponentName.IME] layer becomes visible during the transition
+ */
+ @Presubmit
+ @Test
+ fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
+
+ /**
+ * Checks that [FlickerComponentName.IME] layer is invisible at the start of the transition
+ */
+ @Presubmit
+ @Test
+ fun imeLayerNotExistsStart() {
+ testSpec.assertLayersStart {
+ this.isInvisible(FlickerComponentName.IME)
+ }
+ }
+
+ /**
+ * Checks that [FlickerComponentName.IME] layer is visible at the end of the transition
+ */
+ @Presubmit
+ @Test
+ fun imeLayerExistsEnd() {
+ testSpec.assertLayersEnd {
+ this.isVisible(FlickerComponentName.IME)
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(
+ repetitions = 5,
+ supportedRotations = listOf(Surface.ROTATION_0),
+ supportedNavigationModes = listOf(
+ WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+ WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+ )
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index cae1b16c1c8c..7bf0186cd857 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -20,6 +20,7 @@ import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -28,16 +29,14 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.helpers.ImeAppHelper
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.appWindowAlwaysVisibleOnTop
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -81,11 +80,11 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) {
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
@@ -93,19 +92,23 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) {
@Presubmit
@Test
- fun appWindowAlwaysVisibleOnTop() = testSpec.appWindowAlwaysVisibleOnTop(testApp.`package`)
+ fun appWindowAlwaysVisibleOnTop() {
+ testSpec.assertWm {
+ this.isAppWindowOnTop(testApp.component)
+ }
+ }
@Presubmit
@Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
+ fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
@Presubmit
@Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
+ fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
@Presubmit
@Test
- fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation)
+ fun entireScreenCovered() = testSpec.entireScreenCovered()
@Presubmit
@Test
@@ -115,21 +118,17 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) {
@Test
fun layerAlwaysVisible() {
testSpec.assertLayers {
- this.isVisible(testApp.`package`)
+ this.isVisible(testApp.component)
}
}
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() {
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
- }
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
@Presubmit
@Test
- fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation)
- }
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@Presubmit
@Test
@@ -139,7 +138,7 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) {
}
}
- @Presubmit
+ @FlakyTest
@Test
fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
testSpec.assertWm {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index b7673d5b0107..f6febe9e2234 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.ime
import android.app.Instrumentation
+import android.os.SystemProperties
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
@@ -26,23 +27,22 @@ import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.launcherWindowBecomesInvisible
-import com.android.server.wm.flicker.appLayerReplacesLauncher
+import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.endRotation
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -61,7 +61,8 @@ import org.junit.runners.Parameterized
class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation)
- private val testAppComponentName = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME
+ private val isShellTransitionsEnabled =
+ SystemProperties.getBoolean("persist.debug.shell_transit", false)
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
@@ -73,14 +74,14 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) {
}
eachRun {
device.pressRecentApps()
- wmHelper.waitImeWindowGone()
+ wmHelper.waitImeGone()
wmHelper.waitForAppTransitionIdle()
this.setRotation(testSpec.config.startRotation)
}
}
transitions {
device.reopenAppFromOverview(wmHelper)
- wmHelper.waitImeWindowShown()
+ wmHelper.waitImeShown()
}
teardown {
test {
@@ -92,72 +93,136 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) {
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
@Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
@Presubmit
@Test
fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ val component = FlickerComponentName("", "RecentTaskScreenshotSurface")
testSpec.assertWm {
- this.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ this.visibleWindowsShownMoreThanOneConsecutiveEntry(
+ ignoreWindows = listOf(FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT,
+ component)
+ )
}
}
@Presubmit
@Test
- fun launcherWindowBecomesInvisible() = testSpec.launcherWindowBecomesInvisible()
+ fun launcherWindowBecomesInvisible() {
+ testSpec.assertWm {
+ this.isAppWindowVisible(LAUNCHER_COMPONENT)
+ .then()
+ .isAppWindowInvisible(LAUNCHER_COMPONENT)
+ }
+ }
@Presubmit
@Test
- fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible(true)
+ fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled)
@Presubmit
@Test
- fun imeAppWindowIsAlwaysVisible() = testSpec.imeAppWindowIsAlwaysVisible(testApp, true)
+ fun imeAppWindowVisibilityLegacy() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ // the app starts visible in live tile, and stays visible for the duration of entering
+ // and exiting overview. However, legacy transitions seem to have a bug which causes
+ // everything to restart during the test, so expect the app to disappear and come back.
+ // Since we log 1x per frame, sometimes the activity visibility and the app visibility
+ // are updated together, sometimes not, thus ignore activity check at the start
+ testSpec.assertWm {
+ this.isAppWindowVisible(testApp.component)
+ .then()
+ .isAppWindowInvisible(testApp.component)
+ .then()
+ .isAppWindowVisible(testApp.component)
+ }
+ }
@Presubmit
@Test
- // During testing the launcher is always in portrait mode
- fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation,
- testSpec.config.endRotation)
+ fun imeAppWindowVisibility() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ // the app starts visible in live tile, and stays visible for the duration of entering
+ // and exiting overview. Since we log 1x per frame, sometimes the activity visibility
+ // and the app visibility are updated together, sometimes not, thus ignore activity
+ // check at the start
+ testSpec.assertWm {
+ this.isAppWindowVisible(testApp.component)
+ }
+ }
@Presubmit
@Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
+ // During testing the launcher is always in portrait mode
+ fun entireScreenCovered() = testSpec.entireScreenCovered()
@Presubmit
@Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
+ fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
@Presubmit
@Test
- fun imeLayerIsAlwaysVisible() = testSpec.imeLayerIsAlwaysVisible(true)
+ fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
@Presubmit
@Test
- fun appLayerReplacesLauncher() =
- testSpec.appLayerReplacesLauncher(testAppComponentName.className)
+ fun imeLayerIsBecomesVisibleLegacy() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ testSpec.assertLayers {
+ this.isVisible(FlickerComponentName.IME)
+ .then()
+ .isInvisible(FlickerComponentName.IME)
+ .then()
+ .isVisible(FlickerComponentName.IME)
+ }
+ }
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() {
- testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation)
+ fun imeLayerIsBecomesVisible() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ testSpec.assertLayers {
+ this.isVisible(FlickerComponentName.IME)
+ }
}
@Presubmit
@Test
- fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation)
+ fun appLayerReplacesLauncher() {
+ testSpec.assertLayers {
+ this.isVisible(LAUNCHER_COMPONENT)
+ .then()
+ .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
+ .then()
+ .isVisible(testApp.component)
+ }
}
@Presubmit
@Test
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
+
+ @Presubmit
+ @Test
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
+
+ @Presubmit
+ @Test
fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ // depends on how much of the animation transactions are sent to SF at once
+ // sometimes this layer appears for 2-3 frames, sometimes for only 1
+ val recentTaskComponent = FlickerComponentName("", "RecentTaskScreenshotSurface")
testSpec.assertLayers {
- this.visibleLayersShownMoreThanOneConsecutiveEntry()
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT, recentTaskComponent)
+ )
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index 0cae37c8d5ab..4c506b0fea4d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -18,26 +18,24 @@ package com.android.server.wm.flicker.ime
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
+import android.view.Surface
import android.view.WindowManagerPolicyConstants
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
-
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.FixMethodOrder
import org.junit.Test
@@ -53,11 +51,12 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
+@Group4
+@Presubmit
class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val testApp = SimpleAppHelper(instrumentation)
- private val imeTestApp = ImeAppHelper(instrumentation)
+ private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation)
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
@@ -66,7 +65,13 @@ class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParame
eachRun {
this.setRotation(testSpec.config.startRotation)
testApp.launchViaIntent(wmHelper)
+ wmHelper.waitForFullScreenApp(testApp.component)
+ wmHelper.waitForAppTransitionIdle()
+
imeTestApp.launchViaIntent(wmHelper)
+ wmHelper.waitForFullScreenApp(testApp.component)
+ wmHelper.waitForAppTransitionIdle()
+
imeTestApp.openIME(device, wmHelper)
}
}
@@ -74,57 +79,87 @@ class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParame
eachRun {
device.pressHome()
wmHelper.waitForHomeActivityVisible()
- }
- test {
- imeTestApp.exit(wmHelper)
+ testApp.exit()
+ imeTestApp.exit()
}
}
transitions {
// [Step1]: Swipe right from imeTestApp to testApp task
+ createTag(TAG_IME_VISIBLE)
val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
- val displayCenterX = displayBounds.bounds.width() / 2
- device.swipe(displayCenterX, displayBounds.bounds.height(),
- displayBounds.bounds.width(), displayBounds.bounds.height(), 20)
+ device.swipe(0, displayBounds.bounds.height(),
+ displayBounds.bounds.width(), displayBounds.bounds.height(), 50)
+
wmHelper.waitForFullScreenApp(testApp.component)
+ wmHelper.waitForAppTransitionIdle()
+ createTag(TAG_IME_INVISIBLE)
}
transitions {
// [Step2]: Swipe left to back to imeTestApp task
val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
- val displayCenterX = displayBounds.bounds.width() / 2
device.swipe(displayBounds.bounds.width(), displayBounds.bounds.height(),
- displayCenterX, displayBounds.bounds.height(), 20)
+ 0, displayBounds.bounds.height(), 50)
wmHelper.waitForFullScreenApp(imeTestApp.component)
}
}
}
- @FlakyTest
@Test
- fun imeAppWindowIsAlwaysVisible() = testSpec.imeAppWindowIsAlwaysVisible(imeTestApp)
+ fun imeAppWindowVisibility() {
+ testSpec.assertWm {
+ isAppWindowVisible(imeTestApp.component)
+ .then()
+ .isAppWindowVisible(testApp.component)
+ .then()
+ .isAppWindowVisible(imeTestApp.component)
+ }
+ }
- @FlakyTest
@Test
- fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
+ fun navBarLayerIsVisibleAroundSwitching() {
+ testSpec.assertLayersStart {
+ isVisible(FlickerComponentName.NAV_BAR)
+ }
+ testSpec.assertLayersEnd {
+ isVisible(FlickerComponentName.NAV_BAR)
+ }
+ }
- @FlakyTest
@Test
- fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+ fun statusBarLayerIsVisibleAroundSwitching() {
+ testSpec.assertLayersStart {
+ isVisible(FlickerComponentName.STATUS_BAR)
+ }
+ testSpec.assertLayersEnd {
+ isVisible(FlickerComponentName.STATUS_BAR)
+ }
+ }
- @Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ fun imeLayerIsVisibleWhenSwitchingToImeApp() {
+ testSpec.assertLayersStart {
+ isVisible(FlickerComponentName.IME)
+ }
+ testSpec.assertLayersTag(TAG_IME_VISIBLE) {
+ isVisible(FlickerComponentName.IME)
+ }
+ testSpec.assertLayersEnd {
+ isVisible(FlickerComponentName.IME)
+ }
+ }
- @FlakyTest
@Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
+ fun imeLayerIsInvisibleWhenSwitchingToTestApp() {
+ testSpec.assertLayersTag(TAG_IME_INVISIBLE) {
+ isInvisible(FlickerComponentName.IME)
+ }
+ }
- @Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
- @FlakyTest
@Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
companion object {
@Parameterized.Parameters(name = "{0}")
@@ -134,10 +169,13 @@ class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParame
.getConfigNonRotationTests(
repetitions = 3,
supportedNavigationModes = listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
+ ),
+ supportedRotations = listOf(Surface.ROTATION_0)
)
}
+
+ private const val TAG_IME_VISIBLE = "imeVisible"
+ private const val TAG_IME_INVISIBLE = "imeInVisible"
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
new file mode 100644
index 000000000000..f74a7718461f
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -0,0 +1,161 @@
+/*
+ * 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.flicker.launch
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.parser.toFlickerComponent
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test the back and forward transition between 2 activities.
+ *
+ * To run this test: `atest FlickerTests:ActivitiesTransitionTest`
+ *
+ * Actions:
+ * Launch an app
+ * Launch a secondary activity within the app
+ * Close the secondary activity back to the initial one
+ *
+ * Notes:
+ * 1. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group4
+class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) {
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation)
+
+ /**
+ * Entry point for the test runner. It will use this method to initialize and cache
+ * flicker executions
+ */
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ withTestName { testSpec.name }
+ repeat { testSpec.config.repetitions }
+ setup {
+ eachRun {
+ testApp.launchViaIntent(wmHelper)
+ wmHelper.waitForFullScreenApp(testApp.component)
+ }
+ }
+ teardown {
+ test {
+ testApp.exit()
+ }
+ }
+ transitions {
+ testApp.openSecondActivity(device, wmHelper)
+ device.pressBack()
+ wmHelper.waitForAppTransitionIdle()
+ wmHelper.waitForFullScreenApp(testApp.component)
+ }
+ }
+ }
+
+ /**
+ * Checks that the [ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME] activity is visible at
+ * the start of the transition, that
+ * [ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME] becomes visible during the
+ * transition, and that [ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME] is again visible
+ * at the end
+ */
+ @Presubmit
+ @Test
+ fun finishSubActivity() {
+ val buttonActivityComponent = ActivityOptions
+ .BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
+ val imeAutoFocusActivityComponent = ActivityOptions
+ .SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
+ testSpec.assertWm {
+ this.isAppWindowOnTop(buttonActivityComponent)
+ .then()
+ .isAppWindowOnTop(imeAutoFocusActivityComponent)
+ .then()
+ .isAppWindowOnTop(buttonActivityComponent)
+ }
+ }
+
+ /**
+ * Checks that all parts of the screen are covered during the transition
+ */
+ @Presubmit
+ @Test
+ fun entireScreenCovered() = testSpec.entireScreenCovered()
+
+ /**
+ * Checks that the [LAUNCHER_COMPONENT] window is not on top. The launcher cannot be
+ * asserted with `isAppWindowVisible` because it contains 2 windows with the exact same name,
+ * and both are never simultaneously visible
+ */
+ @Presubmit
+ @Test
+ fun launcherWindowNotOnTop() {
+ testSpec.assertWm {
+ this.isAppWindowNotOnTop(LAUNCHER_COMPONENT)
+ }
+ }
+
+ /**
+ * Checks that the [LAUNCHER_COMPONENT] layer is never visible during the transition
+ */
+ @Presubmit
+ @Test
+ fun launcherLayerNotVisible() {
+ testSpec.assertLayers { this.isInvisible(LAUNCHER_COMPONENT) }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(repetitions = 5)
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 9ff0bdfe66ba..be919cd67c1e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -16,6 +16,8 @@
package com.android.server.wm.flicker.launch
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -33,8 +35,21 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test cold launch app from launcher.
+ * Test cold launching an app from launcher
+ *
* To run this test: `atest FlickerTests:OpenAppColdTest`
+ *
+ * Actions:
+ * Make sure no apps are running on the device
+ * Launch an app [testApp] and wait animation to complete
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [OpenAppTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@@ -42,6 +57,9 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSpec) {
+ /**
+ * Defines the transition used to run the test
+ */
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = {
super.transition(this, it)
@@ -62,43 +80,46 @@ class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp
}
}
+ /** {@inheritDoc} */
@FlakyTest
@Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ override fun navBarLayerRotatesAndScales() {
+ super.navBarLayerRotatesAndScales()
}
- @FlakyTest
+ /** {@inheritDoc} */
+ @Postsubmit
@Test
- override fun navBarLayerIsAlwaysVisible() {
- super.navBarLayerIsAlwaysVisible()
- }
+ override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
- @FlakyTest
+ /** {@inheritDoc} */
+ @Postsubmit
@Test
- override fun navBarLayerRotatesAndScales() {
- super.navBarLayerRotatesAndScales()
- }
+ override fun appWindowReplacesLauncherAsTopWindow() =
+ super.appWindowReplacesLauncherAsTopWindow()
- @FlakyTest
+ /** {@inheritDoc} */
+ @Presubmit
@Test
- override fun statusBarLayerIsAlwaysVisible() {
- super.statusBarLayerIsAlwaysVisible()
- }
+ override fun launcherWindowBecomesInvisible() = super.launcherWindowBecomesInvisible()
- @FlakyTest
+ /** {@inheritDoc} */
+ @Presubmit
@Test
- override fun appLayerReplacesLauncher() {
- super.appLayerReplacesLauncher()
- }
+ override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
- @FlakyTest
+ /** {@inheritDoc} */
+ @Presubmit
@Test
- override fun appWindowReplacesLauncherAsTopWindow() {
- super.appWindowReplacesLauncherAsTopWindow()
- }
+ override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index b073a7ca1495..663af703f76d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.launch
+import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -26,6 +27,7 @@ import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -33,8 +35,23 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Launch an app from the recents app view (the overview)
+ * Test launching an app from the recents app view (the overview)
+ *
* To run this test: `atest FlickerTests:OpenAppFromOverviewTest`
+ *
+ * Actions:
+ * Launch [testApp]
+ * Press recents
+ * Relaunch an app [testApp] by selecting it in the overview screen, and wait animation to
+ * complete (only this action is traced)
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [OpenAppTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@@ -42,6 +59,9 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSpec) {
+ /**
+ * Defines the transition used to run the test
+ */
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = {
super.transition(this, it)
@@ -59,41 +79,47 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) : OpenAppTransitio
}
transitions {
device.reopenAppFromOverview(wmHelper)
+ wmHelper.waitFor(
+ WindowManagerConditionsFactory.hasLayersAnimating().negate(),
+ WindowManagerConditionsFactory.isWMStateComplete(),
+ WindowManagerConditionsFactory.isHomeActivityVisible().negate()
+ )
wmHelper.waitForFullScreenApp(testApp.component)
}
}
+ /** {@inheritDoc} */
@FlakyTest
@Test
- override fun navBarLayerIsAlwaysVisible() {
- super.navBarLayerIsAlwaysVisible()
- }
+ override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- @FlakyTest
+ /** {@inheritDoc} */
+ @Presubmit
@Test
- override fun statusBarLayerIsAlwaysVisible() {
- super.statusBarLayerIsAlwaysVisible()
- }
+ override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
- @FlakyTest
+ /** {@inheritDoc} */
+ @Presubmit
@Test
- override fun navBarLayerRotatesAndScales() {
- super.navBarLayerRotatesAndScales()
- }
+ override fun launcherWindowBecomesInvisible() = super.launcherWindowBecomesInvisible()
- @FlakyTest
+ /** {@inheritDoc} */
+ @Presubmit
@Test
- override fun statusBarLayerRotatesScales() {
- super.statusBarLayerRotatesScales()
- }
+ override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
- @FlakyTest
+ /** {@inheritDoc} */
+ @Presubmit
@Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
- }
+ override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
new file mode 100644
index 000000000000..cf10c5366ce9
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -0,0 +1,252 @@
+/*
+ * 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.flicker.launch
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching an app while the device is locked
+ *
+ * To run this test: `atest FlickerTests:OpenAppNonResizeableTest`
+ *
+ * Actions:
+ * Lock the device.
+ * Launch an app on top of the lock screen [testApp] and wait animation to complete
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [OpenAppTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSpec) {
+ override val testApp = NonResizeableAppHelper(instrumentation)
+ private val colorFadComponent = FlickerComponentName("", "ColorFade BLAST#")
+
+ /**
+ * Defines the transition used to run the test
+ */
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = { args ->
+ super.transition(this, args)
+ setup {
+ eachRun {
+ device.sleep()
+ wmHelper.waitFor("noAppWindowsOnTop") {
+ it.wmState.topVisibleAppWindow.isEmpty()
+ }
+ }
+ }
+ teardown {
+ eachRun {
+ testApp.exit(wmHelper)
+ }
+ }
+ transitions {
+ testApp.launchViaIntent(wmHelper)
+ wmHelper.waitForFullScreenApp(testApp.component)
+ }
+ }
+
+ /**
+ * Checks that the nav bar layer starts visible, becomes invisible during unlocking animation
+ * and becomes visible at the end
+ */
+ @Postsubmit
+ @Test
+ fun navBarLayerVisibilityChanges() {
+ testSpec.assertLayers {
+ this.isVisible(FlickerComponentName.NAV_BAR)
+ .then()
+ .isInvisible(FlickerComponentName.NAV_BAR)
+ .then()
+ .isVisible(FlickerComponentName.NAV_BAR)
+ }
+ }
+
+ /**
+ * Checks that the app layer doesn't exist at the start of the transition, that it is
+ * created (invisible) and becomes visible during the transition
+ */
+ @FlakyTest
+ @Test
+ fun appLayerBecomesVisible() {
+ testSpec.assertLayers {
+ this.notContains(testApp.component)
+ .then()
+ .isInvisible(testApp.component)
+ .then()
+ .isVisible(testApp.component)
+ }
+ }
+
+ /**
+ * Checks that the app window doesn't exist at the start of the transition, that it is
+ * created (invisible - optional) and becomes visible during the transition
+ *
+ * The `isAppWindowInvisible` step is optional because we log once per frame, upon logging,
+ * the window may be visible or not depending on what was processed until that moment.
+ */
+ @Presubmit
+ @Test
+ fun appWindowBecomesVisible() {
+ testSpec.assertWm {
+ this.notContains(testApp.component)
+ .then()
+ .isAppWindowInvisible(testApp.component, isOptional = true)
+ .then()
+ .isAppWindowVisible(testApp.component)
+ }
+ }
+
+ /**
+ * Checks if [testApp] is visible at the end of the transition
+ */
+ @Presubmit
+ @Test
+ fun appWindowBecomesVisibleAtEnd() {
+ testSpec.assertWmEnd {
+ this.isAppWindowVisible(testApp.component)
+ }
+ }
+
+ /**
+ * Checks that the nav bar starts the transition visible, then becomes invisible during
+ * then unlocking animation and becomes visible at the end of the transition
+ */
+ @Postsubmit
+ @Test
+ fun navBarWindowsVisibilityChanges() {
+ testSpec.assertWm {
+ this.isAboveAppWindowVisible(FlickerComponentName.NAV_BAR)
+ .then()
+ .isNonAppWindowInvisible(FlickerComponentName.NAV_BAR)
+ .then()
+ .isAboveAppWindowVisible(FlickerComponentName.NAV_BAR)
+ }
+ }
+
+ /** {@inheritDoc} */
+ @FlakyTest
+ @Test
+ override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
+
+ /** {@inheritDoc} */
+ @FlakyTest
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ @FlakyTest
+ @Test
+ override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
+
+ /** {@inheritDoc} */
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun entireScreenCovered() = super.entireScreenCovered()
+
+ /**
+ * Checks that the focus changes from the launcher to [testApp]
+ */
+ @FlakyTest
+ @Test
+ override fun focusChanges() = super.focusChanges()
+
+ /**
+ * Checks that the screen is locked at the start of the transition ([colorFadComponent])
+ * layer is visible
+ */
+ @Postsubmit
+ @Test
+ fun screenLockedStart() {
+ testSpec.assertLayersStart {
+ isVisible(colorFadComponent)
+ }
+ }
+
+ /**
+ * This test checks if the launcher is visible at the start and the app at the end,
+ * it cannot use the regular assertion (check over time), because on lock screen neither
+ * the app not the launcher are visible, and there is no top visible window.
+ */
+ @Postsubmit
+ @Test
+ override fun appWindowReplacesLauncherAsTopWindow() {
+ testSpec.assertWm {
+ this.invoke("noAppWindowsOnTop") {
+ Truth.assertWithMessage("Should not have any app window on top " +
+ "when the screen is locked")
+ .that(it.wmState.topVisibleAppWindow)
+ .isEmpty()
+ }.then()
+ .isAppWindowOnTop(testApp.component)
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(
+ repetitions = 5,
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
+ supportedRotations = listOf(Surface.ROTATION_0)
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index b304d5f999df..7af7b3ab6f24 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -18,34 +18,38 @@ package com.android.server.wm.flicker.launch
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.appLayerReplacesLauncher
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.endRotation
-import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.replacesLayer
import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.launcherWindowBecomesInvisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.Test
+/**
+ * Base class for app launch tests
+ */
abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) {
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
- protected val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
+ protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
+ /**
+ * Defines the transition used to run the test
+ */
protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = {
withTestName { testSpec.name }
repeat { testSpec.config.repetitions }
@@ -62,6 +66,10 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) {
}
}
+ /**
+ * Entry point for the test runner. It will use this method to initialize and cache
+ * flicker executions
+ */
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
@@ -69,42 +77,56 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) {
}
}
- @Presubmit
- @Test
- open fun navBarWindowIsAlwaysVisible() {
- testSpec.navBarWindowIsAlwaysVisible()
+ /**
+ * Checks that the navigation bar window is visible during the whole transition
+ */
+ open fun navBarWindowIsVisible() {
+ testSpec.navBarWindowIsVisible()
}
- @Presubmit
- @Test
- open fun navBarLayerIsAlwaysVisible() {
- testSpec.navBarLayerIsAlwaysVisible(rotatesScreen = testSpec.isRotated)
+ /**
+ * Checks that the navigation bar layer is visible at the start and end of the trace
+ */
+ open fun navBarLayerIsVisible() {
+ testSpec.navBarLayerIsVisible()
}
+ /**
+ * Checks the position of the navigation bar at the start and end of the transition
+ */
@Presubmit
@Test
- open fun navBarLayerRotatesAndScales() {
- testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation)
- }
+ open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
+ /**
+ * Checks that the status bar window is visible during the whole transition
+ */
@Presubmit
@Test
- open fun statusBarWindowIsAlwaysVisible() {
- testSpec.statusBarWindowIsAlwaysVisible()
+ open fun statusBarWindowIsVisible() {
+ testSpec.statusBarWindowIsVisible()
}
+ /**
+ * Checks that the status bar layer is visible at the start and end of the trace
+ */
@Presubmit
@Test
- open fun statusBarLayerIsAlwaysVisible() {
- testSpec.statusBarLayerIsAlwaysVisible(rotatesScreen = testSpec.isRotated)
+ open fun statusBarLayerIsVisible() {
+ testSpec.statusBarLayerIsVisible()
}
+ /**
+ * Checks the position of the status bar at the start and end of the transition
+ */
@Presubmit
@Test
- open fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation)
- }
+ open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
+ /**
+ * Checks that all windows that are visible on the trace, are visible for at least 2
+ * consecutive entries.
+ */
@Presubmit
@Test
open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
@@ -113,6 +135,10 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) {
}
}
+ /**
+ * Checks that all layers that are visible on the trace, are visible for at least 2
+ * consecutive entries.
+ */
@Presubmit
@Test
open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
@@ -121,34 +147,60 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) {
}
}
+ /**
+ * Checks that all parts of the screen are covered during the transition
+ */
@Presubmit
@Test
- // During testing the launcher is always in portrait mode
- open fun noUncoveredRegions() {
- testSpec.noUncoveredRegions(Surface.ROTATION_0, testSpec.config.endRotation)
- }
+ open fun entireScreenCovered() = testSpec.entireScreenCovered()
+ /**
+ * Checks that the focus changes from the launcher to [testApp]
+ */
@Presubmit
@Test
open fun focusChanges() {
- testSpec.focusChanges("NexusLauncherActivity", testApp.`package`)
+ testSpec.assertEventLog {
+ this.focusChanges("NexusLauncherActivity", testApp.`package`)
+ }
}
- @Presubmit
- @Test
+ /**
+ * Checks that [LAUNCHER_COMPONENT] layer is visible at the start of the transition, and
+ * is replaced by [testApp], which remains visible until the end
+ */
open fun appLayerReplacesLauncher() {
- testSpec.appLayerReplacesLauncher(testApp.`package`)
+ testSpec.replacesLayer(LAUNCHER_COMPONENT, testApp.component)
}
+ /**
+ * Checks that [LAUNCHER_COMPONENT] window is visible at the start of the transition, and
+ * is replaced by a snapshot or splash screen (optional), and finally, is replaced by
+ * [testApp], which remains visible until the end
+ */
@Presubmit
@Test
open fun appWindowReplacesLauncherAsTopWindow() {
- testSpec.appWindowReplacesLauncherAsTopWindow(testApp)
+ testSpec.assertWm {
+ this.isAppWindowOnTop(LAUNCHER_COMPONENT)
+ .then()
+ .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true)
+ .then()
+ .isAppWindowOnTop(FlickerComponentName.SPLASH_SCREEN, isOptional = true)
+ .then()
+ .isAppWindowOnTop(testApp.component)
+ }
}
- @Presubmit
- @Test
+ /**
+ * Checks that [LAUNCHER_COMPONENT] window is visible at the start, and
+ * becomes invisible during the transition
+ */
open fun launcherWindowBecomesInvisible() {
- testSpec.launcherWindowBecomesInvisible()
+ testSpec.assertWm {
+ this.isAppWindowOnTop(LAUNCHER_COMPONENT)
+ .then()
+ .isAppWindowNotOnTop(LAUNCHER_COMPONENT)
+ }
}
} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index e2705c764917..5edee0cf0ca0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.launch
+import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -32,8 +33,22 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test warm launch app.
+ * Test warm launching an app from launcher
+ *
* To run this test: `atest FlickerTests:OpenAppWarmTest`
+ *
+ * Actions:
+ * Launch [testApp]
+ * Press home
+ * Relaunch an app [testApp] and wait animation to complete (only this action is traced)
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [OpenAppTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@@ -41,6 +56,9 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
class OpenAppWarmTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSpec) {
+ /**
+ * Defines the transition used to run the test
+ */
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = {
super.transition(this, it)
@@ -65,19 +83,38 @@ class OpenAppWarmTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp
}
}
+ /** {@inheritDoc} */
@FlakyTest
@Test
- override fun navBarLayerIsAlwaysVisible() {
- super.navBarLayerIsAlwaysVisible()
- }
+ override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- @FlakyTest
+ /** {@inheritDoc} */
+ @Presubmit
@Test
- override fun navBarLayerRotatesAndScales() {
- super.navBarLayerRotatesAndScales()
- }
+ override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun launcherWindowBecomesInvisible() = super.launcherWindowBecomesInvisible()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
+
+ /** {@inheritDoc} */
+ @Presubmit
+ @Test
+ override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
new file mode 100644
index 000000000000..495e2d62a11d
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -0,0 +1,248 @@
+/*
+ * 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.flicker.launch
+
+import android.app.Instrumentation
+import android.app.WallpaperManager
+import android.platform.test.annotations.Postsubmit
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.navBarLayerIsVisible
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerIsVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.flicker.testapp.ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME
+import com.android.server.wm.flicker.testapp.ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.FlickerComponentName.Companion.SPLASH_SCREEN
+import com.android.server.wm.traces.common.FlickerComponentName.Companion.WALLPAPER_BBQ_WRAPPER
+import com.android.server.wm.traces.parser.toFlickerComponent
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test the back and forward transition between 2 activities.
+ *
+ * To run this test: `atest FlickerTests:ActivitiesTransitionTest`
+ *
+ * Actions:
+ * Launch the NewTaskLauncherApp [mTestApp]
+ * Open a new task (SimpleActivity) from the NewTaskLauncherApp [mTestApp]
+ * Go back to the NewTaskLauncherApp [mTestApp]
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group4
+class TaskTransitionTest(val testSpec: FlickerTestParameter) {
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val mTestApp: NewTasksAppHelper = NewTasksAppHelper(instrumentation)
+
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ withTestName { testSpec.name }
+ repeat { testSpec.config.repetitions }
+ setup {
+ eachRun {
+ mTestApp.launchViaIntent(wmHelper)
+ wmHelper.waitForFullScreenApp(mTestApp.component)
+ }
+ }
+ teardown {
+ test {
+ mTestApp.exit()
+ }
+ }
+ transitions {
+ mTestApp.openNewTask(device, wmHelper)
+ device.pressBack()
+ wmHelper.waitForAppTransitionIdle()
+ wmHelper.waitForFullScreenApp(mTestApp.component)
+ }
+ }
+ }
+
+ /**
+ * Checks that the wallpaper window is never visible when performing task transitions.
+ * A solid color background should be shown instead.
+ */
+ @Postsubmit
+ @Test
+ fun wallpaperWindowIsNeverVisible() {
+ testSpec.assertWm {
+ this.isNonAppWindowInvisible(WALLPAPER)
+ }
+ }
+
+ /**
+ * Checks that the wallpaper layer is never visible when performing task transitions.
+ * A solid color background should be shown instead.
+ */
+ @Postsubmit
+ @Test
+ fun wallpaperLayerIsNeverVisible() {
+ testSpec.assertLayers {
+ this.isInvisible(WALLPAPER)
+ this.isInvisible(WALLPAPER_BBQ_WRAPPER)
+ }
+ }
+
+ /**
+ * Check that the launcher window is never visible when performing task transitions.
+ * A solid color background should be shown above it.
+ */
+ @Postsubmit
+ @Test
+ fun launcherWindowIsNeverVisible() {
+ testSpec.assertWm {
+ this.isAppWindowInvisible(LAUNCHER_COMPONENT)
+ }
+ }
+
+ /**
+ * Checks that the launcher layer is never visible when performing task transitions.
+ * A solid color background should be shown above it.
+ */
+ @Postsubmit
+ @Test
+ fun launcherLayerIsNeverVisible() {
+ testSpec.assertLayers {
+ this.isInvisible(LAUNCHER_COMPONENT)
+ }
+ }
+
+ /**
+ * Checks that a color background is visible while the task transition is occurring.
+ */
+ @Postsubmit
+ @Test
+ fun colorLayerIsVisibleDuringTransition() {
+ val bgColorLayer = FlickerComponentName("", "colorBackgroundLayer")
+ val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
+
+ testSpec.assertLayers {
+ this.coversExactly(displayBounds, LAUNCH_NEW_TASK_ACTIVITY)
+ .isInvisible(bgColorLayer)
+ .then()
+ // Transitioning
+ .isVisible(bgColorLayer)
+ .then()
+ // Fully transitioned to simple SIMPLE_ACTIVITY
+ .coversExactly(displayBounds, SIMPLE_ACTIVITY)
+ .isInvisible(bgColorLayer)
+ .then()
+ // Transitioning back
+ .isVisible(bgColorLayer)
+ .then()
+ // Fully transitioned back to LAUNCH_NEW_TASK_ACTIVITY
+ .isInvisible(bgColorLayer)
+ .coversExactly(displayBounds, LAUNCH_NEW_TASK_ACTIVITY)
+ }
+ }
+
+ /**
+ * Checks that we start with the LaunchNewTask activity on top and then open up
+ * the SimpleActivity and then go back to the LaunchNewTask activity.
+ */
+ @Postsubmit
+ @Test
+ fun newTaskOpensOnTopAndThenCloses() {
+ testSpec.assertWm {
+ this.isAppWindowOnTop(LAUNCH_NEW_TASK_ACTIVITY)
+ .then()
+ .isAppWindowOnTop(SPLASH_SCREEN, isOptional = true)
+ .then()
+ .isAppWindowOnTop(SIMPLE_ACTIVITY)
+ .then()
+ .isAppWindowOnTop(SPLASH_SCREEN, isOptional = true)
+ .then()
+ .isAppWindowOnTop(LAUNCH_NEW_TASK_ACTIVITY)
+ }
+ }
+
+ /**
+ * Checks that all parts of the screen are covered at the start and end of the transition
+ */
+ @Postsubmit
+ @Test
+ fun entireScreenCovered() = testSpec.entireScreenCovered()
+
+ /**
+ * Checks that the navbar window is visible throughout the transition
+ */
+ @Postsubmit
+ @Test
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
+
+ /**
+ * Checks that the navbar layer is visible throughout the transition
+ */
+ @Postsubmit
+ @Test
+ fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
+
+ /**
+ * Checks that the status bar window is visible throughout the transition
+ */
+ @Postsubmit
+ @Test
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
+
+ /**
+ * Checks that the status bar layer is visible throughout the transition
+ */
+ @Postsubmit
+ @Test
+ fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
+
+ companion object {
+ private val WALLPAPER = getWallpaperPackage(InstrumentationRegistry.getInstrumentation())
+ private val LAUNCH_NEW_TASK_ACTIVITY =
+ LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
+ private val SIMPLE_ACTIVITY = SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
+
+ private fun getWallpaperPackage(instrumentation: Instrumentation): FlickerComponentName {
+ val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext)
+
+ return wallpaperManager.wallpaperInfo.component.toFlickerComponent()
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(repetitions = 5)
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
new file mode 100644
index 000000000000..52904cce8772
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -0,0 +1,330 @@
+/*
+ * 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.flicker.quickswitch
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.RequiresDevice
+import android.view.Surface
+import android.view.WindowManagerPolicyConstants
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isRotated
+import com.android.server.wm.flicker.navBarLayerIsVisible
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerIsVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switching back to previous app from last opened app
+ *
+ * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest`
+ *
+ * Actions:
+ * Launch an app [testApp1]
+ * Launch another app [testApp2]
+ * Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
+ *
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParameter) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ private val testApp1 = SimpleAppHelper(instrumentation)
+ private val testApp2 = NonResizeableAppHelper(instrumentation)
+
+ private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
+
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ setup {
+ eachRun {
+ testApp1.launchViaIntent(wmHelper)
+ wmHelper.waitForFullScreenApp(testApp1.component)
+
+ testApp2.launchViaIntent(wmHelper)
+ wmHelper.waitForFullScreenApp(testApp2.component)
+ }
+ }
+ transitions {
+ // Swipe right from bottom to quick switch back
+ // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle
+ // as to not accidentally trigger a swipe back or forward action which would result
+ // in the same behavior but not testing quick swap.
+ device.swipe(
+ startDisplayBounds.bounds.right / 3,
+ startDisplayBounds.bounds.bottom,
+ 2 * startDisplayBounds.bounds.right / 3,
+ startDisplayBounds.bounds.bottom,
+ if (testSpec.config.startRotation.isRotated()) 75 else 30
+ )
+
+ wmHelper.waitForFullScreenApp(testApp1.component)
+ wmHelper.waitForAppTransitionIdle()
+ }
+
+ teardown {
+ test {
+ testApp1.exit()
+ testApp2.exit()
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks that the transition starts with [testApp2]'s windows filling/covering exactly the
+ * entirety of the display.
+ */
+ @Postsubmit
+ @Test
+ fun startsWithApp2WindowsCoverFullScreen() {
+ testSpec.assertWmStart {
+ this.frameRegion(testApp2.component).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Checks that the transition starts with [testApp2]'s layers filling/covering exactly the
+ * entirety of the display.
+ */
+ @Postsubmit
+ @Test
+ fun startsWithApp2LayersCoverFullScreen() {
+ testSpec.assertLayersStart {
+ this.visibleRegion(testApp2.component).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Checks that the transition starts with [testApp2] being the top window.
+ */
+ @Postsubmit
+ @Test
+ fun startsWithApp2WindowBeingOnTop() {
+ testSpec.assertWmStart {
+ this.isAppWindowOnTop(testApp2.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp1] windows fill the entire screen (i.e. is "fullscreen") at the end of the
+ * transition once we have fully quick switched from [testApp2] back to the [testApp1].
+ */
+ @Postsubmit
+ @Test
+ fun endsWithApp1WindowsCoveringFullScreen() {
+ testSpec.assertWmEnd {
+ this.frameRegion(testApp1.component).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Checks that [testApp1] layers fill the entire screen (i.e. is "fullscreen") at the end of the
+ * transition once we have fully quick switched from [testApp2] back to the [testApp1].
+ */
+ @Postsubmit
+ @Test
+ fun endsWithApp1LayersCoveringFullScreen() {
+ testSpec.assertLayersEnd {
+ this.visibleRegion(testApp1.component).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Checks that [testApp1] is the top window at the end of the transition once we have fully quick
+ * switched from [testApp2] back to the [testApp1].
+ */
+ @Postsubmit
+ @Test
+ fun endsWithApp1BeingOnTop() {
+ testSpec.assertWmEnd {
+ this.isAppWindowOnTop(testApp1.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp1]'s window starts off invisible and becomes visible at some point before
+ * the end of the transition and then stays visible until the end of the transition.
+ */
+ @Postsubmit
+ @Test
+ fun app1WindowBecomesAndStaysVisible() {
+ testSpec.assertWm {
+ this.isAppWindowInvisible(testApp1.component)
+ .then()
+ .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
+ .then()
+ .isAppWindowVisible(testApp1.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp1]'s layer starts off invisible and becomes visible at some point before
+ * the end of the transition and then stays visible until the end of the transition.
+ */
+ @Postsubmit
+ @Test
+ fun app1LayerBecomesAndStaysVisible() {
+ testSpec.assertLayers {
+ this.isInvisible(testApp1.component)
+ .then()
+ .isVisible(testApp1.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp2]'s window starts off visible and becomes invisible at some point before
+ * the end of the transition and then stays invisible until the end of the transition.
+ */
+ @Postsubmit
+ @Test
+ fun app2WindowBecomesAndStaysInvisible() {
+ testSpec.assertWm {
+ this.isAppWindowVisible(testApp2.component)
+ .then()
+ .isAppWindowInvisible(testApp2.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp2]'s layer starts off visible and becomes invisible at some point before
+ * the end of the transition and then stays invisible until the end of the transition.
+ */
+ @Postsubmit
+ @Test
+ fun app2LayerBecomesAndStaysInvisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp2.component)
+ .then()
+ .isInvisible(testApp2.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp2]'s window is visible at least until [testApp1]'s window is visible.
+ * Ensures that at any point, either [testApp1] or [testApp2]'s windows are at least partially
+ * visible.
+ */
+ @Postsubmit
+ @Test
+ fun app1WindowIsVisibleOnceApp2WindowIsInvisible() {
+ testSpec.assertWm {
+ this.isAppWindowVisible(testApp2.component)
+ .then()
+ // TODO: Do we actually want to test this? Seems too implementation specific...
+ .isAppWindowVisible(LAUNCHER_COMPONENT, isOptional = true)
+ .then()
+ .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
+ .then()
+ .isAppWindowVisible(testApp1.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp2]'s layer is visible at least until [testApp1]'s window is visible.
+ * Ensures that at any point, either [testApp1] or [testApp2]'s windows are at least partially
+ * visible.
+ */
+ @Postsubmit
+ @Test
+ fun app1LayerIsVisibleOnceApp2LayerIsInvisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp2.component)
+ .then()
+ .isVisible(LAUNCHER_COMPONENT, isOptional = true)
+ .then()
+ .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
+ .then()
+ .isVisible(testApp1.component)
+ }
+ }
+
+ /**
+ * Checks that the navbar window is visible throughout the entire transition.
+ */
+ @Postsubmit
+ @Test
+ fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsVisible()
+
+ /**
+ * Checks that the navbar layer is visible throughout the entire transition.
+ */
+ @Postsubmit
+ @Test
+ fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible()
+
+ /**
+ * Checks that the navbar is always in the right position and covers the expected region.
+ *
+ * NOTE: This doesn't check that the navbar is visible or not.
+ */
+ @Postsubmit
+ @Test
+ fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales()
+
+ /**
+ * Checks that the status bar window is visible throughout the entire transition.
+ */
+ @Postsubmit
+ @Test
+ fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible()
+
+ /**
+ * Checks that the status bar layer is visible throughout the entire transition.
+ */
+ @Postsubmit
+ @Test
+ fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(
+ repetitions = 5,
+ supportedNavigationModes = listOf(
+ WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+ ),
+ supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
new file mode 100644
index 000000000000..842aa2b548db
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -0,0 +1,347 @@
+/*
+ * 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.flicker.quickswitch
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.RequiresDevice
+import android.view.Surface
+import android.view.WindowManagerPolicyConstants
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isRotated
+import com.android.server.wm.flicker.navBarLayerIsVisible
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerIsVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switching back to previous app from last opened app
+ *
+ * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest`
+ *
+ * Actions:
+ * Launch an app [testApp1]
+ * Launch another app [testApp2]
+ * Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
+ * Swipe left from the bottom of the screen to quick switch forward to the second app [testApp2]
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestParameter) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ private val testApp1 = SimpleAppHelper(instrumentation)
+ private val testApp2 = NonResizeableAppHelper(instrumentation)
+
+ private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
+
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ withTestName { testSpec.name }
+ repeat { testSpec.config.repetitions }
+ setup {
+ eachRun {
+ testApp1.launchViaIntent(wmHelper)
+ wmHelper.waitForFullScreenApp(testApp1.component)
+
+ testApp2.launchViaIntent(wmHelper)
+ wmHelper.waitForFullScreenApp(testApp2.component)
+
+ // Swipe right from bottom to quick switch back
+ // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle
+ // as to not accidentally trigger a swipe back or forward action which would result
+ // in the same behavior but not testing quick swap.
+ device.swipe(
+ startDisplayBounds.bounds.right / 3,
+ startDisplayBounds.bounds.bottom,
+ 2 * startDisplayBounds.bounds.right / 3,
+ startDisplayBounds.bounds.bottom,
+ if (testSpec.config.startRotation.isRotated()) 75 else 30
+ )
+
+ wmHelper.waitForFullScreenApp(testApp1.component)
+ wmHelper.waitForAppTransitionIdle()
+ }
+ }
+ transitions {
+ // Swipe left from bottom to quick switch forward
+ // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle
+ // as to not accidentally trigger a swipe back or forward action which would result
+ // in the same behavior but not testing quick swap.
+ device.swipe(
+ 2 * startDisplayBounds.bounds.right / 3,
+ startDisplayBounds.bounds.bottom,
+ startDisplayBounds.bounds.right / 3,
+ startDisplayBounds.bounds.bottom,
+ if (testSpec.config.startRotation.isRotated()) 75 else 30
+ )
+
+ wmHelper.waitForFullScreenApp(testApp2.component)
+ wmHelper.waitForAppTransitionIdle()
+ }
+
+ teardown {
+ test {
+ testApp1.exit()
+ testApp2.exit()
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks that the transition starts with [testApp1]'s windows filling/covering exactly the
+ * entirety of the display.
+ */
+ @Postsubmit
+ @Test
+ fun startsWithApp1WindowsCoverFullScreen() {
+ testSpec.assertWmStart {
+ this.frameRegion(testApp1.component).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Checks that the transition starts with [testApp1]'s layers filling/covering exactly the
+ * entirety of the display.
+ */
+ @Postsubmit
+ @Test
+ fun startsWithApp1LayersCoverFullScreen() {
+ testSpec.assertLayersStart {
+ this.visibleRegion(testApp1.component).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Checks that the transition starts with [testApp1] being the top window.
+ */
+ @Postsubmit
+ @Test
+ fun startsWithApp1WindowBeingOnTop() {
+ testSpec.assertWmStart {
+ this.isAppWindowOnTop(testApp1.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp2] windows fill the entire screen (i.e. is "fullscreen") at the end of the
+ * transition once we have fully quick switched from [testApp1] back to the [testApp2].
+ */
+ @Postsubmit
+ @Test
+ fun endsWithApp2WindowsCoveringFullScreen() {
+ testSpec.assertWmEnd {
+ this.frameRegion(testApp2.component).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Checks that [testApp2] layers fill the entire screen (i.e. is "fullscreen") at the end of the
+ * transition once we have fully quick switched from [testApp1] back to the [testApp2].
+ */
+ @Postsubmit
+ @Test
+ fun endsWithApp2LayersCoveringFullScreen() {
+ testSpec.assertLayersEnd {
+ this.visibleRegion(testApp2.component).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Checks that [testApp2] is the top window at the end of the transition once we have fully quick
+ * switched from [testApp1] back to the [testApp2].
+ */
+ @Postsubmit
+ @Test
+ fun endsWithApp2BeingOnTop() {
+ testSpec.assertWmEnd {
+ this.isAppWindowOnTop(testApp2.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp2]'s window starts off invisible and becomes visible at some point before
+ * the end of the transition and then stays visible until the end of the transition.
+ */
+ @Postsubmit
+ @Test
+ fun app2WindowBecomesAndStaysVisible() {
+ testSpec.assertWm {
+ this.isAppWindowInvisible(testApp2.component)
+ .then()
+ .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
+ .then()
+ .isAppWindowVisible(testApp2.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp2]'s layer starts off invisible and becomes visible at some point before
+ * the end of the transition and then stays visible until the end of the transition.
+ */
+ @Postsubmit
+ @Test
+ fun app2LayerBecomesAndStaysVisible() {
+ testSpec.assertLayers {
+ this.isInvisible(testApp2.component)
+ .then()
+ .isVisible(testApp2.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp1]'s window starts off visible and becomes invisible at some point before
+ * the end of the transition and then stays invisible until the end of the transition.
+ */
+ @Postsubmit
+ @Test
+ fun app1WindowBecomesAndStaysInvisible() {
+ testSpec.assertWm {
+ this.isAppWindowVisible(testApp1.component)
+ .then()
+ .isAppWindowInvisible(testApp1.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp1]'s layer starts off visible and becomes invisible at some point before
+ * the end of the transition and then stays invisible until the end of the transition.
+ */
+ @Postsubmit
+ @Test
+ fun app1LayerBecomesAndStaysInvisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp1.component)
+ .then()
+ .isInvisible(testApp1.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp1]'s window is visible at least until [testApp2]'s window is visible.
+ * Ensures that at any point, either [testApp2] or [testApp1]'s windows are at least partially
+ * visible.
+ */
+ @Postsubmit
+ @Test
+ fun app2WindowIsVisibleOnceApp1WindowIsInvisible() {
+ testSpec.assertWm {
+ this.isAppWindowVisible(testApp1.component)
+ .then()
+ .isAppWindowVisible(LAUNCHER_COMPONENT, isOptional = true)
+ .then()
+ .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
+ .then()
+ .isAppWindowVisible(testApp2.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp1]'s layer is visible at least until [testApp2]'s window is visible.
+ * Ensures that at any point, either [testApp2] or [testApp1]'s windows are at least partially
+ * visible.
+ */
+ @Postsubmit
+ @Test
+ fun app2LayerIsVisibleOnceApp1LayerIsInvisible() {
+ testSpec.assertLayers {
+ this.isVisible(testApp1.component)
+ .then()
+ .isVisible(LAUNCHER_COMPONENT, isOptional = true)
+ .then()
+ .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
+ .then()
+ .isVisible(testApp2.component)
+ }
+ }
+
+ /**
+ * Checks that the navbar window is visible throughout the entire transition.
+ */
+ @Postsubmit
+ @Test
+ fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsVisible()
+
+ /**
+ * Checks that the navbar layer is visible throughout the entire transition.
+ */
+ @Postsubmit
+ @Test
+ fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible()
+
+ /**
+ * Checks that the navbar is always in the right position and covers the expected region.
+ *
+ * NOTE: This doesn't check that the navbar is visible or not.
+ */
+ @Postsubmit
+ @Test
+ fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales()
+
+ /**
+ * Checks that the status bar window is visible throughout the entire transition.
+ */
+ @Postsubmit
+ @Test
+ fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible()
+
+ /**
+ * Checks that the status bar layer is visible throughout the entire transition.
+ */
+ @Postsubmit
+ @Test
+ fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(
+ repetitions = 5,
+ supportedNavigationModes = listOf(
+ WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+ ),
+ supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
new file mode 100644
index 000000000000..10ca0d9b323b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -0,0 +1,346 @@
+/*
+ * 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.flicker.quickswitch
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresDevice
+import android.view.Surface
+import android.view.WindowManagerPolicyConstants
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.LAUNCHER_COMPONENT
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.navBarLayerIsVisible
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerIsVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switching to last opened app from launcher
+ *
+ * To run this test: `atest FlickerTests:QuickSwitchFromLauncherTest`
+ *
+ * Actions:
+ * Launch an app
+ * Navigate home to show launcher
+ * Swipe right from the bottom of the screen to quick switch back to the app
+ *
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group4
+class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val testApp = SimpleAppHelper(instrumentation)
+ private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
+
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ setup {
+ eachRun {
+ testApp.launchViaIntent(wmHelper)
+ device.pressHome()
+ wmHelper.waitForHomeActivityVisible()
+ wmHelper.waitForWindowSurfaceDisappeared(testApp.component)
+ }
+ }
+ transitions {
+ // Swipe right from bottom to quick switch back
+ // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle
+ // as to not accidentally trigger a swipe back or forward action which would result
+ // in the same behavior but not testing quick swap.
+ device.swipe(
+ startDisplayBounds.bounds.right / 3,
+ startDisplayBounds.bounds.bottom,
+ 2 * startDisplayBounds.bounds.right / 3,
+ startDisplayBounds.bounds.bottom,
+ 50
+ )
+
+ wmHelper.waitForFullScreenApp(testApp.component)
+ wmHelper.waitForAppTransitionIdle()
+ }
+
+ teardown {
+ eachRun {
+ testApp.exit()
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks that [testApp] windows fill the entire screen (i.e. is "fullscreen") at the end of the
+ * transition once we have fully quick switched from the launcher back to the [testApp].
+ */
+ @Presubmit
+ @Test
+ fun endsWithAppWindowsCoveringFullScreen() {
+ testSpec.assertWmEnd {
+ this.frameRegion(testApp.component).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Checks that [testApp] layers fill the entire screen (i.e. is "fullscreen") at the end of the
+ * transition once we have fully quick switched from the launcher back to the [testApp].
+ */
+ @Presubmit
+ @Test
+ fun endsWithAppLayersCoveringFullScreen() {
+ testSpec.assertLayersEnd {
+ this.visibleRegion(testApp.component).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Checks that [testApp] is the top window at the end of the transition once we have fully quick
+ * switched from the launcher back to the [testApp].
+ */
+ @Presubmit
+ @Test
+ fun endsWithAppBeingOnTop() {
+ testSpec.assertWmEnd {
+ this.isAppWindowOnTop(testApp.component)
+ }
+ }
+
+ /**
+ * Checks that the transition starts with the home activity being tagged as visible.
+ */
+ @Presubmit
+ @Test
+ fun startsWithHomeActivityFlaggedVisible() {
+ testSpec.assertWmStart {
+ this.isHomeActivityVisible()
+ }
+ }
+
+ /**
+ * Checks that the transition starts with the launcher windows filling/covering exactly the
+ * entirety of the display.
+ */
+ @Presubmit
+ @Test
+ fun startsWithLauncherWindowsCoverFullScreen() {
+ testSpec.assertWmStart {
+ this.frameRegion(LAUNCHER_COMPONENT).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Checks that the transition starts with the launcher layers filling/covering exactly the
+ * entirety of the display.
+ */
+ @Presubmit
+ @Test
+ fun startsWithLauncherLayersCoverFullScreen() {
+ testSpec.assertLayersStart {
+ this.visibleRegion(LAUNCHER_COMPONENT).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Checks that the transition starts with the launcher being the top window.
+ */
+ @Presubmit
+ @Test
+ fun startsWithLauncherBeingOnTop() {
+ testSpec.assertWmStart {
+ this.isAppWindowOnTop(LAUNCHER_COMPONENT)
+ }
+ }
+
+ /**
+ * Checks that the transition ends with the home activity being flagged as not visible. By this
+ * point we should have quick switched away from the launcher back to the [testApp].
+ */
+ @Presubmit
+ @Test
+ fun endsWithHomeActivityFlaggedInvisible() {
+ testSpec.assertWmEnd {
+ this.isHomeActivityInvisible()
+ }
+ }
+
+ /**
+ * Checks that [testApp]'s window starts off invisible and becomes visible at some point before
+ * the end of the transition and then stays visible until the end of the transition.
+ */
+ @Presubmit
+ @Test
+ fun appWindowBecomesAndStaysVisible() {
+ testSpec.assertWm {
+ this.isAppWindowInvisible(testApp.component)
+ .then()
+ .isAppWindowVisible(testApp.component)
+ }
+ }
+
+ /**
+ * Checks that [testApp]'s layer starts off invisible and becomes visible at some point before
+ * the end of the transition and then stays visible until the end of the transition.
+ */
+ @Presubmit
+ @Test
+ fun appLayerBecomesAndStaysVisible() {
+ testSpec.assertLayers {
+ this.isInvisible(testApp.component)
+ .then()
+ .isVisible(testApp.component)
+ }
+ }
+
+ /**
+ * Checks that the launcher window starts off visible and becomes invisible at some point before
+ * the end of the transition and then stays invisible until the end of the transition.
+ */
+ @Presubmit
+ @Test
+ fun launcherWindowBecomesAndStaysInvisible() {
+ testSpec.assertWm {
+ this.isAppWindowOnTop(LAUNCHER_COMPONENT)
+ .then()
+ .isAppWindowNotOnTop(LAUNCHER_COMPONENT)
+ }
+ }
+
+ /**
+ * Checks that the launcher layer starts off visible and becomes invisible at some point before
+ * the end of the transition and then stays invisible until the end of the transition.
+ */
+ @Presubmit
+ @Test
+ fun launcherLayerBecomesAndStaysInvisible() {
+ testSpec.assertLayers {
+ this.isVisible(LAUNCHER_COMPONENT)
+ .then()
+ .isInvisible(LAUNCHER_COMPONENT)
+ }
+ }
+
+ /**
+ * Checks that the launcher window is visible at least until the app window is visible. Ensures
+ * that at any point, either the launcher or [testApp] windows are at least partially visible.
+ */
+ @Presubmit
+ @Test
+ fun appWindowIsVisibleOnceLauncherWindowIsInvisible() {
+ testSpec.assertWm {
+ this.isAppWindowOnTop(LAUNCHER_COMPONENT)
+ .then()
+ .isAppWindowVisible(FlickerComponentName.SNAPSHOT)
+ .then()
+ .isAppWindowVisible(testApp.component)
+ }
+ }
+
+ /**
+ * Checks that the launcher layer is visible at least until the app layer is visible. Ensures
+ * that at any point, either the launcher or [testApp] layers are at least partially visible.
+ */
+ @Presubmit
+ @Test
+ fun appLayerIsVisibleOnceLauncherLayerIsInvisible() {
+ testSpec.assertLayers {
+ this.isVisible(LAUNCHER_COMPONENT)
+ .then()
+ .isVisible(FlickerComponentName.SNAPSHOT)
+ .then()
+ .isVisible(testApp.component)
+ }
+ }
+
+ /**
+ * Checks that the navbar window is visible throughout the entire transition.
+ */
+ @Presubmit
+ @Test
+ fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsVisible()
+
+ /**
+ * Checks that the navbar layer is visible throughout the entire transition.
+ */
+ @Presubmit
+ @Test
+ fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible()
+
+ /**
+ * Checks that the navbar is always in the right position and covers the expected region.
+ *
+ * NOTE: This doesn't check that the navbar is visible or not.
+ */
+ @Presubmit
+ @Test
+ fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales()
+
+ /**
+ * Checks that the status bar window is visible throughout the entire transition.
+ */
+ @Presubmit
+ @Test
+ fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible()
+
+ /**
+ * Checks that the status bar layer is visible throughout the entire transition.
+ */
+ @Presubmit
+ @Test
+ fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible()
+
+ /**
+ * Checks that the screen is always fully covered by visible layers throughout the transition.
+ */
+ @Presubmit
+ @Test
+ fun screenIsAlwaysFilled() = testSpec.entireScreenCovered()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(
+ repetitions = 5,
+ supportedNavigationModes = listOf(
+ WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+ ),
+ // TODO: Test with 90 rotation
+ supportedRotations = listOf(Surface.ROTATION_0)
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 69e8a8d08e58..fd8abc621b33 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -26,15 +26,52 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
+import com.android.server.wm.flicker.statusBarLayerIsVisible
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.FixMethodOrder
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Cycle through supported app rotations.
+ * Test opening an app and cycling through app rotations
+ *
+ * Currently runs:
+ * 0 -> 90 degrees
+ * 90 -> 0 degrees
+ *
+ * Actions:
+ * Launch an app (via intent)
+ * Set initial device orientation
+ * Start tracing
+ * Change device orientation
+ * Stop tracing
+ *
* To run this test: `atest FlickerTests:ChangeAppRotationTest`
+ *
+ * To run only the presubmit assertions add: `--
+ * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
+ * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
+ *
+ * To run only the postsubmit assertions add: `--
+ * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
+ * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
+ *
+ * To run only the flaky assertions add: `--
+ * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [RotationTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@@ -44,6 +81,9 @@ import org.junit.runners.Parameterized
class ChangeAppRotationTest(
testSpec: FlickerTestParameter
) : RotationTransition(testSpec) {
+ @get:Rule
+ val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec)
+
override val testApp = SimpleAppHelper(instrumentation)
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = {
@@ -55,45 +95,94 @@ class ChangeAppRotationTest(
}
}
+ @Postsubmit
+ @Test
+ fun runPresubmitAssertion() {
+ flickerRule.checkPresubmitAssertions()
+ }
+
+ @Postsubmit
+ @Test
+ fun runPostsubmitAssertion() {
+ flickerRule.checkPostsubmitAssertions()
+ }
+
+ @FlakyTest
+ @Test
+ fun runFlakyAssertion() {
+ flickerRule.checkFlakyAssertions()
+ }
+
+ /** {@inheritDoc} */
@FlakyTest(bugId = 190185577)
@Test
override fun focusDoesNotChange() {
super.focusDoesNotChange()
}
- @Postsubmit
+ /**
+ * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition,
+ * doesn't flicker, and disappears before the transition is complete
+ */
+ @Presubmit
@Test
- fun screenshotLayerBecomesInvisible() {
+ fun rotationLayerAppearsAndVanishes() {
testSpec.assertLayers {
- this.isVisible(testApp.getPackage())
+ this.isVisible(testApp.component)
.then()
- .isVisible(SCREENSHOT_LAYER)
+ .isVisible(FlickerComponentName.ROTATION)
.then()
- .isVisible(testApp.getPackage())
+ .isVisible(testApp.component)
+ .isInvisible(FlickerComponentName.ROTATION)
}
}
- @Postsubmit
+ /**
+ * Checks that the status bar window is visible and above the app windows in all WM
+ * trace entries
+ */
+ @Presubmit
@Test
- override fun statusBarLayerRotatesScales() {
- super.statusBarLayerRotatesScales()
+ fun statusBarWindowIsVisible() {
+ testSpec.statusBarWindowIsVisible()
}
+ /**
+ * Checks that the status bar layer is visible at the start and end of the transition
+ */
@Presubmit
@Test
- override fun navBarWindowIsAlwaysVisible() {
- super.navBarWindowIsAlwaysVisible()
+ fun statusBarLayerIsVisible() {
+ testSpec.statusBarLayerIsVisible()
}
+ /**
+ * Checks the position of the status bar at the start and end of the transition
+ */
+ @Presubmit
+ @Test
+ fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
+
+ /** {@inheritDoc} */
@FlakyTest
@Test
- override fun statusBarLayerIsAlwaysVisible() {
- super.statusBarLayerIsAlwaysVisible()
+ override fun navBarLayerRotatesAndScales() {
+ super.navBarLayerRotatesAndScales()
}
- companion object {
- private const val SCREENSHOT_LAYER = "RotationLayer"
+ /** {@inheritDoc} */
+ @FlakyTest
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index 4b888cd5aad0..e850632ed8af 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -18,33 +18,28 @@ package com.android.server.wm.flicker.rotation
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
-import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.endRotation
-import com.android.server.wm.flicker.focusDoesNotChange
import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.startRotation
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.Test
+/**
+ * Base class for app rotation tests
+ */
abstract class RotationTransition(protected val testSpec: FlickerTestParameter) {
protected abstract val testApp: StandardAppHelper
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
- protected val startingPos get() = WindowUtils.getDisplayBounds(testSpec.config.startRotation)
- protected val endingPos get() = WindowUtils.getDisplayBounds(testSpec.config.endRotation)
protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = {
setup {
@@ -62,6 +57,10 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter)
}
}
+ /**
+ * Entry point for the test runner. It will use this method to initialize and cache
+ * flicker executions
+ */
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
@@ -69,57 +68,53 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter)
}
}
- @FlakyTest
- @Test
- open fun navBarWindowIsAlwaysVisible() {
- testSpec.navBarWindowIsAlwaysVisible()
- }
-
- @FlakyTest
- @Test
- open fun navBarLayerIsAlwaysVisible() {
- testSpec.navBarLayerIsAlwaysVisible(rotatesScreen = true)
- }
-
- @FlakyTest
- @Test
- open fun navBarLayerRotatesAndScales() {
- testSpec.navBarLayerRotatesAndScales(
- testSpec.config.startRotation, testSpec.config.endRotation)
- }
-
+ /**
+ * Checks that the navigation bar window is visible and above the app windows in all WM
+ * trace entries
+ */
@Presubmit
@Test
- open fun statusBarWindowIsAlwaysVisible() {
- testSpec.statusBarWindowIsAlwaysVisible()
+ open fun navBarWindowIsVisible() {
+ testSpec.navBarWindowIsVisible()
}
- @FlakyTest
+ /**
+ * Checks that the navigation bar layer is visible at the start and end of the transition
+ */
+ @Presubmit
@Test
- open fun statusBarLayerIsAlwaysVisible() {
- testSpec.statusBarLayerIsAlwaysVisible(rotatesScreen = true)
+ open fun navBarLayerIsVisible() {
+ testSpec.navBarLayerIsVisible()
}
- @FlakyTest
+ /**
+ * Checks the position of the navigation bar at the start and end of the transition
+ */
+ @Presubmit
@Test
- open fun statusBarLayerRotatesScales() {
- testSpec.statusBarLayerRotatesScales(
- testSpec.config.startRotation, testSpec.config.endRotation)
- }
+ open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
- @FlakyTest
+ /**
+ * Checks that all layers that are visible on the trace, are visible for at least 2
+ * consecutive entries.
+ */
+ @Presubmit
@Test
open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
testSpec.assertLayers {
this.visibleLayersShownMoreThanOneConsecutiveEntry(
- ignoreLayers = listOf(WindowManagerStateHelper.SPLASH_SCREEN_NAME,
- WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME,
- "SecondaryHomeHandle"
+ ignoreLayers = listOf(FlickerComponentName.SPLASH_SCREEN,
+ FlickerComponentName.SNAPSHOT,
+ FlickerComponentName("", "SecondaryHomeHandle")
)
)
}
}
+ /**
+ * Checks that all windows that are visible on the trace, are visible for at least 2
+ * consecutive entries.
+ */
@Presubmit
@Test
open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
@@ -128,32 +123,47 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter)
}
}
+ /**
+ * Checks that all parts of the screen are covered during the transition
+ */
@Presubmit
@Test
- open fun noUncoveredRegions() {
- testSpec.noUncoveredRegions(testSpec.config.startRotation,
- testSpec.config.endRotation, allStates = false)
- }
+ open fun entireScreenCovered() = testSpec.entireScreenCovered()
+ /**
+ * Checks that the focus doesn't change during animation
+ */
@Presubmit
@Test
open fun focusDoesNotChange() {
- testSpec.focusDoesNotChange()
+ testSpec.assertEventLog {
+ this.focusDoesNotChange()
+ }
}
+ /**
+ * Checks that [testApp] layer covers the entire screen at the start of the transition
+ */
@Presubmit
@Test
open fun appLayerRotates_StartingPos() {
testSpec.assertLayersStart {
- this.visibleRegion(testApp.getPackage()).coversExactly(startingPos)
+ this.entry.displays.map { display ->
+ this.visibleRegion(testApp.component).coversExactly(display.layerStackSpace)
+ }
}
}
+ /**
+ * Checks that [testApp] layer covers the entire screen at the end of the transition
+ */
@Presubmit
@Test
open fun appLayerRotates_EndingPos() {
testSpec.assertLayersEnd {
- this.visibleRegion(testApp.getPackage()).coversExactly(endingPos)
+ this.entry.displays.map { display ->
+ this.visibleRegion(testApp.component).coversExactly(display.layerStackSpace)
+ }
}
}
} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index b153bece1133..310f04b9710f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -16,8 +16,8 @@
package com.android.server.wm.flicker.rotation
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
+import android.view.WindowManager
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -27,6 +27,7 @@ import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,8 +35,41 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Cycle through supported app rotations using seamless rotations.
+ * Test opening an app and cycling through app rotations using seamless rotations
+ *
+ * Currently runs:
+ * 0 -> 90 degrees
+ * 0 -> 90 degrees (with starved UI thread)
+ * 90 -> 0 degrees
+ * 90 -> 0 degrees (with starved UI thread)
+ *
+ * Actions:
+ * Launch an app in fullscreen and supporting seamless rotation (via intent)
+ * Set initial device orientation
+ * Start tracing
+ * Change device orientation
+ * Stop tracing
+ *
* To run this test: `atest FlickerTests:SeamlessAppRotationTest`
+ *
+ * To run only the presubmit assertions add: `--
+ * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
+ * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
+ *
+ * To run only the postsubmit assertions add: `--
+ * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
+ * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
+ *
+ * To run only the flaky assertions add: `--
+ * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [RotationTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@@ -60,45 +94,97 @@ class SeamlessAppRotationTest(
}
}
- @FlakyTest(bugId = 140855415)
+ /**
+ * Checks that [testApp] window is always in full screen
+ */
+ @Presubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() {
- super.statusBarWindowIsAlwaysVisible()
+ fun appWindowFullScreen() {
+ testSpec.assertWm {
+ this.invoke("isFullScreen") {
+ val appWindow = it.windowState(testApp.`package`)
+ val flags = appWindow.windowState?.attributes?.flags ?: 0
+ appWindow.verify("isFullScreen")
+ .that(flags.and(WindowManager.LayoutParams.FLAG_FULLSCREEN))
+ .isGreaterThan(0)
+ }
+ }
}
- @FlakyTest(bugId = 140855415)
+ /**
+ * Checks that [testApp] window is always with seamless rotation
+ */
+ @Presubmit
@Test
- override fun statusBarLayerIsAlwaysVisible() {
- super.statusBarLayerIsAlwaysVisible()
+ fun appWindowSeamlessRotation() {
+ testSpec.assertWm {
+ this.invoke("isRotationSeamless") {
+ val appWindow = it.windowState(testApp.`package`)
+ val rotationAnimation = appWindow.windowState?.attributes?.rotationAnimation ?: 0
+ appWindow.verify("isRotationSeamless")
+ .that(rotationAnimation
+ .and(WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS))
+ .isGreaterThan(0)
+ }
+ }
}
+ /**
+ * Checks that [testApp] window is always visible
+ */
@Presubmit
@Test
fun appLayerAlwaysVisible() {
testSpec.assertLayers {
- isVisible(testApp.`package`)
+ isVisible(testApp.component)
}
}
- @FlakyTest(bugId = 185400889)
+ /**
+ * Checks that [testApp] layer covers the entire screen during the whole transition
+ */
+ @Presubmit
@Test
fun appLayerRotates() {
testSpec.assertLayers {
- this.coversExactly(startingPos, testApp.`package`)
- .then()
- .coversExactly(endingPos, testApp.`package`)
+ this.invoke("entireScreenCovered") { entry ->
+ entry.entry.displays.map { display ->
+ entry.visibleRegion(testApp.component).coversExactly(display.layerStackSpace)
+ }
+ }
}
}
- @Postsubmit
+ /**
+ * Checks that the [FlickerComponentName.STATUS_BAR] window is invisible during the whole
+ * transition
+ */
+ @Presubmit
@Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ fun statusBarWindowIsAlwaysInvisible() {
+ testSpec.assertWm {
+ this.isAboveAppWindowInvisible(FlickerComponentName.STATUS_BAR)
+ }
}
- companion object {
- private val testFactory = FlickerTestParameterFactory.getInstance()
+ /**
+ * Checks that the [FlickerComponentName.STATUS_BAR] layer is invisible during the whole
+ * transition
+ */
+ @Presubmit
+ @Test
+ fun statusBarLayerIsAlwaysInvisible() {
+ testSpec.assertLayers {
+ this.isInvisible(FlickerComponentName.STATUS_BAR)
+ }
+ }
+
+ /** {@inheritDoc} */
+ @FlakyTest
+ @Test
+ override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
+ companion object {
private val Map<String, Any?>.starveUiThread
get() = this.getOrDefault(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) as Boolean
@@ -110,20 +196,34 @@ class SeamlessAppRotationTest(
return config
}
+ /**
+ * Creates the test configurations for seamless rotation based on the default rotation
+ * tests from [FlickerTestParameterFactory.getConfigRotationTests], but adding an
+ * additional flag ([ActivityOptions.EXTRA_STARVE_UI_THREAD]) to indicate if the app
+ * should starve the UI thread of not
+ */
@JvmStatic
private fun getConfigurations(): List<FlickerTestParameter> {
- return testFactory.getConfigRotationTests(repetitions = 2).flatMap {
- val defaultRun = it.createConfig(starveUiThread = false)
- val busyUiRun = it.createConfig(starveUiThread = true)
- listOf(
- FlickerTestParameter(defaultRun),
- FlickerTestParameter(busyUiRun,
- name = "${FlickerTestParameter.defaultName(busyUiRun)}_BUSY_UI_THREAD"
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigRotationTests(repetitions = 2)
+ .flatMap {
+ val defaultRun = it.createConfig(starveUiThread = false)
+ val busyUiRun = it.createConfig(starveUiThread = true)
+ listOf(
+ FlickerTestParameter(defaultRun),
+ FlickerTestParameter(busyUiRun,
+ name = "${FlickerTestParameter.defaultName(busyUiRun)}_BUSY_UI_THREAD"
+ )
)
- )
- }
+ }
}
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 1599ed4b280f..cb37fc7b47e9 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -59,5 +59,36 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".NonResizeableActivity"
+ android:resizeableActivity="false"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableActivity"
+ android:label="NonResizeableApp"
+ android:exported="true"
+ android:showOnLockScreen="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".ButtonActivity"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.ButtonActivity"
+ android:configChanges="orientation|screenSize"
+ android:label="ButtonActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".LaunchNewTaskActivity"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewTaskActivity"
+ android:configChanges="orientation|screenSize"
+ android:label="LaunchNewTaskActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml
new file mode 100644
index 000000000000..fe7bced690f9
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/holo_orange_light">
+ <Button
+ android:id="@+id/launch_second_activity"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Second activity" />
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
index 4708cfd48381..c55e7c2720db 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
@@ -23,5 +23,6 @@
<EditText android:id="@+id/plain_text_input"
android:layout_height="wrap_content"
android:layout_width="match_parent"
+ android:imeOptions="flagNoExtractUi"
android:inputType="text"/>
</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_non_resizeable.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_non_resizeable.xml
new file mode 100644
index 000000000000..6d5a9dd29248
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_non_resizeable.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@android:color/holo_orange_light">
+
+ <TextView
+ android:id="@+id/NonResizeableTest"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:gravity="center_vertical|center_horizontal"
+ android:text="NonResizeableActivity"
+ android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml
new file mode 100644
index 000000000000..8f75d175d00a
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/holo_orange_light">
+ <Button
+ android:id="@+id/launch_new_task"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="New task" />
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 0ccc49897202..baf36ab0e132 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -41,4 +41,19 @@ public class ActivityOptions {
public static final ComponentName SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME =
new ComponentName(FLICKER_APP_PACKAGE,
FLICKER_APP_PACKAGE + ".SimpleActivity");
+
+ public static final String NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME = "NonResizeableApp";
+ public static final ComponentName NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME =
+ new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".NonResizeableActivity");
+
+ public static final String BUTTON_ACTIVITY_LAUNCHER_NAME = "ButtonApp";
+ public static final ComponentName BUTTON_ACTIVITY_COMPONENT_NAME =
+ new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".ButtonActivity");
+
+ public static final String LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME = "LaunchNewTaskApp";
+ public static final ComponentName LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME =
+ new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity");
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java
new file mode 100644
index 000000000000..b42ac2a6fd97
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+import android.widget.Button;
+
+public class ButtonActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ WindowManager.LayoutParams p = getWindow().getAttributes();
+ p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
+ .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+ getWindow().setAttributes(p);
+ setContentView(R.layout.activity_button);
+
+ Button button = findViewById(R.id.launch_second_activity);
+ button.setOnClickListener(v -> {
+ Intent intent = new Intent(ButtonActivity.this, SimpleActivity.class);
+ startActivity(intent);
+ });
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java
new file mode 100644
index 000000000000..1809781b33e6
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+import android.widget.Button;
+
+public class LaunchNewTaskActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ WindowManager.LayoutParams p = getWindow().getAttributes();
+ p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
+ .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+ getWindow().setAttributes(p);
+ setContentView(R.layout.task_button);
+
+ Button button = findViewById(R.id.launch_new_task);
+ button.setOnClickListener(v -> {
+ Intent intent = new Intent(LaunchNewTaskActivity.this, SimpleActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+ startActivity(intent);
+ });
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableActivity.java
new file mode 100644
index 000000000000..61019d8b3716
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableActivity.java
@@ -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 com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.os.Bundle;
+
+public class NonResizeableActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.activity_non_resizeable);
+
+ setShowWhenLocked(true);
+ setTurnScreenOn(true);
+ KeyguardManager keyguardManager = getSystemService(KeyguardManager.class);
+ if (keyguardManager != null) {
+ keyguardManager.requestDismissKeyguard(this, null);
+ }
+ }
+}
diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
index bcd6ed73e133..824f91e1e826 100644
--- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
+++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
@@ -45,6 +45,7 @@ public final class FrameworksTestsFilter extends SelectTest {
// Test specifications for FrameworksMockingCoreTests.
"android.app.activity.ActivityThreadClientTest",
"android.view.DisplayTest",
+ "android.window.ConfigurationHelperTest",
// Test specifications for FrameworksCoreTests.
"android.app.servertransaction.", // all tests under the package.
"android.view.CutoutSpecificationTest",
@@ -59,10 +60,8 @@ public final class FrameworksTestsFilter extends SelectTest {
"android.view.RoundedCornersTest",
"android.view.WindowMetricsTest",
"android.view.PendingInsetsControllerTest",
- "android.window.WindowContextTest",
- "android.window.WindowMetricsHelperTest",
+ "android.window.", // all tests under the package.
"android.app.activity.ActivityThreadTest",
- "android.window.WindowContextControllerTest"
};
public FrameworksTestsFilter(Bundle testArgs) {
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index 96f6512fe7f5..cbcbf05c860c 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -27,7 +27,7 @@ namespace aapt {
static ApiVersion sDevelopmentSdkLevel = 10000;
static const auto sDevelopmentSdkCodeNames = std::unordered_set<StringPiece>({
- "Q", "R", "S"
+ "Q", "R", "S", "Sv2"
});
static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = {
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index 72eaa3561a02..a8845ef96bc3 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -120,6 +120,13 @@ bool BinaryResourceParser::Parse() {
static_cast<int>(parser.chunk()->type)));
}
}
+
+ if (!staged_entries_to_remove_.empty()) {
+ diag_->Error(DiagMessage(source_) << "didn't find " << staged_entries_to_remove_.size()
+ << " original staged resources");
+ return false;
+ }
+
return true;
}
@@ -393,6 +400,12 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
return false;
}
+ if (const auto to_remove_it = staged_entries_to_remove_.find({name, res_id});
+ to_remove_it != staged_entries_to_remove_.end()) {
+ staged_entries_to_remove_.erase(to_remove_it);
+ continue;
+ }
+
NewResourceBuilder res_builder(name);
res_builder.SetValue(std::move(resource_value), config)
.SetId(res_id, OnIdConflict::CREATE_ENTRY)
@@ -533,9 +546,8 @@ bool BinaryResourceParser::ParseStagedAliases(const ResChunk_header* chunk) {
// Since a the finalized resource entry is cloned and added to the resource table under the
// staged resource id, remove the cloned resource entry from the table.
if (!table_->RemoveResource(resource_name, staged_id)) {
- diag_->Error(DiagMessage(source_) << "failed to find resource entry for staged "
- << " resource ID " << staged_id);
- return false;
+ // If we haven't seen this resource yet let's add a record to skip it when parsing.
+ staged_entries_to_remove_.insert({resource_name, staged_id});
}
}
return true;
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h
index cd71d160703a..1c83166c5cce 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.h
+++ b/tools/aapt2/format/binary/BinaryResourceParser.h
@@ -119,6 +119,10 @@ class BinaryResourceParser {
// A mapping of resource ID to type spec flags.
std::unordered_map<ResourceId, uint32_t> entry_type_spec_flags_;
+
+ // A collection of staged resources that got finalized already and we're supposed to prune -
+ // but the original staged resource record hasn't been parsed yet.
+ std::set<std::pair<ResourceName, ResourceId>> staged_entries_to_remove_;
};
} // namespace aapt
diff --git a/tools/finalize_res/finalize_res.py b/tools/finalize_res/finalize_res.py
index aaf01875024e..724443c01852 100755
--- a/tools/finalize_res/finalize_res.py
+++ b/tools/finalize_res/finalize_res.py
@@ -17,6 +17,7 @@
"""
Finalize resource values in <staging-public-group> tags
+and convert those to <staging-public-group-final>
Usage: finalize_res.py core/res/res/values/public.xml public_finalized.xml
"""
@@ -24,18 +25,40 @@ Usage: finalize_res.py core/res/res/values/public.xml public_finalized.xml
import re, sys, codecs
def finalize_item(raw):
- global _type, _id
- _id += 1
- return '<public type="%s" name="%s" id="%s" />' % (_type, raw.group(1), '0x{0:0{1}x}'.format(_id-1,8))
+ global _type_ids, _type
+ id = _type_ids[_type]
+ _type_ids[_type] += 1
+ name = raw.group(1)
+ val = '<public type="%s" name="%s" id="%s" />' % (_type, name, '0x{0:0{1}x}'.format(id,8))
+ if re.match(r'_*removed.+', name):
+ val = '<!-- ' + val.replace('<public', '< public') + ' -->'
+ return val
def finalize_group(raw):
- global _type, _id
+ global _type_ids, _type
_type = raw.group(1)
- _id = int(raw.group(2), 16)
- return re.sub(r'<public name="(.+?)" */>', finalize_item, raw.group(3))
+ id = int(raw.group(2), 16)
+ _type_ids[_type] = _type_ids.get(_type, id)
+ (res, count) = re.subn(r' {0,2}<public name="(.+?)" */>', finalize_item, raw.group(3))
+ if count > 0:
+ res = raw.group(0).replace("staging-public-group", "staging-public-group-final") + '\n' + res
+ return res
+
+def collect_ids(raw):
+ global _type_ids
+ for m in re.finditer(r'<public type="(.+?)" name=".+?" id="(.+?)" />', raw):
+ type = m.group(1)
+ id = int(m.group(2), 16)
+ _type_ids[type] = max(id + 1, _type_ids.get(type, 0))
with open(sys.argv[1]) as f:
+ global _type_ids, _type
+ _type_ids = {}
raw = f.read()
+ collect_ids(raw)
raw = re.sub(r'<staging-public-group type="(.+?)" first-id="(.+?)">(.+?)</staging-public-group>', finalize_group, raw, flags=re.DOTALL)
+ raw = re.sub(r' *\n', '\n', raw)
+ raw = re.sub(r'\n{3,}', '\n\n', raw)
with open(sys.argv[2], "w") as f:
f.write(raw)
+