summaryrefslogtreecommitdiff
path: root/libs/WindowManager/Shell/src
diff options
context:
space:
mode:
Diffstat (limited to 'libs/WindowManager/Shell/src')
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java63
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java237
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java111
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java156
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt79
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java97
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java251
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt109
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.kt23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java84
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java305
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java146
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt130
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java74
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java114
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt124
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl)7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPipAnimationListener.aidl (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl)2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java)14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java)22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java166
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt218
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java386
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt340
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java110
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt121
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java119
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java214
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchGesture.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java277
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java577
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java141
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuActionView.java56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuIconsAlgorithm.java71
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java600
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java66
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java235
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java120
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java159
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java227
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java614
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java321
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java52
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java181
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java225
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java156
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java)58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java218
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java83
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java364
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java458
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java352
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java97
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt106
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskResizeAnimationListener.kt41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java146
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt8
194 files changed, 9114 insertions, 3841 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
index 88525aabe53b..ef9bf008b294 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
@@ -16,7 +16,10 @@
package com.android.wm.shell;
-import com.android.wm.shell.protolog.ShellProtoLogImpl;
+import com.android.internal.protolog.LegacyProtoLogImpl;
+import com.android.internal.protolog.common.ILogger;
+import com.android.internal.protolog.common.IProtoLog;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
@@ -24,19 +27,19 @@ import java.io.PrintWriter;
import java.util.Arrays;
/**
- * Controls the {@link ShellProtoLogImpl} in WMShell via adb shell commands.
+ * Controls the {@link ProtoLog} in WMShell via adb shell commands.
*
* Use with {@code adb shell dumpsys activity service SystemUIService WMShell protolog ...}.
*/
public class ProtoLogController implements ShellCommandHandler.ShellCommandActionHandler {
private final ShellCommandHandler mShellCommandHandler;
- private final ShellProtoLogImpl mShellProtoLog;
+ private final IProtoLog mShellProtoLog;
public ProtoLogController(ShellInit shellInit,
ShellCommandHandler shellCommandHandler) {
shellInit.addInitCallback(this::onInit, this);
mShellCommandHandler = shellCommandHandler;
- mShellProtoLog = ShellProtoLogImpl.getSingleInstance();
+ mShellProtoLog = ProtoLog.getSingleInstance();
}
void onInit() {
@@ -45,22 +48,35 @@ public class ProtoLogController implements ShellCommandHandler.ShellCommandActio
@Override
public boolean onShellCommand(String[] args, PrintWriter pw) {
+ final ILogger logger = pw::println;
switch (args[0]) {
case "status": {
- pw.println(mShellProtoLog.getStatus());
+ if (android.tracing.Flags.perfettoProtologTracing()) {
+ pw.println("(Deprecated) legacy command. Use Perfetto commands instead.");
+ return false;
+ }
+ ((LegacyProtoLogImpl) mShellProtoLog).getStatus();
return true;
}
case "start": {
- mShellProtoLog.startProtoLog(pw);
+ if (android.tracing.Flags.perfettoProtologTracing()) {
+ pw.println("(Deprecated) legacy command. Use Perfetto commands instead.");
+ return false;
+ }
+ ((LegacyProtoLogImpl) mShellProtoLog).startProtoLog(pw);
return true;
}
case "stop": {
- mShellProtoLog.stopProtoLog(pw, true /* writeToFile */);
+ if (android.tracing.Flags.perfettoProtologTracing()) {
+ pw.println("(Deprecated) legacy command. Use Perfetto commands instead.");
+ return false;
+ }
+ ((LegacyProtoLogImpl) mShellProtoLog).stopProtoLog(pw, true);
return true;
}
case "enable-text": {
String[] groups = Arrays.copyOfRange(args, 1, args.length);
- int result = mShellProtoLog.startTextLogging(groups, pw);
+ int result = mShellProtoLog.startLoggingToLogcat(groups, logger);
if (result == 0) {
pw.println("Starting logging on groups: " + Arrays.toString(groups));
return true;
@@ -69,7 +85,7 @@ public class ProtoLogController implements ShellCommandHandler.ShellCommandActio
}
case "disable-text": {
String[] groups = Arrays.copyOfRange(args, 1, args.length);
- int result = mShellProtoLog.stopTextLogging(groups, pw);
+ int result = mShellProtoLog.stopLoggingToLogcat(groups, logger);
if (result == 0) {
pw.println("Stopping logging on groups: " + Arrays.toString(groups));
return true;
@@ -78,19 +94,23 @@ public class ProtoLogController implements ShellCommandHandler.ShellCommandActio
}
case "enable": {
String[] groups = Arrays.copyOfRange(args, 1, args.length);
- return mShellProtoLog.startTextLogging(groups, pw) == 0;
+ return mShellProtoLog.startLoggingToLogcat(groups, logger) == 0;
}
case "disable": {
String[] groups = Arrays.copyOfRange(args, 1, args.length);
- return mShellProtoLog.stopTextLogging(groups, pw) == 0;
+ return mShellProtoLog.stopLoggingToLogcat(groups, logger) == 0;
}
case "save-for-bugreport": {
+ if (android.tracing.Flags.perfettoProtologTracing()) {
+ pw.println("(Deprecated) legacy command");
+ return false;
+ }
if (!mShellProtoLog.isProtoEnabled()) {
pw.println("Logging to proto is not enabled for WMShell.");
return false;
}
- mShellProtoLog.stopProtoLog(pw, true /* writeToFile */);
- mShellProtoLog.startProtoLog(pw);
+ ((LegacyProtoLogImpl) mShellProtoLog).stopProtoLog(pw, true /* writeToFile */);
+ ((LegacyProtoLogImpl) mShellProtoLog).startProtoLog(pw);
return true;
}
default: {
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 fe65fdd30e48..d8d0d876b4f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -643,19 +643,6 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
}
- /** Helper to set int metadata on the Surface corresponding to the task id. */
- public void setSurfaceMetadata(int taskId, int key, int value) {
- synchronized (mLock) {
- final TaskAppearedInfo info = mTasks.get(taskId);
- if (info == null || info.getLeash() == null) {
- return;
- }
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- t.setMetadata(info.getLeash(), key, value);
- t.apply();
- }
- }
-
private boolean updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash,
TaskListener oldListener, TaskListener newListener) {
if (oldListener == newListener) return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 8241e1a481ee..8d30db64a3e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -29,7 +29,7 @@ import android.window.TransitionInfo;
import androidx.annotation.NonNull;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/**
* Wrapper to handle the ActivityEmbedding animation update in one
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index ac75c73d7e6d..539832e3cf3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -20,6 +20,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
+import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationSpec.createShowSnapshotForClosingAnimation;
import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
@@ -45,7 +46,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter;
import com.android.wm.shell.common.ScreenshotUtils;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
@@ -330,6 +331,11 @@ class ActivityEmbeddingAnimationRunner {
if (!animation.hasExtension()) {
continue;
}
+ if (adapter.mChange.hasFlags(FLAG_TRANSLUCENT)
+ && adapter.mChange.getActivityComponent() != null) {
+ // Skip edge extension for translucent activity.
+ continue;
+ }
final TransitionInfo.Change change = adapter.mChange;
if (TransitionUtil.isOpeningType(adapter.mChange.getMode())) {
// Need to screenshot after startTransaction is applied otherwise activity
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index efa5a1a64ade..0272f1cda6ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -38,7 +38,7 @@ import android.view.animation.TranslateAnimation;
import android.window.TransitionInfo;
import com.android.internal.policy.TransitionAnimation;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/** Animation spec for ActivityEmbedding transition. */
// TODO(b/206557124): provide an easier way to customize animation
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index b4e852cfaa48..1f9358e2aa91 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -38,9 +38,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.List;
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 2ec9e8b12fc6..19963675ff86 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
@@ -71,6 +71,13 @@ public class Interpolators {
*/
public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
0.05f, 0.7f, 0.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);
+
/**
* Interpolator to be used when animating a move based on a click. Pair with enough duration.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
index e06d3ef4e1ab..5b0de5070a60 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
@@ -21,5 +21,4 @@ package com.android.wm.shell.back;
*/
class BackAnimationConstants {
static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.20f;
- static final float PROGRESS_COMMIT_THRESHOLD = 0.1f;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index d8c691b01b61..2606fb661e80 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -17,7 +17,7 @@
package com.android.wm.shell.back;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
-import static com.android.window.flags.Flags.predictiveBackSystemAnimations;
+import static com.android.window.flags.Flags.predictiveBackSystemAnims;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -70,9 +70,11 @@ import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -112,6 +114,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
/** Tracks if we should start the back gesture on the next motion move event */
private boolean mShouldStartOnNextMoveEvent = false;
private boolean mOnBackStartDispatched = false;
+ private boolean mPointerPilfered = false;
private final FlingAnimationUtils mFlingAnimationUtils;
@@ -124,6 +127,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private final Context mContext;
private final ContentResolver mContentResolver;
private final ShellController mShellController;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellExecutor mShellExecutor;
private final Handler mBgHandler;
@@ -173,6 +177,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private StatusBarCustomizer mCustomizer;
private boolean mTrackingLatency;
+ // Keep previous navigation type before remove mBackNavigationInfo.
+ @BackNavigationInfo.BackTargetType
+ private int mPreviousNavigationType;
+
public BackAnimationController(
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
@@ -180,7 +188,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context,
@NonNull BackAnimationBackground backAnimationBackground,
- ShellBackAnimationRegistry shellBackAnimationRegistry) {
+ ShellBackAnimationRegistry shellBackAnimationRegistry,
+ ShellCommandHandler shellCommandHandler) {
this(
shellInit,
shellController,
@@ -190,7 +199,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
context,
context.getContentResolver(),
backAnimationBackground,
- shellBackAnimationRegistry);
+ shellBackAnimationRegistry,
+ shellCommandHandler);
}
@VisibleForTesting
@@ -203,7 +213,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
Context context,
ContentResolver contentResolver,
@NonNull BackAnimationBackground backAnimationBackground,
- ShellBackAnimationRegistry shellBackAnimationRegistry) {
+ ShellBackAnimationRegistry shellBackAnimationRegistry,
+ ShellCommandHandler shellCommandHandler) {
mShellController = shellController;
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
@@ -219,6 +230,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
.build();
mShellBackAnimationRegistry = shellBackAnimationRegistry;
mLatencyTracker = LatencyTracker.getInstance(mContext);
+ mShellCommandHandler = shellCommandHandler;
}
private void onInit() {
@@ -227,12 +239,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
createAdapter();
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
this::createExternalInterface, this);
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
private void setupAnimationDeveloperSettingsObserver(
@NonNull ContentResolver contentResolver,
@NonNull @ShellBackgroundThread final Handler backgroundHandler) {
- if (predictiveBackSystemAnimations()) {
+ if (predictiveBackSystemAnims()) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation aconfig flag is enabled, therefore "
+ "developer settings flag is ignored and no content observer registered");
return;
@@ -255,7 +268,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
*/
@ShellBackgroundThread
private void updateEnableAnimationFromFlags() {
- boolean isEnabled = predictiveBackSystemAnimations() || isDeveloperSettingEnabled();
+ boolean isEnabled = predictiveBackSystemAnims() || isDeveloperSettingEnabled();
mEnableAnimations.set(isEnabled);
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
}
@@ -392,10 +405,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@VisibleForTesting
void onPilferPointers() {
- mCurrentTracker.updateStartLocation();
+ mPointerPilfered = true;
// Dispatch onBackStarted, only to app callbacks.
// System callbacks will receive onBackStarted when the remote animation starts.
- if (!shouldDispatchToAnimator()) {
+ if (!shouldDispatchToAnimator() && mActiveCallback != null) {
+ mCurrentTracker.updateStartLocation();
tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null));
}
}
@@ -543,9 +557,19 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
&& mBackNavigationInfo.isPrepareRemoteAnimation();
}
- private void tryDispatchOnBackStarted(IOnBackInvokedCallback callback,
+ private void tryDispatchOnBackStarted(
+ IOnBackInvokedCallback callback,
+ BackMotionEvent backEvent) {
+ if (mOnBackStartDispatched || callback == null || !mPointerPilfered) {
+ return;
+ }
+ dispatchOnBackStarted(callback, backEvent);
+ }
+
+ private void dispatchOnBackStarted(
+ IOnBackInvokedCallback callback,
BackMotionEvent backEvent) {
- if (callback == null || mOnBackStartDispatched) {
+ if (callback == null) {
return;
}
try {
@@ -850,9 +874,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mActiveCallback = null;
mShouldStartOnNextMoveEvent = false;
mOnBackStartDispatched = false;
+ mPointerPilfered = false;
mShellBackAnimationRegistry.resetDefaultCrossActivity();
cancelLatencyTracking();
if (mBackNavigationInfo != null) {
+ mPreviousNavigationType = mBackNavigationInfo.getType();
mBackNavigationInfo.onBackNavigationFinished(triggerBack);
mBackNavigationInfo = null;
}
@@ -932,9 +958,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (apps.length >= 1) {
mCurrentTracker.updateStartLocation();
- tryDispatchOnBackStarted(
- mActiveCallback,
- mCurrentTracker.createStartEvent(apps[0]));
+ BackMotionEvent startEvent =
+ mCurrentTracker.createStartEvent(apps[0]);
+ dispatchOnBackStarted(mActiveCallback, startEvent);
}
// Dispatch the first progress after animation start for
@@ -957,7 +983,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mShellExecutor.execute(
() -> {
if (!mShellBackAnimationRegistry.cancel(
- mBackNavigationInfo.getType())) {
+ mBackNavigationInfo != null
+ ? mBackNavigationInfo.getType()
+ : mPreviousNavigationType)) {
return;
}
if (!mBackGestureStarted) {
@@ -968,4 +996,20 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
};
mBackAnimationAdapter = new BackAnimationAdapter(runner);
}
+
+ /**
+ * Description of current BackAnimationController state.
+ */
+ private void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "BackAnimationController state:");
+ pw.println(prefix + " mEnableAnimations=" + mEnableAnimations.get());
+ pw.println(prefix + " mBackGestureStarted=" + mBackGestureStarted);
+ pw.println(prefix + " mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress);
+ pw.println(prefix + " mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent);
+ pw.println(prefix + " mCurrentTracker state:");
+ mCurrentTracker.dump(pw, prefix + " ");
+ pw.println(prefix + " mQueuedTracker state:");
+ mQueuedTracker.dump(pw, prefix + " ");
+ }
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
index 215a6cc99e58..d6f7c367f772 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
@@ -18,9 +18,9 @@ package com.android.wm.shell.back;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.window.BackEvent.EDGE_RIGHT;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
-import static com.android.wm.shell.back.BackAnimationConstants.PROGRESS_COMMIT_THRESHOLD;
import static com.android.wm.shell.back.BackAnimationConstants.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
@@ -40,7 +40,6 @@ import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
-import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.window.BackEvent;
import android.window.BackMotionEvent;
@@ -51,6 +50,7 @@ import com.android.internal.dynamicanimation.animation.SpringAnimation;
import com.android.internal.dynamicanimation.animation.SpringForce;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.annotations.ShellMainThread;
import javax.inject.Inject;
@@ -65,7 +65,7 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
/** Duration of post animation after gesture committed. */
private static final int POST_ANIMATION_DURATION = 350;
- private static final Interpolator INTERPOLATOR = new DecelerateInterpolator();
+ private static final Interpolator INTERPOLATOR = Interpolators.STANDARD_DECELERATE;
private static final FloatProperty<CrossActivityBackAnimation> ENTER_PROGRESS_PROP =
new FloatProperty<>("enter-alpha") {
@Override
@@ -91,7 +91,7 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
}
};
private static final float MIN_WINDOW_ALPHA = 0.01f;
- private static final float WINDOW_X_SHIFT_DP = 96;
+ private static final float WINDOW_X_SHIFT_DP = 48;
private static final int SCALE_FACTOR = 100;
// TODO(b/264710590): Use the progress commit threshold from ViewConfiguration once it exists.
private static final float TARGET_COMMIT_PROGRESS = 0.5f;
@@ -126,6 +126,8 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
private boolean mBackInProgress = false;
+ private boolean mIsRightEdge;
+ private boolean mTriggerBack = false;
private PointF mTouchPos = new PointF();
private IRemoteAnimationFinishedCallback mFinishCallback;
@@ -197,6 +199,10 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
}
private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) {
+ if (leash == null || !leash.isValid()) {
+ return;
+ }
+
final float scale = targetRect.width() / mStartTaskRect.width();
mTransformMatrix.reset();
mTransformMatrix.setScale(scale, scale);
@@ -209,11 +215,16 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
private void finishAnimation() {
if (mEnteringTarget != null) {
- mEnteringTarget.leash.release();
+ if (mEnteringTarget.leash != null && mEnteringTarget.leash.isValid()) {
+ mTransaction.setCornerRadius(mEnteringTarget.leash, 0);
+ mEnteringTarget.leash.release();
+ }
mEnteringTarget = null;
}
if (mClosingTarget != null) {
- mClosingTarget.leash.release();
+ if (mClosingTarget.leash != null) {
+ mClosingTarget.leash.release();
+ }
mClosingTarget = null;
}
if (mBackground != null) {
@@ -241,14 +252,15 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
private void onGestureProgress(@NonNull BackEvent backEvent) {
if (!mBackInProgress) {
+ mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT;
mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
mBackInProgress = true;
}
mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
float progress = backEvent.getProgress();
- float springProgress = (progress > PROGRESS_COMMIT_THRESHOLD
- ? mapLinear(progress, 0.1f, 1, TARGET_COMMIT_PROGRESS, 1)
+ float springProgress = (mTriggerBack
+ ? mapLinear(progress, 0f, 1, TARGET_COMMIT_PROGRESS, 1)
: mapLinear(progress, 0, 1f, 0, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
mLeavingProgressSpring.animateToFinalPosition(springProgress);
mEnteringProgressSpring.animateToFinalPosition(springProgress);
@@ -256,7 +268,9 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
}
private void onGestureCommitted() {
- if (mEnteringTarget == null || mClosingTarget == null) {
+ if (mEnteringTarget == null || mClosingTarget == null || mClosingTarget.leash == null
+ || mEnteringTarget.leash == null || !mEnteringTarget.leash.isValid()
+ || !mClosingTarget.leash.isValid()) {
finishAnimation();
return;
}
@@ -271,7 +285,7 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
ValueAnimator valueAnimator =
ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION);
- valueAnimator.setInterpolator(new DecelerateInterpolator());
+ valueAnimator.setInterpolator(INTERPOLATOR);
valueAnimator.addUpdateListener(animation -> {
float progress = animation.getAnimatedFraction();
updatePostCommitEnteringAnimation(progress);
@@ -296,12 +310,16 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
- float alpha = mapRange(progress, mEnteringProgress, 1.0f);
-
+ float alpha = mapRange(progress, getPreCommitEnteringAlpha(), 1.0f);
mEnteringRect.set(left, top, left + width, top + height);
applyTransform(mEnteringTarget.leash, mEnteringRect, alpha);
}
+ private float getPreCommitEnteringAlpha() {
+ return Math.max(smoothstep(ENTER_ALPHA_THRESHOLD, 0.7f, mEnteringProgress),
+ MIN_WINDOW_ALPHA);
+ }
+
private float getEnteringProgress() {
return mEnteringProgress * SCALE_FACTOR;
}
@@ -311,9 +329,7 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
if (mEnteringTarget != null && mEnteringTarget.leash != null) {
transformWithProgress(
mEnteringProgress,
- Math.max(
- smoothstep(ENTER_ALPHA_THRESHOLD, 1, mEnteringProgress),
- MIN_WINDOW_ALPHA), /* alpha */
+ getPreCommitEnteringAlpha(),
mEnteringTarget.leash,
mEnteringRect,
-mWindowXShift,
@@ -322,6 +338,11 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
}
}
+ private float getPreCommitLeavingAlpha() {
+ return Math.max(1 - smoothstep(0, ENTER_ALPHA_THRESHOLD, mLeavingProgress),
+ MIN_WINDOW_ALPHA);
+ }
+
private float getLeavingProgress() {
return mLeavingProgress * SCALE_FACTOR;
}
@@ -331,20 +352,17 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
if (mClosingTarget != null && mClosingTarget.leash != null) {
transformWithProgress(
mLeavingProgress,
- Math.max(
- 1 - smoothstep(0, ENTER_ALPHA_THRESHOLD, mLeavingProgress),
- MIN_WINDOW_ALPHA),
+ getPreCommitLeavingAlpha(),
mClosingTarget.leash,
mClosingRect,
0,
- mWindowXShift
+ mIsRightEdge ? 0 : mWindowXShift
);
}
}
private void transformWithProgress(float progress, float alpha, SurfaceControl surface,
RectF targetRect, float deltaXMin, float deltaXMax) {
- final float touchY = mTouchPos.y;
final int width = mStartTaskRect.width();
final int height = mStartTaskRect.height();
@@ -376,12 +394,14 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
+ mTriggerBack = backEvent.getTriggerBack();
mProgressAnimator.onBackStarted(backEvent,
CrossActivityBackAnimation.this::onGestureProgress);
}
@Override
public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
+ mTriggerBack = backEvent.getTriggerBack();
mProgressAnimator.onBackProgressed(backEvent);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 80fc3a867d48..4b3154190910 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -91,7 +91,8 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
private final PointF mInitialTouchPos = new PointF();
private final Interpolator mPostAnimationInterpolator = Interpolators.EMPHASIZED;
- private final Interpolator mProgressInterpolator = new DecelerateInterpolator();
+ private final Interpolator mProgressInterpolator = Interpolators.STANDARD_DECELERATE;
+ private final Interpolator mVerticalMoveInterpolator = new DecelerateInterpolator();
private final Matrix mTransformMatrix = new Matrix();
private final float[] mTmpFloat9 = new float[9];
@@ -136,6 +137,9 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
mStartTaskRect.offsetTo(0, 0);
+ // inset bottom in case of pinned taskbar being present
+ mStartTaskRect.inset(0, 0, 0, mClosingTarget.contentInsets.bottom);
+
// Draw background.
mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(),
BACKGROUNDCOLOR, mTransaction);
@@ -166,7 +170,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
float yDirection = rawYDelta < 0 ? -1 : 1;
// limit yDelta interpretation to 1/2 of screen height in either direction
float deltaYRatio = Math.min(height / 2f, Math.abs(rawYDelta)) / (height / 2f);
- float interpolatedYRatio = mProgressInterpolator.getInterpolation(deltaYRatio);
+ float interpolatedYRatio = mVerticalMoveInterpolator.getInterpolation(deltaYRatio);
// limit y-shift so surface never passes 8dp screen margin
float deltaY = yDirection * interpolatedYRatio * Math.max(0f,
(height - scaledHeight) / 2f - mVerticalMargin);
@@ -205,7 +209,9 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
float top = mapRange(progress, mClosingStartRect.top, targetTop);
float width = mapRange(progress, mClosingStartRect.width(), targetWidth);
float height = mapRange(progress, mClosingStartRect.height(), targetHeight);
- mTransaction.setLayer(mClosingTarget.leash, 0);
+ if (mClosingTarget.leash != null && mClosingTarget.leash.isValid()) {
+ mTransaction.setLayer(mClosingTarget.leash, 0);
+ }
mClosingCurrentRect.set(left, top, left + width, top + height);
applyTransform(mClosingTarget.leash, mClosingCurrentRect, mCornerRadius);
@@ -223,7 +229,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
/** Transform the target window to match the target rect. */
private void applyTransform(SurfaceControl leash, RectF targetRect, float cornerRadius) {
- if (leash == null) {
+ if (leash == null || !leash.isValid()) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index 4bd56d460818..8f04f126960c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -24,6 +24,8 @@ import android.view.RemoteAnimationTarget;
import android.window.BackEvent;
import android.window.BackMotionEvent;
+import java.io.PrintWriter;
+
/**
* Helper class to record the touch location for gesture and generate back events.
*/
@@ -61,6 +63,10 @@ class TouchTracker {
if ((touchX < mStartThresholdX && mSwipeEdge == BackEvent.EDGE_LEFT)
|| (touchX > mStartThresholdX && mSwipeEdge == BackEvent.EDGE_RIGHT)) {
mStartThresholdX = touchX;
+ if ((mSwipeEdge == BackEvent.EDGE_LEFT && mStartThresholdX < mInitTouchX)
+ || (mSwipeEdge == BackEvent.EDGE_RIGHT && mStartThresholdX > mInitTouchX)) {
+ mInitTouchX = mStartThresholdX;
+ }
}
mLatestTouchX = touchX;
mLatestTouchY = touchY;
@@ -129,6 +135,7 @@ class TouchTracker {
/* progress = */ 0,
/* velocityX = */ 0,
/* velocityY = */ 0,
+ /* triggerBack = */ mTriggerBack,
/* swipeEdge = */ mSwipeEdge,
/* departingAnimationTarget = */ target);
}
@@ -204,6 +211,7 @@ class TouchTracker {
/* progress = */ progress,
/* velocityX = */ mLatestVelocityX,
/* velocityY = */ mLatestVelocityY,
+ /* triggerBack = */ mTriggerBack,
/* swipeEdge = */ mSwipeEdge,
/* departingAnimationTarget = */ null);
}
@@ -219,6 +227,12 @@ class TouchTracker {
mNonLinearFactor = nonLinearFactor;
}
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "TouchTracker state:");
+ pw.println(prefix + " mState=" + mState);
+ pw.println(prefix + " mTriggerBack=" + mTriggerBack);
+ }
+
enum TouchTrackerState {
INITIAL, ACTIVE, FINISHED
}
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 7a3210e0a46d..da530d740d48 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
@@ -19,6 +19,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.os.AsyncTask.Status.FINISHED;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.DimenRes;
import android.annotation.Hide;
@@ -47,6 +48,7 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
@@ -64,7 +66,7 @@ public class Bubble implements BubbleViewProvider {
private static final String TAG = "Bubble";
/** A string suffix used in app bubbles' {@link #mKey}. */
- private static final String KEY_APP_BUBBLE = "key_app_bubble";
+ public static final String KEY_APP_BUBBLE = "key_app_bubble";
/** Whether the bubble is an app bubble. */
private final boolean mIsAppBubble;
@@ -105,6 +107,8 @@ public class Bubble implements BubbleViewProvider {
private BubbleExpandedView mExpandedView;
@Nullable
private BubbleBarExpandedView mBubbleBarExpandedView;
+ @Nullable
+ private BubbleTaskView mBubbleTaskView;
private BubbleViewInfoTask mInflationTask;
private boolean mInflateSynchronously;
@@ -394,6 +398,17 @@ public class Bubble implements BubbleViewProvider {
}
/**
+ * Returns the existing {@link #mBubbleTaskView} if it's not {@code null}. Otherwise a new
+ * instance of {@link BubbleTaskView} is created.
+ */
+ public BubbleTaskView getOrCreateBubbleTaskView(BubbleTaskViewFactory taskViewFactory) {
+ if (mBubbleTaskView == null) {
+ mBubbleTaskView = taskViewFactory.create();
+ }
+ return mBubbleTaskView;
+ }
+
+ /**
* @return the ShortcutInfo id if it exists, or the metadata shortcut id otherwise.
*/
String getShortcutId() {
@@ -415,6 +430,10 @@ public class Bubble implements BubbleViewProvider {
* the bubble.
*/
void cleanupExpandedView() {
+ cleanupExpandedView(true);
+ }
+
+ private void cleanupExpandedView(boolean cleanupTaskView) {
if (mExpandedView != null) {
mExpandedView.cleanUpExpandedState();
mExpandedView = null;
@@ -423,17 +442,38 @@ public class Bubble implements BubbleViewProvider {
mBubbleBarExpandedView.cleanUpExpandedState();
mBubbleBarExpandedView = null;
}
+ if (cleanupTaskView) {
+ cleanupTaskView();
+ }
if (mIntent != null) {
mIntent.unregisterCancelListener(mIntentCancelListener);
}
mIntentActive = false;
}
+ private void cleanupTaskView() {
+ if (mBubbleTaskView != null) {
+ mBubbleTaskView.cleanup();
+ mBubbleTaskView = null;
+ }
+ }
+
/**
* Call when all the views should be removed/cleaned up.
*/
- void cleanupViews() {
- cleanupExpandedView();
+ public void cleanupViews() {
+ ProtoLog.d(WM_SHELL_BUBBLES, "Bubble#cleanupViews=%s", getKey());
+ cleanupViews(true);
+ }
+
+ /**
+ * Call when all the views should be removed/cleaned up.
+ *
+ * <p>If we're switching between bar and floating modes, pass {@code false} on
+ * {@code cleanupTaskView} to avoid recreating it in the new mode.
+ */
+ void cleanupViews(boolean cleanupTaskView) {
+ cleanupExpandedView(cleanupTaskView);
mIconView = null;
}
@@ -468,14 +508,18 @@ public class Bubble implements BubbleViewProvider {
*
* @param callback the callback to notify one the bubble is ready to be displayed.
* @param context the context for the bubble.
- * @param controller the bubble controller.
+ * @param expandedViewManager the bubble expanded view manager.
+ * @param taskViewFactory the task view factory used to create the task view for the bubble.
+ * @param positioner the bubble positioner.
* @param stackView the view the bubble is added to, iff showing as floating.
* @param layerView the layer the bubble is added to, iff showing in the bubble bar.
- * @param iconFactory the icon factory use to create images for the bubble.
+ * @param iconFactory the icon factory used to create images for the bubble.
*/
void inflate(BubbleViewInfoTask.Callback callback,
Context context,
- BubbleController controller,
+ BubbleExpandedViewManager expandedViewManager,
+ BubbleTaskViewFactory taskViewFactory,
+ BubblePositioner positioner,
@Nullable BubbleStackView stackView,
@Nullable BubbleBarLayerView layerView,
BubbleIconFactory iconFactory,
@@ -485,7 +529,9 @@ public class Bubble implements BubbleViewProvider {
}
mInflationTask = new BubbleViewInfoTask(this,
context,
- controller,
+ expandedViewManager,
+ taskViewFactory,
+ positioner,
stackView,
layerView,
iconFactory,
@@ -866,9 +912,8 @@ public class Bubble implements BubbleViewProvider {
if (uid != -1) {
intent.putExtra(Settings.EXTRA_APP_UID, uid);
}
- intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
return intent;
}
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 249f52bd6156..96aaf02cb5e3 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
@@ -23,7 +23,6 @@ import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
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.Bubbles.DISMISS_BLOCKED;
@@ -114,6 +113,7 @@ import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewTaskController;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -122,6 +122,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -137,7 +138,7 @@ import java.util.function.IntConsumer;
* The controller manages addition, removal, and visible state of bubbles on screen.
*/
public class BubbleController implements ConfigurationChangeListener,
- RemoteCallable<BubbleController> {
+ RemoteCallable<BubbleController>, Bubbles.SysuiProxy.Provider {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -173,7 +174,7 @@ public class BubbleController implements ConfigurationChangeListener,
private final Context mContext;
private final BubblesImpl mImpl = new BubblesImpl();
private Bubbles.BubbleExpandListener mExpandListener;
- @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
+ @Nullable private final BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
private final FloatingContentCoordinator mFloatingContentCoordinator;
private final BubbleDataRepository mDataRepository;
private final WindowManagerShellWrapper mWindowManagerShellWrapper;
@@ -191,18 +192,20 @@ public class BubbleController implements ConfigurationChangeListener,
private final ShellCommandHandler mShellCommandHandler;
private final IWindowManager mWmService;
private final BubbleProperties mBubbleProperties;
+ private final BubbleTaskViewFactory mBubbleTaskViewFactory;
+ private final BubbleExpandedViewManager mExpandedViewManager;
// Used to post to main UI thread
private final ShellExecutor mMainExecutor;
private final Handler mMainHandler;
private final ShellExecutor mBackgroundExecutor;
- private BubbleLogger mLogger;
- private BubbleData mBubbleData;
+ private final BubbleLogger mLogger;
+ private final BubbleData mBubbleData;
@Nullable private BubbleStackView mStackView;
@Nullable private BubbleBarLayerView mLayerView;
private BubbleIconFactory mBubbleIconFactory;
- private BubblePositioner mBubblePositioner;
+ private final BubblePositioner mBubblePositioner;
private Bubbles.SysuiProxy mSysuiProxy;
// Tracks the id of the current (foreground) user.
@@ -232,15 +235,22 @@ public class BubbleController implements ConfigurationChangeListener,
/** Whether or not the BubbleStackView has been added to the WindowManager. */
private boolean mAddedToWindowManager = false;
- /** Saved screen density, used to detect display size changes in {@link #onConfigChanged}. */
+ /**
+ * Saved screen density, used to detect display size changes in {@link #onConfigurationChanged}.
+ */
private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED;
- /** Saved screen bounds, used to detect screen size changes in {@link #onConfigChanged}. **/
- private Rect mScreenBounds = new Rect();
+ /**
+ * Saved screen bounds, used to detect screen size changes in {@link #onConfigurationChanged}.
+ */
+ private final Rect mScreenBounds = new Rect();
- /** Saved font scale, used to detect font size changes in {@link #onConfigChanged}. */
+ /** Saved font scale, used to detect font size changes in {@link #onConfigurationChanged}. */
private float mFontScale = 0;
+ /** Saved locale, used to detect local changes in {@link #onConfigurationChanged}. */
+ private Locale mLocale = null;
+
/** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */
private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED;
@@ -253,9 +263,9 @@ public class BubbleController implements ConfigurationChangeListener,
private boolean mIsStatusBarShade = true;
/** One handed mode controller to register transition listener. */
- private Optional<OneHandedController> mOneHandedOptional;
+ private final Optional<OneHandedController> mOneHandedOptional;
/** Drag and drop controller to register listener for onDragStarted. */
- private Optional<DragAndDropController> mDragAndDropController;
+ private final DragAndDropController mDragAndDropController;
/** Used to send bubble events to launcher. */
private Bubbles.BubbleStateListener mBubbleStateListener;
@@ -281,7 +291,7 @@ public class BubbleController implements ConfigurationChangeListener,
BubblePositioner positioner,
DisplayController displayController,
Optional<OneHandedController> oneHandedOptional,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
@@ -330,6 +340,16 @@ public class BubbleController implements ConfigurationChangeListener,
mWmService = wmService;
mBubbleProperties = bubbleProperties;
shellInit.addInitCallback(this::onInit, this);
+ mBubbleTaskViewFactory = new BubbleTaskViewFactory() {
+ @Override
+ public BubbleTaskView create() {
+ TaskViewTaskController taskViewTaskController = new TaskViewTaskController(
+ context, organizer, taskViewTransitions, syncQueue);
+ TaskView taskView = new TaskView(context, taskViewTaskController);
+ return new BubbleTaskView(taskView, mainExecutor);
+ }
+ };
+ mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this);
}
private void registerOneHandedState(OneHandedController oneHanded) {
@@ -431,6 +451,9 @@ public class BubbleController implements ConfigurationChangeListener,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
for (Bubble b : mBubbleData.getBubbles()) {
if (task.taskId == b.getTaskId()) {
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "onActivityRestartAttempt - taskId=%d selecting matching bubble=%s",
+ task.taskId, b.getKey());
mBubbleData.setSelectedBubble(b);
mBubbleData.setExpanded(true);
return;
@@ -438,6 +461,9 @@ public class BubbleController implements ConfigurationChangeListener,
}
for (Bubble b : mBubbleData.getOverflowBubbles()) {
if (task.taskId == b.getTaskId()) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onActivityRestartAttempt - taskId=%d "
+ + "selecting matching overflow bubble=%s",
+ task.taskId, b.getKey());
promoteBubbleFromOverflow(b);
mBubbleData.setExpanded(true);
return;
@@ -459,7 +485,12 @@ public class BubbleController implements ConfigurationChangeListener,
});
mOneHandedOptional.ifPresent(this::registerOneHandedState);
- mDragAndDropController.ifPresent(controller -> controller.addListener(this::collapseStack));
+ mDragAndDropController.addListener(new DragAndDropController.DragAndDropListener() {
+ @Override
+ public void onDragStarted() {
+ collapseStack();
+ }
+ });
// Clear out any persisted bubbles on disk that no longer have a valid user.
List<UserInfo> users = mUserManager.getAliveUsers();
@@ -577,10 +608,15 @@ public class BubbleController implements ConfigurationChangeListener,
// Hide the stack temporarily if the status bar has been made invisible, and the stack
// is collapsed. An expanded stack should remain visible until collapsed.
mStackView.setTemporarilyInvisible(!visible && !isStackExpanded());
+ ProtoLog.d(WM_SHELL_BUBBLES, "onStatusBarVisibilityChanged=%b stackExpanded=%b",
+ visible, isStackExpanded());
}
}
private void onZenStateChanged() {
+ if (hasBubbles()) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onZenStateChanged");
+ }
for (Bubble b : mBubbleData.getBubbles()) {
b.setShowDot(b.showInShade());
}
@@ -589,9 +625,10 @@ public class BubbleController implements ConfigurationChangeListener,
@VisibleForTesting
public void onStatusBarStateChanged(boolean isShade) {
boolean didChange = mIsStatusBarShade != isShade;
- if (DEBUG_BUBBLE_CONTROLLER) {
- Log.d(TAG, "onStatusBarStateChanged isShade=" + isShade + " didChange=" + didChange);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onStatusBarStateChanged "
+ + "isShade=%b didChange=%b mNotifEntryToExpandOnShadeUnlock=%s",
+ isShade, didChange, (mNotifEntryToExpandOnShadeUnlock != null
+ ? mNotifEntryToExpandOnShadeUnlock.getKey() : "null"));
mIsStatusBarShade = isShade;
if (!mIsStatusBarShade && didChange) {
// Only collapse stack on change
@@ -607,6 +644,8 @@ public class BubbleController implements ConfigurationChangeListener,
@VisibleForTesting
public void onBubbleMetadataFlagChanged(Bubble bubble) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onBubbleMetadataFlagChanged=%s flags=%d",
+ bubble.getKey(), bubble.getFlags());
// Make sure NoMan knows suppression state so that anyone querying it can tell.
try {
mBarService.onBubbleMetadataFlagChanged(bubble.getKey(), bubble.getFlags());
@@ -619,6 +658,8 @@ public class BubbleController implements ConfigurationChangeListener,
/** Called when the current user changes. */
@VisibleForTesting
public void onUserChanged(int newUserId) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onUserChanged currentUser=%d newUser=%d",
+ mCurrentUserId, newUserId);
saveBubbles(mCurrentUserId);
mCurrentUserId = newUserId;
@@ -646,6 +687,17 @@ public class BubbleController implements ConfigurationChangeListener,
mDataRepository.removeBubblesForUser(removedUserId, parentUserId);
}
+ /** Called when sensitive notification state has changed */
+ public void onSensitiveNotificationProtectionStateChanged(
+ boolean sensitiveNotificationProtectionActive) {
+ if (mStackView != null) {
+ mStackView.onSensitiveNotificationProtectionStateChanged(
+ sensitiveNotificationProtectionActive);
+ ProtoLog.d(WM_SHELL_BUBBLES, "onSensitiveNotificationProtectionStateChanged=%b",
+ sensitiveNotificationProtectionActive);
+ }
+ }
+
/** Whether bubbles are showing in the bubble bar. */
public boolean isShowingAsBubbleBar() {
return canShowAsBubbleBar() && mBubbleStateListener != null;
@@ -706,6 +758,7 @@ public class BubbleController implements ConfigurationChangeListener,
return mBubbleIconFactory;
}
+ @Override
public Bubbles.SysuiProxy getSysuiProxy() {
return mSysuiProxy;
}
@@ -725,15 +778,16 @@ public class BubbleController implements ConfigurationChangeListener,
// window to show this in, but we use a separate code path.
// TODO(b/273312602): consider foldables where we do need a stack view when folded
if (mLayerView == null) {
- mLayerView = new BubbleBarLayerView(mContext, this);
+ mLayerView = new BubbleBarLayerView(mContext, this, mBubbleData);
mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
}
} else {
if (mStackView == null) {
+ BubbleStackViewManager bubbleStackViewManager =
+ BubbleStackViewManager.fromBubbleController(this);
mStackView = new BubbleStackView(
- mContext, this, mBubbleData, mSurfaceSynchronizer,
- mFloatingContentCoordinator,
- mMainExecutor);
+ mContext, bubbleStackViewManager, mBubblePositioner, mBubbleData,
+ mSurfaceSynchronizer, mFloatingContentCoordinator, this, mMainExecutor);
mStackView.onOrientationChanged();
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
@@ -781,7 +835,13 @@ public class BubbleController implements ConfigurationChangeListener,
try {
mAddedToWindowManager = true;
registerBroadcastReceiver();
- mBubbleData.getOverflow().initialize(this, isShowingAsBubbleBar());
+ if (isShowingAsBubbleBar()) {
+ mBubbleData.getOverflow().initializeForBubbleBar(
+ mExpandedViewManager, mBubblePositioner);
+ } else {
+ mBubbleData.getOverflow().initialize(
+ mExpandedViewManager, mStackView, mBubblePositioner);
+ }
// (TODO: b/273314541) some duplication in the inset listener
if (isShowingAsBubbleBar()) {
mWindowManager.addView(mLayerView, mWmLayoutParams);
@@ -819,6 +879,7 @@ public class BubbleController implements ConfigurationChangeListener,
*/
void updateWindowFlagsForBackpress(boolean interceptBack) {
if (mAddedToWindowManager) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "updateFlagsForBackPress interceptBack=%b", interceptBack);
mWmLayoutParams.flags = interceptBack
? 0
: WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -893,11 +954,13 @@ public class BubbleController implements ConfigurationChangeListener,
* Called by the BubbleStackView and whenever all bubbles have animated out, and none have been
* added in the meantime.
*/
- @VisibleForTesting
public void onAllBubblesAnimatedOut() {
if (mStackView != null) {
mStackView.setVisibility(INVISIBLE);
removeFromWindowManagerMaybe();
+ } else if (mLayerView != null) {
+ mLayerView.setVisibility(INVISIBLE);
+ removeFromWindowManagerMaybe();
}
}
@@ -960,7 +1023,9 @@ public class BubbleController implements ConfigurationChangeListener,
for (Bubble b : mBubbleData.getBubbles()) {
b.inflate(null /* callback */,
mContext,
- this,
+ mExpandedViewManager,
+ mBubbleTaskViewFactory,
+ mBubblePositioner,
mStackView,
mLayerView,
mBubbleIconFactory,
@@ -969,7 +1034,9 @@ public class BubbleController implements ConfigurationChangeListener,
for (Bubble b : mBubbleData.getOverflowBubbles()) {
b.inflate(null /* callback */,
mContext,
- this,
+ mExpandedViewManager,
+ mBubbleTaskViewFactory,
+ mBubblePositioner,
mStackView,
mLayerView,
mBubbleIconFactory,
@@ -1005,12 +1072,18 @@ public class BubbleController implements ConfigurationChangeListener,
mLayoutDirection = newConfig.getLayoutDirection();
mStackView.onLayoutDirectionChanged(mLayoutDirection);
}
+ Locale newLocale = newConfig.locale;
+ if (newLocale != null && !newLocale.equals(mLocale)) {
+ mLocale = newLocale;
+ mStackView.updateLocale();
+ }
}
}
private void onNotificationPanelExpandedChanged(boolean expanded) {
- ProtoLog.d(WM_SHELL_BUBBLES, "onNotificationPanelExpandedChanged: expanded=%b", expanded);
if (mStackView != null && mStackView.isExpanded()) {
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "onNotificationPanelExpandedChanged expanded=%b", expanded);
if (expanded) {
mStackView.stopMonitoringSwipeUpGesture();
} else {
@@ -1047,7 +1120,6 @@ public class BubbleController implements ConfigurationChangeListener,
return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow();
}
- @VisibleForTesting
public boolean isStackExpanded() {
return mBubbleData.isExpanded();
}
@@ -1092,6 +1164,7 @@ public class BubbleController implements ConfigurationChangeListener,
/** Promote the provided bubble from the overflow view. */
public void promoteBubbleFromOverflow(Bubble bubble) {
mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK);
+ ProtoLog.d(WM_SHELL_BUBBLES, "promoteBubbleFromOverflow=%s", bubble.getKey());
bubble.setInflateSynchronously(mInflateSynchronously);
bubble.setShouldAutoExpand(true);
bubble.markAsAccessedAt(System.currentTimeMillis());
@@ -1105,9 +1178,8 @@ public class BubbleController implements ConfigurationChangeListener,
* <p>This is used by external callers (launcher).
*/
@VisibleForTesting
- public void expandStackAndSelectBubbleFromLauncher(String key, int bubbleBarOffsetX,
- int bubbleBarOffsetY) {
- mBubblePositioner.setBubbleBarPosition(bubbleBarOffsetX, bubbleBarOffsetY);
+ public void expandStackAndSelectBubbleFromLauncher(String key, Rect bubbleBarBounds) {
+ mBubblePositioner.setBubbleBarPosition(bubbleBarBounds);
if (BubbleOverflow.KEY.equals(key)) {
mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
@@ -1208,11 +1280,8 @@ public class BubbleController implements ConfigurationChangeListener,
// Skip update, but store it in user bubbles so it gets restored after user switch
mSavedUserBubbleData.get(bubbleUserId, new UserBubbleData()).add(notif.getKey(),
true /* shownInShade */);
- if (DEBUG_BUBBLE_CONTROLLER) {
- Log.d(TAG,
- "Ignore update to bubble for not active user. Bubble userId=" + bubbleUserId
- + " current userId=" + mCurrentUserId);
- }
+ Log.w(TAG, "updateBubble, ignore update for non-active user=" + bubbleUserId
+ + " currentUser=" + mCurrentUserId);
}
}
@@ -1249,6 +1318,9 @@ public class BubbleController implements ConfigurationChangeListener,
}
String appBubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user);
+ Log.i(TAG, "showOrHideAppBubble, key= " + appBubbleKey + " stackVisibility= "
+ + (mStackView != null ? mStackView.getVisibility() : " null ")
+ + " statusBarShade=" + mIsStatusBarShade);
PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier());
if (!isResizableActivity(intent, packageManager, appBubbleKey)) return;
@@ -1258,19 +1330,31 @@ public class BubbleController implements ConfigurationChangeListener,
if (isStackExpanded()) {
if (selectedBubble != null && appBubbleKey.equals(selectedBubble.getKey())) {
// App bubble is expanded, lets collapse
+ Log.i(TAG, " showOrHideAppBubble, selected bubble is app bubble, collapsing");
collapseStack();
} else {
// App bubble is not selected, select it
+ Log.i(TAG, " showOrHideAppBubble, expanded, selecting existing app bubble");
mBubbleData.setSelectedBubble(existingAppBubble);
}
} else {
// App bubble is not selected, select it & expand
+ Log.i(TAG, " showOrHideAppBubble, expand and select existing app bubble");
mBubbleData.setSelectedBubble(existingAppBubble);
mBubbleData.setExpanded(true);
}
} else {
- // App bubble does not exist, lets add and expand it
- Bubble b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
+ // Check if it exists in the overflow
+ Bubble b = mBubbleData.getOverflowBubbleWithKey(appBubbleKey);
+ if (b != null) {
+ // It's in the overflow, so remove it & reinflate
+ Log.i(TAG, " showOrHideAppBubble, expanding app bubble from overflow");
+ mBubbleData.removeOverflowBubble(b);
+ } else {
+ // App bubble does not exist, lets add and expand it
+ Log.i(TAG, " showOrHideAppBubble, creating and expanding app bubble");
+ b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
+ }
b.setShouldAutoExpand(true);
inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
}
@@ -1341,7 +1425,9 @@ public class BubbleController implements ConfigurationChangeListener,
bubble.inflate(
(b) -> mBubbleData.overflowBubble(Bubbles.DISMISS_RELOAD_FROM_DISK, bubble),
mContext,
- this,
+ mExpandedViewManager,
+ mBubbleTaskViewFactory,
+ mBubblePositioner,
mStackView,
mLayerView,
mBubbleIconFactory,
@@ -1361,14 +1447,22 @@ public class BubbleController implements ConfigurationChangeListener,
mStackView.resetOverflowView();
mStackView.removeAllViews();
}
- // cleanup existing bubble views so they can be recreated later if needed.
- mBubbleData.getBubbles().forEach(Bubble::cleanupViews);
+ // cleanup existing bubble views so they can be recreated later if needed, but retain
+ // TaskView.
+ mBubbleData.getBubbles().forEach(b -> b.cleanupViews(/* cleanupTaskView= */ false));
// remove the current bubble container from window manager, null it out, and create a new
// container based on the current mode.
removeFromWindowManagerMaybe();
mLayerView = null;
mStackView = null;
+
+ if (!mBubbleData.hasBubbles()) {
+ // if there are no bubbles, don't create the stack or layer views. they will be created
+ // later when the first bubble is added.
+ return;
+ }
+
ensureBubbleViewsAndWindowCreated();
// inflate bubble views
@@ -1387,7 +1481,9 @@ public class BubbleController implements ConfigurationChangeListener,
Bubble bubble = mBubbleData.getBubbles().get(i);
bubble.inflate(callback,
mContext,
- this,
+ mExpandedViewManager,
+ mBubbleTaskViewFactory,
+ mBubblePositioner,
mStackView,
mLayerView,
mBubbleIconFactory,
@@ -1462,8 +1558,14 @@ public class BubbleController implements ConfigurationChangeListener,
// Lazy init stack view when a bubble is created
ensureBubbleViewsAndWindowCreated();
bubble.setInflateSynchronously(mInflateSynchronously);
- bubble.inflate(b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
- mContext, this, mStackView, mLayerView,
+ bubble.inflate(
+ b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
+ mContext,
+ mExpandedViewManager,
+ mBubbleTaskViewFactory,
+ mBubblePositioner,
+ mStackView,
+ mLayerView,
mBubbleIconFactory,
false /* skipInflation */);
}
@@ -1473,7 +1575,6 @@ public class BubbleController implements ConfigurationChangeListener,
* <p>
* Must be called from the main thread.
*/
- @VisibleForTesting
@MainThread
public void removeBubble(String key, int reason) {
if (mBubbleData.hasAnyBubbleWithKey(key)) {
@@ -1698,8 +1799,12 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public void removeBubble(Bubble removedBubble) {
if (mLayerView != null) {
- // TODO: need to check if there's something that needs to happen here, e.g. if
- // the currently selected & expanded bubble is removed?
+ mLayerView.removeBubble(removedBubble, () -> {
+ if (!mBubbleData.hasBubbles() && !isStackExpanded()) {
+ mLayerView.setVisibility(INVISIBLE);
+ removeFromWindowManagerMaybe();
+ }
+ });
}
}
@@ -1754,18 +1859,19 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public void applyUpdate(BubbleData.Update update) {
- if (DEBUG_BUBBLE_CONTROLLER) {
- Log.d(TAG, "applyUpdate:" + " bubbleAdded=" + (update.addedBubble != null)
- + " bubbleRemoved="
- + (update.removedBubbles != null && update.removedBubbles.size() > 0)
- + " bubbleUpdated=" + (update.updatedBubble != null)
- + " orderChanged=" + update.orderChanged
- + " expandedChanged=" + update.expandedChanged
- + " selectionChanged=" + update.selectionChanged
- + " suppressed=" + (update.suppressedBubble != null)
- + " unsuppressed=" + (update.unsuppressedBubble != null)
- + " shouldShowEducation=" + update.shouldShowEducation);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "mBubbleDataListener#applyUpdate:"
+ + " added=%s removed=%b updated=%s orderChanged=%b expansionChanged=%b"
+ + " expanded=%b selectionChanged=%b selected=%s"
+ + " suppressed=%s unsupressed=%s shouldShowEducation=%b",
+ update.addedBubble != null ? update.addedBubble.getKey() : "null",
+ !update.removedBubbles.isEmpty(),
+ update.updatedBubble != null ? update.updatedBubble.getKey() : "null",
+ update.orderChanged, update.expandedChanged, update.expanded,
+ update.selectionChanged,
+ update.selectedBubble != null ? update.selectedBubble.getKey() : "null",
+ update.suppressedBubble != null ? update.suppressedBubble.getKey() : "null",
+ update.unsuppressedBubble != null ? update.unsuppressedBubble.getKey() : "null",
+ update.shouldShowEducation);
ensureBubbleViewsAndWindowCreated();
@@ -1959,7 +2065,8 @@ public class BubbleController implements ConfigurationChangeListener,
if (mStackView == null && mLayerView == null) {
return;
}
-
+ ProtoLog.v(WM_SHELL_BUBBLES, "updateBubbleViews mIsStatusBarShade=%s hasBubbles=%s",
+ mIsStatusBarShade, hasBubbles());
if (!mIsStatusBarShade) {
// Bubbles don't appear when the device is locked.
if (mStackView != null) {
@@ -2163,10 +2270,10 @@ public class BubbleController implements ConfigurationChangeListener,
}
@Override
- public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
+ public void showBubble(String key, Rect bubbleBarBounds) {
mMainExecutor.execute(
() -> mController.expandStackAndSelectBubbleFromLauncher(
- key, bubbleBarOffsetX, bubbleBarOffsetY));
+ key, bubbleBarBounds));
}
@Override
@@ -2497,6 +2604,14 @@ public class BubbleController implements ConfigurationChangeListener,
mMainExecutor.execute(
() -> BubbleController.this.onNotificationPanelExpandedChanged(expanded));
}
+
+ @Override
+ public void onSensitiveNotificationProtectionStateChanged(
+ boolean sensitiveNotificationProtectionActive) {
+ mMainExecutor.execute(
+ () -> BubbleController.this.onSensitiveNotificationProtectionStateChanged(
+ sensitiveNotificationProtectionActive));
+ }
}
/**
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 bbb4b74c2a17..6c2f925119f3 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
@@ -15,11 +15,10 @@
*/
package com.android.wm.shell.bubbles;
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
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.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.NonNull;
import android.app.PendingIntent;
@@ -36,8 +35,8 @@ import android.view.View;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubbles.DismissReason;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
@@ -179,7 +178,7 @@ public class BubbleData {
* This interface reports changes to the state and appearance of bubbles which should be applied
* as necessary to the UI.
*/
- interface Listener {
+ public interface Listener {
/** Reports changes have have occurred as a result of the most recent operation. */
void applyUpdate(Update update);
}
@@ -332,9 +331,6 @@ public class BubbleData {
}
public void setExpanded(boolean expanded) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setExpanded: " + expanded);
- }
setExpandedInternal(expanded);
dispatchPendingChanges();
}
@@ -346,9 +342,8 @@ public class BubbleData {
* updated to have the correct state.
*/
public void setSelectedBubbleFromLauncher(BubbleViewProvider bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setSelectedBubbleFromLauncher: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleFromLauncher=%s",
+ (bubble != null ? bubble.getKey() : "null"));
mExpanded = true;
if (Objects.equals(bubble, mSelectedBubble)) {
return;
@@ -369,9 +364,6 @@ public class BubbleData {
}
public void setSelectedBubble(BubbleViewProvider bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setSelectedBubble: " + bubble);
- }
setSelectedBubbleInternal(bubble);
dispatchPendingChanges();
}
@@ -425,16 +417,19 @@ public class BubbleData {
/**
* When this method is called it is expected that all info in the bubble has completed loading.
- * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleController, BubbleStackView,
- * BubbleIconFactory, boolean)
+ * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleExpandedViewManager,
+ * BubbleTaskViewFactory, BubblePositioner, BubbleStackView,
+ * com.android.wm.shell.bubbles.bar.BubbleBarLayerView,
+ * com.android.launcher3.icons.BubbleIconFactory, boolean)
*/
void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "notificationEntryUpdated: " + bubble);
- }
mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here
Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey());
suppressFlyout |= !bubble.isTextChanged();
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "notifEntryUpdated=%s prevBubble=%b suppressFlyout=%b showInShade=%b autoExpand=%b",
+ bubble.getKey(), (prevBubble != null), suppressFlyout, showInShade,
+ bubble.shouldAutoExpand());
if (prevBubble == null) {
// Create a new bubble
@@ -482,14 +477,24 @@ public class BubbleData {
* Dismisses the bubble with the matching key, if it exists.
*/
public void dismissBubbleWithKey(String key, @DismissReason int reason) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "notificationEntryRemoved: key=" + key + " reason=" + reason);
- }
doRemove(key, reason);
dispatchPendingChanges();
}
/**
+ * Explicitly removes a bubble from the overflow, if it exists.
+ *
+ * @param bubble the bubble to remove.
+ */
+ public void removeOverflowBubble(Bubble bubble) {
+ if (bubble == null) return;
+ if (mOverflowBubbles.remove(bubble)) {
+ mStateChange.removedOverflowBubble = bubble;
+ dispatchPendingChanges();
+ }
+ }
+
+ /**
* Adds a group key indicating that the summary for this group should be suppressed.
*
* @param groupKey the group key of the group whose summary should be suppressed.
@@ -589,9 +594,7 @@ public class BubbleData {
}
private void doAdd(Bubble bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doAdd: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doAdd=%s", bubble.getKey());
mBubbles.add(0, bubble);
mStateChange.addedBubble = bubble;
// Adding the first bubble doesn't change the order
@@ -620,9 +623,7 @@ public class BubbleData {
}
private void doUpdate(Bubble bubble, boolean reorder) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doUpdate: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "BubbleData - doUpdate=%s", bubble.getKey());
mStateChange.updatedBubble = bubble;
if (!isExpanded() && reorder) {
int prevPos = mBubbles.indexOf(bubble);
@@ -649,9 +650,6 @@ public class BubbleData {
}
private void doRemove(String key, @DismissReason int reason) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doRemove: " + key);
- }
// If it was pending remove it
if (mPendingBubbles.containsKey(key)) {
mPendingBubbles.remove(key);
@@ -672,9 +670,7 @@ public class BubbleData {
&& shouldRemoveHiddenBubble) {
Bubble b = getOverflowBubbleWithKey(key);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Cancel overflow bubble: " + b);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doRemove - cancel overflow bubble=%s", key);
if (b != null) {
b.stopInflation();
}
@@ -685,9 +681,7 @@ public class BubbleData {
}
if (hasSuppressedBubbleWithKey(key) && shouldRemoveHiddenBubble) {
Bubble b = getSuppressedBubbleWithKey(key);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Cancel suppressed bubble: " + b);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doRemove - cancel suppressed bubble=%s", key);
if (b != null) {
mSuppressedBubbles.remove(b.getLocusId());
b.stopInflation();
@@ -697,6 +691,7 @@ public class BubbleData {
return;
}
Bubble bubbleToRemove = mBubbles.get(indexToRemove);
+ ProtoLog.d(WM_SHELL_BUBBLES, "doRemove=%s", bubbleToRemove.getKey());
bubbleToRemove.stopInflation();
overflowBubble(reason, bubbleToRemove);
@@ -730,17 +725,12 @@ public class BubbleData {
}
// Move selection to the new bubble at the same position.
int newIndex = Math.min(indexOfSelected, mBubbles.size() - 1);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setNewSelectedIndex: " + indexOfSelected);
- }
BubbleViewProvider newSelected = mBubbles.get(newIndex);
setSelectedBubbleInternal(newSelected);
}
private void doSuppress(Bubble bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doSuppressed: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doSuppress=%s", bubble.getKey());
mStateChange.suppressedBubble = bubble;
bubble.setSuppressBubble(true);
@@ -763,9 +753,7 @@ public class BubbleData {
}
private void doUnsuppress(Bubble bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doUnsuppressed: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doUnsuppress=%s", bubble.getKey());
bubble.setSuppressBubble(false);
mStateChange.unsuppressedBubble = bubble;
mBubbles.add(bubble);
@@ -787,9 +775,7 @@ public class BubbleData {
|| reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
return;
}
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Overflowing: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "overflowBubble=%s", bubble.getKey());
mLogger.logOverflowAdd(bubble, reason);
mOverflowBubbles.remove(bubble);
mOverflowBubbles.add(0, bubble);
@@ -798,9 +784,7 @@ public class BubbleData {
if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
// Remove oldest bubble.
Bubble oldest = mOverflowBubbles.get(mOverflowBubbles.size() - 1);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Overflow full. Remove: " + oldest);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "overflow full, remove=%s", oldest.getKey());
mStateChange.bubbleRemoved(oldest, Bubbles.DISMISS_OVERFLOW_MAX_REACHED);
mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_MAX_REACHED);
mOverflowBubbles.remove(oldest);
@@ -809,9 +793,7 @@ public class BubbleData {
}
public void dismissAll(@DismissReason int reason) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "dismissAll: reason=" + reason);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "dismissAll reason=%d", reason);
if (mBubbles.isEmpty() && mSuppressedBubbles.isEmpty()) {
return;
}
@@ -837,9 +819,10 @@ public class BubbleData {
* @param visible whether the task with the locusId is visible or not.
*/
public void onLocusVisibilityChanged(int taskId, LocusId locusId, boolean visible) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "onLocusVisibilityChanged: " + locusId + " visible=" + visible);
- }
+ if (locusId == null) return;
+
+ ProtoLog.d(WM_SHELL_BUBBLES, "onLocusVisibilityChanged=%s visible=%b taskId=%d",
+ locusId.getId(), visible, taskId);
Bubble matchingBubble = getBubbleInStackWithLocusId(locusId);
// Don't add the locus if it's from a bubble'd activity, we only suppress for non-bubbled.
@@ -896,9 +879,8 @@ public class BubbleData {
* @param bubble the new selected bubble
*/
private void setSelectedBubbleInternal(@Nullable BubbleViewProvider bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleInternal=%s",
+ (bubble != null ? bubble.getKey() : "null"));
if (Objects.equals(bubble, mSelectedBubble)) {
return;
}
@@ -955,12 +937,10 @@ public class BubbleData {
* @param shouldExpand the new requested state
*/
private void setExpandedInternal(boolean shouldExpand) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setExpandedInternal: shouldExpand=" + shouldExpand);
- }
if (mExpanded == shouldExpand) {
return;
}
+ ProtoLog.d(WM_SHELL_BUBBLES, "setExpandedInternal=%b", shouldExpand);
if (shouldExpand) {
if (mBubbles.isEmpty() && !mShowingOverflow) {
Log.e(TAG, "Attempt to expand stack when empty!");
@@ -1012,9 +992,6 @@ public class BubbleData {
* @return true if the position of any bubbles changed as a result
*/
private boolean repackAll() {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "repackAll()");
- }
if (mBubbles.isEmpty()) {
return false;
}
@@ -1055,7 +1032,6 @@ public class BubbleData {
/**
* The set of bubbles in row.
*/
- @VisibleForTesting(visibility = PACKAGE)
public List<Bubble> getBubbles() {
return Collections.unmodifiableList(mBubbles);
}
@@ -1144,7 +1120,6 @@ public class BubbleData {
return null;
}
- @VisibleForTesting(visibility = PRIVATE)
public Bubble getOverflowBubbleWithKey(String key) {
for (int i = 0; i < mOverflowBubbles.size(); i++) {
Bubble bubble = mOverflowBubbles.get(i);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index f56b1712c5c1..f1a68e246fe1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
@@ -37,17 +37,7 @@ public class BubbleDebugConfig {
// Default log tag for the Bubbles package.
public static final String TAG_BUBBLES = "Bubbles";
-
- static final boolean DEBUG_BUBBLE_CONTROLLER = false;
- static final boolean DEBUG_BUBBLE_DATA = false;
- static final boolean DEBUG_BUBBLE_STACK_VIEW = false;
- static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
- static final boolean DEBUG_EXPERIMENTS = true;
- static final boolean DEBUG_OVERFLOW = false;
public static final boolean DEBUG_USER_EDUCATION = false;
- static final boolean DEBUG_POSITIONER = false;
- public static final boolean DEBUG_COLLAPSE_ANIMATOR = false;
- public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false;
private static final boolean FORCE_SHOW_USER_EDUCATION = false;
private static final String FORCE_SHOW_USER_EDUCATION_SETTING =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt
index e57f02c71e44..bd4708259b50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEducationController.kt
@@ -40,7 +40,13 @@ class BubbleEducationController(private val context: Context) {
/** Whether education view should show for the collapsed stack. */
fun shouldShowStackEducation(bubble: BubbleViewProvider?): Boolean {
- val shouldShow = bubble != null &&
+ if (BubbleDebugConfig.neverShowUserEducation(context)) {
+ logDebug("Show stack edu: never")
+ return false
+ }
+
+ val shouldShow =
+ bubble != null &&
bubble.isConversationBubble && // show education for conversation bubbles only
(!hasSeenStackEducation || BubbleDebugConfig.forceShowUserEducation(context))
logDebug("Show stack edu: $shouldShow")
@@ -49,7 +55,13 @@ class BubbleEducationController(private val context: Context) {
/** Whether the educational view should show for the expanded view "manage" menu. */
fun shouldShowManageEducation(bubble: BubbleViewProvider?): Boolean {
- val shouldShow = bubble != null &&
+ if (BubbleDebugConfig.neverShowUserEducation(context)) {
+ logDebug("Show manage edu: never")
+ return false
+ }
+
+ val shouldShow =
+ bubble != null &&
bubble.isConversationBubble && // show education for conversation bubbles only
(!hasSeenManageEducation || BubbleDebugConfig.forceShowUserEducation(context))
logDebug("Show manage edu: $shouldShow")
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 a3eb429b1d7e..74f087b6d8f8 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
@@ -23,16 +23,14 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
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 static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -49,7 +47,6 @@ import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
-import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.IntProperty;
@@ -70,11 +67,11 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.AlphaOptimizedButton;
import com.android.wm.shell.common.TriangleShape;
import com.android.wm.shell.taskview.TaskView;
-import com.android.wm.shell.taskview.TaskViewTaskController;
import java.io.PrintWriter;
@@ -146,7 +143,6 @@ public class BubbleExpandedView extends LinearLayout {
private AlphaOptimizedButton mManageButton;
private TaskView mTaskView;
- private TaskViewTaskController mTaskViewTaskController;
private BubbleOverflowContainerView mOverflowView;
private int mTaskId = INVALID_TASK_ID;
@@ -180,7 +176,9 @@ public class BubbleExpandedView extends LinearLayout {
private float mCornerRadius = 0f;
private int mBackgroundColorFloating;
private boolean mUsingMaxHeight;
+ private int mLeftClip = 0;
private int mTopClip = 0;
+ private int mRightClip = 0;
private int mBottomClip = 0;
@Nullable private Bubble mBubble;
private PendingIntent mPendingIntent;
@@ -188,7 +186,7 @@ public class BubbleExpandedView extends LinearLayout {
private boolean mIsOverflow;
private boolean mIsClipping;
- private BubbleController mController;
+ private BubbleExpandedViewManager mManager;
private BubbleStackView mStackView;
private BubblePositioner mPositioner;
@@ -204,13 +202,9 @@ public class BubbleExpandedView extends LinearLayout {
@Override
public void onInitialized() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onInitialized: destroyed=" + mDestroyed
- + " initialized=" + mInitialized
- + " bubble=" + getBubbleKey());
- }
-
if (mDestroyed || mInitialized) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: destroyed=%b initialized=%b bubble=%s",
+ mDestroyed, mInitialized, getBubbleKey());
return;
}
@@ -221,10 +215,8 @@ public class BubbleExpandedView extends LinearLayout {
// TODO: I notice inconsistencies in lifecycle
// Post to keep the lifecycle normal
post(() -> {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onInitialized: calling startActivity, bubble="
- + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: calling startActivity, bubble=%s",
+ getBubbleKey());
try {
Rect launchBounds = new Rect();
mTaskView.getBoundsOnScreen(launchBounds);
@@ -271,7 +263,7 @@ public class BubbleExpandedView extends LinearLayout {
// the bubble again so we'll just remove it.
Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+ ", " + e.getMessage() + "; removing bubble");
- mController.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
+ mManager.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
}
});
mInitialized = true;
@@ -284,16 +276,14 @@ public class BubbleExpandedView extends LinearLayout {
@Override
public void onTaskCreated(int taskId, ComponentName name) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskCreated: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskCreated: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
// The taskId is saved to use for removeTask, preventing appearance in recent tasks.
mTaskId = taskId;
if (mBubble != null && mBubble.isAppBubble()) {
// Let the controller know sooner what the taskId is.
- mController.setAppBubbleTaskId(mBubble.getKey(), mTaskId);
+ mManager.setAppBubbleTaskId(mBubble.getKey(), mTaskId);
}
// With the task org, the taskAppeared callback will only happen once the task has
@@ -303,17 +293,17 @@ public class BubbleExpandedView extends LinearLayout {
@Override
public void onTaskVisibilityChanged(int taskId, boolean visible) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskVisibilityChanged=%b bubble=%s taskId=%d",
+ visible, getBubbleKey(), taskId);
setContentVisibility(visible);
}
@Override
public void onTaskRemovalStarted(int taskId) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
if (mBubble != null) {
- mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ mManager.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
}
if (mTaskView != null) {
// Release the surface
@@ -365,7 +355,8 @@ public class BubbleExpandedView extends LinearLayout {
mExpandedViewContainer.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
- Rect clip = new Rect(0, mTopClip, view.getWidth(), view.getHeight() - mBottomClip);
+ Rect clip = new Rect(mLeftClip, mTopClip, view.getWidth() - mRightClip,
+ view.getHeight() - mBottomClip);
outline.setRoundRect(clip, mCornerRadius);
}
});
@@ -433,16 +424,20 @@ public class BubbleExpandedView extends LinearLayout {
* Initialize {@link BubbleController} and {@link BubbleStackView} here, this method must need
* to be called after view inflate.
*/
- void initialize(BubbleController controller, BubbleStackView stackView, boolean isOverflow) {
- mController = controller;
+ void initialize(BubbleExpandedViewManager expandedViewManager,
+ BubbleStackView stackView,
+ BubblePositioner positioner,
+ boolean isOverflow,
+ @Nullable BubbleTaskView bubbleTaskView) {
+ mManager = expandedViewManager;
mStackView = stackView;
mIsOverflow = isOverflow;
- mPositioner = mController.getPositioner();
+ mPositioner = positioner;
if (mIsOverflow) {
mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate(
R.layout.bubble_overflow_container, null /* root */);
- mOverflowView.setBubbleController(mController);
+ mOverflowView.initialize(expandedViewManager, positioner);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
mExpandedViewContainer.addView(mOverflowView, lp);
mExpandedViewContainer.setLayoutParams(
@@ -450,18 +445,22 @@ public class BubbleExpandedView extends LinearLayout {
bringChildToFront(mOverflowView);
mManageButton.setVisibility(GONE);
} else {
- mTaskViewTaskController = new TaskViewTaskController(mContext,
- mController.getTaskOrganizer(),
- mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
- mTaskView = new TaskView(mContext, mTaskViewTaskController);
- mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener);
+ mTaskView = bubbleTaskView.getTaskView();
+ bubbleTaskView.setDelegateListener(mTaskViewListener);
// set a fixed width so it is not recalculated as part of a rotation. the width will be
// updated manually after the rotation.
FrameLayout.LayoutParams lp =
new FrameLayout.LayoutParams(getContentWidth(), MATCH_PARENT);
+ if (mTaskView.getParent() != null) {
+ ((ViewGroup) mTaskView.getParent()).removeView(mTaskView);
+ }
mExpandedViewContainer.addView(mTaskView, lp);
bringChildToFront(mTaskView);
+ if (bubbleTaskView.isCreated()) {
+ mTaskViewListener.onTaskCreated(
+ bubbleTaskView.getTaskId(), bubbleTaskView.getComponentName());
+ }
}
}
@@ -518,6 +517,15 @@ public class BubbleExpandedView extends LinearLayout {
}
}
+ void updateLocale() {
+ if (mManageButton != null) {
+ mManageButton.setText(mContext.getString(R.string.manage_bubbles_text));
+ }
+ if (mOverflowView != null) {
+ mOverflowView.updateLocale();
+ }
+ }
+
void applyThemeAttrs() {
final TypedArray ta = mContext.obtainStyledAttributes(new int[]{
android.R.attr.dialogCornerRadius,
@@ -644,9 +652,6 @@ public class BubbleExpandedView extends LinearLayout {
super.onDetachedFromWindow();
mImeVisible = false;
mNeedsNewHeight = false;
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey());
- }
}
/**
@@ -763,8 +768,26 @@ public class BubbleExpandedView extends LinearLayout {
onContainerClipUpdate();
}
+ /**
+ * Sets the clipping for the view.
+ */
+ public void setTaskViewClip(Rect rect) {
+ mLeftClip = rect.left;
+ mTopClip = rect.top;
+ mRightClip = rect.right;
+ mBottomClip = rect.bottom;
+ onContainerClipUpdate();
+ }
+
+ /**
+ * Returns a rect representing the clipping for the view.
+ */
+ public Rect getTaskViewClip() {
+ return new Rect(mLeftClip, mTopClip, mRightClip, mBottom);
+ }
+
private void onContainerClipUpdate() {
- if (mTopClip == 0 && mBottomClip == 0) {
+ if (mTopClip == 0 && mBottomClip == 0 && mRightClip == 0 && mLeftClip == 0) {
if (mIsClipping) {
mIsClipping = false;
if (mTaskView != null) {
@@ -782,8 +805,10 @@ public class BubbleExpandedView extends LinearLayout {
}
mExpandedViewContainer.invalidateOutline();
if (mTaskView != null) {
- mTaskView.setClipBounds(new Rect(0, mTopClip, mTaskView.getWidth(),
- mTaskView.getHeight() - mBottomClip));
+ Rect clipBounds = new Rect(mLeftClip, mTopClip,
+ mTaskView.getWidth() - mRightClip,
+ mTaskView.getHeight() - mBottomClip);
+ mTaskView.setClipBounds(clipBounds);
}
}
}
@@ -805,10 +830,6 @@ public class BubbleExpandedView extends LinearLayout {
* and setting {@code false} actually means rendering the contents in transparent.
*/
public void setContentVisibility(boolean visibility) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "setContentVisibility: visibility=" + visibility
- + " bubble=" + getBubbleKey());
- }
mIsContentVisible = visibility;
if (mTaskView != null && !mIsAnimating) {
mTaskView.setAlpha(visibility ? 1f : 0f);
@@ -867,15 +888,12 @@ public class BubbleExpandedView extends LinearLayout {
* Sets the bubble used to populate this view.
*/
void update(Bubble bubble) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "update: bubble=" + bubble);
- }
if (mStackView == null) {
Log.w(TAG, "Stack is null for bubble: " + bubble);
return;
}
boolean isNew = mBubble == null || didBackingContentChange(bubble);
- if (isNew || bubble != null && bubble.getKey().equals(mBubble.getKey())) {
+ if (isNew || bubble.getKey().equals(mBubble.getKey())) {
mBubble = bubble;
mManageButton.setContentDescription(getResources().getString(
R.string.bubbles_settings_button_description, bubble.getAppName()));
@@ -958,11 +976,6 @@ public class BubbleExpandedView extends LinearLayout {
}
mNeedsNewHeight = false;
}
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "updateHeight: bubble=" + getBubbleKey()
- + " height=" + height
- + " mNeedsNewHeight=" + mNeedsNewHeight);
- }
}
}
@@ -974,10 +987,6 @@ public class BubbleExpandedView extends LinearLayout {
* waiting for layout.
*/
public void updateView(int[] containerLocationOnScreen) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "updateView: bubble="
- + getBubbleKey());
- }
mExpandedViewContainerLocation = containerLocationOnScreen;
updateHeight();
if (mTaskView != null
@@ -1101,31 +1110,8 @@ public class BubbleExpandedView extends LinearLayout {
return ((LinearLayout.LayoutParams) mManageButton.getLayoutParams()).getMarginStart();
}
- /**
- * Cleans up anything related to the task. The TaskView itself is released after the task
- * has been removed.
- *
- * If this view should be reused after this method is called, then
- * {@link #initialize(BubbleController, BubbleStackView, boolean)} must be invoked first.
- */
+ /** Hide the task view. */
public void cleanUpExpandedState() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
- }
- if (getTaskId() != INVALID_TASK_ID) {
- // Ensure the task is removed from WM
- if (ENABLE_SHELL_TRANSITIONS) {
- if (mTaskView != null) {
- mTaskView.removeTask();
- }
- } else {
- try {
- ActivityTaskManager.getService().removeTask(getTaskId());
- } catch (RemoteException e) {
- Log.w(TAG, e.getMessage());
- }
- }
- }
if (mTaskView != null) {
mTaskView.setVisibility(GONE);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
new file mode 100644
index 000000000000..b0d3cc4a5d5c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+/** Manager interface for bubble expanded views. */
+interface BubbleExpandedViewManager {
+
+ val overflowBubbles: List<Bubble>
+ fun setOverflowListener(listener: BubbleData.Listener)
+ fun collapseStack()
+ fun updateWindowFlagsForBackpress(intercept: Boolean)
+ fun promoteBubbleFromOverflow(bubble: Bubble)
+ fun removeBubble(key: String, reason: Int)
+ fun dismissBubble(bubble: Bubble, reason: Int)
+ fun setAppBubbleTaskId(key: String, taskId: Int)
+ fun isStackExpanded(): Boolean
+ fun isShowingAsBubbleBar(): Boolean
+
+ companion object {
+ /**
+ * Convenience function for creating a [BubbleExpandedViewManager] that delegates to the
+ * given `controller`.
+ */
+ @JvmStatic
+ fun fromBubbleController(controller: BubbleController): BubbleExpandedViewManager {
+ return object : BubbleExpandedViewManager {
+
+ override val overflowBubbles: List<Bubble>
+ get() = controller.overflowBubbles
+
+ override fun setOverflowListener(listener: BubbleData.Listener) {
+ controller.setOverflowListener(listener)
+ }
+
+ override fun collapseStack() {
+ controller.collapseStack()
+ }
+
+ override fun updateWindowFlagsForBackpress(intercept: Boolean) {
+ controller.updateWindowFlagsForBackpress(intercept)
+ }
+
+ override fun promoteBubbleFromOverflow(bubble: Bubble) {
+ controller.promoteBubbleFromOverflow(bubble)
+ }
+
+ override fun removeBubble(key: String, reason: Int) {
+ controller.removeBubble(key, reason)
+ }
+
+ override fun dismissBubble(bubble: Bubble, reason: Int) {
+ controller.dismissBubble(bubble, reason)
+ }
+
+ override fun setAppBubbleTaskId(key: String, taskId: Int) {
+ controller.setAppBubbleTaskId(key, taskId)
+ }
+
+ override fun isStackExpanded(): Boolean = controller.isStackExpanded
+
+ override fun isShowingAsBubbleBar(): Boolean = controller.isShowingAsBubbleBar
+ }
+ }
+ }
+}
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 22e836aacfc5..f32974e1765d 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
@@ -29,6 +29,7 @@ import android.util.PathParser
import android.view.LayoutInflater
import android.view.View.VISIBLE
import android.widget.FrameLayout
+import androidx.core.content.ContextCompat
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView
@@ -55,13 +56,32 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl
}
/** Call before use and again if cleanUpExpandedState was called. */
- fun initialize(controller: BubbleController, forBubbleBar: Boolean) {
- if (forBubbleBar) {
- createBubbleBarExpandedView().initialize(controller, true /* isOverflow */)
- } else {
- createExpandedView()
- .initialize(controller, controller.stackView, true /* isOverflow */)
- }
+ fun initialize(
+ expandedViewManager: BubbleExpandedViewManager,
+ stackView: BubbleStackView,
+ positioner: BubblePositioner
+ ) {
+ createExpandedView()
+ .initialize(
+ expandedViewManager,
+ stackView,
+ positioner,
+ /* isOverflow= */ true,
+ /* bubbleTaskView= */ null
+ )
+ }
+
+ fun initializeForBubbleBar(
+ expandedViewManager: BubbleExpandedViewManager,
+ positioner: BubblePositioner
+ ) {
+ createBubbleBarExpandedView()
+ .initialize(
+ expandedViewManager,
+ positioner,
+ /* isOverflow= */ true,
+ /* bubbleTaskView= */ null
+ )
}
fun cleanUpExpandedState() {
@@ -113,7 +133,10 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl
context,
res.getDimensionPixelSize(R.dimen.bubble_size),
res.getDimensionPixelSize(R.dimen.bubble_badge_size),
- res.getColor(com.android.launcher3.icons.R.color.important_conversation),
+ ContextCompat.getColor(
+ context,
+ com.android.launcher3.icons.R.color.important_conversation
+ ),
res.getDimensionPixelSize(com.android.internal.R.dimen.importance_ring_stroke_width)
)
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 9655470ce914..633b01bde4ca 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
@@ -16,9 +16,9 @@
package com.android.wm.shell.bubbles;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_OVERFLOW;
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.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.NonNull;
import android.content.Context;
@@ -28,7 +28,6 @@ import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -43,6 +42,7 @@ import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ContrastColorUtil;
import com.android.wm.shell.R;
@@ -62,7 +62,8 @@ public class BubbleOverflowContainerView extends LinearLayout {
private ImageView mEmptyStateImage;
private int mHorizontalMargin;
private int mVerticalMargin;
- private BubbleController mController;
+ private BubbleExpandedViewManager mExpandedViewManager;
+ private BubblePositioner mPositioner;
private BubbleOverflowAdapter mAdapter;
private RecyclerView mRecyclerView;
private List<Bubble> mOverflowBubbles = new ArrayList<>();
@@ -70,7 +71,7 @@ public class BubbleOverflowContainerView extends LinearLayout {
private View.OnKeyListener mKeyListener = (view, i, keyEvent) -> {
if (keyEvent.getAction() == KeyEvent.ACTION_UP
&& keyEvent.getKeyCode() == KeyEvent.KEYCODE_BACK) {
- mController.collapseStack();
+ mExpandedViewManager.collapseStack();
return true;
}
return false;
@@ -126,8 +127,11 @@ public class BubbleOverflowContainerView extends LinearLayout {
setFocusableInTouchMode(true);
}
- public void setBubbleController(BubbleController controller) {
- mController = controller;
+ /** Initializes the view. Must be called after creation. */
+ public void initialize(BubbleExpandedViewManager expandedViewManager,
+ BubblePositioner positioner) {
+ mExpandedViewManager = expandedViewManager;
+ mPositioner = positioner;
}
public void show() {
@@ -149,9 +153,9 @@ public class BubbleOverflowContainerView extends LinearLayout {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- if (mController != null) {
+ if (mExpandedViewManager != null) {
// For the overflow to get key events (e.g. back press) we need to adjust the flags
- mController.updateWindowFlagsForBackpress(true);
+ mExpandedViewManager.updateWindowFlagsForBackpress(true);
}
setOnKeyListener(mKeyListener);
}
@@ -159,8 +163,8 @@ public class BubbleOverflowContainerView extends LinearLayout {
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- if (mController != null) {
- mController.updateWindowFlagsForBackpress(false);
+ if (mExpandedViewManager != null) {
+ mExpandedViewManager.updateWindowFlagsForBackpress(false);
}
setOnKeyListener(null);
}
@@ -177,15 +181,15 @@ public class BubbleOverflowContainerView extends LinearLayout {
mRecyclerView.addItemDecoration(new OverflowItemDecoration());
}
mAdapter = new BubbleOverflowAdapter(getContext(), mOverflowBubbles,
- mController::promoteBubbleFromOverflow,
- mController.getPositioner());
+ mExpandedViewManager::promoteBubbleFromOverflow,
+ mPositioner);
mRecyclerView.setAdapter(mAdapter);
mOverflowBubbles.clear();
- mOverflowBubbles.addAll(mController.getOverflowBubbles());
+ mOverflowBubbles.addAll(mExpandedViewManager.getOverflowBubbles());
mAdapter.notifyDataSetChanged();
- mController.setOverflowListener(mDataListener);
+ mExpandedViewManager.setOverflowListener(mDataListener);
updateEmptyStateVisibility();
updateTheme();
}
@@ -238,6 +242,11 @@ public class BubbleOverflowContainerView extends LinearLayout {
mEmptyStateSubtitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
}
+ public void updateLocale() {
+ mEmptyStateTitle.setText(mContext.getString(R.string.bubble_overflow_empty_title));
+ mEmptyStateSubtitle.setText(mContext.getString(R.string.bubble_overflow_empty_subtitle));
+ }
+
private final BubbleData.Listener mDataListener = new BubbleData.Listener() {
@Override
@@ -245,9 +254,6 @@ public class BubbleOverflowContainerView extends LinearLayout {
Bubble toRemove = update.removedOverflowBubble;
if (toRemove != null) {
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, "remove: " + toRemove);
- }
toRemove.cleanupViews();
final int indexToRemove = mOverflowBubbles.indexOf(toRemove);
mOverflowBubbles.remove(toRemove);
@@ -257,9 +263,6 @@ public class BubbleOverflowContainerView extends LinearLayout {
Bubble toAdd = update.addedOverflowBubble;
if (toAdd != null) {
final int indexToAdd = mOverflowBubbles.indexOf(toAdd);
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, "add: " + toAdd + " prevIndex: " + indexToAdd);
- }
if (indexToAdd > 0) {
mOverflowBubbles.remove(toAdd);
mOverflowBubbles.add(0, toAdd);
@@ -272,10 +275,9 @@ public class BubbleOverflowContainerView extends LinearLayout {
updateEmptyStateVisibility();
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, BubbleDebugConfig.formatBubblesString(
- mController.getOverflowBubbles(), null));
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "Apply overflow update, added=%s removed=%s",
+ (toAdd != null ? toAdd.getKey() : "null"),
+ (toRemove != null ? toRemove.getKey() : "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 662a5c47d633..a5853d621cb5 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
@@ -16,19 +16,20 @@
package com.android.wm.shell.bubbles;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
+
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Insets;
-import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.util.Log;
import android.view.Surface;
import android.view.WindowManager;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R;
@@ -37,9 +38,6 @@ import com.android.wm.shell.R;
* placement and positioning calculations to refer to.
*/
public class BubblePositioner {
- private static final String TAG = BubbleDebugConfig.TAG_WITH_CLASS_NAME
- ? "BubblePositioner"
- : BubbleDebugConfig.TAG_BUBBLES;
/** The screen edge the bubble stack is pinned to */
public enum StackPinnedEdge {
@@ -95,10 +93,9 @@ public class BubblePositioner {
private int mMinimumFlyoutWidthLargeScreen;
private PointF mRestingStackPosition;
- private int[] mPaddings = new int[4];
private boolean mShowingInBubbleBar;
- private final Point mBubbleBarPosition = new Point();
+ private final Rect mBubbleBarBounds = new Rect();
public BubblePositioner(Context context, WindowManager windowManager) {
mContext = context;
@@ -112,21 +109,24 @@ public class BubblePositioner {
*/
public void update(DeviceConfig deviceConfig) {
mDeviceConfig = deviceConfig;
-
- if (BubbleDebugConfig.DEBUG_POSITIONER) {
- Log.w(TAG, "update positioner:"
- + " rotation: " + mRotation
- + " insets: " + deviceConfig.getInsets()
- + " isLargeScreen: " + deviceConfig.isLargeScreen()
- + " isSmallTablet: " + deviceConfig.isSmallTablet()
- + " showingInBubbleBar: " + mShowingInBubbleBar
- + " bounds: " + deviceConfig.getWindowBounds());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "update positioner: "
+ + "rotation=%d insets=%s largeScreen=%b "
+ + "smallTablet=%b isBubbleBar=%b bounds=%s",
+ mRotation, deviceConfig.getInsets(), deviceConfig.isLargeScreen(),
+ deviceConfig.isSmallTablet(), mShowingInBubbleBar,
+ deviceConfig.getWindowBounds());
updateInternal(mRotation, deviceConfig.getInsets(), deviceConfig.getWindowBounds());
}
@VisibleForTesting
public void updateInternal(int rotation, Insets insets, Rect bounds) {
+ BubbleStackView.RelativeStackPosition prevStackPosition = null;
+ if (mRestingStackPosition != null && mScreenRect != null && !mScreenRect.equals(bounds)) {
+ // Save the resting position as a relative position with the previous bounds, at the
+ // end of the update we'll restore it based on the new bounds.
+ prevStackPosition = new BubbleStackView.RelativeStackPosition(getRestingPosition(),
+ getAllowableStackPositionRegion(1));
+ }
mRotation = rotation;
mInsets = insets;
@@ -189,6 +189,12 @@ public class BubblePositioner {
R.dimen.bubbles_flyout_min_width_large_screen);
mMaxBubbles = calculateMaxBubbles();
+
+ if (prevStackPosition != null) {
+ // Get the new resting position based on the updated values
+ mRestingStackPosition = prevStackPosition.getAbsolutePositionInRegion(
+ getAllowableStackPositionRegion(1));
+ }
}
/**
@@ -344,46 +350,44 @@ public class BubblePositioner {
final int pointerTotalHeight = getPointerSize();
final int expandedViewLargeScreenInsetFurthestEdge =
getExpandedViewLargeScreenInsetFurthestEdge(isOverflow);
+ int[] paddings = new int[4];
if (mDeviceConfig.isLargeScreen()) {
// Note:
// If we're in portrait OR if we're a small tablet, then the two insets values will
// be equal. If we're landscape and a large tablet, the two values will be different.
// [left, top, right, bottom]
- mPaddings[0] = onLeft
+ paddings[0] = onLeft
? mExpandedViewLargeScreenInsetClosestEdge - pointerTotalHeight
: expandedViewLargeScreenInsetFurthestEdge;
- mPaddings[1] = 0;
- mPaddings[2] = onLeft
+ paddings[1] = 0;
+ paddings[2] = onLeft
? expandedViewLargeScreenInsetFurthestEdge
: mExpandedViewLargeScreenInsetClosestEdge - pointerTotalHeight;
// Overflow doesn't show manage button / get padding from it so add padding here
- mPaddings[3] = isOverflow ? mExpandedViewPadding : 0;
- return mPaddings;
+ paddings[3] = isOverflow ? mExpandedViewPadding : 0;
+ return paddings;
} 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)
+ ? (mPositionRect.width() - rightPadding - mOverflowWidth)
: 0;
} else {
leftPadding += mBubbleSize - pointerTotalHeight;
rightPadding += isOverflow
- ? (mPositionRect.width() - leftPadding - expandedViewWidth)
+ ? (mPositionRect.width() - leftPadding - mOverflowWidth)
: 0;
}
}
// [left, top, right, bottom]
- mPaddings[0] = leftPadding;
- mPaddings[1] = showBubblesVertically() ? 0 : mPointerMargin;
- mPaddings[2] = rightPadding;
- mPaddings[3] = 0;
- return mPaddings;
+ paddings[0] = leftPadding;
+ paddings[1] = showBubblesVertically() ? 0 : mPointerMargin;
+ paddings[2] = rightPadding;
+ paddings[3] = 0;
+ return paddings;
}
}
@@ -391,11 +395,11 @@ public class BubblePositioner {
public int getTaskViewContentWidth(boolean onLeft) {
int[] paddings = getExpandedViewContainerPadding(onLeft, /* isOverflow = */ false);
int pointerOffset = showBubblesVertically() ? getPointerSize() : 0;
- return mPositionRect.width() - paddings[0] - paddings[2] - pointerOffset;
+ return mScreenRect.width() - paddings[0] - paddings[2] - pointerOffset;
}
/** Gets the y position of the expanded view if it was top-aligned. */
- public float getExpandedViewYTopAligned() {
+ public int getExpandedViewYTopAligned() {
final int top = getAvailableRect().top;
if (showBubblesVertically()) {
return top - mPointerWidth + mExpandedViewPadding;
@@ -413,7 +417,7 @@ public class BubblePositioner {
return getExpandedViewHeightForLargeScreen();
}
// Subtract top insets because availableRect.height would account for that
- int expandedContainerY = (int) getExpandedViewYTopAligned() - getInsets().top;
+ int expandedContainerY = getExpandedViewYTopAligned() - getInsets().top;
int paddingTop = showBubblesVertically()
? 0
: mPointerHeight;
@@ -474,11 +478,11 @@ public class BubblePositioner {
public float getExpandedViewY(BubbleViewProvider bubble, float bubblePosition) {
boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey());
float expandedViewHeight = getExpandedViewHeight(bubble);
- float topAlignment = getExpandedViewYTopAligned();
+ int topAlignment = getExpandedViewYTopAligned();
int manageButtonHeight =
isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
- // On largescreen portrait bubbles are bottom aligned.
+ // On large screen portrait bubbles are bottom aligned.
if (areBubblesBottomAligned() && expandedViewHeight == MAX_HEIGHT) {
return mPositionRect.bottom - manageButtonHeight
- getExpandedViewHeightForLargeScreen() - mPointerWidth;
@@ -794,15 +798,10 @@ public class BubblePositioner {
}
/**
- * Sets the position of the bubble bar in screen coordinates.
- *
- * @param offsetX the offset of the bubble bar from the edge of the screen on the X axis
- * @param offsetY the offset of the bubble bar from the edge of the screen on the Y axis
+ * Sets the position of the bubble bar in display coordinates.
*/
- public void setBubbleBarPosition(int offsetX, int offsetY) {
- mBubbleBarPosition.set(
- getAvailableRect().width() - offsetX,
- getAvailableRect().height() + mInsets.top + mInsets.bottom - offsetY);
+ public void setBubbleBarPosition(Rect bubbleBarBounds) {
+ mBubbleBarBounds.set(bubbleBarBounds);
}
/**
@@ -823,7 +822,7 @@ public class BubblePositioner {
/** The bottom position of the expanded view when showing above the bubble bar. */
public int getExpandedViewBottomForBubbleBar() {
- return mBubbleBarPosition.y - mExpandedViewPadding;
+ return mBubbleBarBounds.top - mExpandedViewPadding;
}
/**
@@ -834,9 +833,9 @@ public class BubblePositioner {
}
/**
- * Returns the on screen co-ordinates of the bubble bar.
+ * Returns the display coordinates of the bubble bar.
*/
- public Point getBubbleBarPosition() {
- return mBubbleBarPosition;
+ public Rect getBubbleBarBounds() {
+ return mBubbleBarBounds;
}
}
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 b7f749e8a8b6..474430eb44ab 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
@@ -21,7 +21,6 @@ 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;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
@@ -204,8 +203,9 @@ public class BubbleStackView extends FrameLayout
Choreographer.getInstance().postFrameCallback(frameCallback);
}
};
- private final BubbleController mBubbleController;
+ private final BubbleStackViewManager mManager;
private final BubbleData mBubbleData;
+ private final Bubbles.SysuiProxy.Provider mSysuiProxyProvider;
private StackViewState mStackViewState = new StackViewState();
private final ValueAnimator mDismissBubbleAnimator;
@@ -291,6 +291,11 @@ public class BubbleStackView extends FrameLayout
*/
private boolean mRemovingLastBubbleWhileExpanded = false;
+ /**
+ * Whether sensitive notification protection should disable flyout
+ */
+ private boolean mSensitiveNotificationProtectionActive = false;
+
/** Animator for animating the expanded view's alpha (including the TaskView inside it). */
private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
@@ -300,6 +305,9 @@ public class BubbleStackView extends FrameLayout
*/
private int mPointerIndexDown = -1;
+ /** Indicates whether bubbles should be reordered at the end of a gesture. */
+ private boolean mShouldReorderBubblesAfterGestureCompletes = false;
+
@Nullable
private BubblesNavBarGestureTracker mBubblesNavBarGestureTracker;
@@ -438,43 +446,42 @@ public class BubbleStackView extends FrameLayout
/** Magnet listener that handles animating and dismissing individual dragged-out bubbles. */
private final MagnetizedObject.MagnetListener mIndividualBubbleMagnetListener =
new MagnetizedObject.MagnetListener() {
+
@Override
- public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- if (mExpandedAnimationController.getDraggedOutBubble() == null) {
- return;
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject draggedObject) {
+ if (draggedObject.getUnderlyingObject() instanceof View view) {
+ animateDismissBubble(view, true);
}
- animateDismissBubble(
- mExpandedAnimationController.getDraggedOutBubble(), true);
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject draggedObject,
float velX, float velY, boolean wasFlungOut) {
- if (mExpandedAnimationController.getDraggedOutBubble() == null) {
- return;
- }
- animateDismissBubble(
- mExpandedAnimationController.getDraggedOutBubble(), false);
+ if (draggedObject.getUnderlyingObject() instanceof View view) {
+ animateDismissBubble(view, false);
- if (wasFlungOut) {
- mExpandedAnimationController.snapBubbleBack(
- mExpandedAnimationController.getDraggedOutBubble(), velX, velY);
- mDismissView.hide();
- } else {
- mExpandedAnimationController.onUnstuckFromTarget();
+ if (wasFlungOut) {
+ mExpandedAnimationController.snapBubbleBack(view, velX, velY);
+ mDismissView.hide();
+ } else {
+ mExpandedAnimationController.onUnstuckFromTarget();
+ }
}
}
@Override
- public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- if (mExpandedAnimationController.getDraggedOutBubble() == null) {
- return;
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject) {
+ if (draggedObject.getUnderlyingObject() instanceof View view) {
+ mExpandedAnimationController.dismissDraggedOutBubble(
+ view /* bubble */,
+ mDismissView.getHeight() /* translationYBy */,
+ () -> dismissBubbleIfExists(
+ mBubbleData.getBubbleWithView(view)) /* after */);
}
- mExpandedAnimationController.dismissDraggedOutBubble(
- mExpandedAnimationController.getDraggedOutBubble() /* bubble */,
- mDismissView.getHeight() /* translationYBy */,
- BubbleStackView.this::dismissMagnetizedObject /* after */);
mDismissView.hide();
}
};
@@ -484,12 +491,14 @@ public class BubbleStackView extends FrameLayout
new MagnetizedObject.MagnetListener() {
@Override
public void onStuckToTarget(
- @NonNull MagnetizedObject.MagneticTarget target) {
+ @NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject) {
animateDismissBubble(mBubbleContainer, true);
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject,
float velX, float velY, boolean wasFlungOut) {
animateDismissBubble(mBubbleContainer, false);
if (wasFlungOut) {
@@ -502,14 +511,14 @@ public class BubbleStackView extends FrameLayout
}
@Override
- public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject) {
mStackAnimationController.animateStackDismissal(
mDismissView.getHeight() /* translationYBy */,
() -> {
+ mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
resetDismissAnimator();
- dismissMagnetizedObject();
- }
- );
+ } /*after */);
mDismissView.hide();
}
};
@@ -849,6 +858,7 @@ public class BubbleStackView extends FrameLayout
private BubbleOverflow mBubbleOverflow;
private StackEducationView mStackEduView;
+ private StackEducationView.Manager mStackEducationViewManager;
private ManageEducationView mManageEduView;
private DismissView mDismissView;
@@ -864,15 +874,18 @@ public class BubbleStackView extends FrameLayout
private BubblePositioner mPositioner;
@SuppressLint("ClickableViewAccessibility")
- public BubbleStackView(Context context, BubbleController bubbleController,
- BubbleData data, @Nullable SurfaceSynchronizer synchronizer,
+ public BubbleStackView(Context context, BubbleStackViewManager bubbleStackViewManager,
+ BubblePositioner bubblePositioner, BubbleData data,
+ @Nullable SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
+ Bubbles.SysuiProxy.Provider sysuiProxyProvider,
ShellExecutor mainExecutor) {
super(context);
mMainExecutor = mainExecutor;
- mBubbleController = bubbleController;
+ mManager = bubbleStackViewManager;
mBubbleData = data;
+ mSysuiProxyProvider = sysuiProxyProvider;
Resources res = getResources();
mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
@@ -882,7 +895,7 @@ public class BubbleStackView extends FrameLayout
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
- mPositioner = mBubbleController.getPositioner();
+ mPositioner = bubblePositioner;
final TypedArray ta = mContext.obtainStyledAttributes(
new int[]{android.R.attr.dialogCornerRadius});
@@ -892,7 +905,7 @@ public class BubbleStackView extends FrameLayout
final Runnable onBubbleAnimatedOut = () -> {
if (getBubbleCount() == 0) {
mExpandedViewTemporarilyHidden = false;
- mBubbleController.onAllBubblesAnimatedOut();
+ mManager.onAllBubblesAnimatedOut();
}
};
mStackAnimationController = new StackAnimationController(
@@ -1177,14 +1190,17 @@ public class BubbleStackView extends FrameLayout
if (mStackAnimationController.isStackOnLeftSide()) {
int availableRectOffsetX =
mPositioner.getAvailableRect().left - mPositioner.getScreenRect().left;
- animate().translationX(-(mBubbleSize + availableRectOffsetX)).start();
+ mBubbleContainer
+ .animate()
+ .translationX(-(mBubbleSize + availableRectOffsetX))
+ .start();
} else {
int availableRectOffsetX =
mPositioner.getAvailableRect().right - mPositioner.getScreenRect().right;
- animate().translationX(mBubbleSize - availableRectOffsetX).start();
+ mBubbleContainer.animate().translationX(mBubbleSize - availableRectOffsetX).start();
}
} else {
- animate().translationX(0).start();
+ mBubbleContainer.animate().translationX(0).start();
}
};
@@ -1291,16 +1307,12 @@ public class BubbleStackView extends FrameLayout
// We only show user education for conversation bubbles right now
return false;
}
- final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION);
+ final boolean seen = getPrefBoolean(ManageEducationView.PREF_MANAGED_EDUCATION);
final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext))
&& mExpandedBubble != null && mExpandedBubble.getExpandedView() != null;
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Show manage edu: " + shouldShow);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "Show manage edu=%b", shouldShow);
if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) {
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Want to show manage edu, but it is forced hidden");
- }
+ Log.w(TAG, "Want to show manage edu, but it is forced hidden");
return false;
}
return shouldShow;
@@ -1342,15 +1354,11 @@ public class BubbleStackView extends FrameLayout
// We only show user education for conversation bubbles right now
return false;
}
- final boolean seen = getPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION);
+ final boolean seen = getPrefBoolean(StackEducationView.PREF_STACK_EDUCATION);
final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext);
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Show stack edu: " + shouldShow);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "Show stack edu=%b", shouldShow);
if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) {
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Want to show stack edu, but it is forced hidden");
- }
+ Log.w(TAG, "Want to show stack edu, but it is forced hidden");
return false;
}
return shouldShow;
@@ -1369,7 +1377,9 @@ public class BubbleStackView extends FrameLayout
return false;
}
if (mStackEduView == null) {
- mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
+ mStackEducationViewManager = mManager::updateWindowFlagsForBackpress;
+ mStackEduView =
+ new StackEducationView(mContext, mPositioner, mStackEducationViewManager);
addView(mStackEduView);
}
return showStackEdu();
@@ -1398,7 +1408,9 @@ public class BubbleStackView extends FrameLayout
private void updateUserEdu() {
if (isStackEduVisible() && !mStackEduView.isHiding()) {
removeView(mStackEduView);
- mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
+ mStackEducationViewManager = mManager::updateWindowFlagsForBackpress;
+ mStackEduView =
+ new StackEducationView(mContext, mPositioner, mStackEducationViewManager);
addView(mStackEduView);
showStackEdu();
}
@@ -1435,6 +1447,12 @@ public class BubbleStackView extends FrameLayout
}
}
+ void updateLocale() {
+ if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) {
+ mBubbleOverflow.getExpandedView().updateLocale();
+ }
+ }
+
private void updateOverflow() {
mBubbleOverflow.update();
mBubbleContainer.reorderView(mBubbleOverflow.getIconView(),
@@ -1493,7 +1511,7 @@ public class BubbleStackView extends FrameLayout
mBubbleSize = mPositioner.getBubbleSize();
for (Bubble b : mBubbleData.getBubbles()) {
if (b.getIconView() == null) {
- Log.d(TAG, "Display size changed. Icon null: " + b);
+ Log.w(TAG, "Display size changed. Icon null: " + b);
continue;
}
b.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize));
@@ -1799,10 +1817,6 @@ public class BubbleStackView extends FrameLayout
// via BubbleData.Listener
@SuppressLint("ClickableViewAccessibility")
void addBubble(Bubble bubble) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "addBubble: " + bubble);
- }
-
final boolean firstBubble = getBubbleCount() == 0;
if (firstBubble && shouldShowStackEdu()) {
@@ -1848,9 +1862,6 @@ public class BubbleStackView extends FrameLayout
// via BubbleData.Listener
void removeBubble(Bubble bubble) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "removeBubble: " + bubble);
- }
if (isExpanded() && getBubbleCount() == 1) {
mRemovingLastBubbleWhileExpanded = true;
// We're expanded while the last bubble is being removed. Let the scrim animate away
@@ -1897,7 +1908,7 @@ public class BubbleStackView extends FrameLayout
bubble.cleanupViews();
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
} else {
- Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
+ Log.w(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
}
}
@@ -1925,7 +1936,18 @@ public class BubbleStackView extends FrameLayout
/**
* Update bubble order and pointer position.
*/
- public void updateBubbleOrder(List<Bubble> bubbles, boolean updatePointerPositoion) {
+ public void updateBubbleOrder(List<Bubble> bubbles, boolean updatePointerPosition) {
+ // Don't reorder bubbles in the middle of a gesture because that would remove bubbles from
+ // view hierarchy and will cancel all touch events. Instead wait until the gesture is
+ // finished and then reorder.
+ if (mIsGestureInProgress) {
+ mShouldReorderBubblesAfterGestureCompletes = true;
+ return;
+ }
+ updateBubbleOrderInternal(bubbles, updatePointerPosition);
+ }
+
+ private void updateBubbleOrderInternal(List<Bubble> bubbles, boolean updatePointerPosition) {
final Runnable reorder = () -> {
for (int i = 0; i < bubbles.size(); i++) {
Bubble bubble = bubbles.get(i);
@@ -1936,13 +1958,13 @@ public class BubbleStackView extends FrameLayout
reorder.run();
updateBadges(false /* setBadgeForCollapsedStack */);
updateZOrder();
- } else if (!isExpansionAnimating()) {
+ } else {
List<View> bubbleViews = bubbles.stream()
.map(b -> b.getIconView()).collect(Collectors.toList());
mStackAnimationController.animateReorder(bubbleViews, reorder);
}
- if (updatePointerPositoion) {
+ if (updatePointerPosition) {
updatePointerPosition(false /* forIme */);
}
}
@@ -1954,10 +1976,6 @@ public class BubbleStackView extends FrameLayout
*/
// via BubbleData.Listener
public void setSelectedBubble(@Nullable BubbleViewProvider bubbleToSelect) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
- }
-
if (bubbleToSelect == null) {
mBubbleData.setShowingOverflow(false);
return;
@@ -2050,10 +2068,6 @@ public class BubbleStackView extends FrameLayout
*/
// via BubbleData.Listener
public void setExpanded(boolean shouldExpand) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "setExpanded: " + shouldExpand);
- }
-
if (!shouldExpand) {
// If we're collapsing, release the animating-out surface immediately since we have no
// need for it, and this ensures it cannot remain visible as we collapse.
@@ -2068,7 +2082,7 @@ public class BubbleStackView extends FrameLayout
hideCurrentInputMethod();
- mBubbleController.getSysuiProxy().onStackExpandChanged(shouldExpand);
+ mSysuiProxyProvider.getSysuiProxy().onStackExpandChanged(shouldExpand);
if (wasExpanded) {
stopMonitoringSwipeUpGesture();
@@ -2081,7 +2095,7 @@ public class BubbleStackView extends FrameLayout
logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
logBubbleEvent(mExpandedBubble,
FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
- mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> {
+ mManager.checkNotificationPanelExpandedState(notifPanelExpanded -> {
if (!notifPanelExpanded && mIsExpanded) {
startMonitoringSwipeUpGesture();
}
@@ -2095,7 +2109,6 @@ public class BubbleStackView extends FrameLayout
* Monitor for swipe up gesture that is used to collapse expanded view
*/
void startMonitoringSwipeUpGesture() {
- ProtoLog.d(WM_SHELL_BUBBLES, "startMonitoringSwipeUpGesture");
stopMonitoringSwipeUpGestureInternal();
if (isGestureNavEnabled()) {
@@ -2143,7 +2156,6 @@ public class BubbleStackView extends FrameLayout
* Stop monitoring for swipe up gesture
*/
void stopMonitoringSwipeUpGesture() {
- ProtoLog.d(WM_SHELL_BUBBLES, "stopMonitoringSwipeUpGesture");
stopMonitoringSwipeUpGestureInternal();
}
@@ -2171,9 +2183,6 @@ public class BubbleStackView extends FrameLayout
}
void setBubbleSuppressed(Bubble bubble, boolean suppressed) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "setBubbleSuppressed: suppressed=" + suppressed + " bubble=" + bubble);
- }
if (suppressed) {
int index = getBubbleIndex(bubble);
mBubbleContainer.removeViewAt(index);
@@ -2196,13 +2205,18 @@ public class BubbleStackView extends FrameLayout
}
}
+ void onSensitiveNotificationProtectionStateChanged(
+ boolean sensitiveNotificationProtectionActive) {
+ mSensitiveNotificationProtectionActive = sensitiveNotificationProtectionActive;
+ }
+
/**
* Asks the BubbleController to hide the IME from anywhere, whether it's focused on Bubbles or
* not.
*/
void hideCurrentInputMethod() {
mPositioner.setImeVisible(false, 0);
- mBubbleController.hideCurrentInputMethod();
+ mManager.hideCurrentInputMethod();
}
/** Set the stack position to whatever the positioner says. */
@@ -2308,6 +2322,8 @@ public class BubbleStackView extends FrameLayout
}
private void animateExpansion() {
+ ProtoLog.d(WM_SHELL_BUBBLES, "animateExpansion, expandedBubble=%s",
+ mExpandedBubble != null ? mExpandedBubble.getKey() : "null");
cancelDelayedExpandCollapseSwitchAnimations();
final boolean showVertically = mPositioner.showBubblesVertically();
mIsExpanded = true;
@@ -2323,7 +2339,8 @@ public class BubbleStackView extends FrameLayout
updateOverflowVisibility();
updatePointerPosition(false /* forIme */);
mExpandedAnimationController.expandFromStack(() -> {
- if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
+ if (mIsExpanded && mExpandedBubble != null
+ && mExpandedBubble.getExpandedView() != null) {
maybeShowManageEdu();
}
updateOverflowDotVisibility(true /* expanding */);
@@ -2384,7 +2401,7 @@ public class BubbleStackView extends FrameLayout
}
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
- if (mExpandedBubble.getExpandedView() != null) {
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
mExpandedBubble.getExpandedView().setContentAlpha(0f);
mExpandedBubble.getExpandedView().setBackgroundAlpha(0f);
@@ -2433,7 +2450,7 @@ public class BubbleStackView extends FrameLayout
private void animateCollapse() {
cancelDelayedExpandCollapseSwitchAnimations();
-
+ ProtoLog.d(WM_SHELL_BUBBLES, "animateCollapse");
if (isManageEduVisible()) {
mManageEduView.hide();
}
@@ -2476,11 +2493,6 @@ public class BubbleStackView extends FrameLayout
mManageEduView.hide();
}
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "animateCollapse");
- Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
- mExpandedBubble));
- }
updateZOrder();
updateBadges(true /* setBadgeForCollapsedStack */);
afterExpandedViewAnimation();
@@ -2580,6 +2592,18 @@ public class BubbleStackView extends FrameLayout
mExpandedViewTemporarilyHidden = false;
mIsBubbleSwitchAnimating = false;
mExpandedViewContainer.setAnimationMatrix(null);
+
+ // When a bubble is being dragged, the expanded view is temporarily hidden.
+ // If the motion ends with dismissing the bubble, with multiple bubbles in
+ // the stack, we'll end up here to switch to the new bubble. However, the
+ // expanded view animation might not actually set the z ordering for the
+ // expanded view correctly, because the view may still be temporarily
+ // hidden. So set it again here.
+ BubbleExpandedView bev = mExpandedBubble.getExpandedView();
+ if (bev != null) {
+ mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
+ mExpandedBubble.getExpandedView().setAnimating(false);
+ }
})
.start();
}, 25);
@@ -2659,7 +2683,7 @@ public class BubbleStackView extends FrameLayout
if (!mExpandedBubble.getExpandedView().isUsingMaxHeight()) {
mExpandedViewContainer.animate().translationY(newExpandedViewTop);
}
- List<Animator> animList = new ArrayList();
+ 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;
@@ -2703,6 +2727,12 @@ public class BubbleStackView extends FrameLayout
ev.getAction() != MotionEvent.ACTION_UP
&& ev.getAction() != MotionEvent.ACTION_CANCEL;
+ // If there is a deferred reorder action, and the gesture is over, run it now.
+ if (mShouldReorderBubblesAfterGestureCompletes && !mIsGestureInProgress) {
+ mShouldReorderBubblesAfterGestureCompletes = false;
+ updateBubbleOrderInternal(mBubbleData.getBubbles(), false);
+ }
+
return dispatched;
}
@@ -2747,19 +2777,6 @@ public class BubbleStackView extends FrameLayout
return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
}
- /**
- * Dismisses the magnetized object - either an individual bubble, if we're expanded, or the
- * stack, if we're collapsed.
- */
- private void dismissMagnetizedObject() {
- if (mIsExpanded) {
- final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject();
- dismissBubbleIfExists(mBubbleData.getBubbleWithView(draggedOutBubbleView));
- } else {
- mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
- }
- }
-
private void dismissBubbleIfExists(@Nullable BubbleViewProvider bubble) {
if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
if (mIsExpanded && mBubbleData.getBubbles().size() > 1
@@ -2842,6 +2859,7 @@ public class BubbleStackView extends FrameLayout
|| isExpanded()
|| mIsExpansionAnimating
|| mIsGestureInProgress
+ || mSensitiveNotificationProtectionActive
|| mBubbleToExpandAfterFlyoutCollapse != null
|| bubbleView == null) {
if (bubbleView != null && mFlyout.getVisibility() != VISIBLE) {
@@ -2862,7 +2880,7 @@ public class BubbleStackView extends FrameLayout
if (!shouldShowFlyout(bubble)) {
return;
}
-
+ ProtoLog.d(WM_SHELL_BUBBLES, "animateFlyout=%s", bubble.getKey());
mFlyoutDragDeltaX = 0f;
clearFlyoutOnHide();
mAfterFlyoutHidden = () -> {
@@ -3004,6 +3022,9 @@ public class BubbleStackView extends FrameLayout
@VisibleForTesting
public void showManageMenu(boolean show) {
if ((mManageMenu.getVisibility() == VISIBLE) == show) return;
+ ProtoLog.d(WM_SHELL_BUBBLES, "showManageMenu=%b for bubble=%s",
+ show, (mExpandedBubble != null ? mExpandedBubble.getKey() : "null"));
+
mShowingManage = show;
// This should not happen, since the manage menu is only visible when there's an expanded
@@ -3011,7 +3032,7 @@ public class BubbleStackView extends FrameLayout
if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
mManageMenu.setVisibility(View.INVISIBLE);
mManageMenuScrim.setVisibility(INVISIBLE);
- mBubbleController.getSysuiProxy().onManageMenuExpandChanged(false /* show */);
+ mSysuiProxyProvider.getSysuiProxy().onManageMenuExpandChanged(false /* show */);
return;
}
if (show) {
@@ -3025,7 +3046,7 @@ public class BubbleStackView extends FrameLayout
}
};
- mBubbleController.getSysuiProxy().onManageMenuExpandChanged(show);
+ mSysuiProxyProvider.getSysuiProxy().onManageMenuExpandChanged(show);
mManageMenuScrim.animate()
.setInterpolator(show ? ALPHA_IN : ALPHA_OUT)
.alpha(show ? BUBBLE_EXPANDED_SCRIM_ALPHA : 0f)
@@ -3135,10 +3156,6 @@ public class BubbleStackView extends FrameLayout
}
private void updateExpandedBubble() {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "updateExpandedBubble()");
- }
-
mExpandedViewContainer.removeAllViews();
if (mIsExpanded && mExpandedBubble != null
&& mExpandedBubble.getExpandedView() != null) {
@@ -3286,9 +3303,6 @@ public class BubbleStackView extends FrameLayout
}
private void updateExpandedView() {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "updateExpandedView: mIsExpanded=" + mIsExpanded);
- }
boolean isOverflowExpanded = mExpandedBubble != null
&& BubbleOverflow.KEY.equals(mExpandedBubble.getKey());
int[] paddings = mPositioner.getExpandedViewContainerPadding(
@@ -3513,7 +3527,14 @@ public class BubbleStackView extends FrameLayout
*/
void onVerticalOffsetChanged(int offset) {
// adjust dismiss view vertical position, so that it is still visible to the user
- mDismissView.setPadding(/* left = */ 0, /* top = */ 0, /* right = */ 0, offset);
+ ViewGroup.LayoutParams lp = mDismissView.getLayoutParams();
+ if (lp instanceof FrameLayout.LayoutParams) {
+ FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) lp;
+ layoutParams.bottomMargin = offset;
+ mDismissView.setLayoutParams(layoutParams);
+ }
+ mMagneticTarget.setScreenVerticalOffset(offset);
+ mMagneticTarget.updateLocationOnScreen();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt
new file mode 100644
index 000000000000..fb597a05663b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackViewManager.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import java.util.function.Consumer
+
+/** Defines callbacks from [BubbleStackView] to its manager. */
+interface BubbleStackViewManager {
+
+ /** Notifies that all bubbles animated out. */
+ fun onAllBubblesAnimatedOut()
+
+ /** Notifies whether backpress should be intercepted. */
+ fun updateWindowFlagsForBackpress(interceptBack: Boolean)
+
+ /**
+ * Checks the current expansion state of the notification panel, and invokes [callback] with the
+ * result.
+ */
+ fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>)
+
+ /** Requests to hide the current input method. */
+ fun hideCurrentInputMethod()
+
+ companion object {
+
+ @JvmStatic
+ fun fromBubbleController(controller: BubbleController) = object : BubbleStackViewManager {
+ override fun onAllBubblesAnimatedOut() {
+ controller.onAllBubblesAnimatedOut()
+ }
+
+ override fun updateWindowFlagsForBackpress(interceptBack: Boolean) {
+ controller.updateWindowFlagsForBackpress(interceptBack)
+ }
+
+ override fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) {
+ controller.isNotificationPanelExpanded(callback)
+ }
+
+ override fun hideCurrentInputMethod() {
+ controller.hideCurrentInputMethod()
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
new file mode 100644
index 000000000000..65f8e48eb822
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.app.ActivityTaskManager
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.content.ComponentName
+import android.os.RemoteException
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
+import java.util.concurrent.Executor
+
+/**
+ * A wrapper class around [TaskView] for bubble expanded views.
+ *
+ * [delegateListener] allows callers to change listeners after a task has been created.
+ */
+class BubbleTaskView(val taskView: TaskView, executor: Executor) {
+
+ /** Whether the task is already created. */
+ var isCreated = false
+ private set
+
+ /** The task id. */
+ var taskId = INVALID_TASK_ID
+ private set
+
+ /** The component name of the application running in the task. */
+ var componentName: ComponentName? = null
+ private set
+
+ /** [TaskView.Listener] for users of this class. */
+ var delegateListener: TaskView.Listener? = null
+
+ /** A [TaskView.Listener] that delegates to [delegateListener]. */
+ @get:VisibleForTesting
+ val listener = object : TaskView.Listener {
+ override fun onInitialized() {
+ delegateListener?.onInitialized()
+ }
+
+ override fun onReleased() {
+ delegateListener?.onReleased()
+ }
+
+ override fun onTaskCreated(taskId: Int, name: ComponentName) {
+ delegateListener?.onTaskCreated(taskId, name)
+ this@BubbleTaskView.taskId = taskId
+ isCreated = true
+ componentName = name
+ }
+
+ override fun onTaskVisibilityChanged(taskId: Int, visible: Boolean) {
+ delegateListener?.onTaskVisibilityChanged(taskId, visible)
+ }
+
+ override fun onTaskRemovalStarted(taskId: Int) {
+ delegateListener?.onTaskRemovalStarted(taskId)
+ }
+
+ override fun onBackPressedOnTaskRoot(taskId: Int) {
+ delegateListener?.onBackPressedOnTaskRoot(taskId)
+ }
+ }
+
+ init {
+ taskView.setListener(executor, listener)
+ }
+
+ /**
+ * Removes the [TaskView] from window manager.
+ *
+ * This should be called after all other cleanup animations have finished.
+ */
+ fun cleanup() {
+ if (taskId != INVALID_TASK_ID) {
+ // Ensure the task is removed from WM
+ if (ENABLE_SHELL_TRANSITIONS) {
+ taskView.removeTask()
+ } else {
+ try {
+ ActivityTaskManager.getService().removeTask(taskId)
+ } catch (e: RemoteException) {
+ Log.w(TAG, e.message ?: "")
+ }
+ }
+ }
+ }
+
+ private companion object {
+ const val TAG = "BubbleTaskView"
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.kt
new file mode 100644
index 000000000000..230626f49c51
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+/** Factory for creating [BubbleTaskView]s. */
+fun interface BubbleTaskViewFactory {
+ /** Creates a new instance of [BubbleTaskView]. */
+ fun create(): BubbleTaskView
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index da4a9898a44c..21b70b8e32da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -20,7 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -29,16 +29,14 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
-import android.os.RemoteException;
import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
import androidx.annotation.Nullable;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.taskview.TaskView;
-import com.android.wm.shell.taskview.TaskViewTaskController;
/**
* Handles creating and updating the {@link TaskView} associated with a {@link Bubble}.
@@ -64,8 +62,7 @@ public class BubbleTaskViewHelper {
}
private final Context mContext;
- private final BubbleController mController;
- private final @ShellMainThread ShellExecutor mMainExecutor;
+ private final BubbleExpandedViewManager mExpandedViewManager;
private final BubbleTaskViewHelper.Listener mListener;
private final View mParentView;
@@ -73,7 +70,6 @@ public class BubbleTaskViewHelper {
private Bubble mBubble;
@Nullable
private PendingIntent mPendingIntent;
- private TaskViewTaskController mTaskViewTaskController;
@Nullable
private TaskView mTaskView;
private int mTaskId = INVALID_TASK_ID;
@@ -84,11 +80,8 @@ public class BubbleTaskViewHelper {
@Override
public void onInitialized() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onInitialized: destroyed=" + mDestroyed
- + " initialized=" + mInitialized
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: destroyed=%b initialized=%b bubble=%s",
+ mDestroyed, mInitialized, getBubbleKey());
if (mDestroyed || mInitialized) {
return;
@@ -104,10 +97,8 @@ public class BubbleTaskViewHelper {
// TODO: I notice inconsistencies in lifecycle
// Post to keep the lifecycle normal
mParentView.post(() -> {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onInitialized: calling startActivity, bubble="
- + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: calling startActivity, bubble=%s",
+ getBubbleKey());
try {
options.setTaskAlwaysOnTop(true);
options.setLaunchedFromBubble(true);
@@ -151,7 +142,8 @@ public class BubbleTaskViewHelper {
// the bubble again so we'll just remove it.
Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+ ", " + e.getMessage() + "; removing bubble");
- mController.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
+ mExpandedViewManager.removeBubble(
+ getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
}
mInitialized = true;
});
@@ -164,10 +156,8 @@ public class BubbleTaskViewHelper {
@Override
public void onTaskCreated(int taskId, ComponentName name) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskCreated: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskCreated: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
// The taskId is saved to use for removeTask, preventing appearance in recent tasks.
mTaskId = taskId;
@@ -183,37 +173,41 @@ public class BubbleTaskViewHelper {
@Override
public void onTaskRemovalStarted(int taskId) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
if (mBubble != null) {
- mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ mExpandedViewManager.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ }
+ if (mTaskView != null) {
+ mTaskView.release();
+ ((ViewGroup) mParentView).removeView(mTaskView);
+ mTaskView = null;
}
}
@Override
public void onBackPressedOnTaskRoot(int taskId) {
- if (mTaskId == taskId && mController.isStackExpanded()) {
+ if (mTaskId == taskId && mExpandedViewManager.isStackExpanded()) {
mListener.onBackPressed();
}
}
};
public BubbleTaskViewHelper(Context context,
- BubbleController controller,
+ BubbleExpandedViewManager expandedViewManager,
BubbleTaskViewHelper.Listener listener,
+ BubbleTaskView bubbleTaskView,
View parent) {
mContext = context;
- mController = controller;
- mMainExecutor = mController.getMainExecutor();
+ mExpandedViewManager = expandedViewManager;
mListener = listener;
mParentView = parent;
- mTaskViewTaskController = new TaskViewTaskController(mContext,
- mController.getTaskOrganizer(),
- mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
- mTaskView = new TaskView(mContext, mTaskViewTaskController);
- mTaskView.setListener(mMainExecutor, mTaskViewListener);
+ mTaskView = bubbleTaskView.getTaskView();
+ bubbleTaskView.setDelegateListener(mTaskViewListener);
+ if (bubbleTaskView.isCreated()) {
+ mTaskId = bubbleTaskView.getTaskId();
+ mListener.onTaskCreated();
+ }
}
/**
@@ -231,24 +225,6 @@ public class BubbleTaskViewHelper {
return false;
}
- /** Cleans up anything related to the task and {@code TaskView}. */
- public void cleanUpTaskView() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
- }
- if (mTaskId != INVALID_TASK_ID) {
- try {
- ActivityTaskManager.getService().removeTask(mTaskId);
- } catch (RemoteException e) {
- Log.w(TAG, e.getMessage());
- }
- }
- if (mTaskView != null) {
- mTaskView.release();
- mTaskView = null;
- }
- }
-
/** Returns the bubble key associated with this view. */
@Nullable
public String getBubbleKey() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index bb30c5eeebcf..69119cf4338e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -70,7 +70,9 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
private Bubble mBubble;
private WeakReference<Context> mContext;
- private WeakReference<BubbleController> mController;
+ private WeakReference<BubbleExpandedViewManager> mExpandedViewManager;
+ private WeakReference<BubbleTaskViewFactory> mTaskViewFactory;
+ private WeakReference<BubblePositioner> mPositioner;
private WeakReference<BubbleStackView> mStackView;
private WeakReference<BubbleBarLayerView> mLayerView;
private BubbleIconFactory mIconFactory;
@@ -84,7 +86,9 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
*/
BubbleViewInfoTask(Bubble b,
Context context,
- BubbleController controller,
+ BubbleExpandedViewManager expandedViewManager,
+ BubbleTaskViewFactory taskViewFactory,
+ BubblePositioner positioner,
@Nullable BubbleStackView stackView,
@Nullable BubbleBarLayerView layerView,
BubbleIconFactory factory,
@@ -93,7 +97,9 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
Executor mainExecutor) {
mBubble = b;
mContext = new WeakReference<>(context);
- mController = new WeakReference<>(controller);
+ mExpandedViewManager = new WeakReference<>(expandedViewManager);
+ mTaskViewFactory = new WeakReference<>(taskViewFactory);
+ mPositioner = new WeakReference<>(positioner);
mStackView = new WeakReference<>(stackView);
mLayerView = new WeakReference<>(layerView);
mIconFactory = factory;
@@ -109,11 +115,13 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
return null;
}
if (mLayerView.get() != null) {
- return BubbleViewInfo.populateForBubbleBar(mContext.get(), mController.get(),
- mLayerView.get(), mIconFactory, mBubble, mSkipInflation);
+ return BubbleViewInfo.populateForBubbleBar(mContext.get(), mExpandedViewManager.get(),
+ mTaskViewFactory.get(), mPositioner.get(), mLayerView.get(), mIconFactory,
+ mBubble, mSkipInflation);
} else {
- return BubbleViewInfo.populate(mContext.get(), mController.get(), mStackView.get(),
- mIconFactory, mBubble, mSkipInflation);
+ return BubbleViewInfo.populate(mContext.get(), mExpandedViewManager.get(),
+ mTaskViewFactory.get(), mPositioner.get(), mStackView.get(), mIconFactory,
+ mBubble, mSkipInflation);
}
}
@@ -135,7 +143,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
}
private boolean verifyState() {
- if (mController.get().isShowingAsBubbleBar()) {
+ if (mExpandedViewManager.get().isShowingAsBubbleBar()) {
return mLayerView.get() != null;
} else {
return mStackView.get() != null;
@@ -167,16 +175,23 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
Bitmap badgeBitmap;
@Nullable
- public static BubbleViewInfo populateForBubbleBar(Context c, BubbleController controller,
- BubbleBarLayerView layerView, BubbleIconFactory iconFactory, Bubble b,
+ public static BubbleViewInfo populateForBubbleBar(Context c,
+ BubbleExpandedViewManager expandedViewManager,
+ BubbleTaskViewFactory taskViewFactory,
+ BubblePositioner positioner,
+ BubbleBarLayerView layerView,
+ BubbleIconFactory iconFactory,
+ Bubble b,
boolean skipInflation) {
BubbleViewInfo info = new BubbleViewInfo();
if (!skipInflation && !b.isInflated()) {
+ BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory);
LayoutInflater inflater = LayoutInflater.from(c);
info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
- info.bubbleBarExpandedView.initialize(controller, false /* isOverflow */);
+ info.bubbleBarExpandedView.initialize(
+ expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView);
}
if (!populateCommonInfo(info, c, b, iconFactory)) {
@@ -189,8 +204,13 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
@VisibleForTesting
@Nullable
- public static BubbleViewInfo populate(Context c, BubbleController controller,
- BubbleStackView stackView, BubbleIconFactory iconFactory, Bubble b,
+ public static BubbleViewInfo populate(Context c,
+ BubbleExpandedViewManager expandedViewManager,
+ BubbleTaskViewFactory taskViewFactory,
+ BubblePositioner positioner,
+ BubbleStackView stackView,
+ BubbleIconFactory iconFactory,
+ Bubble b,
boolean skipInflation) {
BubbleViewInfo info = new BubbleViewInfo();
@@ -199,11 +219,14 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
LayoutInflater inflater = LayoutInflater.from(c);
info.imageView = (BadgedImageView) inflater.inflate(
R.layout.bubble_view, stackView, false /* attachToRoot */);
- info.imageView.initialize(controller.getPositioner());
+ info.imageView.initialize(positioner);
+ BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory);
info.expandedView = (BubbleExpandedView) inflater.inflate(
R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
- info.expandedView.initialize(controller, stackView, false /* isOverflow */);
+ info.expandedView.initialize(
+ expandedViewManager, stackView, positioner, false /* isOverflow */,
+ bubbleTaskView);
}
if (!populateCommonInfo(info, c, b, iconFactory)) {
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 759246eb285d..26077cf7057b 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
@@ -286,6 +286,16 @@ public interface Bubbles {
void onUserRemoved(int removedUserId);
/**
+ * Called when the Sensitive notification protection state has changed, such as when media
+ * projection starts and stops.
+ *
+ * @param sensitiveNotificationProtectionActive {@code true} if notifications should be
+ * protected
+ */
+ void onSensitiveNotificationProtectionStateChanged(
+ boolean sensitiveNotificationProtectionActive);
+
+ /**
* A listener to be notified of bubble state changes, used by launcher to render bubbles in
* its process.
*/
@@ -321,6 +331,13 @@ public interface Bubbles {
/** Callback to tell SysUi components execute some methods. */
interface SysuiProxy {
+
+ /** Provider interface for {@link SysuiProxy}. */
+ interface Provider {
+ /** Returns {@link SysuiProxy}. */
+ SysuiProxy getSysuiProxy();
+ }
+
void isNotificationPanelExpand(Consumer<Boolean> callback);
void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
index 9e8a385262e4..c1f704ab455d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
@@ -24,8 +24,8 @@ import android.window.TransitionInfo;
import androidx.annotation.NonNull;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
/**
* Observer used to identify tasks that are opening or moving to front. If a bubble activity is
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 5776ad109d19..7a5afec934f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -17,6 +17,7 @@
package com.android.wm.shell.bubbles;
import android.content.Intent;
+import android.graphics.Rect;
import com.android.wm.shell.bubbles.IBubblesListener;
/**
@@ -29,7 +30,7 @@ interface IBubbles {
oneway void unregisterBubbleListener(in IBubblesListener listener) = 2;
- oneway void showBubble(in String key, in int bubbleBarOffsetX, in int bubbleBarOffsetY) = 3;
+ oneway void showBubble(in String key, in Rect bubbleBarBounds) = 3;
oneway void removeBubble(in String key) = 4;
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 61e17c8ec459..da71b1c741bb 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
@@ -33,15 +33,16 @@ 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(context: Context, positioner: BubblePositioner) : LinearLayout(context) {
-
- private val TAG =
- if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "ManageEducationView"
- else BubbleDebugConfig.TAG_BUBBLES
-
- private val ANIMATE_DURATION: Long = 200
+class ManageEducationView(
+ context: Context,
+ private val positioner: BubblePositioner
+) : LinearLayout(context) {
+
+ companion object {
+ const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
+ private const val ANIMATE_DURATION: Long = 200
+ }
- private val positioner: BubblePositioner = positioner
private val manageView by lazy { requireViewById<ViewGroup>(R.id.manage_education_view) }
private val manageButton by lazy { requireViewById<Button>(R.id.manage_button) }
private val gotItButton by lazy { requireViewById<Button>(R.id.got_it) }
@@ -128,7 +129,7 @@ class ManageEducationView(context: Context, positioner: BubblePositioner) : Line
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.alpha(1f)
}
- setShouldShow(false)
+ updateManageEducationSeen()
}
/**
@@ -218,13 +219,11 @@ class ManageEducationView(context: Context, positioner: BubblePositioner) : Line
}
}
- private fun setShouldShow(shouldShow: Boolean) {
+ private fun updateManageEducationSeen() {
context
.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
.edit()
- .putBoolean(PREF_MANAGED_EDUCATION, !shouldShow)
+ .putBoolean(PREF_MANAGED_EDUCATION, true)
.apply()
}
}
-
-const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS
index 8271014d290e..08c70314973e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS
@@ -1,2 +1,6 @@
# WM shell sub-module bubble owner
madym@google.com
+atsjenk@google.com
+liranb@google.com
+sukeshram@google.com
+mpodolian@google.com
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 2cabb65abe7a..c4108c4129e9 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
@@ -34,19 +34,21 @@ import com.android.wm.shell.animation.Interpolators
*/
class StackEducationView(
context: Context,
- positioner: BubblePositioner,
- controller: BubbleController
+ private val positioner: BubblePositioner,
+ private val manager: Manager
) : LinearLayout(context) {
- private val TAG =
- if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
- else BubbleDebugConfig.TAG_BUBBLES
-
- private val ANIMATE_DURATION: Long = 200
- private val ANIMATE_DURATION_SHORT: Long = 40
+ companion object {
+ const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
+ private const val ANIMATE_DURATION: Long = 200
+ private const val ANIMATE_DURATION_SHORT: Long = 40
+ }
- private val positioner: BubblePositioner = positioner
- private val controller: BubbleController = controller
+ /** Callbacks to notify managers of [StackEducationView] about events. */
+ interface Manager {
+ /** Notifies whether backpress should be intercepted. */
+ fun updateWindowFlagsForBackpress(interceptBack: Boolean)
+ }
private val view by lazy { requireViewById<View>(R.id.stack_education_layout) }
private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) }
@@ -97,7 +99,7 @@ class StackEducationView(
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
setOnKeyListener(null)
- controller.updateWindowFlagsForBackpress(false /* interceptBack */)
+ manager.updateWindowFlagsForBackpress(false /* interceptBack */)
}
private fun setTextColor() {
@@ -128,7 +130,7 @@ class StackEducationView(
isHiding = false
if (visibility == VISIBLE) return false
- controller.updateWindowFlagsForBackpress(true /* interceptBack */)
+ manager.updateWindowFlagsForBackpress(true /* interceptBack */)
layoutParams.width =
if (positioner.isLargeScreen || positioner.isLandscape)
context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
@@ -175,7 +177,7 @@ class StackEducationView(
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.alpha(1f)
}
- setShouldShow(false)
+ updateStackEducationSeen()
return true
}
@@ -189,20 +191,18 @@ class StackEducationView(
if (visibility != VISIBLE || isHiding) return
isHiding = true
- controller.updateWindowFlagsForBackpress(false /* interceptBack */)
+ manager.updateWindowFlagsForBackpress(false /* interceptBack */)
animate()
.alpha(0f)
.setDuration(if (isExpanding) ANIMATE_DURATION_SHORT else ANIMATE_DURATION)
.withEndAction { visibility = GONE }
}
- private fun setShouldShow(shouldShow: Boolean) {
+ private fun updateStackEducationSeen() {
context
.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
.edit()
- .putBoolean(PREF_STACK_EDUCATION, !shouldShow)
+ .putBoolean(PREF_STACK_EDUCATION, true)
.apply()
}
}
-
-const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
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 5b0239f6d659..7798aa753aa2 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
@@ -420,7 +420,7 @@ public class ExpandedAnimationController
bubbleView.setTranslationY(y);
}
- final float expandedY = mPositioner.getExpandedViewYTopAligned();
+ final int expandedY = mPositioner.getExpandedViewYTopAligned();
final boolean draggedOutEnough =
y > expandedY + mBubbleSizePx || y < expandedY - mBubbleSizePx;
if (draggedOutEnough != mBubbleDraggedOutEnough) {
@@ -563,7 +563,7 @@ public class ExpandedAnimationController
? p.x - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
: p.x + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
animationForChild(child)
- .translationX(fromX, p.y)
+ .translationX(fromX, p.x)
.start();
} else {
float fromY = p.y - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
@@ -634,4 +634,9 @@ public class ExpandedAnimationController
.start();
}
}
+
+ /** Returns true if we're in the middle of a collapse or expand animation. */
+ boolean isAnimating() {
+ return mAnimatingCollapse || mAnimatingExpand;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
index 845dca34b41f..e43609fe8ff0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
@@ -15,14 +15,11 @@
*/
package com.android.wm.shell.bubbles.animation;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_COLLAPSE_ANIMATOR;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_EXPANDED_VIEW_DRAGGING;
-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.BubbleExpandedView.BACKGROUND_ALPHA;
import static com.android.wm.shell.bubbles.BubbleExpandedView.BOTTOM_CLIP_PROPERTY;
import static com.android.wm.shell.bubbles.BubbleExpandedView.CONTENT_ALPHA;
import static com.android.wm.shell.bubbles.BubbleExpandedView.MANAGE_BUTTON_ALPHA;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -31,7 +28,6 @@ import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
-import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.ViewConfiguration;
@@ -41,6 +37,7 @@ import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.animation.FlingAnimationUtils;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.bubbles.BubbleExpandedView;
@@ -55,8 +52,6 @@ import java.util.List;
*/
public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimationController {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "ExpandedViewAnimCtrl" : TAG_BUBBLES;
-
private static final float COLLAPSE_THRESHOLD = 0.02f;
private static final int COLLAPSE_DURATION_MS = 250;
@@ -121,9 +116,6 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio
@Override
public void setExpandedView(BubbleExpandedView expandedView) {
if (mExpandedView != null) {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "updating expandedView, resetting previous");
- }
if (mCollapseAnimation != null) {
mCollapseAnimation.cancel();
}
@@ -140,17 +132,14 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio
if (mExpandedView != null) {
mDraggedAmount = OverScroll.dampedScroll(distance, mExpandedView.getContentHeight());
- if (DEBUG_COLLAPSE_ANIMATOR && DEBUG_EXPANDED_VIEW_DRAGGING) {
- Log.d(TAG, "updateDrag: distance=" + distance + " dragged=" + mDraggedAmount);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "updateDrag: distance=%f dragged=%d", distance, mDraggedAmount);
setCollapsedAmount(mDraggedAmount);
if (!mNotifiedAboutThreshold && isPastCollapseThreshold()) {
mNotifiedAboutThreshold = true;
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "notifying over collapse threshold");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "notifying over collapse threshold");
vibrateIfEnabled();
}
}
@@ -172,45 +161,35 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio
if (mSwipeDownVelocity > mMinFlingVelocity) {
// Swipe velocity is positive and over fling velocity.
// This is a swipe down, always reset to expanded state, regardless of dragged amount.
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG,
- "not collapsing expanded view, swipe down velocity: " + mSwipeDownVelocity
- + " minV: " + mMinFlingVelocity);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "not collapsing expanded view, swipe down velocity=%f minV=%d",
+ mSwipeDownVelocity, mMinFlingVelocity);
return false;
}
if (mSwipeUpVelocity > mMinFlingVelocity) {
// Swiping up and over fling velocity, collapse the view.
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG,
- "collapse expanded view, swipe up velocity: " + mSwipeUpVelocity + " minV: "
- + mMinFlingVelocity);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "collapse expanded view, swipe up velocity=%f minV=%d",
+ mSwipeUpVelocity, mMinFlingVelocity);
return true;
}
if (isPastCollapseThreshold()) {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "collapse expanded view, past threshold, dragged: " + mDraggedAmount);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "collapse expanded view, past threshold, dragged=%d", mDraggedAmount);
return true;
}
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "not collapsing expanded view");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "not collapsing expanded view");
return false;
}
@Override
public void animateCollapse(Runnable startStackCollapse, Runnable after) {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG,
- "expandedView animate collapse swipeVel=" + mSwipeUpVelocity + " minFlingVel="
- + mMinFlingVelocity);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d",
+ mSwipeUpVelocity, mMinFlingVelocity);
if (mExpandedView != null) {
// Mark it as animating immediately to avoid updates to the view before animation starts
mExpandedView.setAnimating(true);
@@ -243,9 +222,7 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio
@Override
public void animateBackToExpanded() {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "expandedView animate back to expanded");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate back to expanded");
BubbleExpandedView expandedView = mExpandedView;
if (expandedView == null) {
return;
@@ -298,9 +275,7 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio
@Override
public void reset() {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "reset expandedView collapsed state");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "reset expandedView collapsed state");
if (mExpandedView == null) {
return;
}
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 e48732801094..bb0dd95b042f 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
@@ -769,8 +769,10 @@ public class StackAnimationController extends
boolean swapped = false;
for (int newIndex = 0; newIndex < bubbleViews.size(); newIndex++) {
View view = bubbleViews.get(newIndex);
- final int oldIndex = mLayout.indexOfChild(view);
- swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after);
+ if (view != null) {
+ final int oldIndex = mLayout.indexOfChild(view);
+ swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after);
+ }
}
if (!swapped) {
// All bubbles were at the right position. Make sure badges and z order is correct.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 7f34ee0cdd3d..8946f41e96a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -15,14 +15,29 @@
*/
package com.android.wm.shell.bubbles.bar;
+import static android.view.View.ALPHA;
+import static android.view.View.SCALE_X;
+import static android.view.View.SCALE_Y;
+import static android.view.View.TRANSLATION_X;
+import static android.view.View.TRANSLATION_Y;
import static android.view.View.VISIBLE;
+import static android.view.View.X;
+import static android.view.View.Y;
+
+import static com.android.wm.shell.animation.Interpolators.EMPHASIZED;
+import static com.android.wm.shell.animation.Interpolators.EMPHASIZED_DECELERATE;
+import static com.android.wm.shell.bubbles.bar.BubbleBarExpandedView.CORNER_RADIUS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
+import android.graphics.Rect;
import android.util.Log;
+import android.util.Size;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
@@ -33,6 +48,7 @@ import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject.MagneticTarget;
/**
* Helper class to animate a {@link BubbleBarExpandedView} on a bubble.
@@ -44,6 +60,17 @@ public class BubbleBarAnimationHelper {
private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f;
private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f;
private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
+ private static final int EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION = 400;
+ private static final int EXPANDED_VIEW_ANIMATE_TO_REST_DURATION = 400;
+ private static final int EXPANDED_VIEW_DISMISS_DURATION = 250;
+ private static final int EXPANDED_VIEW_DRAG_ANIMATION_DURATION = 400;
+ /**
+ * Additional scale applied to expanded view when it is positioned inside a magnetic target.
+ */
+ private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.2f;
+ private static final float EXPANDED_VIEW_DRAG_SCALE = 0.4f;
+ private static final float DISMISS_VIEW_SCALE = 1.25f;
+ private static final int HANDLE_ALPHA_ANIMATION_DURATION = 100;
/** Spring config for the expanded view scale-in animation. */
private final PhysicsAnimator.SpringConfig mScaleInSpringConfig =
@@ -59,9 +86,13 @@ public class BubbleBarAnimationHelper {
/** Animator for animating the expanded view's alpha (including the TaskView inside it). */
private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
+ @Nullable
+ private Animator mRunningDragAnimator;
+
private final Context mContext;
private final BubbleBarLayerView mLayerView;
private final BubblePositioner mPositioner;
+ private final int[] mTmpLocation = new int[2];
private BubbleViewProvider mExpandedBubble;
private boolean mIsExpanded = false;
@@ -136,12 +167,12 @@ public class BubbleBarAnimationHelper {
bbev.setVisibility(VISIBLE);
// Set the pivot point for the scale, so the view animates out from the bubble bar.
- Point bubbleBarPosition = mPositioner.getBubbleBarPosition();
+ Rect bubbleBarBounds = mPositioner.getBubbleBarBounds();
mExpandedViewContainerMatrix.setScale(
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
- bubbleBarPosition.x,
- bubbleBarPosition.y);
+ bubbleBarBounds.centerX(),
+ bubbleBarBounds.top);
bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
@@ -181,7 +212,8 @@ public class BubbleBarAnimationHelper {
Log.w(TAG, "Trying to animate collapse without a bubble");
return;
}
-
+ bbev.setScaleX(1f);
+ bbev.setScaleY(1f);
mExpandedViewContainerMatrix.setScaleX(1f);
mExpandedViewContainerMatrix.setScaleY(1f);
@@ -209,11 +241,210 @@ public class BubbleBarAnimationHelper {
}
/**
+ * Animate the expanded bubble when it is being dragged
+ */
+ public void animateStartDrag() {
+ final BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
+ Log.w(TAG, "Trying to animate start drag without a bubble");
+ return;
+ }
+ setDragPivot(bbev);
+ // Corner radius gets scaled, apply the reverse scale to ensure we have the desired radius
+ final float cornerRadius = bbev.getDraggedCornerRadius() / EXPANDED_VIEW_DRAG_SCALE;
+
+ AnimatorSet contentAnim = new AnimatorSet();
+ contentAnim.playTogether(
+ ObjectAnimator.ofFloat(bbev, SCALE_X, EXPANDED_VIEW_DRAG_SCALE),
+ ObjectAnimator.ofFloat(bbev, SCALE_Y, EXPANDED_VIEW_DRAG_SCALE),
+ ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, cornerRadius)
+ );
+ contentAnim.setDuration(EXPANDED_VIEW_DRAG_ANIMATION_DURATION).setInterpolator(EMPHASIZED);
+
+ ObjectAnimator handleAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 0f)
+ .setDuration(HANDLE_ALPHA_ANIMATION_DURATION);
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(contentAnim, handleAnim);
+ animatorSet.addListener(new DragAnimatorListenerAdapter(bbev));
+ startNewDragAnimation(animatorSet);
+ }
+
+ /**
+ * Animates dismissal of currently expanded bubble
+ *
+ * @param endRunnable a runnable to run at the end of the animation
+ */
+ public void animateDismiss(Runnable endRunnable) {
+ mIsExpanded = false;
+ final BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
+ Log.w(TAG, "Trying to animate dismiss without a bubble");
+ return;
+ }
+
+ int[] location = bbev.getLocationOnScreen();
+ int diffFromBottom = mPositioner.getScreenRect().bottom - location[1];
+
+ cancelAnimations();
+ bbev.animate()
+ // 2x distance from bottom so the view flies out
+ .translationYBy(diffFromBottom * 2)
+ .setDuration(EXPANDED_VIEW_DISMISS_DURATION)
+ .withEndAction(endRunnable)
+ .start();
+ }
+
+ /**
+ * Animate current expanded bubble back to its rest position
+ */
+ public void animateToRestPosition() {
+ BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
+ Log.w(TAG, "Trying to animate expanded view to rest position without a bubble");
+ return;
+ }
+ Point restPoint = getExpandedViewRestPosition(getExpandedViewSize());
+
+ AnimatorSet contentAnim = new AnimatorSet();
+ contentAnim.playTogether(
+ ObjectAnimator.ofFloat(bbev, X, restPoint.x),
+ ObjectAnimator.ofFloat(bbev, Y, restPoint.y),
+ ObjectAnimator.ofFloat(bbev, SCALE_X, 1f),
+ ObjectAnimator.ofFloat(bbev, SCALE_Y, 1f),
+ ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, bbev.getRestingCornerRadius())
+ );
+ contentAnim.setDuration(EXPANDED_VIEW_ANIMATE_TO_REST_DURATION).setInterpolator(EMPHASIZED);
+
+ ObjectAnimator handleAlphaAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 1f)
+ .setDuration(HANDLE_ALPHA_ANIMATION_DURATION);
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(contentAnim, handleAlphaAnim);
+ animatorSet.addListener(new DragAnimatorListenerAdapter(bbev) {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ bbev.resetPivot();
+ }
+ });
+ startNewDragAnimation(animatorSet);
+ }
+
+ /**
+ * Animates currently expanded bubble into the given {@link MagneticTarget}.
+ *
+ * @param target magnetic target to snap to
+ * @param endRunnable a runnable to run at the end of the animation
+ */
+ public void animateIntoTarget(MagneticTarget target, @Nullable Runnable endRunnable) {
+ BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
+ Log.w(TAG, "Trying to snap the expanded view to target without a bubble");
+ return;
+ }
+
+ setDragPivot(bbev);
+
+ // When the view animates into the target, it is scaled down with the pivot at center top.
+ // Find the point on the view that would be the center of the view at its final scale.
+ // Once we know that, we can calculate x and y distance from the center of the target view
+ // and use that for the translation animation to ensure that the view at final scale is
+ // placed at the center of the target.
+
+ // Set mTmpLocation to the current location of the view on the screen, taking into account
+ // any scale applied.
+ bbev.getLocationOnScreen(mTmpLocation);
+ // Since pivotX is at the center of the x-axis, even at final scale, center of the view on
+ // x-axis will be the same as the center of the view at current size.
+ // Get scaled width of the view and adjust mTmpLocation so that point on x-axis is at the
+ // center of the view at its current size.
+ float currentWidth = bbev.getWidth() * bbev.getScaleX();
+ mTmpLocation[0] += (int) (currentWidth / 2f);
+ // Since pivotY is at the top of the view, at final scale, top coordinate of the view
+ // remains the same.
+ // Get height of the view at final scale and adjust mTmpLocation so that point on y-axis is
+ // moved down by half of the height at final scale.
+ float targetHeight = bbev.getHeight() * EXPANDED_VIEW_IN_TARGET_SCALE;
+ mTmpLocation[1] += (int) (targetHeight / 2f);
+ // mTmpLocation is now set to the point on the view that will be the center of the view once
+ // scale is applied.
+
+ // Calculate the difference between the target's center coordinates and mTmpLocation
+ float xDiff = target.getCenterOnScreen().x - mTmpLocation[0];
+ float yDiff = target.getCenterOnScreen().y - mTmpLocation[1];
+
+ // Corner radius gets scaled, apply the reverse scale to ensure we have the desired radius
+ final float cornerRadius = bbev.getDraggedCornerRadius() / EXPANDED_VIEW_IN_TARGET_SCALE;
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(
+ // Move expanded view to the center of dismiss view
+ ObjectAnimator.ofFloat(bbev, TRANSLATION_X, bbev.getTranslationX() + xDiff),
+ ObjectAnimator.ofFloat(bbev, TRANSLATION_Y, bbev.getTranslationY() + yDiff),
+ // Scale expanded view down
+ ObjectAnimator.ofFloat(bbev, SCALE_X, EXPANDED_VIEW_IN_TARGET_SCALE),
+ ObjectAnimator.ofFloat(bbev, SCALE_Y, EXPANDED_VIEW_IN_TARGET_SCALE),
+ // Update corner radius for expanded view
+ ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, cornerRadius),
+ // Scale dismiss view up
+ ObjectAnimator.ofFloat(target.getTargetView(), SCALE_X, DISMISS_VIEW_SCALE),
+ ObjectAnimator.ofFloat(target.getTargetView(), SCALE_Y, DISMISS_VIEW_SCALE)
+ );
+ animatorSet.setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION).setInterpolator(
+ EMPHASIZED_DECELERATE);
+ animatorSet.addListener(new DragAnimatorListenerAdapter(bbev) {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ }
+ });
+ startNewDragAnimation(animatorSet);
+ }
+
+ /**
+ * Animate currently expanded view when it is released from dismiss view
+ */
+ public void animateUnstuckFromDismissView(MagneticTarget target) {
+ BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
+ Log.w(TAG, "Trying to unsnap the expanded view from dismiss without a bubble");
+ return;
+ }
+ setDragPivot(bbev);
+ // Corner radius gets scaled, apply the reverse scale to ensure we have the desired radius
+ final float cornerRadius = bbev.getDraggedCornerRadius() / EXPANDED_VIEW_DRAG_SCALE;
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(
+ ObjectAnimator.ofFloat(bbev, SCALE_X, EXPANDED_VIEW_DRAG_SCALE),
+ ObjectAnimator.ofFloat(bbev, SCALE_Y, EXPANDED_VIEW_DRAG_SCALE),
+ ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, cornerRadius),
+ ObjectAnimator.ofFloat(target.getTargetView(), SCALE_X, 1f),
+ ObjectAnimator.ofFloat(target.getTargetView(), SCALE_Y, 1f)
+ );
+ animatorSet.setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION).setInterpolator(
+ EMPHASIZED_DECELERATE);
+ animatorSet.addListener(new DragAnimatorListenerAdapter(bbev));
+ startNewDragAnimation(animatorSet);
+ }
+
+ /**
* Cancel current animations
*/
public void cancelAnimations() {
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
mExpandedViewAlphaAnimator.cancel();
+ BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev != null) {
+ bbev.animate().cancel();
+ }
+ if (mRunningDragAnimator != null) {
+ mRunningDragAnimator.cancel();
+ mRunningDragAnimator = null;
+ }
}
private @Nullable BubbleBarExpandedView getExpandedView() {
@@ -231,21 +462,65 @@ public class BubbleBarAnimationHelper {
return;
}
- boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
- final int padding = mPositioner.getBubbleBarExpandedViewPadding();
- final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded);
- final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
+ final Size size = getExpandedViewSize();
+ Point position = getExpandedViewRestPosition(size);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) bbev.getLayoutParams();
- lp.width = width;
- lp.height = height;
+ lp.width = size.getWidth();
+ lp.height = size.getHeight();
bbev.setLayoutParams(lp);
+ bbev.setX(position.x);
+ bbev.setY(position.y);
+ bbev.updateLocation();
+ bbev.maybeShowOverflow();
+ }
+
+ private Point getExpandedViewRestPosition(Size size) {
+ final int padding = mPositioner.getBubbleBarExpandedViewPadding();
+ Point point = new Point();
if (mLayerView.isOnLeft()) {
- bbev.setX(mPositioner.getInsets().left + padding);
+ point.x = mPositioner.getInsets().left + padding;
} else {
- bbev.setX(mPositioner.getAvailableRect().width() - width - padding);
+ point.x = mPositioner.getAvailableRect().width() - size.getWidth() - padding;
+ }
+ point.y = mPositioner.getExpandedViewBottomForBubbleBar() - size.getHeight();
+ return point;
+ }
+
+ private Size getExpandedViewSize() {
+ boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
+ final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded);
+ final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
+ return new Size(width, height);
+ }
+
+ private void startNewDragAnimation(Animator animator) {
+ cancelAnimations();
+ mRunningDragAnimator = animator;
+ animator.start();
+ }
+
+ private static void setDragPivot(BubbleBarExpandedView bbev) {
+ bbev.setPivotX(bbev.getWidth() / 2f);
+ bbev.setPivotY(0f);
+ }
+
+ private class DragAnimatorListenerAdapter extends AnimatorListenerAdapter {
+
+ private final BubbleBarExpandedView mBubbleBarExpandedView;
+
+ DragAnimatorListenerAdapter(BubbleBarExpandedView bbev) {
+ mBubbleBarExpandedView = bbev;
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mBubbleBarExpandedView.setAnimating(true);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBubbleBarExpandedView.setAnimating(false);
+ mRunningDragAnimator = null;
}
- bbev.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height);
- bbev.updateLocation();
- bbev.maybeShowOverflow();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index d073f1df938a..271fb9abce6a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.bubbles.bar;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
@@ -25,27 +27,26 @@ import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.FloatProperty;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
-import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubble;
-import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleExpandedViewManager;
import com.android.wm.shell.bubbles.BubbleOverflowContainerView;
+import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleTaskView;
import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.taskview.TaskView;
import java.util.function.Supplier;
-/**
- * Expanded view of a bubble when it's part of the bubble bar.
- *
- * {@link BubbleController#isShowingAsBubbleBar()}
- */
+/** Expanded view of a bubble when it's part of the bubble bar. */
public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskViewHelper.Listener {
/**
* The expanded view listener notifying the {@link BubbleBarLayerView} about the internal
@@ -60,24 +61,46 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
void onBackPressed();
}
+ /**
+ * A property wrapper around corner radius for the expanded view, handled by
+ * {@link #setCornerRadius(float)} and {@link #getCornerRadius()} methods.
+ */
+ public static final FloatProperty<BubbleBarExpandedView> CORNER_RADIUS = new FloatProperty<>(
+ "cornerRadius") {
+ @Override
+ public void setValue(BubbleBarExpandedView bbev, float radius) {
+ bbev.setCornerRadius(radius);
+ }
+
+ @Override
+ public Float get(BubbleBarExpandedView bbev) {
+ return bbev.getCornerRadius();
+ }
+ };
+
private static final String TAG = BubbleBarExpandedView.class.getSimpleName();
private static final int INVALID_TASK_ID = -1;
- private BubbleController mController;
+ private BubbleExpandedViewManager mManager;
private boolean mIsOverflow;
private BubbleTaskViewHelper mBubbleTaskViewHelper;
private BubbleBarMenuViewController mMenuViewController;
private @Nullable Supplier<Rect> mLayerBoundsSupplier;
private @Nullable Listener mListener;
- private BubbleBarHandleView mHandleView = new BubbleBarHandleView(getContext());
+ private BubbleBarHandleView mHandleView;
private @Nullable TaskView mTaskView;
private @Nullable BubbleOverflowContainerView mOverflowView;
private int mCaptionHeight;
private int mBackgroundColor;
- private float mCornerRadius = 0f;
+ /** Corner radius used when view is resting */
+ private float mRestingCornerRadius = 0f;
+ /** Corner radius applied while dragging */
+ private float mDraggedCornerRadius = 0f;
+ /** Current corner radius */
+ private float mCurrentCornerRadius = 0f;
/**
* Whether we want the {@code TaskView}'s content to be visible (alpha = 1f). If
@@ -111,15 +134,17 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation));
mCaptionHeight = context.getResources().getDimensionPixelSize(
R.dimen.bubble_bar_expanded_view_caption_height);
- addView(mHandleView);
+ mHandleView = findViewById(R.id.bubble_bar_handle_view);
applyThemeAttrs();
setClipToOutline(true);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCurrentCornerRadius);
}
});
+ // Set a touch sink to ensure that clicks on the caption area do not propagate to the parent
+ setOnTouchListener((v, event) -> true);
}
@Override
@@ -129,25 +154,33 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
mMenuViewController.hideMenu(false /* animated */);
}
- /** Set the BubbleController on the view, must be called before doing anything else. */
- public void initialize(BubbleController controller, boolean isOverflow) {
- mController = controller;
+ /** Initializes the view, must be called before doing anything else. */
+ public void initialize(BubbleExpandedViewManager expandedViewManager,
+ BubblePositioner positioner,
+ boolean isOverflow,
+ @Nullable BubbleTaskView bubbleTaskView) {
+ mManager = expandedViewManager;
mIsOverflow = isOverflow;
if (mIsOverflow) {
mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate(
R.layout.bubble_overflow_container, null /* root */);
- mOverflowView.setBubbleController(mController);
+ mOverflowView.initialize(expandedViewManager, positioner);
addView(mOverflowView);
} else {
-
- mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController,
- /* listener= */ this,
+ mTaskView = bubbleTaskView.getTaskView();
+ mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, expandedViewManager,
+ /* listener= */ this, bubbleTaskView,
/* viewParent= */ this);
- mTaskView = mBubbleTaskViewHelper.getTaskView();
- addView(mTaskView);
+ if (mTaskView.getParent() != null) {
+ ((ViewGroup) mTaskView.getParent()).removeView(mTaskView);
+ }
+ FrameLayout.LayoutParams lp =
+ new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
+ addView(mTaskView, lp);
mTaskView.setEnableSurfaceClipping(true);
- mTaskView.setCornerRadius(mCornerRadius);
+ mTaskView.setCornerRadius(mCurrentCornerRadius);
+ mTaskView.setVisibility(VISIBLE);
// Handle view needs to draw on top of task view.
bringChildToFront(mHandleView);
@@ -168,13 +201,13 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
@Override
public void onOpenAppSettings(Bubble bubble) {
- mController.collapseStack();
+ mManager.collapseStack();
mContext.startActivityAsUser(bubble.getSettingsIntent(mContext), bubble.getUser());
}
@Override
public void onDismissBubble(Bubble bubble) {
- mController.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED);
+ mManager.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED);
}
});
mHandleView.setOnClickListener(view -> {
@@ -189,22 +222,24 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
// TODO (b/275087636): call this when theme/config changes
/** Updates the view based on the current theme. */
public void applyThemeAttrs() {
- boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
- mContext.getResources());
+ mRestingCornerRadius = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_corner_radius
+ );
+ mDraggedCornerRadius = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_corner_radius_dragged
+ );
+
+ mCurrentCornerRadius = mRestingCornerRadius;
+
final TypedArray ta = mContext.obtainStyledAttributes(new int[]{
- android.R.attr.dialogCornerRadius,
android.R.attr.colorBackgroundFloating});
- mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0;
- mCornerRadius = mCornerRadius / 2f;
- mBackgroundColor = ta.getColor(1, Color.WHITE);
-
+ mBackgroundColor = ta.getColor(0, Color.WHITE);
ta.recycle();
-
mCaptionHeight = getResources().getDimensionPixelSize(
R.dimen.bubble_bar_expanded_view_caption_height);
if (mTaskView != null) {
- mTaskView.setCornerRadius(mCornerRadius);
+ mTaskView.setCornerRadius(mCurrentCornerRadius);
updateHandleColor(true /* animated */);
}
}
@@ -212,12 +247,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int height = MeasureSpec.getSize(heightMeasureSpec);
- int menuViewHeight = Math.min(mCaptionHeight, height);
- measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
- MeasureSpec.getMode(heightMeasureSpec)));
-
if (mTaskView != null) {
+ int height = MeasureSpec.getSize(heightMeasureSpec);
measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(height,
MeasureSpec.getMode(heightMeasureSpec)));
}
@@ -226,14 +257,11 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
- final int captionBottom = t + mCaptionHeight;
if (mTaskView != null) {
mTaskView.layout(l, t, r,
t + mTaskView.getMeasuredHeight());
mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
}
- // Handle draws on top of task view in the caption area.
- mHandleView.layout(l, t, r, captionBottom);
}
@Override
@@ -256,14 +284,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
mListener.onBackPressed();
}
- /** Cleans up task view, should be called when the bubble is no longer active. */
+ /** Cleans up the expanded view, should be called when the bubble is no longer active. */
public void cleanUpExpandedState() {
- if (mBubbleTaskViewHelper != null) {
- if (mTaskView != null) {
- removeView(mTaskView);
- }
- mBubbleTaskViewHelper.cleanUpTaskView();
- }
mMenuViewController.hideMenu(false /* animated */);
}
@@ -275,7 +297,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
if (mMenuViewController.isMenuVisible()) {
mMenuViewController.hideMenu(/* animated = */ true);
} else {
- mController.collapseStack();
+ mManager.collapseStack();
}
}
@@ -393,4 +415,30 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
public boolean isAnimating() {
return mIsAnimating;
}
+
+ /** @return corner radius that should be applied while view is in rest */
+ public float getRestingCornerRadius() {
+ return mRestingCornerRadius;
+ }
+
+ /** @return corner radius that should be applied while view is being dragged */
+ public float getDraggedCornerRadius() {
+ return mDraggedCornerRadius;
+ }
+
+ /** @return current corner radius */
+ public float getCornerRadius() {
+ return mCurrentCornerRadius;
+ }
+
+ /** Update corner radius */
+ public void setCornerRadius(float cornerRadius) {
+ if (mCurrentCornerRadius != cornerRadius) {
+ mCurrentCornerRadius = cornerRadius;
+ if (mTaskView != null) {
+ mTaskView.setCornerRadius(cornerRadius);
+ }
+ invalidateOutline();
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index 4ea18f78f5b2..7d37d6068dfb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -16,70 +16,70 @@
package com.android.wm.shell.bubbles.bar
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.graphics.PointF
-import android.graphics.Rect
+import android.annotation.SuppressLint
import android.view.MotionEvent
import android.view.View
-import com.android.wm.shell.animation.Interpolators
import com.android.wm.shell.common.bubbles.DismissView
import com.android.wm.shell.common.bubbles.RelativeTouchListener
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject
/** Controller for handling drag interactions with [BubbleBarExpandedView] */
+@SuppressLint("ClickableViewAccessibility")
class BubbleBarExpandedViewDragController(
private val expandedView: BubbleBarExpandedView,
private val dismissView: DismissView,
+ private val animationHelper: BubbleBarAnimationHelper,
private val onDismissed: () -> Unit
) {
+ var isStuckToDismiss: Boolean = false
+ private set
+
+ private var expandedViewInitialTranslationX = 0f
+ private var expandedViewInitialTranslationY = 0f
+ private val magnetizedExpandedView: MagnetizedObject<BubbleBarExpandedView> =
+ MagnetizedObject.magnetizeView(expandedView)
+ private val magnetizedDismissTarget: MagnetizedObject.MagneticTarget
+
init {
- expandedView.handleView.setOnTouchListener(HandleDragListener())
- }
+ magnetizedExpandedView.magnetListener = MagnetListener()
+ magnetizedExpandedView.animateStuckToTarget =
+ {
+ target: MagnetizedObject.MagneticTarget,
+ _: Float,
+ _: Float,
+ _: Boolean,
+ after: (() -> Unit)? ->
+ animationHelper.animateIntoTarget(target, after)
+ }
- private fun finishDrag(x: Float, y: Float, viewInitialX: Float, viewInitialY: Float) {
- val dismissCircleBounds = Rect().apply { dismissView.circle.getBoundsOnScreen(this) }
- if (dismissCircleBounds.contains(x.toInt(), y.toInt())) {
- onDismissed()
- } else {
- resetExpandedViewPosition(viewInitialX, viewInitialY)
- }
- dismissView.hide()
- }
+ magnetizedDismissTarget =
+ MagnetizedObject.MagneticTarget(dismissView.circle, dismissView.circle.width)
+ magnetizedExpandedView.addTarget(magnetizedDismissTarget)
- private fun resetExpandedViewPosition(initialX: Float, initialY: Float) {
- val listener =
- object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator) {
- expandedView.isAnimating = true
- }
+ val dragMotionEventHandler = HandleDragListener()
- override fun onAnimationEnd(animation: Animator) {
- expandedView.isAnimating = false
- }
+ expandedView.handleView.setOnTouchListener { view, event ->
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+ expandedViewInitialTranslationX = expandedView.translationX
+ expandedViewInitialTranslationY = expandedView.translationY
}
- expandedView
- .animate()
- .translationX(initialX)
- .translationY(initialY)
- .setDuration(RESET_POSITION_ANIM_DURATION)
- .setInterpolator(Interpolators.EMPHASIZED_DECELERATE)
- .setListener(listener)
- .start()
+ val magnetConsumed = magnetizedExpandedView.maybeConsumeMotionEvent(event)
+ // Move events can be consumed by the magnetized object
+ if (event.actionMasked == MotionEvent.ACTION_MOVE && magnetConsumed) {
+ return@setOnTouchListener true
+ }
+ return@setOnTouchListener dragMotionEventHandler.onTouch(view, event) || magnetConsumed
+ }
}
private inner class HandleDragListener : RelativeTouchListener() {
- private val expandedViewRestPosition = PointF()
+ private var isMoving = false
override fun onDown(v: View, ev: MotionEvent): Boolean {
// While animating, don't allow new touch events
- if (expandedView.isAnimating) {
- return false
- }
- expandedViewRestPosition.x = expandedView.translationX
- expandedViewRestPosition.y = expandedView.translationY
- return true
+ return !expandedView.isAnimating
}
override fun onMove(
@@ -90,8 +90,12 @@ class BubbleBarExpandedViewDragController(
dx: Float,
dy: Float
) {
- expandedView.translationX = expandedViewRestPosition.x + dx
- expandedView.translationY = expandedViewRestPosition.y + dy
+ if (!isMoving) {
+ isMoving = true
+ animationHelper.animateStartDrag()
+ }
+ expandedView.translationX = expandedViewInitialTranslationX + dx
+ expandedView.translationY = expandedViewInitialTranslationY + dy
dismissView.show()
}
@@ -105,16 +109,48 @@ class BubbleBarExpandedViewDragController(
velX: Float,
velY: Float
) {
- finishDrag(ev.rawX, ev.rawY, expandedViewRestPosition.x, expandedViewRestPosition.y)
+ finishDrag()
}
override fun onCancel(v: View, ev: MotionEvent, viewInitialX: Float, viewInitialY: Float) {
- resetExpandedViewPosition(expandedViewRestPosition.x, expandedViewRestPosition.y)
- dismissView.hide()
+ finishDrag()
+ }
+
+ private fun finishDrag() {
+ if (!isStuckToDismiss) {
+ animationHelper.animateToRestPosition()
+ dismissView.hide()
+ }
+ isMoving = false
}
}
- companion object {
- const val RESET_POSITION_ANIM_DURATION = 300L
+ private inner class MagnetListener : MagnetizedObject.MagnetListener {
+ override fun onStuckToTarget(
+ target: MagnetizedObject.MagneticTarget,
+ draggedObject: MagnetizedObject<*>
+ ) {
+ isStuckToDismiss = true
+ }
+
+ override fun onUnstuckFromTarget(
+ target: MagnetizedObject.MagneticTarget,
+ draggedObject: MagnetizedObject<*>,
+ velX: Float,
+ velY: Float,
+ wasFlungOut: Boolean
+ ) {
+ isStuckToDismiss = false
+ animationHelper.animateUnstuckFromDismissView(target)
+ }
+
+ override fun onReleasedInTarget(
+ target: MagnetizedObject.MagneticTarget,
+ draggedObject: MagnetizedObject<*>
+ ) {
+ onDismissed()
+ dismissView.hide()
+ }
}
}
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index bdb0e206e490..42799d975e1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.bubbles.bar;
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.Bubbles.DISMISS_USER_GESTURE;
import android.annotation.Nullable;
import android.content.Context;
@@ -25,6 +26,7 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
+import android.view.Gravity;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewTreeObserver;
@@ -32,11 +34,12 @@ import android.view.WindowManager;
import android.widget.FrameLayout;
import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
-import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.bubbles.DeviceConfig;
import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.common.bubbles.DismissView;
@@ -60,6 +63,7 @@ public class BubbleBarLayerView extends FrameLayout
private static final float SCRIM_ALPHA = 0.2f;
private final BubbleController mBubbleController;
+ private final BubbleData mBubbleData;
private final BubblePositioner mPositioner;
private final BubbleBarAnimationHelper mAnimationHelper;
private final BubbleEducationViewController mEducationViewController;
@@ -74,10 +78,6 @@ public class BubbleBarLayerView extends FrameLayout
private DismissView mDismissView;
private @Nullable Consumer<String> mUnBubbleConversationCallback;
- // TODO(b/273310265) - currently the view is always on the right, need to update for RTL.
- /** Whether the expanded view is displaying on the left of the screen or not. */
- private boolean mOnLeft = false;
-
/** Whether a bubble is expanded. */
private boolean mIsExpanded = false;
@@ -88,9 +88,10 @@ public class BubbleBarLayerView extends FrameLayout
private TouchDelegate mHandleTouchDelegate;
private final Rect mHandleTouchBounds = new Rect();
- public BubbleBarLayerView(Context context, BubbleController controller) {
+ public BubbleBarLayerView(Context context, BubbleController controller, BubbleData bubbleData) {
super(context);
mBubbleController = controller;
+ mBubbleData = bubbleData;
mPositioner = mBubbleController.getPositioner();
mAnimationHelper = new BubbleBarAnimationHelper(context,
@@ -154,10 +155,10 @@ public class BubbleBarLayerView extends FrameLayout
return mIsExpanded;
}
- // (TODO: b/273310265): BubblePositioner should be source of truth when this work is done.
+ // TODO(b/313661121) - when dragging is implemented, check user setting first
/** Whether the expanded view is positioned on the left or right side of the screen. */
public boolean isOnLeft() {
- return mOnLeft;
+ return getLayoutDirection() == LAYOUT_DIRECTION_RTL;
}
/** Shows the expanded view of the provided bubble. */
@@ -206,14 +207,17 @@ public class BubbleBarLayerView extends FrameLayout
}
});
- mDragController = new BubbleBarExpandedViewDragController(mExpandedView, mDismissView,
+ mDragController = new BubbleBarExpandedViewDragController(
+ mExpandedView,
+ mDismissView,
+ mAnimationHelper,
() -> {
mBubbleController.dismissBubble(mExpandedBubble.getKey(),
- Bubbles.DISMISS_USER_GESTURE);
+ DISMISS_USER_GESTURE);
return Unit.INSTANCE;
});
- addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
+ addView(mExpandedView, new LayoutParams(width, height, Gravity.LEFT));
}
if (mEducationViewController.isEducationVisible()) {
@@ -227,6 +231,7 @@ public class BubbleBarLayerView extends FrameLayout
// Touch delegate for the menu
BubbleBarHandleView view = mExpandedView.getHandleView();
view.getBoundsOnScreen(mHandleTouchBounds);
+ // Move top value up to ensure touch target is large enough
mHandleTouchBounds.top -= mPositioner.getBubblePaddingTop();
mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds,
mExpandedView.getHandleView());
@@ -236,12 +241,55 @@ public class BubbleBarLayerView extends FrameLayout
showScrim(true);
}
+ /** Removes the given {@code bubble}. */
+ public void removeBubble(Bubble bubble, Runnable endAction) {
+ Runnable cleanUp = () -> {
+ bubble.cleanupViews();
+ endAction.run();
+ };
+ if (mBubbleData.getBubbles().isEmpty()) {
+ // we're removing the last bubble. collapse the expanded view and cleanup bubble views
+ // at the end.
+ collapse(cleanUp);
+ } else {
+ cleanUp.run();
+ }
+ }
+
/** Collapses any showing expanded view */
public void collapse() {
+ collapse(/* endAction= */ null);
+ }
+
+ /**
+ * Collapses any showing expanded view.
+ *
+ * @param endAction an action to run and the end of the collapse animation.
+ */
+ public void collapse(@Nullable Runnable endAction) {
+ if (!mIsExpanded) {
+ if (endAction != null) {
+ endAction.run();
+ }
+ return;
+ }
mIsExpanded = false;
final BubbleBarExpandedView viewToRemove = mExpandedView;
mEducationViewController.hideEducation(/* animated = */ true);
- mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
+ Runnable runnable = () -> {
+ removeView(viewToRemove);
+ if (endAction != null) {
+ endAction.run();
+ }
+ if (mBubbleData.getBubbles().isEmpty()) {
+ mBubbleController.onAllBubblesAnimatedOut();
+ }
+ };
+ if (mDragController != null && mDragController.isStuckToDismiss()) {
+ mAnimationHelper.animateDismiss(runnable);
+ } else {
+ mAnimationHelper.animateCollapse(runnable);
+ }
mBubbleController.getSysuiProxy().onStackExpandChanged(false);
mExpandedView = null;
mDragController = null;
@@ -304,7 +352,7 @@ public class BubbleBarLayerView extends FrameLayout
lp.width = width;
lp.height = height;
mExpandedView.setLayoutParams(lp);
- if (mOnLeft) {
+ if (isOnLeft()) {
mExpandedView.setX(mPositioner.getInsets().left + padding);
} else {
mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
index e1dea3babbc2..33b61b164988 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
@@ -17,18 +17,19 @@
package com.android.wm.shell.bubbles.properties
import android.os.SystemProperties
+import com.android.wm.shell.Flags
/** Provides bubble properties in production. */
object ProdBubbleProperties : BubbleProperties {
- // TODO(b/256873975) Should use proper flag when available to shell/launcher
- private var _isBubbleBarEnabled =
+ private var _isBubbleBarEnabled = Flags.enableBubbleBar() ||
SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
override val isBubbleBarEnabled
get() = _isBubbleBarEnabled
override fun refresh() {
- _isBubbleBarEnabled = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
+ _isBubbleBarEnabled = Flags.enableBubbleBar() ||
+ SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
}
}
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 2ea43162d225..ad01d0fa311a 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
@@ -20,12 +20,12 @@ import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_CANCEL;
import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_END;
import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_START;
import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
-import static android.view.inputmethod.ImeTracker.TOKEN_NONE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.res.Configuration;
@@ -51,6 +51,7 @@ import android.view.inputmethod.InputMethodManagerGlobal;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
@@ -122,7 +123,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
if (mDisplayController.getDisplayLayout(displayId).rotation()
!= pd.mRotation && isImeShowing(displayId)) {
- pd.startAnimation(true, false /* forceRestart */, null /* statsToken */);
+ pd.startAnimation(true, false /* forceRestart */,
+ SoftInputShowHideReason.DISPLAY_CONFIGURATION_CHANGED);
}
}
@@ -257,7 +259,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
mInsetsState.set(insetsState, true /* copySources */);
if (mImeShowing && !Objects.equals(oldFrame, newFrame) && newSourceVisible) {
if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
- startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
+ startAnimation(mImeShowing, true /* forceRestart */,
+ SoftInputShowHideReason.DISPLAY_INSETS_CHANGED);
}
}
@@ -291,7 +294,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
final boolean positionChanged =
!imeSourceControl.getSurfacePosition().equals(lastSurfacePosition);
if (positionChanged) {
- startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
+ startAnimation(mImeShowing, true /* forceRestart */,
+ SoftInputShowHideReason.DISPLAY_CONTROLS_CHANGED);
}
} else {
if (!haveSameLeash(mImeSourceControl, imeSourceControl)) {
@@ -384,7 +388,20 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
private void startAnimation(final boolean show, final boolean forceRestart,
- @Nullable ImeTracker.Token statsToken) {
+ @SoftInputShowHideReason int reason) {
+ final var imeSource = mInsetsState.peekSource(InsetsSource.ID_IME);
+ if (imeSource == null || mImeSourceControl == null) {
+ return;
+ }
+ final var statsToken = ImeTracker.forLogging().onStart(
+ show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_WM_SHELL,
+ reason, false /* fromUser */);
+
+ startAnimation(show, forceRestart, statsToken);
+ }
+
+ private void startAnimation(final boolean show, final boolean forceRestart,
+ @NonNull final ImeTracker.Token statsToken) {
final InsetsSource imeSource = mInsetsState.peekSource(InsetsSource.ID_IME);
if (imeSource == null || mImeSourceControl == null) {
ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
@@ -458,7 +475,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
mAnimation.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled = false;
- @Nullable
+ @NonNull
private final ImeTracker.Token mStatsToken = statsToken;
@Override
@@ -484,7 +501,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
if (DEBUG_IME_VISIBILITY) {
EventLog.writeEvent(IMF_IME_REMOTE_ANIM_START,
- statsToken != null ? statsToken.getTag() : TOKEN_NONE,
+ mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
mDisplayId, mAnimationDirection, alpha, startY , endY,
Objects.toString(mImeSourceControl.getLeash()),
Objects.toString(mImeSourceControl.getInsetsHint()),
@@ -500,7 +517,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
mCancelled = true;
if (DEBUG_IME_VISIBILITY) {
EventLog.writeEvent(IMF_IME_REMOTE_ANIM_CANCEL,
- statsToken != null ? statsToken.getTag() : TOKEN_NONE, mDisplayId,
+ mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
+ mDisplayId,
Objects.toString(mImeSourceControl.getInsetsHint()));
}
}
@@ -528,7 +546,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
if (DEBUG_IME_VISIBILITY) {
EventLog.writeEvent(IMF_IME_REMOTE_ANIM_END,
- statsToken != null ? statsToken.getTag() : TOKEN_NONE,
+ mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
mDisplayId, mAnimationDirection, endY,
Objects.toString(mImeSourceControl.getLeash()),
Objects.toString(mImeSourceControl.getInsetsHint()),
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
index 9bdda14cf00b..ca06024a9adb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -277,8 +277,7 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
*
* @param types {@link InsetsType} to show
* @param fromIme true if this request originated from IME (InputMethodService).
- * @param statsToken the token tracking the current IME show request
- * or {@code null} otherwise.
+ * @param statsToken the token tracking the current IME request or {@code null} otherwise.
*/
default void showInsets(@InsetsType int types, boolean fromIme,
@Nullable ImeTracker.Token statsToken) {}
@@ -288,8 +287,7 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
*
* @param types {@link InsetsType} to hide
* @param fromIme true if this request originated from IME (InputMethodService).
- * @param statsToken the token tracking the current IME hide request
- * or {@code null} otherwise.
+ * @param statsToken the token tracking the current IME request or {@code null} otherwise.
*/
default void hideInsets(@InsetsType int types, boolean fromIme,
@Nullable ImeTracker.Token statsToken) {}
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 1959eb03a6b3..86cec02ab138 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
@@ -22,12 +22,7 @@ import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.os.Process.SYSTEM_UID;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
-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.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -40,11 +35,9 @@ import android.graphics.Rect;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.DisplayMetrics;
-import android.util.Size;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
-import android.view.Gravity;
import android.view.InsetsState;
import android.view.Surface;
import android.view.WindowInsets;
@@ -226,25 +219,22 @@ public class DisplayLayout {
/**
* Apply a rotation to this layout and its parameters.
- * @param res
- * @param targetRotation
*/
- public void rotateTo(Resources res, @Surface.Rotation int targetRotation) {
- final int rotationDelta = (targetRotation - mRotation + 4) % 4;
- final boolean changeOrient = (rotationDelta % 2) != 0;
-
+ public void rotateTo(Resources res, @Surface.Rotation int toRotation) {
final int origWidth = mWidth;
final int origHeight = mHeight;
+ final int fromRotation = mRotation;
+ final int rotationDelta = (toRotation - fromRotation + 4) % 4;
+ final boolean changeOrient = (rotationDelta % 2) != 0;
- mRotation = targetRotation;
+ mRotation = toRotation;
if (changeOrient) {
mWidth = origHeight;
mHeight = origWidth;
}
- if (mCutout != null && !mCutout.isEmpty()) {
- mCutout = calculateDisplayCutoutForRotation(mCutout, rotationDelta, origWidth,
- origHeight);
+ if (mCutout != null) {
+ mCutout = mCutout.getRotated(origWidth, origHeight, fromRotation, toRotation);
}
recalcInsets(res);
@@ -398,96 +388,6 @@ public class DisplayLayout {
}
}
- /** Calculate the DisplayCutout for a particular display size/rotation. */
- public static DisplayCutout calculateDisplayCutoutForRotation(
- DisplayCutout cutout, int rotation, int displayWidth, int displayHeight) {
- if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
- return null;
- }
- if (rotation == ROTATION_0) {
- return computeSafeInsets(cutout, displayWidth, displayHeight);
- }
- final Insets waterfallInsets = rotateInsets(cutout.getWaterfallInsets(), rotation);
- final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- Rect[] cutoutRects = cutout.getBoundingRectsAll();
- final Rect[] newBounds = new Rect[cutoutRects.length];
- final Rect displayBounds = new Rect(0, 0, displayWidth, displayHeight);
- for (int i = 0; i < cutoutRects.length; ++i) {
- final Rect rect = new Rect(cutoutRects[i]);
- if (!rect.isEmpty()) {
- rotateBounds(rect, displayBounds, rotation);
- }
- newBounds[getBoundIndexFromRotation(i, rotation)] = rect;
- }
- final DisplayCutout.CutoutPathParserInfo info = cutout.getCutoutPathParserInfo();
- final DisplayCutout.CutoutPathParserInfo newInfo = new DisplayCutout.CutoutPathParserInfo(
- info.getDisplayWidth(), info.getDisplayHeight(), info.getPhysicalDisplayWidth(),
- info.getPhysicalDisplayHeight(), info.getDensity(), info.getCutoutSpec(), rotation,
- info.getScale(), info.getPhysicalPixelDisplaySizeRatio());
- return computeSafeInsets(
- DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo),
- rotated ? displayHeight : displayWidth,
- rotated ? displayWidth : displayHeight);
- }
-
- private static int getBoundIndexFromRotation(int index, int rotation) {
- return (index - rotation) < 0
- ? index - rotation + DisplayCutout.BOUNDS_POSITION_LENGTH
- : index - rotation;
- }
-
- /** Calculate safe insets. */
- public static DisplayCutout computeSafeInsets(DisplayCutout inner,
- int displayWidth, int displayHeight) {
- if (inner == DisplayCutout.NO_CUTOUT) {
- return null;
- }
-
- final Size displaySize = new Size(displayWidth, displayHeight);
- final Rect safeInsets = computeSafeInsets(displaySize, inner);
- return inner.replaceSafeInsets(safeInsets);
- }
-
- private static Rect computeSafeInsets(
- Size displaySize, DisplayCutout cutout) {
- if (displaySize.getWidth() == displaySize.getHeight()) {
- throw new UnsupportedOperationException("not implemented: display=" + displaySize
- + " cutout=" + cutout);
- }
-
- int leftInset = Math.max(cutout.getWaterfallInsets().left,
- findCutoutInsetForSide(displaySize, cutout.getBoundingRectLeft(), Gravity.LEFT));
- int topInset = Math.max(cutout.getWaterfallInsets().top,
- findCutoutInsetForSide(displaySize, cutout.getBoundingRectTop(), Gravity.TOP));
- int rightInset = Math.max(cutout.getWaterfallInsets().right,
- findCutoutInsetForSide(displaySize, cutout.getBoundingRectRight(), Gravity.RIGHT));
- int bottomInset = Math.max(cutout.getWaterfallInsets().bottom,
- findCutoutInsetForSide(displaySize, cutout.getBoundingRectBottom(),
- Gravity.BOTTOM));
-
- return new Rect(leftInset, topInset, rightInset, bottomInset);
- }
-
- private static int findCutoutInsetForSide(Size display, Rect boundingRect, int gravity) {
- if (boundingRect.isEmpty()) {
- return 0;
- }
-
- int inset = 0;
- switch (gravity) {
- case Gravity.TOP:
- return Math.max(inset, boundingRect.bottom);
- case Gravity.BOTTOM:
- return Math.max(inset, display.getHeight() - boundingRect.top);
- case Gravity.LEFT:
- return Math.max(inset, boundingRect.right);
- case Gravity.RIGHT:
- return Math.max(inset, display.getWidth() - boundingRect.left);
- default:
- throw new IllegalArgumentException("unknown gravity: " + gravity);
- }
- }
-
static boolean hasNavigationBar(DisplayInfo info, Context context, int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
// Allow a system property to override this. Used by the emulator.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
new file mode 100644
index 000000000000..4c34971c4fb1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.LauncherApps
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import android.view.WindowManager
+import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI
+import com.android.internal.annotations.VisibleForTesting
+import com.android.wm.shell.R
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL
+import com.android.wm.shell.util.KtProtoLog
+import java.util.Arrays
+
+/**
+ * Helper for multi-instance related checks.
+ */
+class MultiInstanceHelper @JvmOverloads constructor(
+ private val context: Context,
+ private val packageManager: PackageManager,
+ private val staticAppsSupportingMultiInstance: Array<String> = context.resources
+ .getStringArray(R.array.config_appsSupportMultiInstancesSplit)) {
+
+ /**
+ * Returns whether a specific component desires to be launched in multiple instances.
+ */
+ @VisibleForTesting
+ fun supportsMultiInstanceSplit(componentName: ComponentName?): Boolean {
+ if (componentName == null || componentName.packageName == null) {
+ // TODO(b/262864589): Handle empty component case
+ return false
+ }
+
+ // Check the pre-defined allow list
+ val packageName = componentName.packageName
+ for (pkg in staticAppsSupportingMultiInstance) {
+ if (pkg == packageName) {
+ KtProtoLog.v(WM_SHELL, "application=%s in allowlist supports multi-instance",
+ packageName)
+ return true
+ }
+ }
+
+ // Check the activity property first
+ try {
+ val activityProp = packageManager.getProperty(
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName)
+ // If the above call doesn't throw a NameNotFoundException, then the activity property
+ // should override the application property value
+ if (activityProp.isBoolean) {
+ KtProtoLog.v(WM_SHELL, "activity=%s supports multi-instance", componentName)
+ return activityProp.boolean
+ } else {
+ KtProtoLog.w(WM_SHELL, "Warning: property=%s for activity=%s has non-bool type=%d",
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, activityProp.type)
+ }
+ } catch (nnfe: PackageManager.NameNotFoundException) {
+ // Not specified in the activity, fall through
+ }
+
+ // Check the application property otherwise
+ try {
+ val appProp = packageManager.getProperty(
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName)
+ if (appProp.isBoolean) {
+ KtProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName)
+ return appProp.boolean
+ } else {
+ KtProtoLog.w(WM_SHELL,
+ "Warning: property=%s for application=%s has non-bool type=%d",
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, appProp.type)
+ }
+ } catch (nnfe: PackageManager.NameNotFoundException) {
+ // Not specified in either application or activity
+ }
+ return false
+ }
+
+ companion object {
+ /** Returns the component from a PendingIntent */
+ @JvmStatic
+ fun getComponent(pendingIntent: PendingIntent?): ComponentName? {
+ return pendingIntent?.intent?.component
+ }
+
+ /** Returns the component from a shortcut */
+ @JvmStatic
+ fun getShortcutComponent(packageName: String, shortcutId: String,
+ user: UserHandle, launcherApps: LauncherApps): ComponentName? {
+ val query = LauncherApps.ShortcutQuery()
+ query.setPackage(packageName)
+ query.setShortcutIds(Arrays.asList(shortcutId))
+ query.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED)
+ val shortcuts = launcherApps.getShortcuts(query, user)
+ val info = if (shortcuts != null && shortcuts.size > 0) shortcuts[0] else null
+ return info?.activity
+ }
+
+ /** Returns true if package names and user ids match. */
+ @JvmStatic
+ fun samePackage(packageName1: String?, packageName2: String?,
+ userId1: Int, userId2: Int): Boolean {
+ return (packageName1 != null && packageName1 == packageName2) && (userId1 == userId2)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 1c74f415ec90..e4cf6d13cb1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -49,6 +49,7 @@ import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import android.view.inputmethod.ImeTracker;
import android.window.ClientWindowFrames;
+import android.window.InputTransferToken;
import com.android.internal.os.IResultReceiver;
@@ -196,7 +197,7 @@ public class SystemWindows {
/**
* Gets a token associated with the view that can be used to grant the view focus.
*/
- public IBinder getFocusGrantToken(View view) {
+ public InputTransferToken getFocusGrantToken(View view) {
SurfaceControlViewHost root = mViewRoots.get(view);
if (root == null) {
Slog.e(TAG, "Couldn't get focus grant token since view does not exist in "
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
index aac1d0626d30..11e477716eb0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
@@ -91,8 +91,9 @@ abstract class MagnetizedObject<T : Any>(
* to [onUnstuckFromTarget] or [onReleasedInTarget].
*
* @param target The target that the object is now stuck to.
+ * @param draggedObject The object that is stuck to the target.
*/
- fun onStuckToTarget(target: MagneticTarget)
+ fun onStuckToTarget(target: MagneticTarget, draggedObject: MagnetizedObject<*>)
/**
* Called when the object is no longer stuck to a target. This means that either touch
@@ -110,6 +111,7 @@ abstract class MagnetizedObject<T : Any>(
* and [maybeConsumeMotionEvent] is now returning false.
*
* @param target The target that this object was just unstuck from.
+ * @param draggedObject The object being unstuck from the target.
* @param velX The X velocity of the touch gesture when it exited the magnetic field.
* @param velY The Y velocity of the touch gesture when it exited the magnetic field.
* @param wasFlungOut Whether the object was unstuck via a fling gesture. This means that
@@ -119,6 +121,7 @@ abstract class MagnetizedObject<T : Any>(
*/
fun onUnstuckFromTarget(
target: MagneticTarget,
+ draggedObject: MagnetizedObject<*>,
velX: Float,
velY: Float,
wasFlungOut: Boolean
@@ -129,8 +132,9 @@ abstract class MagnetizedObject<T : Any>(
* velocity to reach it.
*
* @param target The target that the object was released in.
+ * @param draggedObject The object released in the target.
*/
- fun onReleasedInTarget(target: MagneticTarget)
+ fun onReleasedInTarget(target: MagneticTarget, draggedObject: MagnetizedObject<*>)
}
private val animator: PhysicsAnimator<T> = PhysicsAnimator.getInstance(underlyingObject)
@@ -352,8 +356,8 @@ abstract class MagnetizedObject<T : Any>(
val targetObjectIsInMagneticFieldOf = associatedTargets.firstOrNull { target ->
val distanceFromTargetCenter = hypot(
- ev.rawX - target.centerOnScreen.x,
- ev.rawY - target.centerOnScreen.y)
+ ev.rawX - target.centerOnDisplayX(),
+ ev.rawY - target.centerOnDisplayY())
distanceFromTargetCenter < target.magneticFieldRadiusPx
}
@@ -386,7 +390,7 @@ abstract class MagnetizedObject<T : Any>(
// animate sticking to the magnet.
targetObjectIsStuckTo = targetObjectIsInMagneticFieldOf
cancelAnimations()
- magnetListener.onStuckToTarget(targetObjectIsInMagneticFieldOf!!)
+ magnetListener.onStuckToTarget(targetObjectIsInMagneticFieldOf!!, this)
animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false, null)
vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
@@ -397,7 +401,8 @@ abstract class MagnetizedObject<T : Any>(
// move the object out of the target using its own movement logic.
cancelAnimations()
magnetListener.onUnstuckFromTarget(
- targetObjectIsStuckTo!!, velocityTracker.xVelocity, velocityTracker.yVelocity,
+ targetObjectIsStuckTo!!, this,
+ velocityTracker.xVelocity, velocityTracker.yVelocity,
wasFlungOut = false)
targetObjectIsStuckTo = null
@@ -406,7 +411,6 @@ abstract class MagnetizedObject<T : Any>(
// First, check for relevant gestures concluding with an ACTION_UP.
if (ev.action == MotionEvent.ACTION_UP) {
-
velocityTracker.computeCurrentVelocity(1000 /* units */)
val velX = velocityTracker.xVelocity
val velY = velocityTracker.yVelocity
@@ -421,10 +425,11 @@ abstract class MagnetizedObject<T : Any>(
// the upward direction, tell the listener so the object can be animated out of
// the target.
magnetListener.onUnstuckFromTarget(
- targetObjectIsStuckTo!!, velX, velY, wasFlungOut = true)
+ targetObjectIsStuckTo!!, this,
+ velX, velY, wasFlungOut = true)
} else {
// If the object is stuck and not flung away, it was released inside the target.
- magnetListener.onReleasedInTarget(targetObjectIsStuckTo!!)
+ magnetListener.onReleasedInTarget(targetObjectIsStuckTo!!, this)
vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
}
@@ -441,11 +446,11 @@ abstract class MagnetizedObject<T : Any>(
if (flungToTarget != null) {
// If this is a fling-to-target, animate the object to the magnet and then release
// it.
- magnetListener.onStuckToTarget(flungToTarget)
+ magnetListener.onStuckToTarget(flungToTarget, this)
targetObjectIsStuckTo = flungToTarget
animateStuckToTarget(flungToTarget, velX, velY, true) {
- magnetListener.onReleasedInTarget(flungToTarget)
+ magnetListener.onReleasedInTarget(flungToTarget, this)
targetObjectIsStuckTo = null
vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK)
}
@@ -542,7 +547,7 @@ abstract class MagnetizedObject<T : Any>(
// Whether velocity is sufficient, depending on whether we're flinging into a target at the
// top or the bottom of the screen.
val velocitySufficient =
- if (rawY < target.centerOnScreen.y) velY > flingToTargetMinVelocity
+ if (rawY < target.centerOnDisplayY()) velY > flingToTargetMinVelocity
else velY < flingToTargetMinVelocity
if (!velocitySufficient) {
@@ -560,15 +565,15 @@ abstract class MagnetizedObject<T : Any>(
val yIntercept = rawY - slope * rawX
// ...calculate the x value when y = the target's y-coordinate.
- targetCenterXIntercept = (target.centerOnScreen.y - yIntercept) / slope
+ targetCenterXIntercept = (target.centerOnDisplayY() - yIntercept) / slope
}
// The width of the area we're looking for a fling towards.
val targetAreaWidth = target.targetView.width * flingToTargetWidthPercent
// Velocity was sufficient, so return true if the intercept is within the target area.
- return targetCenterXIntercept > target.centerOnScreen.x - targetAreaWidth / 2 &&
- targetCenterXIntercept < target.centerOnScreen.x + targetAreaWidth / 2
+ return targetCenterXIntercept > target.centerOnDisplayX() - targetAreaWidth / 2 &&
+ targetCenterXIntercept < target.centerOnDisplayX() + targetAreaWidth / 2
}
/** Cancel animations on this object's x/y properties. */
@@ -601,6 +606,22 @@ abstract class MagnetizedObject<T : Any>(
) {
val centerOnScreen = PointF()
+ /**
+ * Set screen vertical offset amount.
+ *
+ * Screen surface may be vertically shifted in some cases, for example when one-handed mode
+ * is enabled. [MagneticTarget] and [MagnetizedObject] set their location in screen
+ * coordinates (see [MagneticTarget.centerOnScreen] and
+ * [MagnetizedObject.getLocationOnScreen] respectively).
+ *
+ * When a [MagnetizedObject] is dragged, the touch location is determined by
+ * [MotionEvent.getRawX] and [MotionEvent.getRawY]. These work in display coordinates. When
+ * screen is shifted due to one-handed mode, display coordinates and screen coordinates do
+ * not match. To determine if a [MagnetizedObject] is dragged into a [MagneticTarget], view
+ * location on screen is translated to display coordinates using this offset value.
+ */
+ var screenVerticalOffset: Int = 0
+
private val tempLoc = IntArray(2)
fun updateLocationOnScreen() {
@@ -614,6 +635,23 @@ abstract class MagnetizedObject<T : Any>(
tempLoc[1] + targetView.height / 2f - targetView.translationY)
}
}
+
+ /**
+ * Get target center coordinate on x-axis on display. [centerOnScreen] has to be up to date
+ * by calling [updateLocationOnScreen] first.
+ */
+ fun centerOnDisplayX(): Float {
+ return centerOnScreen.x
+ }
+
+ /**
+ * Get target center coordinate on y-axis on display. [centerOnScreen] has to be up to date
+ * by calling [updateLocationOnScreen] first. Use [screenVerticalOffset] to update the
+ * screen offset compared to the display.
+ */
+ fun centerOnDisplayY(): Float {
+ return centerOnScreen.y + screenVerticalOffset
+ }
}
companion object {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl
index 3906599b7581..b5f25433f9aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import android.app.PictureInPictureParams;
import android.view.SurfaceControl;
@@ -22,7 +22,7 @@ import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
-import com.android.wm.shell.pip.IPipAnimationListener;
+import com.android.wm.shell.common.pip.IPipAnimationListener;
/**
* Interface that is exposed to remote callers to manipulate the Pip feature.
@@ -52,9 +52,10 @@ interface IPip {
* @param componentName ComponentName represents the Activity
* @param destinationBounds the destination bounds the PiP window lands into
* @param overlay an optional overlay to fade out after entering PiP
+ * @param appBounds the bounds used to set the buffer size of the optional content overlay
*/
oneway void stopSwipePipToHome(int taskId, in ComponentName componentName,
- in Rect destinationBounds, in SurfaceControl overlay) = 2;
+ in Rect destinationBounds, in SurfaceControl overlay, in Rect appBounds) = 2;
/**
* Notifies the swiping Activity to PiP onto home transition is aborted
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPipAnimationListener.aidl
index 062e3ba26356..b8d1966100a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPipAnimationListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPipAnimationListener.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
/**
* Listener interface that Launcher attaches to SystemUI to get Pip animation callbacks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
index a9f687fc9b2d..6ffeb97f50fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
@@ -125,11 +125,15 @@ public class PipBoundsAlgorithm {
public Rect getEntryDestinationBoundsIgnoringKeepClearAreas() {
final PipBoundsState.PipReentryState reentryState = mPipBoundsState.getReentryState();
- final Rect destinationBounds = reentryState != null
- ? getDefaultBounds(reentryState.getSnapFraction(), reentryState.getSize())
- : getDefaultBounds();
+ final Rect destinationBounds = getDefaultBounds();
+ if (reentryState != null) {
+ final Size scaledBounds = new Size(
+ Math.round(mPipBoundsState.getMaxSize().x * reentryState.getBoundsScale()),
+ Math.round(mPipBoundsState.getMaxSize().y * reentryState.getBoundsScale()));
+ destinationBounds.set(getDefaultBounds(reentryState.getSnapFraction(), scaledBounds));
+ }
- final boolean useCurrentSize = reentryState != null && reentryState.getSize() != null;
+ final boolean useCurrentSize = reentryState != null;
Rect aspectRatioBounds = transformBoundsToAspectRatioIfValid(destinationBounds,
mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */,
useCurrentSize);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index 8b6c7b663f82..b87c2f6ebad5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -140,7 +140,7 @@ public class PipBoundsState {
// spec takes the aspect ratio of the bounds into account, so both width and height
// scale by the same factor.
addPipExclusionBoundsChangeCallback((bounds) -> {
- mBoundsScale = Math.min((float) bounds.width() / mMaxSize.x, 1.0f);
+ updateBoundsScale();
});
}
@@ -152,6 +152,11 @@ public class PipBoundsState {
mSizeSpecSource.onConfigurationChanged();
}
+ /** Update the bounds scale percentage value. */
+ public void updateBoundsScale() {
+ mBoundsScale = Math.min((float) mBounds.width() / mMaxSize.x, 1.0f);
+ }
+
private void reloadResources() {
mStashOffset = mContext.getResources().getDimensionPixelSize(R.dimen.pip_stash_offset);
}
@@ -223,6 +228,14 @@ public class PipBoundsState {
mExpandedMovementBounds.set(bounds);
}
+ /** Updates the min and max sizes based on the size spec and aspect ratio. */
+ public void updateMinMaxSize(float aspectRatio) {
+ final Size minSize = mSizeSpecSource.getMinSize(aspectRatio);
+ mMinSize.set(minSize.getWidth(), minSize.getHeight());
+ final Size maxSize = mSizeSpecSource.getMaxSize(aspectRatio);
+ mMaxSize.set(maxSize.getWidth(), maxSize.getHeight());
+ }
+
/** Sets the max possible size for resize. */
public void setMaxSize(int width, int height) {
mMaxSize.set(width, height);
@@ -255,7 +268,7 @@ public class PipBoundsState {
mStashedState = stashedState;
try {
- ActivityTaskManager.getService().onPictureInPictureStateChanged(
+ ActivityTaskManager.getService().onPictureInPictureUiStateChanged(
new PictureInPictureUiState(stashedState != STASH_TYPE_NONE /* isStashed */)
);
} catch (RemoteException | IllegalStateException e) {
@@ -293,8 +306,8 @@ public class PipBoundsState {
}
/** Save the reentry state to restore to when re-entering PIP mode. */
- public void saveReentryState(Size size, float fraction) {
- mPipReentryState = new PipReentryState(size, fraction);
+ public void saveReentryState(float fraction) {
+ mPipReentryState = new PipReentryState(mBoundsScale, fraction);
}
/** Returns the saved reentry state. */
@@ -596,17 +609,16 @@ public class PipBoundsState {
public static final class PipReentryState {
private static final String TAG = PipReentryState.class.getSimpleName();
- private final @Nullable Size mSize;
private final float mSnapFraction;
+ private final float mBoundsScale;
- PipReentryState(@Nullable Size size, float snapFraction) {
- mSize = size;
+ PipReentryState(float boundsScale, float snapFraction) {
+ mBoundsScale = boundsScale;
mSnapFraction = snapFraction;
}
- @Nullable
- public Size getSize() {
- return mSize;
+ public float getBoundsScale() {
+ return mBoundsScale;
}
public float getSnapFraction() {
@@ -616,7 +628,7 @@ public class PipBoundsState {
void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
- pw.println(innerPrefix + "mSize=" + mSize);
+ pw.println(innerPrefix + "mBoundsScale=" + mBoundsScale);
pw.println(innerPrefix + "mSnapFraction=" + mSnapFraction);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
index 1b1ebc39b558..4cbb78f2dae2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,12 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.common.pip;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.graphics.Rect;
-import com.android.wm.shell.common.pip.PipBoundsState;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -50,9 +48,9 @@ public class PipDoubleTapHelper {
@Retention(RetentionPolicy.SOURCE)
@interface PipSizeSpec {}
- static final int SIZE_SPEC_DEFAULT = 0;
- static final int SIZE_SPEC_MAX = 1;
- static final int SIZE_SPEC_CUSTOM = 2;
+ public static final int SIZE_SPEC_DEFAULT = 0;
+ public static final int SIZE_SPEC_MAX = 1;
+ public static final int SIZE_SPEC_CUSTOM = 2;
/**
* Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from.
@@ -84,7 +82,7 @@ public class PipDoubleTapHelper {
* @return pip screen size to switch to
*/
@PipSizeSpec
- static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState,
+ public static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState,
@NonNull Rect userResizeBounds) {
// is pip screen at its maximum
boolean isScreenMax = mPipBoundsState.getBounds().width()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java
index 0775f5279e31..85353d307056 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
@@ -33,12 +33,13 @@ import android.view.SurfaceControl;
import android.view.WindowManager;
import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
import java.util.List;
/**
- * Interface to allow {@link com.android.wm.shell.pip.PipTaskOrganizer} to call into
- * PiP menu when certain events happen (task appear/vanish, PiP move, etc.)
+ * Interface to interact with PiP menu when certain events happen
+ * (task appear/vanish, PiP move, etc.).
*/
public interface PipMenuController {
@@ -52,15 +53,15 @@ public interface PipMenuController {
float ALPHA_NO_CHANGE = -1f;
/**
- * Called when
- * {@link PipTaskOrganizer#onTaskAppeared(RunningTaskInfo, SurfaceControl)}
+ * Called when out implementation of
+ * {@link ShellTaskOrganizer.TaskListener#onTaskAppeared(RunningTaskInfo, SurfaceControl)}
* is called.
*/
void attach(SurfaceControl leash);
/**
- * Called when
- * {@link PipTaskOrganizer#onTaskVanished(RunningTaskInfo)} is called.
+ * Called when our implementation of
+ * {@link ShellTaskOrganizer.TaskListener#onTaskVanished(RunningTaskInfo)} is called.
*/
void detach();
@@ -101,11 +102,6 @@ public interface PipMenuController {
default void updateMenuBounds(Rect destinationBounds) {}
/**
- * Update when the current focused task changes.
- */
- default void onFocusTaskChanged(RunningTaskInfo taskInfo) {}
-
- /**
* Returns a default LayoutParams for the PIP Menu.
* @param context the context.
* @param width the PIP stack width.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java
new file mode 100644
index 000000000000..317e48e19c13
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip;
+
+import static android.window.SystemPerformanceHinter.HINT_SF;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE;
+
+import android.window.SystemPerformanceHinter;
+import android.window.SystemPerformanceHinter.HighPerfSession;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.function.Consumer;
+
+/**
+ * Manages system performance hints for PiP CUJs and interactions.
+ */
+public class PipPerfHintController {
+ private static final String TAG = PipPerfHintController.class.getSimpleName();
+
+ // Delay until signal about a session cleanup is sent.
+ private static final int SESSION_TIMEOUT_DELAY = 20_000;
+
+ // Maximum number of possible high perf session.
+ private static final int SESSION_POOL_SIZE = 20;
+
+ private final SystemPerformanceHinter mSystemPerformanceHinter;
+ @NonNull
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
+ @NonNull
+ private final ShellExecutor mMainExecutor;
+
+
+ public PipPerfHintController(@NonNull PipDisplayLayoutState pipDisplayLayoutState,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @NonNull SystemPerformanceHinter systemPerformanceHinter) {
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+ mMainExecutor = mainExecutor;
+ mSystemPerformanceHinter = systemPerformanceHinter;
+ }
+
+ /**
+ * Starts a high perf session.
+ *
+ * @param timeoutCallback an optional callback to be executed upon session timeout.
+ * @return a wrapper around the session to allow for early closing; null if no free sessions
+ * left available in the pool.
+ */
+ @Nullable
+ public PipHighPerfSession startSession(@Nullable Consumer<PipHighPerfSession> timeoutCallback,
+ String reason) {
+ if (PipHighPerfSession.getActiveSessionsCount() == SESSION_POOL_SIZE) {
+ return null;
+ }
+
+ HighPerfSession highPerfSession = mSystemPerformanceHinter.startSession(HINT_SF,
+ mPipDisplayLayoutState.getDisplayId(), "pip-high-perf-session");
+ PipHighPerfSession pipHighPerfSession = new PipHighPerfSession(highPerfSession, reason);
+
+ if (timeoutCallback != null) {
+ mMainExecutor.executeDelayed(() -> {
+ if (PipHighPerfSession.hasClosedOrFinalized(pipHighPerfSession)) {
+ // If the session is either directly closed or GC collected before timeout
+ // was reached, do not send the timeout callback.
+ return;
+ }
+ // The session hasn't been closed yet, so do that now, along with any cleanup.
+ pipHighPerfSession.close();
+ ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE, "%s: high perf session %s timed out", TAG,
+ pipHighPerfSession.toString());
+ timeoutCallback.accept(pipHighPerfSession);
+ }, SESSION_TIMEOUT_DELAY);
+ }
+ ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE, "%s: high perf session %s is started",
+ TAG, pipHighPerfSession.toString());
+ return pipHighPerfSession;
+ }
+
+ /**
+ * Dumps the inner state.
+ */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "activeSessionCount="
+ + PipHighPerfSession.getActiveSessionsCount());
+ }
+
+ /**
+ * A wrapper around {@link HighPerfSession} to keep track of some extra metadata about
+ * the session's status.
+ */
+ public class PipHighPerfSession implements AutoCloseable{
+
+ // THe actual HighPerfSession we wrap around.
+ private final HighPerfSession mSession;
+
+ private final String mReason;
+
+ /**
+ * Keeps track of all active sessions using weakly referenced keys.
+ * This makes sure that that sessions do not get accidentally leaked if not closed.
+ */
+ private static Map<PipHighPerfSession, Boolean> sActiveSessions = new WeakHashMap<>();
+
+ private PipHighPerfSession(HighPerfSession session, String reason) {
+ mSession = session;
+ mReason = reason;
+ sActiveSessions.put(this, true);
+ }
+
+ /**
+ * Closes a high perf session.
+ */
+ @Override
+ public void close() {
+ sActiveSessions.remove(this);
+ mSession.close();
+ ProtoLog.d(WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: high perf session %s is closed",
+ TAG, toString());
+ }
+
+ @Override
+ public void finalize() {
+ // The entry should be removed from the weak hash map as well by default.
+ mSession.close();
+ }
+
+ @Override
+ public String toString() {
+ return "[" + super.toString() + "] initially started due to: " + mReason;
+ }
+
+ private static boolean hasClosedOrFinalized(PipHighPerfSession pipHighPerfSession) {
+ return !sActiveSessions.containsKey(pipHighPerfSession);
+ }
+
+ private static int getActiveSessionsCount() {
+ return sActiveSessions.size();
+ }
+ }
+}
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 f801b0d01084..a87116ea4670 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
@@ -75,7 +75,6 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
private SurfaceControlViewHost mViewHost;
private DividerHandleView mHandle;
private DividerRoundedCorner mCorners;
- private View mBackground;
private int mTouchElevation;
private VelocityTracker mVelocityTracker;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
index 7237d2bde39f..37ccd15587e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
@@ -1,2 +1,4 @@
# WM shell sub-modules splitscreen owner
chenghsiuchang@google.com
+jeremysim@google.com
+peanutbutter@google.com
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
index 2b1037711249..dae62ac74483 100644
--- 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
@@ -138,8 +138,10 @@ public class SplitDecorManager extends WindowlessWindowManager {
mViewHost.setView(rootLayout, lp);
}
- /** Releases the surfaces for split decor. */
- public void release(SurfaceControl.Transaction t) {
+ /**
+ * Cancels any currently running animations.
+ */
+ public void cancelRunningAnimations() {
if (mFadeAnimator != null) {
if (mFadeAnimator.isRunning()) {
mFadeAnimator.cancel();
@@ -152,6 +154,11 @@ public class SplitDecorManager extends WindowlessWindowManager {
}
mScreenshotAnimator = null;
}
+ }
+
+ /** Releases the surfaces for split decor. */
+ public void release(SurfaceControl.Transaction t) {
+ cancelRunningAnimations();
if (mViewHost != null) {
mViewHost.release();
mViewHost = null;
@@ -277,7 +284,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
}
@Override
- public void onAnimationEnd(@androidx.annotation.NonNull Animator animation) {
+ public void onAnimationEnd(@NonNull Animator animation) {
mRunningAnimationCount--;
animT.remove(mScreenshot);
animT.apply();
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 53caddb52f23..6b2d544c192a 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
@@ -510,16 +510,18 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
}
- /** Updates divide position and split bounds base on the ratio within root bounds. */
+ /**
+ * Updates divide position and split bounds base on the ratio within root bounds. Falls back
+ * to middle position if the provided SnapTarget is not supported.
+ */
public void setDivideRatio(@PersistentSnapPosition int snapPosition) {
final DividerSnapAlgorithm.SnapTarget snapTarget = mDividerSnapAlgorithm.findSnapTarget(
snapPosition);
- if (snapTarget == null) {
- throw new IllegalArgumentException("No SnapTarget for position " + snapPosition);
- }
-
- setDividePosition(snapTarget.position, false /* applyLayoutChange */);
+ setDividePosition(snapTarget != null
+ ? snapTarget.position
+ : mDividerSnapAlgorithm.getMiddleTarget().position,
+ false /* applyLayoutChange */);
}
/** Resets divider position. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index 49db8d9c54a6..e8c809e5db4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -20,10 +20,11 @@ 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_UNDEFINED;
-import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import android.annotation.IntDef;
+import com.android.wm.shell.shared.TransitionUtil;
+
/** Helper utility class of methods and constants that are available to be imported in Launcher. */
public class SplitScreenConstants {
/** Duration used for every split fade-in or fade-out. */
@@ -126,7 +127,7 @@ public class SplitScreenConstants {
WINDOWING_MODE_FREEFORM};
/** 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;
+ public static final int FLAG_IS_DIVIDER_BAR = TransitionUtil.FLAG_IS_DIVIDER_BAR;
public static final String splitPositionToString(@SplitPosition int pos) {
switch (pos) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index 0693543515b4..f9259e79472e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.common.split;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
@@ -24,19 +24,27 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.PendingIntent;
-import android.content.Context;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.util.ArrayUtils;
import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
+import java.util.Arrays;
+import java.util.List;
+
/** Helper utility class for split screen components to use. */
public class SplitScreenUtils {
/** Reverse the split position. */
@@ -91,12 +99,6 @@ public class SplitScreenUtils {
return taskInfo != null ? taskInfo.userId : -1;
}
- /** Returns true if package names and user ids match. */
- public static boolean samePackage(String packageName1, String packageName2,
- int userId1, int userId2) {
- return (packageName1 != null && packageName1.equals(packageName2)) && (userId1 == userId2);
- }
-
/** Generates a common log message for split screen failures */
public static String splitFailureMessage(String caller, String reason) {
return "(" + caller + ") Splitscreen aborted: " + reason;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index 09d99b204bdb..fa2e23647a39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -71,6 +71,8 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
private static final String HAS_SEEN_VERTICAL_REACHABILITY_EDUCATION_KEY_PREFIX =
"has_seen_vertical_reachability_education";
+ private static final int MAX_PERCENTAGE_VAL = 100;
+
/**
* The {@link SharedPreferences} instance for the restart dialog and the reachability
* education.
@@ -82,6 +84,12 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
*/
private final SharedPreferences mLetterboxEduSharedPreferences;
+ /**
+ * The minimum tolerance of the percentage of activity bounds within its task to hide
+ * size compat restart button.
+ */
+ private final int mHideSizeCompatRestartButtonTolerance;
+
// Whether the extended restart dialog is enabled
private boolean mIsRestartDialogEnabled;
@@ -106,6 +114,9 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
R.bool.config_letterboxIsRestartDialogEnabled);
mIsReachabilityEducationEnabled = context.getResources().getBoolean(
R.bool.config_letterboxIsReachabilityEducationEnabled);
+ final int tolerance = context.getResources().getInteger(
+ R.integer.config_letterboxRestartButtonHideTolerance);
+ mHideSizeCompatRestartButtonTolerance = getHideSizeCompatRestartButtonTolerance(tolerance);
mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG,
DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG);
@@ -179,6 +190,10 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
|| !hasSeenVerticalReachabilityEducation(taskInfo));
}
+ int getHideSizeCompatRestartButtonTolerance() {
+ return mHideSizeCompatRestartButtonTolerance;
+ }
+
boolean getHasSeenLetterboxEducation(int userId) {
return mLetterboxEduSharedPreferences
.getBoolean(dontShowLetterboxEduKey(userId), /* default= */ false);
@@ -218,6 +233,15 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
}
}
+ // Returns the minimum tolerance of the percentage of activity bounds within its task to hide
+ // size compat restart button. Value lower than 0 or higher than 100 will be ignored.
+ // 100 is the default value where the activity has to fit exactly within the task to allow
+ // size compat restart button to be hidden. 0 means size compat restart button will always
+ // be hidden.
+ private int getHideSizeCompatRestartButtonTolerance(int tolerance) {
+ return tolerance < 0 || tolerance > MAX_PERCENTAGE_VAL ? MAX_PERCENTAGE_VAL : tolerance;
+ }
+
private boolean isReachabilityEducationEnabled() {
return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
&& mIsLetterboxReachabilityEducationAllowed);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 00e0cdb034b6..dbf7186def8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -22,7 +22,9 @@ import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPL
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppCompatTaskInfo;
import android.app.AppCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.content.Context;
@@ -33,6 +35,7 @@ import android.view.LayoutInflater;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
@@ -68,6 +71,8 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
@VisibleForTesting
CompatUILayout mLayout;
+ private final float mHideScmTolerance;
+
CompatUIWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, CompatUICallback callback,
ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
@@ -80,6 +85,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
mCompatUIHintsState = compatUIHintsState;
mCompatUIConfiguration = compatUIConfiguration;
mOnRestartButtonClicked = onRestartButtonClicked;
+ mHideScmTolerance = mCompatUIConfiguration.getHideSizeCompatRestartButtonTolerance();
}
@Override
@@ -99,7 +105,8 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
@Override
protected boolean eligibleToShowLayout() {
- return mHasSizeCompat || shouldShowCameraControl();
+ return (mHasSizeCompat && shouldShowSizeCompatRestartButton(getLastTaskInfo()))
+ || shouldShowCameraControl();
}
@Override
@@ -208,6 +215,30 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
updateSurfacePosition(positionX, positionY);
}
+ @VisibleForTesting
+ boolean shouldShowSizeCompatRestartButton(@NonNull TaskInfo taskInfo) {
+ if (!Flags.allowHideScmButton()) {
+ return true;
+ }
+ final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
+ final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ final int letterboxArea = computeArea(appCompatTaskInfo.topActivityLetterboxWidth,
+ appCompatTaskInfo.topActivityLetterboxHeight);
+ final int taskArea = computeArea(taskBounds.width(), taskBounds.height());
+ if (letterboxArea == 0 || taskArea == 0) {
+ return false;
+ }
+ final float percentageAreaOfLetterboxInTask = (float) letterboxArea / taskArea * 100;
+ return percentageAreaOfLetterboxInTask < mHideScmTolerance;
+ }
+
+ private int computeArea(int width, int height) {
+ if (width == 0 || height == 0) {
+ return 0;
+ }
+ return width * height;
+ }
+
private void updateVisibilityOfViews() {
if (mLayout == null) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index 180498c50c78..0564c95aef5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -332,7 +332,7 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
updateSurfacePosition();
}
- @Nullable
+ @NonNull
protected TaskInfo getLastTaskInfo() {
return mTaskInfo;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
index afd3b14e8b1d..7c280994042b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -20,6 +20,7 @@ import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.content.Context;
import android.content.Intent;
@@ -88,7 +89,8 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract
mShellExecutor = shellExecutor;
mUserAspectRatioButtonShownChecker = userAspectRatioButtonStateChecker;
mUserAspectRatioButtonStateConsumer = userAspectRatioButtonShownConsumer;
- mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
+ mHasUserAspectRatioSettingsButton = shouldShowUserAspectRatioSettingsButton(
+ taskInfo.appCompatTaskInfo, taskInfo.baseIntent);
mCompatUIHintsState = compatUIHintsState;
mOnButtonClicked = onButtonClicked;
mDisappearTimeSupplier = disappearTimeSupplier;
@@ -134,7 +136,8 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract
public boolean updateCompatInfo(@NonNull TaskInfo taskInfo,
@NonNull ShellTaskOrganizer.TaskListener taskListener, boolean canShow) {
final boolean prevHasUserAspectRatioSettingsButton = mHasUserAspectRatioSettingsButton;
- mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
+ mHasUserAspectRatioSettingsButton = shouldShowUserAspectRatioSettingsButton(
+ taskInfo.appCompatTaskInfo, taskInfo.baseIntent);
if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
return false;
@@ -227,11 +230,21 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract
return SystemClock.uptimeMillis() + hideDelay;
}
- private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) {
- final Intent intent = taskInfo.baseIntent;
- return taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton
- && (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed
- || taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled)
+ private boolean shouldShowUserAspectRatioSettingsButton(@NonNull AppCompatTaskInfo taskInfo,
+ @NonNull Intent intent) {
+ final Rect stableBounds = getTaskStableBounds();
+ final int letterboxHeight = taskInfo.topActivityLetterboxHeight;
+ final int letterboxWidth = taskInfo.topActivityLetterboxWidth;
+ // App is not visibly letterboxed if it covers status bar/bottom insets or matches the
+ // stable bounds, so don't show the button
+ if (stableBounds.height() <= letterboxHeight && stableBounds.width() <= letterboxWidth) {
+ return false;
+ }
+
+ return taskInfo.topActivityEligibleForUserAspectRatioButton
+ && (taskInfo.topActivityBoundsLetterboxed
+ || taskInfo.isUserFullscreenOverrideEnabled)
+ && !taskInfo.isSystemFullscreenOverrideEnabled
&& Intent.ACTION_MAIN.equals(intent.getAction())
&& intent.hasCategory(Intent.CATEGORY_LAUNCHER)
&& (!mUserAspectRatioButtonShownChecker.get() || isShowingButton());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index b52a118c7f1e..216da070754b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -26,6 +26,7 @@ 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.LaunchAdjacentController;
+import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
@@ -83,19 +84,19 @@ public class TvWMShellModule {
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropController,
Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
+ MultiInstanceHelper multiInstanceHelper,
@ShellMainThread ShellExecutor mainExecutor,
Handler mainHandler,
SystemWindows systemWindows) {
return new TvSplitScreenController(context, shellInit, shellCommandHandler, shellController,
shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController,
- displayImeController, displayInsetsController, dragAndDropController, transitions,
- transactionPool, iconProvider, recentTasks, launchAdjacentController, mainExecutor,
- mainHandler, systemWindows);
+ displayImeController, displayInsetsController, transitions, transactionPool,
+ iconProvider, recentTasks, launchAdjacentController, multiInstanceHelper,
+ mainExecutor, mainHandler, systemWindows);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 3c6bc1754c5c..8b2ec0a35685 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -50,6 +50,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.LaunchAdjacentController;
+import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
@@ -66,6 +67,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
@@ -78,7 +80,6 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
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.FreeformComponents;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
@@ -203,20 +204,6 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static Optional<DragAndDropController> provideDragAndDropController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellCommandHandler shellCommandHandler,
- DisplayController displayController,
- UiEventLogger uiEventLogger,
- IconProvider iconProvider,
- @ShellMainThread ShellExecutor mainExecutor) {
- return Optional.ofNullable(DragAndDropController.create(context, shellInit, shellController,
- shellCommandHandler, displayController, uiEventLogger, iconProvider, mainExecutor));
- }
-
- @WMSingleton
- @Provides
static ShellTaskOrganizer provideShellTaskOrganizer(
Context context,
ShellInit shellInit,
@@ -336,6 +323,12 @@ public abstract class WMShellBaseModule {
return Optional.of(perfHintController.getHinter());
}
+ @WMSingleton
+ @Provides
+ static MultiInstanceHelper provideMultiInstanceHelper(Context context) {
+ return new MultiInstanceHelper(context, context.getPackageManager());
+ }
+
//
// Back animation
//
@@ -363,7 +356,8 @@ public abstract class WMShellBaseModule {
@ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread Handler backgroundHandler,
BackAnimationBackground backAnimationBackground,
- Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry) {
+ Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry,
+ ShellCommandHandler shellCommandHandler) {
if (BackAnimationController.IS_ENABLED) {
return shellBackAnimationRegistry.map(
(animations) ->
@@ -374,7 +368,8 @@ public abstract class WMShellBaseModule {
backgroundHandler,
context,
backAnimationBackground,
- animations));
+ animations,
+ shellCommandHandler));
}
return Optional.empty();
}
@@ -409,6 +404,20 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
+ static Optional<PipPerfHintController> providePipPerfHintController(
+ PipDisplayLayoutState pipDisplayLayoutState,
+ @ShellMainThread ShellExecutor mainExecutor,
+ Optional<SystemPerformanceHinter> systemPerformanceHinterOptional) {
+ if (systemPerformanceHinterOptional.isPresent()) {
+ return Optional.of(new PipPerfHintController(pipDisplayLayoutState, mainExecutor,
+ systemPerformanceHinterOptional.get()));
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ @WMSingleton
+ @Provides
static PipBoundsState providePipBoundsState(Context context,
SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
@@ -909,7 +918,6 @@ public abstract class WMShellBaseModule {
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropControllerOptional,
ShellTaskOrganizer shellTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 71bf487249fb..fb3c35b6a1e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -47,6 +47,7 @@ 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.LaunchAdjacentController;
+import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
@@ -64,6 +65,7 @@ import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.draganddrop.GlobalDragListener;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
@@ -172,7 +174,7 @@ public abstract class WMShellModule {
BubblePositioner positioner,
DisplayController displayController,
@DynamicOverride Optional<OneHandedController> oneHandedOptional,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
@@ -199,9 +201,11 @@ public abstract class WMShellModule {
@Provides
static WindowDecorViewModel provideWindowDecorViewModel(
Context context,
+ @ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellMainThread Choreographer mainChoreographer,
ShellInit shellInit,
+ IWindowManager windowManager,
ShellCommandHandler shellCommandHandler,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
@@ -210,15 +214,16 @@ public abstract class WMShellModule {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RecentsTransitionHandler recentsTransitionHandler,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
if (DesktopModeStatus.isEnabled()) {
return new DesktopModeWindowDecorViewModel(
context,
+ mainExecutor,
mainHandler,
mainChoreographer,
shellInit,
shellCommandHandler,
+ windowManager,
taskOrganizer,
displayController,
shellController,
@@ -226,7 +231,6 @@ public abstract class WMShellModule {
syncQueue,
transitions,
desktopTasksController,
- recentsTransitionHandler,
rootTaskDisplayAreaOrganizer);
}
return new CaptionWindowDecorViewModel(
@@ -235,7 +239,8 @@ public abstract class WMShellModule {
mainChoreographer,
taskOrganizer,
displayController,
- syncQueue);
+ syncQueue,
+ transitions);
}
//
@@ -339,7 +344,7 @@ public abstract class WMShellModule {
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider,
@@ -347,12 +352,14 @@ public abstract class WMShellModule {
LaunchAdjacentController launchAdjacentController,
Optional<WindowDecorViewModel> windowDecorViewModel,
Optional<DesktopTasksController> desktopTasksController,
+ MultiInstanceHelper multiInstanceHelper,
@ShellMainThread ShellExecutor mainExecutor) {
return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
displayImeController, displayInsetsController, dragAndDropController, transitions,
transactionPool, iconProvider, recentTasks, launchAdjacentController,
- windowDecorViewModel, desktopTasksController, mainExecutor);
+ windowDecorViewModel, desktopTasksController, null /* stageCoordinator */,
+ multiInstanceHelper, mainExecutor);
}
//
@@ -495,6 +502,7 @@ public abstract class WMShellModule {
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ DragAndDropController dragAndDropController,
Transitions transitions,
EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
@@ -503,14 +511,15 @@ public abstract class WMShellModule {
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
LaunchAdjacentController launchAdjacentController,
RecentsTransitionHandler recentsTransitionHandler,
+ MultiInstanceHelper multiInstanceHelper,
@ShellMainThread ShellExecutor mainExecutor
) {
return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
- transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler,
- toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler,
- desktopModeTaskRepository, launchAdjacentController, recentsTransitionHandler,
- mainExecutor);
+ dragAndDropController, transitions, enterDesktopTransitionHandler,
+ exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
+ dragToDesktopTransitionHandler, desktopModeTaskRepository, launchAdjacentController,
+ recentsTransitionHandler, multiInstanceHelper, mainExecutor);
}
@WMSingleton
@@ -554,6 +563,35 @@ public abstract class WMShellModule {
}
//
+ // Drag and drop
+ //
+
+ @WMSingleton
+ @Provides
+ static GlobalDragListener provideGlobalDragListener(
+ IWindowManager wmService,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new GlobalDragListener(wmService, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static DragAndDropController provideDragAndDropController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
+ ShellCommandHandler shellCommandHandler,
+ DisplayController displayController,
+ UiEventLogger uiEventLogger,
+ IconProvider iconProvider,
+ GlobalDragListener globalDragListener,
+ Transitions transitions,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
+ displayController, uiEventLogger, iconProvider, globalDragListener, transitions,
+ mainExecutor);
+ }
+
+ //
// Misc
//
@@ -563,6 +601,7 @@ public abstract class WMShellModule {
@ShellCreateTriggerOverride
@Provides
static Object provideIndependentShellComponentsToCreate(
+ DragAndDropController dragAndDropController,
DefaultMixedHandler defaultMixedHandler) {
return new Object();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index ba882c403aed..1e3d7fb06da2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -36,6 +36,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
@@ -124,12 +125,11 @@ public abstract class Pip1Module {
static PhonePipMenuController providesPipPhoneMenuController(Context context,
PipBoundsState pipBoundsState, PipMediaController pipMediaController,
SystemWindows systemWindows,
- Optional<SplitScreenController> splitScreenOptional,
PipUiEventLogger pipUiEventLogger,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler) {
return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
- systemWindows, splitScreenOptional, pipUiEventLogger, mainExecutor, mainHandler);
+ systemWindows, pipUiEventLogger, mainExecutor, mainHandler);
}
@WMSingleton
@@ -144,10 +144,12 @@ public abstract class Pip1Module {
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
- @ShellMainThread ShellExecutor mainExecutor) {
+ @ShellMainThread ShellExecutor mainExecutor,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
pipBoundsState, sizeSpecSource, pipTaskOrganizer, pipMotionHelper,
- floatingContentCoordinator, pipUiEventLogger, mainExecutor);
+ floatingContentCoordinator, pipUiEventLogger, mainExecutor,
+ pipPerfHintControllerOptional);
}
@WMSingleton
@@ -170,6 +172,7 @@ public abstract class Pip1Module {
PipTransitionController pipTransitionController,
PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenControllerOptional,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional,
DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
@@ -177,8 +180,8 @@ public abstract class Pip1Module {
syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState,
pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
- splitScreenControllerOptional, displayController, pipUiEventLogger,
- shellTaskOrganizer, mainExecutor);
+ splitScreenControllerOptional, pipPerfHintControllerOptional, displayController,
+ pipUiEventLogger, shellTaskOrganizer, mainExecutor);
}
@WMSingleton
@@ -210,10 +213,11 @@ public abstract class Pip1Module {
PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
PipTransitionController pipTransitionController,
- FloatingContentCoordinator floatingContentCoordinator) {
+ FloatingContentCoordinator floatingContentCoordinator,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
menuController, pipSnapAlgorithm, pipTransitionController,
- floatingContentCoordinator);
+ floatingContentCoordinator, pipPerfHintControllerOptional);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 3b48c67a5bbd..458ea05e620d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -18,18 +18,23 @@ package com.android.wm.shell.dagger.pip;
import android.annotation.NonNull;
import android.content.Context;
+import android.os.Handler;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip2.phone.PhonePipMenuController;
import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipScheduler;
import com.android.wm.shell.pip2.phone.PipTransition;
@@ -50,15 +55,16 @@ import java.util.Optional;
public abstract class Pip2Module {
@WMSingleton
@Provides
- static PipTransition providePipTransition(@NonNull ShellInit shellInit,
+ static PipTransition providePipTransition(Context context,
+ @NonNull ShellInit shellInit,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
Optional<PipController> pipController,
@NonNull PipScheduler pipScheduler) {
- return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
- pipBoundsAlgorithm, pipScheduler);
+ return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
+ pipBoundsState, null, pipBoundsAlgorithm, pipScheduler);
}
@WMSingleton
@@ -68,13 +74,18 @@ public abstract class Pip2Module {
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
- PipDisplayLayoutState pipDisplayLayoutState) {
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipScheduler pipScheduler,
+ @ShellMainThread ShellExecutor mainExecutor) {
if (!PipUtils.isPip2ExperimentEnabled()) {
return Optional.empty();
} else {
return Optional.ofNullable(PipController.create(
context, shellInit, shellController, displayController, displayInsetsController,
- pipDisplayLayoutState));
+ pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, pipScheduler,
+ mainExecutor));
}
}
@@ -85,4 +96,16 @@ public abstract class Pip2Module {
@ShellMainThread ShellExecutor mainExecutor) {
return new PipScheduler(context, pipBoundsState, mainExecutor);
}
+
+ @WMSingleton
+ @Provides
+ static PhonePipMenuController providePipPhoneMenuController(Context context,
+ PipBoundsState pipBoundsState, PipMediaController pipMediaController,
+ SystemWindows systemWindows,
+ PipUiEventLogger pipUiEventLogger,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler) {
+ return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
+ systemWindows, pipUiEventLogger, mainExecutor, mainHandler);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index 1947097c2f15..54c2aeab4976 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -34,6 +34,7 @@ import com.android.wm.shell.common.pip.LegacySizeSpecSource;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.dagger.WMShellBaseModule;
@@ -212,6 +213,7 @@ public abstract class TvPipModule {
PipParamsChangedForwarder pipParamsChangedForwarder,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreenController> splitScreenControllerOptional,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional,
DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
@@ -219,8 +221,8 @@ public abstract class TvPipModule {
syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState,
tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
pipSurfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
- splitScreenControllerOptional, displayController, pipUiEventLogger,
- shellTaskOrganizer, mainExecutor);
+ splitScreenControllerOptional, pipPerfHintControllerOptional, displayController,
+ pipUiEventLogger, shellTaskOrganizer, mainExecutor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index e732a0354806..1071d728a56d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -47,4 +47,10 @@ public interface DesktopMode {
default void addDesktopGestureExclusionRegionListener(Consumer<Region> listener,
Executor callbackExecutor) { }
+
+ /** Called when requested to go to desktop mode from the current focused app. */
+ void enterDesktop(int displayId);
+
+ /** Called when requested to go to fullscreen from the current focused desktop app. */
+ void moveFocusedTaskToFullscreen(int displayId);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
new file mode 100644
index 000000000000..95d47146e834
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import com.android.internal.util.FrameworkStatsLog
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.util.KtProtoLog
+
+/**
+ * Event logger for logging desktop mode session events
+ */
+class DesktopModeEventLogger {
+ /**
+ * Logs the enter of desktop mode having session id [sessionId] and the reason [enterReason] for
+ * entering desktop mode
+ */
+ fun logSessionEnter(sessionId: Int, enterReason: EnterReason) {
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging session enter, session: %s reason: %s",
+ sessionId, enterReason.name
+ )
+ FrameworkStatsLog.write(DESKTOP_MODE_ATOM_ID,
+ /* event */ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER,
+ /* enterReason */ enterReason.reason,
+ /* exitReason */ 0,
+ /* session_id */ sessionId)
+ }
+
+ /**
+ * Logs the exit of desktop mode having session id [sessionId] and the reason [exitReason] for
+ * exiting desktop mode
+ */
+ fun logSessionExit(sessionId: Int, exitReason: ExitReason) {
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging session exit, session: %s reason: %s",
+ sessionId, exitReason.name
+ )
+ FrameworkStatsLog.write(DESKTOP_MODE_ATOM_ID,
+ /* event */ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__EXIT,
+ /* enterReason */ 0,
+ /* exitReason */ exitReason.reason,
+ /* session_id */ sessionId)
+ }
+
+ /**
+ * Logs that the task with update [taskUpdate] was added in the desktop mode session having
+ * session id [sessionId]
+ */
+ fun logTaskAdded(sessionId: Int, taskUpdate: TaskUpdate) {
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging task added, session: %s taskId: %s",
+ sessionId, taskUpdate.instanceId
+ )
+ FrameworkStatsLog.write(DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
+ /* task_event */
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED,
+ /* instance_id */
+ taskUpdate.instanceId,
+ /* uid */
+ taskUpdate.uid,
+ /* task_height */
+ taskUpdate.taskHeight,
+ /* task_width */
+ taskUpdate.taskWidth,
+ /* task_x */
+ taskUpdate.taskX,
+ /* task_y */
+ taskUpdate.taskY,
+ /* session_id */
+ sessionId)
+ }
+
+ /**
+ * Logs that the task with update [taskUpdate] was removed in the desktop mode session having
+ * session id [sessionId]
+ */
+ fun logTaskRemoved(sessionId: Int, taskUpdate: TaskUpdate) {
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging task remove, session: %s taskId: %s",
+ sessionId, taskUpdate.instanceId
+ )
+ FrameworkStatsLog.write(DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
+ /* task_event */
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED,
+ /* instance_id */
+ taskUpdate.instanceId,
+ /* uid */
+ taskUpdate.uid,
+ /* task_height */
+ taskUpdate.taskHeight,
+ /* task_width */
+ taskUpdate.taskWidth,
+ /* task_x */
+ taskUpdate.taskX,
+ /* task_y */
+ taskUpdate.taskY,
+ /* session_id */
+ sessionId)
+ }
+
+ /**
+ * Logs that the task with update [taskUpdate] had it's info changed in the desktop mode session
+ * having session id [sessionId]
+ */
+ fun logTaskInfoChanged(sessionId: Int, taskUpdate: TaskUpdate) {
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging task info changed, session: %s taskId: %s",
+ sessionId, taskUpdate.instanceId
+ )
+ FrameworkStatsLog.write(DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
+ /* task_event */
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
+ /* instance_id */
+ taskUpdate.instanceId,
+ /* uid */
+ taskUpdate.uid,
+ /* task_height */
+ taskUpdate.taskHeight,
+ /* task_width */
+ taskUpdate.taskWidth,
+ /* task_x */
+ taskUpdate.taskX,
+ /* task_y */
+ taskUpdate.taskY,
+ /* session_id */
+ sessionId)
+ }
+
+ companion object {
+ data class TaskUpdate(
+ val instanceId: Int,
+ val uid: Int,
+ val taskHeight: Int = Int.MIN_VALUE,
+ val taskWidth: Int = Int.MIN_VALUE,
+ val taskX: Int = Int.MIN_VALUE,
+ val taskY: Int = Int.MIN_VALUE,
+ )
+
+ /**
+ * Enum EnterReason mapped to the EnterReason definition in
+ * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
+ */
+ enum class EnterReason(val reason: Int) {
+ UNKNOWN_ENTER(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__UNKNOWN_ENTER
+ ),
+ OVERVIEW(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__OVERVIEW
+ ),
+ APP_HANDLE_DRAG(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__APP_HANDLE_DRAG
+ ),
+ APP_HANDLE_MENU_BUTTON(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__APP_HANDLE_MENU_BUTTON
+ ),
+ APP_FREEFORM_INTENT(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__APP_FREEFORM_INTENT
+ ),
+ KEYBOARD_SHORTCUT_ENTER(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER
+ ),
+ SCREEN_ON(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__SCREEN_ON
+ );
+ }
+
+ /**
+ * Enum ExitReason mapped to the ExitReason definition in
+ * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
+ */
+ enum class ExitReason(val reason: Int) {
+ UNKNOWN_EXIT(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__UNKNOWN_EXIT
+ ),
+ DRAG_TO_EXIT(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__DRAG_TO_EXIT
+ ),
+ APP_HANDLE_MENU_BUTTON_EXIT(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__APP_HANDLE_MENU_BUTTON_EXIT
+ ),
+ KEYBOARD_SHORTCUT_EXIT(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__KEYBOARD_SHORTCUT_EXIT
+ ),
+ RETURN_HOME_OR_OVERVIEW(
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME
+ ),
+ TASK_FINISHED(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__TASK_FINISHED
+ ),
+ SCREEN_OFF(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__SCREEN_OFF
+ )
+ }
+
+ private const val DESKTOP_MODE_ATOM_ID = FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED
+ private const val DESKTOP_MODE_TASK_UPDATE_ATOM_ID =
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
new file mode 100644
index 000000000000..e1e41ee1e64d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.sysui.ShellCommandHandler
+import java.io.PrintWriter
+
+/**
+ * Handles the shell commands for the DesktopTasksController.
+ */
+class DesktopModeShellCommandHandler(private val controller: DesktopTasksController) :
+ ShellCommandHandler.ShellCommandActionHandler {
+
+ override fun onShellCommand(args: Array<String>, pw: PrintWriter): Boolean {
+ return when (args[0]) {
+ "moveToDesktop" -> {
+ if (!runMoveToDesktop(args, pw)) {
+ pw.println("Task not found. Please enter a valid taskId.")
+ false
+ } else {
+ true
+ }
+ }
+
+ else -> {
+ pw.println("Invalid command: ${args[0]}")
+ false
+ }
+ }
+ }
+
+ private fun runMoveToDesktop(args: Array<String>, pw: PrintWriter): Boolean {
+ if (args.size < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments")
+ return false
+ }
+
+ val taskId = try {
+ args[1].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: task id should be an integer")
+ return false
+ }
+
+ return controller.moveToDesktop(taskId, WindowContainerTransaction())
+ }
+
+ override fun printShellCommandHelp(pw: PrintWriter, prefix: String) {
+ pw.println("$prefix moveToDesktop <taskId> ")
+ pw.println("$prefix Move a task with given id to desktop mode.")
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index dc82fc1b35dd..22ba70860587 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -18,21 +18,13 @@ package com.android.wm.shell.desktopmode;
import android.os.SystemProperties;
-import com.android.wm.shell.Flags;
+import com.android.window.flags.Flags;
/**
* Constants for desktop mode feature
*/
public class DesktopModeStatus {
- private static final boolean ENABLE_DESKTOP_WINDOWING = Flags.enableDesktopWindowing();
-
- /**
- * Flag to indicate whether desktop mode proto is available on the device
- */
- private static final boolean IS_PROTO2_ENABLED = SystemProperties.getBoolean(
- "persist.wm.debug.desktop_mode_2", false);
-
/**
* Flag to indicate whether task resizing is veiled.
*/
@@ -55,16 +47,30 @@ public class DesktopModeStatus {
"persist.wm.debug.desktop_stashing", false);
/**
- * Return {@code true} is desktop windowing proto 2 is enabled
+ * Flag to indicate whether to apply shadows to windows in desktop mode.
+ */
+ private static final boolean USE_WINDOW_SHADOWS = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_use_window_shadows", true);
+
+ /**
+ * Flag to indicate whether to apply shadows to the focused window in desktop mode.
+ *
+ * Note: this flag is only relevant if USE_WINDOW_SHADOWS is false.
+ */
+ private static final boolean USE_WINDOW_SHADOWS_FOCUSED_WINDOW = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_use_window_shadows_focused_window", false);
+
+ /**
+ * Flag to indicate whether to apply shadows to windows in desktop mode.
+ */
+ private static final boolean USE_ROUNDED_CORNERS = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_use_rounded_corners", true);
+
+ /**
+ * Return {@code true} if desktop windowing is enabled
*/
public static boolean isEnabled() {
- // Check for aconfig flag first
- if (ENABLE_DESKTOP_WINDOWING) {
- return true;
- }
- // Fall back to sysprop flag
- // TODO(b/304778354): remove sysprop once desktop aconfig flag supports dynamic overriding
- return IS_PROTO2_ENABLED;
+ return Flags.enableDesktopWindowingMode();
}
/**
@@ -81,4 +87,21 @@ public class DesktopModeStatus {
public static boolean isStashingEnabled() {
return IS_STASHING_ENABLED;
}
+
+ /**
+ * Return whether to use window shadows.
+ *
+ * @param isFocusedWindow whether the window to apply shadows to is focused
+ */
+ public static boolean useWindowShadow(boolean isFocusedWindow) {
+ return USE_WINDOW_SHADOWS
+ || (USE_WINDOW_SHADOWS_FOCUSED_WINDOW && isFocusedWindow);
+ }
+
+ /**
+ * Return whether to use rounded corners for windows.
+ */
+ public static boolean useRoundedCorners() {
+ return USE_ROUNDED_CORNERS;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index c0fc02fadd4d..7c8fcbb16711 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -86,10 +86,10 @@ class DesktopModeTaskRepository {
) {
visibleTasksListeners[visibleTasksListener] = executor
displayData.keyIterator().forEach { displayId ->
- val visibleTasks = getVisibleTaskCount(displayId)
+ val visibleTasksCount = getVisibleTaskCount(displayId)
val stashed = isStashed(displayId)
executor.execute {
- visibleTasksListener.onVisibilityChanged(displayId, visibleTasks > 0)
+ visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTasksCount)
visibleTasksListener.onStashedChanged(displayId, stashed)
}
}
@@ -222,10 +222,8 @@ class DesktopModeTaskRepository {
val otherDisplays = displayData.keyIterator().asSequence().filter { it != displayId }
for (otherDisplayId in otherDisplays) {
if (displayData[otherDisplayId].visibleTasks.remove(taskId)) {
- // Task removed from other display, check if we should notify listeners
- if (displayData[otherDisplayId].visibleTasks.isEmpty()) {
- notifyVisibleTaskListeners(otherDisplayId, hasVisibleFreeformTasks = false)
- }
+ notifyVisibleTaskListeners(otherDisplayId,
+ displayData[otherDisplayId].visibleTasks.size)
}
}
}
@@ -248,15 +246,21 @@ class DesktopModeTaskRepository {
)
}
- // Check if count changed and if there was no tasks or this is the first task
- if (prevCount != newCount && (prevCount == 0 || newCount == 0)) {
- notifyVisibleTaskListeners(displayId, newCount > 0)
+ // Check if count changed
+ if (prevCount != newCount) {
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTaskRepo: visibleTaskCount has changed from %d to %d",
+ prevCount,
+ newCount
+ )
+ notifyVisibleTaskListeners(displayId, newCount)
}
}
- private fun notifyVisibleTaskListeners(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+ private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) {
visibleTasksListeners.forEach { (listener, executor) ->
- executor.execute { listener.onVisibilityChanged(displayId, hasVisibleFreeformTasks) }
+ executor.execute { listener.onTasksVisibilityChanged(displayId, visibleTasksCount) }
}
}
@@ -264,6 +268,11 @@ class DesktopModeTaskRepository {
* Get number of tasks that are marked as visible on given [displayId]
*/
fun getVisibleTaskCount(displayId: Int): Int {
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTaskRepo: visibleTaskCount= %d",
+ displayData[displayId]?.visibleTasks?.size ?: 0
+ )
return displayData[displayId]?.visibleTasks?.size ?: 0
}
@@ -292,6 +301,10 @@ class DesktopModeTaskRepository {
taskId
)
freeformTasksInZOrder.remove(taskId)
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTaskRepo: remaining freeform tasks: " + freeformTasksInZOrder.toDumpString()
+ )
}
/**
@@ -379,9 +392,9 @@ class DesktopModeTaskRepository {
*/
interface VisibleTasksListener {
/**
- * Called when the desktop starts or stops showing freeform tasks.
+ * Called when the desktop changes the number of visible freeform tasks.
*/
- fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {}
+ fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {}
/**
* Called when the desktop stashed status changes.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index a587bed3fef0..7091c4b7210a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -16,7 +16,11 @@
package com.android.wm.shell.desktopmode;
-import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -24,11 +28,13 @@ import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.app.ActivityManager;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.Region;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
@@ -37,9 +43,10 @@ import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import android.view.animation.DecelerateInterpolator;
+import androidx.annotation.VisibleForTesting;
+
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -48,99 +55,155 @@ import com.android.wm.shell.common.SyncTransactionQueue;
* Animated visual indicator for Desktop Mode windowing transitions.
*/
public class DesktopModeVisualIndicator {
- public static final int INVALID_INDICATOR = -1;
- /** Indicates impending transition into desktop mode */
- public static final int TO_DESKTOP_INDICATOR = 1;
- /** Indicates impending transition into fullscreen */
- public static final int TO_FULLSCREEN_INDICATOR = 2;
- /** Indicates impending transition into split select on the left side */
- public static final int TO_SPLIT_LEFT_INDICATOR = 3;
- /** Indicates impending transition into split select on the right side */
- public static final int TO_SPLIT_RIGHT_INDICATOR = 4;
+ public enum IndicatorType {
+ /** To be used when we don't want to indicate any transition */
+ NO_INDICATOR,
+ /** Indicates impending transition into desktop mode */
+ TO_DESKTOP_INDICATOR,
+ /** Indicates impending transition into fullscreen */
+ TO_FULLSCREEN_INDICATOR,
+ /** Indicates impending transition into split select on the left side */
+ TO_SPLIT_LEFT_INDICATOR,
+ /** Indicates impending transition into split select on the right side */
+ TO_SPLIT_RIGHT_INDICATOR
+ }
private final Context mContext;
private final DisplayController mDisplayController;
- private final ShellTaskOrganizer mTaskOrganizer;
private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
private final ActivityManager.RunningTaskInfo mTaskInfo;
private final SurfaceControl mTaskSurface;
- private final Rect mIndicatorRange = new Rect();
private SurfaceControl mLeash;
private final SyncTransactionQueue mSyncQueue;
private SurfaceControlViewHost mViewHost;
private View mView;
- private boolean mIsFullscreen;
- private int mType;
+ private IndicatorType mCurrentType;
public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
- Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer,
- RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer, int type) {
+ Context context, SurfaceControl taskSurface,
+ RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) {
mSyncQueue = syncQueue;
mTaskInfo = taskInfo;
mDisplayController = displayController;
mContext = context;
mTaskSurface = taskSurface;
- mTaskOrganizer = taskOrganizer;
mRootTdaOrganizer = taskDisplayAreaOrganizer;
- mType = type;
- defineIndicatorRange();
- createView();
+ mCurrentType = IndicatorType.NO_INDICATOR;
}
/**
- * If an indicator is warranted based on the input and task bounds, return the type of
- * indicator that should be created.
+ * Based on the coordinates of the current drag event, determine which indicator type we should
+ * display, including no visible indicator.
*/
- public static int determineIndicatorType(PointF inputCoordinates, Rect taskBounds,
- DisplayLayout layout, Context context) {
- int transitionAreaHeight = context.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
- int transitionAreaWidth = context.getResources().getDimensionPixelSize(
+ IndicatorType updateIndicatorType(PointF inputCoordinates, int windowingMode) {
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
+ IndicatorType result = IndicatorType.NO_INDICATOR;
+ final int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
- if (taskBounds.top <= transitionAreaHeight) return TO_FULLSCREEN_INDICATOR;
- if (inputCoordinates.x <= transitionAreaWidth) return TO_SPLIT_LEFT_INDICATOR;
- if (inputCoordinates.x >= layout.width() - transitionAreaWidth) {
- return TO_SPLIT_RIGHT_INDICATOR;
+ // Because drags in freeform use task position for indicator calculation, we need to
+ // account for the possibility of the task going off the top of the screen by captionHeight
+ final int captionHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_freeform_decor_caption_height);
+ final Region fullscreenRegion = calculateFullscreenRegion(layout, windowingMode,
+ captionHeight);
+ final Region splitLeftRegion = calculateSplitLeftRegion(layout, windowingMode,
+ transitionAreaWidth, captionHeight);
+ final Region splitRightRegion = calculateSplitRightRegion(layout, windowingMode,
+ transitionAreaWidth, captionHeight);
+ final Region toDesktopRegion = calculateToDesktopRegion(layout, windowingMode,
+ splitLeftRegion, splitRightRegion, fullscreenRegion);
+ if (fullscreenRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
+ result = IndicatorType.TO_FULLSCREEN_INDICATOR;
+ }
+ if (splitLeftRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
+ result = IndicatorType.TO_SPLIT_LEFT_INDICATOR;
}
- return INVALID_INDICATOR;
+ if (splitRightRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
+ result = IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
+ }
+ if (toDesktopRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
+ result = IndicatorType.TO_DESKTOP_INDICATOR;
+ }
+ transitionIndicator(result);
+ return result;
}
- /**
- * Determine range of inputs that will keep this indicator displaying.
- */
- private void defineIndicatorRange() {
- DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
- int captionHeight = mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.freeform_decor_caption_height);
- int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
+ @VisibleForTesting
+ Region calculateFullscreenRegion(DisplayLayout layout,
+ @WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
+ final Region region = new Region();
+ int edgeTransitionHeight = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
- int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
- switch (mType) {
- case TO_DESKTOP_INDICATOR:
- // TO_DESKTOP indicator is only dismissed on release; entire display is valid.
- mIndicatorRange.set(0, 0, layout.width(), layout.height());
- break;
- case TO_FULLSCREEN_INDICATOR:
- // If drag results in caption going above the top edge of the display, we still
- // want to transition to fullscreen.
- mIndicatorRange.set(0, -captionHeight, layout.width(), transitionAreaHeight);
- break;
- case TO_SPLIT_LEFT_INDICATOR:
- mIndicatorRange.set(0, transitionAreaHeight, transitionAreaWidth, layout.height());
- break;
- case TO_SPLIT_RIGHT_INDICATOR:
- mIndicatorRange.set(layout.width() - transitionAreaWidth, transitionAreaHeight,
- layout.width(), layout.height());
- break;
- default:
- break;
+ // A thin, short Rect at the top of the screen.
+ if (windowingMode == WINDOWING_MODE_FREEFORM) {
+ int fromFreeformWidth = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_width);
+ int fromFreeformHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
+ region.union(new Rect((layout.width() / 2) - (fromFreeformWidth / 2),
+ -captionHeight,
+ (layout.width() / 2) + (fromFreeformWidth / 2),
+ fromFreeformHeight));
+ }
+ // A screen-wide, shorter Rect if the task is in fullscreen or split.
+ if (windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+ region.union(new Rect(0,
+ -captionHeight,
+ layout.width(),
+ edgeTransitionHeight));
+ }
+ return region;
+ }
+
+ @VisibleForTesting
+ Region calculateToDesktopRegion(DisplayLayout layout,
+ @WindowConfiguration.WindowingMode int windowingMode,
+ Region splitLeftRegion, Region splitRightRegion,
+ Region toFullscreenRegion) {
+ final Region region = new Region();
+ // If in desktop, we need no region. Otherwise it's the same for all windowing modes.
+ if (windowingMode != WINDOWING_MODE_FREEFORM) {
+ region.union(new Rect(0, 0, layout.width(), layout.height()));
+ region.op(splitLeftRegion, Region.Op.DIFFERENCE);
+ region.op(splitRightRegion, Region.Op.DIFFERENCE);
+ region.op(toFullscreenRegion, Region.Op.DIFFERENCE);
}
+ return region;
}
+ @VisibleForTesting
+ Region calculateSplitLeftRegion(DisplayLayout layout,
+ @WindowConfiguration.WindowingMode int windowingMode,
+ int transitionEdgeWidth, int captionHeight) {
+ final Region region = new Region();
+ // In freeform, keep the top corners clear.
+ int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
+ ? mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) :
+ -captionHeight;
+ region.union(new Rect(0, transitionHeight, transitionEdgeWidth, layout.height()));
+ return region;
+ }
+
+ @VisibleForTesting
+ Region calculateSplitRightRegion(DisplayLayout layout,
+ @WindowConfiguration.WindowingMode int windowingMode,
+ int transitionEdgeWidth, int captionHeight) {
+ final Region region = new Region();
+ // In freeform, keep the top corners clear.
+ int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
+ ? mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) :
+ -captionHeight;
+ region.union(new Rect(layout.width() - transitionEdgeWidth, transitionHeight,
+ layout.width(), layout.height()));
+ return region;
+ }
/**
* Create a fullscreen indicator with no animation
@@ -155,34 +218,15 @@ public class DesktopModeVisualIndicator {
mView = new View(mContext);
final SurfaceControl.Builder builder = new SurfaceControl.Builder();
mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
- String description;
- switch (mType) {
- case TO_DESKTOP_INDICATOR:
- description = "Desktop indicator";
- break;
- case TO_FULLSCREEN_INDICATOR:
- description = "Fullscreen indicator";
- break;
- case TO_SPLIT_LEFT_INDICATOR:
- description = "Split Left indicator";
- break;
- case TO_SPLIT_RIGHT_INDICATOR:
- description = "Split Right indicator";
- break;
- default:
- description = "Invalid indicator";
- break;
- }
mLeash = builder
- .setName(description)
+ .setName("Desktop Mode Visual Indicator")
.setContainerLayer()
.build();
t.show(mLeash);
final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(screenWidth, screenHeight,
- WindowManager.LayoutParams.TYPE_APPLICATION,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
- lp.setTitle(description + " for Task=" + mTaskInfo.taskId);
+ new WindowManager.LayoutParams(screenWidth, screenHeight, TYPE_APPLICATION,
+ FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+ lp.setTitle("Desktop Mode Visual Indicator");
lp.setTrustedOverlay();
final WindowlessWindowManager windowManager = new WindowlessWindowManager(
mTaskInfo.configuration, mLeash,
@@ -201,46 +245,47 @@ public class DesktopModeVisualIndicator {
}
/**
- * Create an indicator. Animator fades it in while expanding the bounds outwards.
+ * Fade indicator in as provided type. Animator fades it in while expanding the bounds outwards.
*/
- public void createIndicatorWithAnimatedBounds() {
- mIsFullscreen = mType == TO_FULLSCREEN_INDICATOR;
+ private void fadeInIndicator(IndicatorType type) {
mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
final VisualIndicatorAnimator animator = VisualIndicatorAnimator
- .animateBounds(mView, mType,
+ .fadeBoundsIn(mView, type,
mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
+ mCurrentType = type;
}
/**
- * Takes existing fullscreen indicator and animates it to freeform bounds
+ * Fade out indicator without fully releasing it. Animator fades it out while shrinking bounds.
*/
- public void transitionFullscreenIndicatorToFreeform() {
- mIsFullscreen = false;
- mType = TO_DESKTOP_INDICATOR;
- final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFreeformAnimator(
- mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
- animator.start();
- }
-
- /**
- * Takes the existing freeform indicator and animates it to fullscreen
- */
- public void transitionFreeformIndicatorToFullscreen() {
- mIsFullscreen = true;
- mType = TO_FULLSCREEN_INDICATOR;
- final VisualIndicatorAnimator animator =
- VisualIndicatorAnimator.toFullscreenAnimatorWithAnimatedBounds(
- mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+ private void fadeOutIndicator() {
+ final VisualIndicatorAnimator animator = VisualIndicatorAnimator
+ .fadeBoundsOut(mView, mCurrentType,
+ mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
+ mCurrentType = IndicatorType.NO_INDICATOR;
}
/**
- * Determine if a MotionEvent is in the same range that enabled the indicator.
- * Used to dismiss the indicator when a transition will no longer result from releasing.
+ * Takes existing indicator and animates it to bounds reflecting a new indicator type.
*/
- public boolean eventOutsideRange(float x, float y) {
- return !mIndicatorRange.contains((int) x, (int) y);
+ private void transitionIndicator(IndicatorType newType) {
+ if (mCurrentType == newType) return;
+ if (mView == null) {
+ createView();
+ }
+ if (mCurrentType == IndicatorType.NO_INDICATOR) {
+ fadeInIndicator(newType);
+ } else if (newType == IndicatorType.NO_INDICATOR) {
+ fadeOutIndicator();
+ } else {
+ final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType(
+ mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType,
+ newType);
+ mCurrentType = newType;
+ animator.start();
+ }
}
/**
@@ -260,13 +305,6 @@ public class DesktopModeVisualIndicator {
}
/**
- * Returns true if visual indicator is fullscreen
- */
- public boolean isFullscreen() {
- return mIsFullscreen;
- }
-
- /**
* Animator for Desktop Mode transitions which supports bounds and alpha animation.
*/
private static class VisualIndicatorAnimator extends ValueAnimator {
@@ -274,6 +312,13 @@ public class DesktopModeVisualIndicator {
private static final float FULLSCREEN_SCALE_ADJUSTMENT_PERCENT = 0.015f;
private static final float INDICATOR_FINAL_OPACITY = 0.7f;
+ /** Determines how this animator will interact with the view's alpha:
+ * Fade in, fade out, or no change to alpha
+ */
+ private enum AlphaAnimType{
+ ALPHA_FADE_IN_ANIM, ALPHA_FADE_OUT_ANIM, ALPHA_NO_CHANGE_ANIM
+ }
+
private final View mView;
private final Rect mStartBounds;
private final Rect mEndBounds;
@@ -288,87 +333,92 @@ public class DesktopModeVisualIndicator {
mRectEvaluator = new RectEvaluator(new Rect());
}
- /**
- * Create animator for visual indicator of fullscreen transition
- *
- * @param view the view for this indicator
- * @param displayLayout information about the display the transitioning task is currently on
- */
- public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds(
- @NonNull View view, @NonNull DisplayLayout displayLayout) {
- final int padding = displayLayout.stableInsets().top;
- Rect startBounds = new Rect(padding, padding,
- displayLayout.width() - padding, displayLayout.height() - padding);
+ private static VisualIndicatorAnimator fadeBoundsIn(
+ @NonNull View view, IndicatorType type, @NonNull DisplayLayout displayLayout) {
+ final Rect startBounds = getIndicatorBounds(displayLayout, type);
view.getBackground().setBounds(startBounds);
final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
view, startBounds, getMaxBounds(startBounds));
animator.setInterpolator(new DecelerateInterpolator());
- setupIndicatorAnimation(animator);
+ setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_IN_ANIM);
return animator;
}
- public static VisualIndicatorAnimator animateBounds(
- @NonNull View view, int type, @NonNull DisplayLayout displayLayout) {
- final int padding = displayLayout.stableInsets().top;
- Rect startBounds = new Rect();
- switch (type) {
- case TO_FULLSCREEN_INDICATOR:
- startBounds.set(padding, padding,
- displayLayout.width() - padding,
- displayLayout.height() - padding);
- break;
- case TO_SPLIT_LEFT_INDICATOR:
- startBounds.set(padding, padding,
- displayLayout.width() / 2 - padding,
- displayLayout.height() - padding);
- break;
- case TO_SPLIT_RIGHT_INDICATOR:
- startBounds.set(displayLayout.width() / 2 + padding, padding,
- displayLayout.width() - padding,
- displayLayout.height() - padding);
- break;
- }
+ private static VisualIndicatorAnimator fadeBoundsOut(
+ @NonNull View view, IndicatorType type, @NonNull DisplayLayout displayLayout) {
+ final Rect endBounds = getIndicatorBounds(displayLayout, type);
+ final Rect startBounds = getMaxBounds(endBounds);
view.getBackground().setBounds(startBounds);
final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, startBounds, getMaxBounds(startBounds));
+ view, startBounds, endBounds);
animator.setInterpolator(new DecelerateInterpolator());
- setupIndicatorAnimation(animator);
+ setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_OUT_ANIM);
return animator;
}
/**
- * Create animator for visual indicator of freeform transition
+ * Create animator for visual indicator changing type (i.e., fullscreen to freeform,
+ * freeform to split, etc.)
*
* @param view the view for this indicator
* @param displayLayout information about the display the transitioning task is currently on
+ * @param origType the original indicator type
+ * @param newType the new indicator type
*/
- public static VisualIndicatorAnimator toFreeformAnimator(@NonNull View view,
- @NonNull DisplayLayout displayLayout) {
- final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE;
- final int width = displayLayout.width();
- final int height = displayLayout.height();
- Rect startBounds = new Rect(0, 0, width, height);
- Rect endBounds = new Rect((int) (adjustmentPercentage * width / 2),
- (int) (adjustmentPercentage * height / 2),
- (int) (displayLayout.width() - (adjustmentPercentage * width / 2)),
- (int) (displayLayout.height() - (adjustmentPercentage * height / 2)));
+ private static VisualIndicatorAnimator animateIndicatorType(@NonNull View view,
+ @NonNull DisplayLayout displayLayout, IndicatorType origType,
+ IndicatorType newType) {
+ final Rect startBounds = getIndicatorBounds(displayLayout, origType);
+ final Rect endBounds = getIndicatorBounds(displayLayout, newType);
final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
view, startBounds, endBounds);
animator.setInterpolator(new DecelerateInterpolator());
- setupIndicatorAnimation(animator);
+ setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_NO_CHANGE_ANIM);
return animator;
}
+ private static Rect getIndicatorBounds(DisplayLayout layout, IndicatorType type) {
+ final int padding = layout.stableInsets().top;
+ switch (type) {
+ case TO_FULLSCREEN_INDICATOR:
+ return new Rect(padding, padding,
+ layout.width() - padding,
+ layout.height() - padding);
+ case TO_DESKTOP_INDICATOR:
+ final float adjustmentPercentage = 1f
+ - DesktopTasksController.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
+ return new Rect((int) (adjustmentPercentage * layout.width() / 2),
+ (int) (adjustmentPercentage * layout.height() / 2),
+ (int) (layout.width() - (adjustmentPercentage * layout.width() / 2)),
+ (int) (layout.height() - (adjustmentPercentage * layout.height() / 2)));
+ case TO_SPLIT_LEFT_INDICATOR:
+ return new Rect(padding, padding,
+ layout.width() / 2 - padding,
+ layout.height() - padding);
+ case TO_SPLIT_RIGHT_INDICATOR:
+ return new Rect(layout.width() / 2 + padding, padding,
+ layout.width() - padding,
+ layout.height() - padding);
+ default:
+ throw new IllegalArgumentException("Invalid indicator type provided.");
+ }
+ }
+
/**
* Add necessary listener for animation of indicator
*/
- private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator) {
+ private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator,
+ AlphaAnimType animType) {
animator.addUpdateListener(a -> {
if (animator.mView != null) {
animator.updateBounds(a.getAnimatedFraction(), animator.mView);
- animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView);
+ if (animType == AlphaAnimType.ALPHA_FADE_IN_ANIM) {
+ animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView);
+ } else if (animType == AlphaAnimType.ALPHA_FADE_OUT_ANIM) {
+ animator.updateIndicatorAlpha(1 - a.getAnimatedFraction(), animator.mView);
+ }
} else {
animator.cancel();
}
@@ -394,7 +444,7 @@ public class DesktopModeVisualIndicator {
if (mStartBounds.equals(mEndBounds)) {
return;
}
- Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
+ final Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
view.getBackground().setBounds(currentBounds);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 144555dd70c3..d1328cabdc99 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -17,20 +17,24 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityOptions
+import android.app.PendingIntent
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
+import android.content.Intent
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.Region
import android.os.IBinder
import android.os.SystemProperties
-import android.util.DisplayMetrics.DENSITY_DEFAULT
+import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE
@@ -45,9 +49,12 @@ import com.android.internal.policy.ScreenDecorationsUtils
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.ExecutorUtils
import com.android.wm.shell.common.ExternalInterfaceBinder
import com.android.wm.shell.common.LaunchAdjacentController
+import com.android.wm.shell.common.MultiInstanceHelper
+import com.android.wm.shell.common.MultiInstanceHelper.Companion.getComponent
import com.android.wm.shell.common.RemoteCallable
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
@@ -55,15 +62,14 @@ import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.annotations.ExternalThread
import com.android.wm.shell.common.annotations.ShellMainThread
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
-import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
-import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.TO_DESKTOP_INDICATOR
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
+import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.splitscreen.SplitScreenController
-import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ENTER_DESKTOP
+import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
@@ -71,8 +77,8 @@ import com.android.wm.shell.sysui.ShellSharedConstants
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -87,6 +93,7 @@ class DesktopTasksController(
private val shellTaskOrganizer: ShellTaskOrganizer,
private val syncQueue: SyncTransactionQueue,
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val dragAndDropController: DragAndDropController,
private val transitions: Transitions,
private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
@@ -96,19 +103,24 @@ class DesktopTasksController(
private val desktopModeTaskRepository: DesktopModeTaskRepository,
private val launchAdjacentController: LaunchAdjacentController,
private val recentsTransitionHandler: RecentsTransitionHandler,
+ private val multiInstanceHelper: MultiInstanceHelper,
@ShellMainThread private val mainExecutor: ShellExecutor
-) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
+) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler,
+ DragAndDropController.DragAndDropListener {
private val desktopMode: DesktopModeImpl
private var visualIndicator: DesktopModeVisualIndicator? = null
+ private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler =
+ DesktopModeShellCommandHandler(this)
+
private val mOnAnimationFinishedCallback = Consumer<SurfaceControl.Transaction> {
t: SurfaceControl.Transaction ->
visualIndicator?.releaseVisualIndicator(t)
visualIndicator = null
}
private val taskVisibilityListener = object : VisibleTasksListener {
- override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
- launchAdjacentController.launchAdjacentEnabled = !hasVisibleFreeformTasks
+ override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
+ launchAdjacentController.launchAdjacentEnabled = visibleTasksCount == 0
}
}
private val dragToDesktopStateListener = object : DragToDesktopStateListener {
@@ -149,6 +161,8 @@ class DesktopTasksController(
private fun onInit() {
KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
shellCommandHandler.addDumpCallback(this::dump, this)
+ shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler,
+ this)
shellController.addExternalInterface(
ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
{ createExternalInterface() },
@@ -169,6 +183,13 @@ class DesktopTasksController(
}
}
)
+ dragAndDropController.addListener(this)
+ }
+
+ fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
+ toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
+ enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
+ dragToDesktopTransitionHandler.setOnTaskResizeAnimatorListener(listener)
}
/** Setter needed to avoid cyclic dependency. */
@@ -230,22 +251,57 @@ class DesktopTasksController(
return desktopModeTaskRepository.getVisibleTaskCount(displayId)
}
+ /** Enter desktop by using the focused task in given `displayId` */
+ fun enterDesktop(displayId: Int) {
+ val allFocusedTasks =
+ shellTaskOrganizer.getRunningTasks(displayId).filter { taskInfo ->
+ taskInfo.isFocused &&
+ (taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN ||
+ taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) &&
+ taskInfo.activityType != ACTIVITY_TYPE_HOME
+ }
+ if (allFocusedTasks.isNotEmpty()) {
+ when (allFocusedTasks.size) {
+ 2 -> {
+ // Split-screen case where there are two focused tasks, then we find the child
+ // task to move to desktop.
+ val splitFocusedTask =
+ if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId)
+ allFocusedTasks[1]
+ else allFocusedTasks[0]
+ moveToDesktop(splitFocusedTask)
+ }
+ 1 -> {
+ // Fullscreen case where we move the current focused task.
+ moveToDesktop(allFocusedTasks[0].taskId)
+ }
+ else -> {
+ KtProtoLog.w(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: Cannot enter desktop, expected less " +
+ "than 3 focused tasks but found %d",
+ allFocusedTasks.size
+ )
+ }
+ }
+ }
+ }
+
/** Move a task with given `taskId` to desktop */
fun moveToDesktop(
- decor: DesktopModeWindowDecoration,
taskId: Int,
wct: WindowContainerTransaction = WindowContainerTransaction()
- ) {
+ ): Boolean {
shellTaskOrganizer.getRunningTaskInfo(taskId)?.let {
- task -> moveToDesktop(decor, task, wct)
- }
+ task -> moveToDesktop(task, wct)
+ } ?: return false
+ return true
}
/**
* Move a task to desktop
*/
fun moveToDesktop(
- decor: DesktopModeWindowDecoration,
task: RunningTaskInfo,
wct: WindowContainerTransaction = WindowContainerTransaction()
) {
@@ -260,7 +316,7 @@ class DesktopTasksController(
addMoveToDesktopChanges(wct, task)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.moveToDesktop(wct, decor)
+ enterDesktopTaskTransitionHandler.moveToDesktop(wct)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
@@ -273,7 +329,6 @@ class DesktopTasksController(
fun startDragToDesktop(
taskInfo: RunningTaskInfo,
dragToDesktopValueAnimator: MoveToDesktopAnimator,
- windowDecor: DesktopModeWindowDecoration
) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
@@ -282,8 +337,7 @@ class DesktopTasksController(
)
dragToDesktopTransitionHandler.startDragToDesktopTransition(
taskInfo.taskId,
- dragToDesktopValueAnimator,
- windowDecor
+ dragToDesktopValueAnimator
)
}
@@ -320,13 +374,24 @@ class DesktopTasksController(
}
/** Move a task with given `taskId` to fullscreen */
- fun moveToFullscreen(taskId: Int, windowDecor: DesktopModeWindowDecoration) {
+ fun moveToFullscreen(taskId: Int) {
shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task ->
- windowDecor.incrementRelayoutBlock()
moveToFullscreenWithAnimation(task, task.positionInParent)
}
}
+ /** Enter fullscreen by moving the focused freeform task in given `displayId` to fullscreen. */
+ fun enterFullscreen(displayId: Int) {
+ if (DesktopModeStatus.isEnabled()) {
+ shellTaskOrganizer
+ .getRunningTasks(displayId)
+ .find { taskInfo ->
+ taskInfo.isFocused && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
+ }
+ ?.let { moveToFullscreenWithAnimation(it, it.positionInParent) }
+ }
+ }
+
/** Move a desktop app to split screen. */
fun moveToSplit(task: RunningTaskInfo) {
KtProtoLog.v(
@@ -349,25 +414,11 @@ class DesktopTasksController(
splitScreenController.prepareExitSplitScreen(
wct,
splitScreenController.getStageOfTask(taskInfo.taskId),
- EXIT_REASON_ENTER_DESKTOP
+ EXIT_REASON_DESKTOP_MODE
)
- getOtherSplitTask(taskInfo.taskId)?.let { otherTaskInfo ->
- wct.removeTask(otherTaskInfo.token)
- }
}
}
- private fun getOtherSplitTask(taskId: Int): RunningTaskInfo? {
- val remainingTaskPosition: Int =
- if (splitScreenController.getSplitPosition(taskId)
- == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
- SPLIT_POSITION_TOP_OR_LEFT
- } else {
- SPLIT_POSITION_BOTTOM_OR_RIGHT
- }
- return splitScreenController.getTaskInfo(remainingTaskPosition)
- }
-
/**
* The second part of the animated drag to desktop transition, called after
* [startDragToDesktop].
@@ -485,7 +536,7 @@ class DesktopTasksController(
}
/** Quick-resizes a desktop task, toggling between the stable bounds and the default bounds. */
- fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo, windowDecor: DesktopModeWindowDecoration) {
+ fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
val stableBounds = Rect()
@@ -494,11 +545,7 @@ class DesktopTasksController(
if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) {
// The desktop task is currently occupying the whole stable bounds, toggle to the
// default bounds.
- getDefaultDesktopTaskBounds(
- density = taskInfo.configuration.densityDpi.toFloat() / DENSITY_DEFAULT,
- stableBounds = stableBounds,
- outBounds = destinationBounds
- )
+ getDefaultDesktopTaskBounds(displayLayout, destinationBounds)
} else {
// Toggle to the stable bounds.
destinationBounds.set(stableBounds)
@@ -506,11 +553,7 @@ class DesktopTasksController(
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- toggleResizeDesktopTaskTransitionHandler.startTransition(
- wct,
- taskInfo.taskId,
- windowDecor
- )
+ toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
@@ -521,11 +564,7 @@ class DesktopTasksController(
*
* @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to.
*/
- fun snapToHalfScreen(
- taskInfo: RunningTaskInfo,
- windowDecor: DesktopModeWindowDecoration,
- position: SnapPosition
- ) {
+ fun snapToHalfScreen(taskInfo: RunningTaskInfo, position: SnapPosition) {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
val stableBounds = Rect()
@@ -555,25 +594,23 @@ class DesktopTasksController(
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- toggleResizeDesktopTaskTransitionHandler.startTransition(
- wct,
- taskInfo.taskId,
- windowDecor
- )
+ toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
}
- private fun getDefaultDesktopTaskBounds(density: Float, stableBounds: Rect, outBounds: Rect) {
- val width = (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f).toInt()
- val height = (DESKTOP_MODE_DEFAULT_HEIGHT_DP * density + 0.5f).toInt()
- outBounds.set(0, 0, width, height)
- // Center the task in stable bounds
+ private fun getDefaultDesktopTaskBounds(displayLayout: DisplayLayout, outBounds: Rect) {
+ // TODO(b/319819547): Account for app constraints so apps do not become letterboxed
+ val screenBounds = Rect(0, 0, displayLayout.width(), displayLayout.height())
+ // Update width and height with default desktop mode values
+ val desiredWidth = screenBounds.width().times(DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
+ val desiredHeight = screenBounds.height().times(DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
+ outBounds.set(0, 0, desiredWidth, desiredHeight)
+ // Center the task in screen bounds
outBounds.offset(
- stableBounds.centerX() - outBounds.centerX(),
- stableBounds.centerY() - outBounds.centerY()
- )
+ screenBounds.centerX() - outBounds.centerX(),
+ screenBounds.centerY() - outBounds.centerY())
}
/**
@@ -721,6 +758,9 @@ class DesktopTasksController(
finishTransaction: SurfaceControl.Transaction
) {
// Add rounded corners to freeform windows
+ if (!DesktopModeStatus.useRoundedCorners()) {
+ return
+ }
val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
info.changes
.filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
@@ -869,31 +909,34 @@ class DesktopTasksController(
*
* @param taskInfo the task being dragged.
* @param taskSurface SurfaceControl of dragged task.
- * @param inputCoordinate coordinates of input. Used for checks against left/right edge of screen.
+ * @param inputX x coordinate of input. Used for checks against left/right edge of screen.
* @param taskBounds bounds of dragged task. Used for checks against status bar height.
*/
fun onDragPositioningMove(
taskInfo: RunningTaskInfo,
taskSurface: SurfaceControl,
- inputCoordinate: PointF,
+ inputX: Float,
taskBounds: Rect
) {
- val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
if (taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) return
- var type = DesktopModeVisualIndicator.determineIndicatorType(inputCoordinate,
- taskBounds, displayLayout, context)
- if (type != DesktopModeVisualIndicator.INVALID_INDICATOR && visualIndicator == null) {
+ updateVisualIndicator(taskInfo, taskSurface, inputX, taskBounds.top.toFloat())
+ }
+
+ fun updateVisualIndicator(
+ taskInfo: RunningTaskInfo,
+ taskSurface: SurfaceControl,
+ inputX: Float,
+ taskTop: Float
+ ) {
+ // If the visual indicator does not exist, create it.
+ if (visualIndicator == null) {
visualIndicator = DesktopModeVisualIndicator(
- syncQueue, taskInfo,
- displayController, context, taskSurface, shellTaskOrganizer,
- rootTaskDisplayAreaOrganizer, type)
- visualIndicator?.createIndicatorWithAnimatedBounds()
- return
- }
- if (visualIndicator?.eventOutsideRange(inputCoordinate.x,
- taskBounds.top.toFloat()) == true) {
- releaseVisualIndicator()
+ syncQueue, taskInfo, displayController, context, taskSurface,
+ rootTaskDisplayAreaOrganizer)
}
+ // Then, update the indicator type.
+ val indicator = visualIndicator ?: return
+ indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode)
}
/**
@@ -903,76 +946,33 @@ class DesktopTasksController(
* @param position position of surface when drag ends.
* @param inputCoordinate the coordinates of the motion event
* @param taskBounds the updated bounds of the task being dragged.
- * @param windowDecor the window decoration for the task being dragged
*/
fun onDragPositioningEnd(
taskInfo: RunningTaskInfo,
position: Point,
inputCoordinate: PointF,
- taskBounds: Rect,
- windowDecor: DesktopModeWindowDecoration
+ taskBounds: Rect
) {
if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
return
}
if (taskBounds.top <= transitionAreaHeight) {
- windowDecor.incrementRelayoutBlock()
moveToFullscreenWithAnimation(taskInfo, position)
+ return
}
if (inputCoordinate.x <= transitionAreaWidth) {
releaseVisualIndicator()
- var wct = WindowContainerTransaction()
- addMoveToSplitChanges(wct, taskInfo)
- splitScreenController.requestEnterSplitSelect(taskInfo, wct,
- SPLIT_POSITION_TOP_OR_LEFT, taskBounds)
+ snapToHalfScreen(taskInfo, SnapPosition.LEFT)
+ return
}
if (inputCoordinate.x >= (displayController.getDisplayLayout(taskInfo.displayId)?.width()
?.minus(transitionAreaWidth) ?: return)) {
releaseVisualIndicator()
- var wct = WindowContainerTransaction()
- addMoveToSplitChanges(wct, taskInfo)
- splitScreenController.requestEnterSplitSelect(taskInfo, wct,
- SPLIT_POSITION_BOTTOM_OR_RIGHT, taskBounds)
- }
- }
-
- /**
- * Perform checks required on drag move. Create/release fullscreen indicator and transitions
- * indicator to freeform or fullscreen dimensions as needed.
- *
- * @param taskInfo the task being dragged.
- * @param taskSurface SurfaceControl of dragged task.
- * @param y coordinate of dragged task. Used for checks against status bar height.
- */
- fun onDragPositioningMoveThroughStatusBar(
- taskInfo: RunningTaskInfo,
- taskSurface: SurfaceControl,
- y: Float
- ) {
- // If the motion event is above the status bar and the visual indicator is not yet visible,
- // return since we do not need to show the visual indicator at this point.
- if (y < getStatusBarHeight(taskInfo) && visualIndicator == null) {
+ snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
return
}
- if (visualIndicator == null) {
- visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
- displayController, context, taskSurface, shellTaskOrganizer,
- rootTaskDisplayAreaOrganizer, TO_DESKTOP_INDICATOR)
- // TODO(b/301106941): don't show the indicator until the drag-to-desktop animation has
- // started, or it'll be visible too early on top of the task surface, especially in
- // the cancel-early case. Also because it shouldn't even be shown in the cancel-early
- // case since its dismissal is tied to the cancel animation end, which doesn't even run
- // in cancel-early.
- visualIndicator?.createIndicatorWithAnimatedBounds()
- }
- val indicator = visualIndicator ?: return
- if (y >= getFreeformTransitionStatusBarDragThreshold(taskInfo)) {
- if (indicator.isFullscreen) {
- indicator.transitionFullscreenIndicatorToFreeform()
- }
- } else if (!indicator.isFullscreen) {
- indicator.transitionFreeformIndicatorToFullscreen()
- }
+ // A freeform drag-move ended, remove the indicator immediately.
+ releaseVisualIndicator()
}
/**
@@ -993,14 +993,6 @@ class DesktopTasksController(
}
/**
- * Returns the threshold at which we transition a task into freeform when dragging a
- * fullscreen task down from the status bar
- */
- private fun getFreeformTransitionStatusBarDragThreshold(taskInfo: RunningTaskInfo): Int {
- return 2 * getStatusBarHeight(taskInfo)
- }
-
- /**
* Update the exclusion region for a specified task
*/
fun onExclusionRegionChanged(taskId: Int, exclusionRegion: Region) {
@@ -1037,6 +1029,50 @@ class DesktopTasksController(
desktopModeTaskRepository.setExclusionRegionListener(listener, callbackExecutor)
}
+ override fun onUnhandledDrag(
+ launchIntent: PendingIntent,
+ dragSurface: SurfaceControl,
+ onFinishCallback: Consumer<Boolean>
+ ): Boolean {
+ // TODO(b/320797628): Pass through which display we are dropping onto
+ val activeTasks = desktopModeTaskRepository.getActiveTasks(DEFAULT_DISPLAY)
+ if (!activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
+ // Not currently in desktop mode, ignore the drop
+ return false
+ }
+
+ val launchComponent = getComponent(launchIntent)
+ if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent)) {
+ // TODO(b/320797628): Should only return early if there is an existing running task, and
+ // notify the user as well. But for now, just ignore the drop.
+ KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "Dropped intent does not support multi-instance")
+ return false
+ }
+
+ // Start a new transition to launch the app
+ val opts = ActivityOptions.makeBasic().apply {
+ launchWindowingMode = WINDOWING_MODE_FREEFORM
+ pendingIntentLaunchFlags =
+ Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+ setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
+ }
+ val wct = WindowContainerTransaction()
+ wct.sendPendingIntent(launchIntent, null, opts.toBundle())
+ transitions.startTransition(TRANSIT_OPEN, wct, null /* handler */)
+
+ // Report that this is handled by the listener
+ onFinishCallback.accept(true)
+
+ // We've assumed responsibility of cleaning up the drag surface, so do that now
+ // TODO(b/320797628): Do an actual animation here for the drag surface
+ val t = SurfaceControl.Transaction()
+ t.remove(dragSurface)
+ t.apply()
+ return true
+ }
+
private fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopTasksController")
@@ -1063,6 +1099,18 @@ class DesktopTasksController(
this@DesktopTasksController.setTaskRegionListener(listener, callbackExecutor)
}
}
+
+ override fun enterDesktop(displayId: Int) {
+ mainExecutor.execute {
+ this@DesktopTasksController.enterDesktop(displayId)
+ }
+ }
+
+ override fun moveFocusedTaskToFullscreen(displayId: Int) {
+ mainExecutor.execute {
+ this@DesktopTasksController.enterFullscreen(displayId)
+ }
+ }
}
/** The interface for calls from outside the host process. */
@@ -1074,14 +1122,16 @@ class DesktopTasksController(
SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>
private val listener: VisibleTasksListener = object : VisibleTasksListener {
- override fun onVisibilityChanged(displayId: Int, visible: Boolean) {
+ override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "IDesktopModeImpl: onVisibilityChanged display=%d visible=%b",
+ "IDesktopModeImpl: onVisibilityChanged display=%d visible=%d",
displayId,
- visible
+ visibleTasksCount
)
- remoteListener.call { l -> l.onVisibilityChanged(displayId, visible) }
+ remoteListener.call {
+ l -> l.onTasksVisibilityChanged(displayId, visibleTasksCount)
+ }
}
override fun onStashedChanged(displayId: Int, stashed: Boolean) {
@@ -1181,13 +1231,9 @@ class DesktopTasksController(
SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 284)
private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000)
- // Override default freeform task width when desktop mode is enabled. In dips.
- private val DESKTOP_MODE_DEFAULT_WIDTH_DP =
- SystemProperties.getInt("persist.wm.debug.desktop_mode.default_width", 840)
-
- // Override default freeform task height when desktop mode is enabled. In dips.
- private val DESKTOP_MODE_DEFAULT_HEIGHT_DP =
- SystemProperties.getInt("persist.wm.debug.desktop_mode.default_height", 630)
+ @JvmField
+ val DESKTOP_MODE_INITIAL_BOUNDS_SCALE = SystemProperties
+ .getInt("persist.wm.debug.freeform_initial_bounds_scale", 75) / 100f
/**
* Check if desktop density override is enabled
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 95d7ad5c416f..af26e2980afe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -26,6 +26,7 @@ import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
@@ -33,10 +34,9 @@ import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.android.wm.shell.util.KtProtoLog
-import com.android.wm.shell.util.TransitionUtil
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.util.function.Supplier
/**
@@ -69,6 +69,7 @@ class DragToDesktopTransitionHandler(
private var dragToDesktopStateListener: DragToDesktopStateListener? = null
private var splitScreenController: SplitScreenController? = null
private var transitionState: TransitionState? = null
+ private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
/** Whether a drag-to-desktop transition is in progress. */
val inProgress: Boolean
@@ -84,6 +85,10 @@ class DragToDesktopTransitionHandler(
splitScreenController = controller
}
+ fun setOnTaskResizeAnimatorListener(listener: OnTaskResizeAnimationListener) {
+ onTaskResizeAnimationListener = listener
+ }
+
/**
* Starts a transition that performs a transient launch of Home so that Home is brought to the
* front while still keeping the currently focused task that is being dragged resumed. This
@@ -96,10 +101,13 @@ class DragToDesktopTransitionHandler(
fun startDragToDesktopTransition(
taskId: Int,
dragToDesktopAnimator: MoveToDesktopAnimator,
- windowDecoration: DesktopModeWindowDecoration
) {
if (inProgress) {
- error("A drag to desktop is already in progress")
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DragToDesktop: Drag to desktop transition already in progress."
+ )
+ return
}
val options = ActivityOptions.makeBasic().apply {
@@ -124,14 +132,12 @@ class DragToDesktopTransitionHandler(
TransitionState.FromSplit(
draggedTaskId = taskId,
dragAnimator = dragToDesktopAnimator,
- windowDecoration = windowDecoration,
startTransitionToken = startTransitionToken
)
} else {
TransitionState.FromFullscreen(
draggedTaskId = taskId,
dragAnimator = dragToDesktopAnimator,
- windowDecoration = windowDecoration,
startTransitionToken = startTransitionToken
)
}
@@ -144,6 +150,12 @@ class DragToDesktopTransitionHandler(
* inside the desktop drop zone.
*/
fun finishDragToDesktopTransition(wct: WindowContainerTransaction) {
+ if (!inProgress) {
+ // Don't attempt to finish a drag to desktop transition since there is no transition in
+ // progress which means that the drag to desktop transition was never successfully
+ // started.
+ return
+ }
if (requireTransitionState().startAborted) {
// Don't attempt to complete the drag-to-desktop since the start transition didn't
// succeed as expected. Just reset the state as if nothing happened.
@@ -161,6 +173,12 @@ class DragToDesktopTransitionHandler(
* means the user wants to remain in their current windowing mode.
*/
fun cancelDragToDesktopTransition() {
+ if (!inProgress) {
+ // Don't attempt to cancel a drag to desktop transition since there is no transition in
+ // progress which means that the drag to desktop transition was never successfully
+ // started.
+ return
+ }
val state = requireTransitionState()
if (state.startAborted) {
// Don't attempt to cancel the drag-to-desktop since the start transition didn't
@@ -223,7 +241,7 @@ class DragToDesktopTransitionHandler(
show(change.leash)
}
} else if (TransitionInfo.isIndependent(change, info)) {
- // Root.
+ // Root(s).
when (state) {
is TransitionState.FromSplit -> {
state.splitRootChange = change
@@ -240,6 +258,9 @@ class DragToDesktopTransitionHandler(
}
}
is TransitionState.FromFullscreen -> {
+ // Most of the time we expect one change/task here, which should be the
+ // same that initiated the drag and that should be layered on top of
+ // everything.
if (change.taskInfo?.taskId == state.draggedTaskId) {
state.draggedTaskChange = change
val bounds = change.endAbsBounds
@@ -249,7 +270,18 @@ class DragToDesktopTransitionHandler(
show(change.leash)
}
} else {
- throw IllegalStateException("Expected root to be dragged task")
+ // It's possible to see an additional change that isn't the dragged
+ // task when the dragged task is translucent and so the task behind it
+ // is included in the transition since it was visible and is now being
+ // occluded by the Home task. Just layer it at the bottom and save it
+ // in case we need to restore order if the drag is cancelled.
+ state.otherRootChanges.add(change)
+ val bounds = change.endAbsBounds
+ startTransaction.apply {
+ setLayer(change.leash, appLayers - i)
+ setWindowCrop(change.leash, bounds.width(), bounds.height())
+ show(change.leash)
+ }
}
}
}
@@ -375,7 +407,7 @@ class DragToDesktopTransitionHandler(
// Accept the merge by applying the merging transaction (applied by #showResizeVeil)
// and finish callback. Show the veil and position the task at the first frame before
// starting the final animation.
- state.windowDecoration.showResizeVeil(t, animStartBounds)
+ onTaskResizeAnimationListener.onAnimationStart(state.draggedTaskId, t, animStartBounds)
finishCallback.onTransitionFinished(null /* wct */)
// Because the task surface was scaled down during the drag, we must use the animated
@@ -399,11 +431,15 @@ class DragToDesktopTransitionHandler(
animBounds.height()
)
}
- state.windowDecoration.updateResizeVeil(tx, animBounds)
+ onTaskResizeAnimationListener.onBoundsChange(
+ state.draggedTaskId,
+ tx,
+ animBounds
+ )
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
- state.windowDecoration.hideResizeVeil()
+ onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
startTransitionFinishCb.onTransitionFinished(null /* null */)
clearState()
}
@@ -499,8 +535,18 @@ class DragToDesktopTransitionHandler(
val wct = WindowContainerTransaction()
when (state) {
is TransitionState.FromFullscreen -> {
+ // There may have been tasks sent behind home that are not the dragged task (like
+ // when the dragged task is translucent and that makes the task behind it visible).
+ // Restore the order of those first.
+ state.otherRootChanges.mapNotNull { it.container }.forEach { wc ->
+ // TODO(b/322852244): investigate why even though these "other" tasks are
+ // reordered in front of home and behind the translucent dragged task, its
+ // surface is not visible on screen.
+ wct.reorder(wc, true /* toTop */)
+ }
val wc = state.draggedTaskChange?.container
?: error("Dragged task should be non-null before cancelling")
+ // Then the dragged task a the very top.
wct.reorder(wc, true /* toTop */)
}
is TransitionState.FromSplit -> {
@@ -536,7 +582,6 @@ class DragToDesktopTransitionHandler(
sealed class TransitionState {
abstract val draggedTaskId: Int
abstract val dragAnimator: MoveToDesktopAnimator
- abstract val windowDecoration: DesktopModeWindowDecoration
abstract val startTransitionToken: IBinder
abstract var startTransitionFinishCb: Transitions.TransitionFinishCallback?
abstract var startTransitionFinishTransaction: SurfaceControl.Transaction?
@@ -549,7 +594,6 @@ class DragToDesktopTransitionHandler(
data class FromFullscreen(
override val draggedTaskId: Int,
override val dragAnimator: MoveToDesktopAnimator,
- override val windowDecoration: DesktopModeWindowDecoration,
override val startTransitionToken: IBinder,
override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
@@ -558,11 +602,11 @@ class DragToDesktopTransitionHandler(
override var draggedTaskChange: Change? = null,
override var cancelled: Boolean = false,
override var startAborted: Boolean = false,
+ var otherRootChanges: MutableList<Change> = mutableListOf()
) : TransitionState()
data class FromSplit(
override val draggedTaskId: Int,
override val dragAnimator: MoveToDesktopAnimator,
- override val windowDecoration: DesktopModeWindowDecoration,
override val startTransitionToken: IBinder,
override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 605600f54823..79bb5408df82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -38,7 +38,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration;
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener;
import java.util.ArrayList;
import java.util.List;
@@ -54,13 +54,11 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
private final Transitions mTransitions;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
- // The size of the screen after drag relative to the fullscreen size
- public static final float FINAL_FREEFORM_SCALE = 0.6f;
public static final int FREEFORM_ANIMATION_DURATION = 336;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
- private DesktopModeWindowDecoration mDesktopModeWindowDecoration;
+ private OnTaskResizeAnimationListener mOnTaskResizeAnimationListener;
public EnterDesktopTaskTransitionHandler(
Transitions transitions) {
this(transitions, SurfaceControl.Transaction::new);
@@ -73,14 +71,15 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
mTransactionSupplier = supplier;
}
+ void setOnTaskResizeAnimationListener(OnTaskResizeAnimationListener listener) {
+ mOnTaskResizeAnimationListener = listener;
+ }
+
/**
* Starts Transition of type TRANSIT_MOVE_TO_DESKTOP
* @param wct WindowContainerTransaction for transition
- * @param decor {@link DesktopModeWindowDecoration} of task being animated
*/
- public void moveToDesktop(@NonNull WindowContainerTransaction wct,
- DesktopModeWindowDecoration decor) {
- mDesktopModeWindowDecoration = decor;
+ public void moveToDesktop(@NonNull WindowContainerTransaction wct) {
final IBinder token = mTransitions.startTransition(TRANSIT_MOVE_TO_DESKTOP, wct, this);
mPendingTransitionTokens.add(token);
}
@@ -136,33 +135,33 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
@NonNull TransitionInfo.Change change,
@NonNull SurfaceControl.Transaction startT,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (mDesktopModeWindowDecoration == null) {
- Slog.e(TAG, "Window Decoration is not available for this transition");
+ final SurfaceControl leash = change.getLeash();
+ final Rect startBounds = change.getStartAbsBounds();
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (mOnTaskResizeAnimationListener == null) {
+ Slog.e(TAG, "onTaskResizeAnimationListener is not available for this transition");
return false;
}
- final SurfaceControl leash = change.getLeash();
- final Rect startBounds = change.getStartAbsBounds();
- startT.setPosition(leash, startBounds.left, startBounds.right)
+ startT.setPosition(leash, startBounds.left, startBounds.top)
.setWindowCrop(leash, startBounds.width(), startBounds.height())
.show(leash);
- mDesktopModeWindowDecoration.showResizeVeil(startT, startBounds);
-
+ mOnTaskResizeAnimationListener.onAnimationStart(taskInfo.taskId, startT, startBounds);
final ValueAnimator animator = ValueAnimator.ofObject(new RectEvaluator(),
change.getStartAbsBounds(), change.getEndAbsBounds());
animator.setDuration(FREEFORM_ANIMATION_DURATION);
SurfaceControl.Transaction t = mTransactionSupplier.get();
animator.addUpdateListener(animation -> {
final Rect animationValue = (Rect) animator.getAnimatedValue();
- t.setPosition(leash, animationValue.left, animationValue.right)
+ t.setPosition(leash, animationValue.left, animationValue.top)
.setWindowCrop(leash, animationValue.width(), animationValue.height())
.show(leash);
- mDesktopModeWindowDecoration.updateResizeVeil(t, animationValue);
+ mOnTaskResizeAnimationListener.onBoundsChange(taskInfo.taskId, t, animationValue);
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mDesktopModeWindowDecoration.hideResizeVeil();
+ mOnTaskResizeAnimationListener.onAnimationEnd(taskInfo.taskId);
mTransitions.getMainExecutor().execute(
() -> finishCallback.onTransitionFinished(null));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
index 39128a863ec9..8ed87f23bf40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
@@ -22,8 +22,8 @@ package com.android.wm.shell.desktopmode;
*/
interface IDesktopTaskListener {
- /** Desktop task visibility has change. Visible if at least 1 task is visible. */
- oneway void onVisibilityChanged(int displayId, boolean visible);
+ /** Desktop tasks visibility has changed. Visible if at least 1 task is visible. */
+ oneway void onTasksVisibilityChanged(int displayId, int visibleTasksCount);
/** Desktop task stashed status has changed. */
oneway void onStashedChanged(int displayId, boolean stashed);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index 9debb25ff296..c469e652b117 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -21,7 +21,6 @@ import android.animation.RectEvaluator
import android.animation.ValueAnimator
import android.graphics.Rect
import android.os.IBinder
-import android.util.SparseArray
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.window.TransitionInfo
@@ -30,7 +29,7 @@ import android.window.WindowContainerTransaction
import androidx.core.animation.addListener
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.util.function.Supplier
/** Handles the animation of quick resizing of desktop tasks. */
@@ -40,7 +39,7 @@ class ToggleResizeDesktopTaskTransitionHandler(
) : Transitions.TransitionHandler {
private val rectEvaluator = RectEvaluator(Rect())
- private val taskToDecorationMap = SparseArray<DesktopModeWindowDecoration>()
+ private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
private var boundsAnimator: Animator? = null
@@ -49,15 +48,12 @@ class ToggleResizeDesktopTaskTransitionHandler(
) : this(transitions, Supplier { SurfaceControl.Transaction() })
/** Starts a quick resize transition. */
- fun startTransition(
- wct: WindowContainerTransaction,
- taskId: Int,
- windowDecoration: DesktopModeWindowDecoration
- ) {
- // Pause relayout until the transition animation finishes.
- windowDecoration.incrementRelayoutBlock()
+ fun startTransition(wct: WindowContainerTransaction) {
transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
- taskToDecorationMap.put(taskId, windowDecoration)
+ }
+
+ fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
+ onTaskResizeAnimationListener = listener
}
override fun startAnimation(
@@ -72,9 +68,6 @@ class ToggleResizeDesktopTaskTransitionHandler(
val taskId = checkNotNull(change.taskInfo).taskId
val startBounds = change.startAbsBounds
val endBounds = change.endAbsBounds
- val windowDecor =
- taskToDecorationMap.removeReturnOld(taskId)
- ?: throw IllegalStateException("Window decoration not found for task $taskId")
val tx = transactionSupplier.get()
boundsAnimator?.cancel()
@@ -92,7 +85,11 @@ class ToggleResizeDesktopTaskTransitionHandler(
)
.setWindowCrop(leash, startBounds.width(), startBounds.height())
.show(leash)
- windowDecor.showResizeVeil(startTransaction, startBounds)
+ onTaskResizeAnimationListener.onAnimationStart(
+ taskId,
+ startTransaction,
+ startBounds
+ )
},
onEnd = {
finishTransaction
@@ -103,7 +100,7 @@ class ToggleResizeDesktopTaskTransitionHandler(
)
.setWindowCrop(leash, endBounds.width(), endBounds.height())
.show(leash)
- windowDecor.hideResizeVeil()
+ onTaskResizeAnimationListener.onAnimationEnd(taskId)
finishCallback.onTransitionFinished(null)
boundsAnimator = null
}
@@ -113,7 +110,7 @@ class ToggleResizeDesktopTaskTransitionHandler(
tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
.setWindowCrop(leash, rect.width(), rect.height())
.show(leash)
- windowDecor.updateResizeVeil(tx, rect)
+ onTaskResizeAnimationListener.onBoundsChange(taskId, tx, rect)
}
start()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
index 73a7348d5aca..3fad28ad232f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
@@ -15,4 +15,4 @@ particular order):
Todo
- Per-feature docs
- Feature flagging
-- Best practices \ No newline at end of file
+- Best practices & patterns \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index fbf326eadcd5..9aa5f4ffcd78 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -102,5 +102,5 @@ AIDL interfaces and constants. Currently, all AIDL files, and classes under the
Launcher uses.
If the new code doesn't fall into those categories, they can be added explicitly in the Shell's
-[Android.bp](frameworks/base/libs/WindowManager/Shell/Android.bp) file under the
+[Android.bp](/libs/WindowManager/Shell/Android.bp) file under the
`wm_shell_util-sources` filegroup. \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
index 6c01d962adc9..7070dead9957 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
@@ -21,16 +21,16 @@ developers to jump into a few select files and understand how different componen
(especially as products override components).
The module dependency tree looks a bit like:
-- [WMShellConcurrencyModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
+- [WMShellConcurrencyModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
(provides threading-related components)
- - [WMShellBaseModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
+ - [WMShellBaseModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
(provides components that are likely common to all products, ie. DisplayController,
Transactions, etc.)
- - [WMShellModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
+ - [WMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
(phone/tablet specific components only)
- - [TvPipModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
+ - [TvPipModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
(PIP specific components for TV)
- - [TvWMShellModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
+ - [TvWMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
(TV specific components only)
- etc.
@@ -43,7 +43,7 @@ In some rare cases, there are base components that can change behavior depending
product it runs on. If there are hooks that can be added to the component, that is the
preferable approach.
-The alternative is to use the [@DynamicOverride](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
+The alternative is to use the [@DynamicOverride](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
annotation to allow the product module to provide an implementation that the base module can
reference. This is most useful if the existence of the entire component is controlled by the
product and the override implementation is optional (there is a default implementation). More
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index f9ea1d4e2a07..438aa768165e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -29,30 +29,78 @@ building to check the log state (is enabled) before printing the print format st
### Kotlin
Protolog tool does not yet have support for Kotlin code (see [b/168581922](https://b.corp.google.com/issues/168581922)).
-For logging in Kotlin, use the [KtProtoLog](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
+For logging in Kotlin, use the [KtProtoLog](/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
class which has a similar API to the Java ProtoLog class.
### Enabling ProtoLog command line logging
-Run these commands to enable protologs for both WM Core and WM Shell to print to logcat.
+Run these commands to enable protologs (in logcat) for WM Core ([list of all core tags](/core/java/com/android/internal/protolog/ProtoLogGroup.java)):
```shell
-adb shell wm logging enable-text NEW_FEATURE
-adb shell wm logging disable-text NEW_FEATURE
+adb shell wm logging enable-text TAG
+adb shell wm logging disable-text TAG
+```
+
+And these commands to enable protologs (in logcat) for WM Shell ([list of all shell tags](/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java)):
+```shell
+adb shell dumpsys activity service SystemUIService WMShell protolog enable-text TAG
+adb shell dumpsys activity service SystemUIService WMShell protolog enable-text TAG
```
## Winscope Tracing
The Winscope tool is extremely useful in determining what is happening on-screen in both
WindowManager and SurfaceFlinger. Follow [go/winscope](http://go/winscope-help) to learn how to
-use the tool.
+use the tool. This trace will contain all the information about the windows/activities/surfaces on
+screen.
+
+## WindowManager/SurfaceFlinger hierarchy dump
+
+A quick way to view the WindowManager hierarchy without a winscope trace is via the wm dumps:
+```shell
+adb shell dumpsys activity containers
+```
+
+Likewise, the SurfaceFlinger hierarchy can be dumped for inspection by running:
+```shell
+adb shell dumpsys SurfaceFlinger
+# Search output for "Layer Hierarchy"
+```
+
+## Tracing global SurfaceControl transaction updates
-In addition, there is limited preliminary support for Winscope tracing componetns in the Shell,
-which involves adding trace fields to [wm_shell_trace.proto](frameworks/base/libs/WindowManager/Shell/proto/wm_shell_trace.proto)
-file and ensure it is updated as a part of `WMShell#writeToProto`.
+While Winscope traces are very useful, it sometimes doesn't give you enough information about which
+part of the code is initiating the transaction updates. In such cases, it can be helpful to get
+stack traces when specific surface transaction calls are made, which is possible by enabling the
+following system properties for example:
+```shell
+# Enabling
+adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha # matches the name of the SurfaceControlTransaction method
+adb shell setprop persist.wm.debug.sc.tx.log_match_name com.android.systemui # matches the name of the surface
+adb reboot
+adb logcat -s "SurfaceControlRegistry"
+
+# Disabling logging
+adb shell setprop persist.wm.debug.sc.tx.log_match_call \"\"
+adb shell setprop persist.wm.debug.sc.tx.log_match_name \"\"
+adb reboot
+```
+
+It is not necessary to set both `log_match_call` and `log_match_name`, but note logs can be quite
+noisy if unfiltered.
-Tracing can be started via the shell command (to be added to the Winscope tool as needed):
+## Tracing activity starts in the app process
+
+It's sometimes useful to know when to see a stack trace of when an activity starts in the app code
+(ie. if you are repro'ing a bug related to activity starts). You can enable this system property to
+get this trace:
```shell
-adb shell cmd statusbar tracing start
-adb shell cmd statusbar tracing stop
+# Enabling
+adb shell setprop persist.wm.debug.start_activity true
+adb reboot
+adb logcat -s "Instrumentation"
+
+# Disabling
+adb shell setprop persist.wm.debug.start_activity \"\"
+adb reboot
```
## Dumps
@@ -69,6 +117,21 @@ If information should be added to the dump, either:
- Update `WMShell` if you are dumping SysUI state
- Inject `ShellCommandHandler` into your Shell class, and add a dump callback
+## Shell commands
+
+It can be useful to add additional shell commands to drive and test specific interactions.
+
+To add a new command for your feature, inject a `ShellCommandHandler` into your class and add a
+shell command handler in your controller.
+
+```shell
+# List all available commands
+adb shell dumpsys activity service SystemUIService WMShell help
+
+# Run a specific command
+adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
+```
+
## Debugging in Android Studio
If you are using the [go/sysui-studio](http://go/sysui-studio) project, then you can debug Shell
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
index a88ef6aea2ec..b489fe8ea1a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
@@ -19,25 +19,24 @@ Currently, the WMShell library is used to drive the windowing experience on hand
## Where does the code live
-The core WMShell library code is currently located in the [frameworks/base/libs/WindowManager/Shell](frameworks/base/libs/WindowManager/Shell)
+The core WMShell library code is currently located in the [frameworks/base/libs/WindowManager/Shell](/libs/WindowManager/Shell)
directory and is included as a part dependency of the host SystemUI apk.
## How do I build the Shell library
-The library can be built directly by running (using [go/makepush](http://go/makepush)):
+The library can be built directly by running:
```shell
-mp :WindowManager-Shell
+m WindowManager-Shell
```
But this is mainly useful for inspecting the contents of the library or verifying it builds. The
-various targets can be found in the Shell library's [Android.bp](frameworks/base/libs/WindowManager/Shell/Android.bp)
+various targets can be found in the Shell library's [Android.bp](/libs/WindowManager/Shell/Android.bp)
file.
Normally, you would build it as a part of the host SystemUI, for example via commandline:
```shell
# Phone SystemUI variant
-mp sysuig
-# Building Shell & SysUI changes along w/ framework changes
-mp core services sysuig
+m SystemUI
+adevice update
```
Or preferably, if you are making WMShell/SysUI only changes (no other framework changes), then
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md
new file mode 100644
index 000000000000..0e20a8ec275c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/patterns/TEMPLATE.md
@@ -0,0 +1,30 @@
+# Pattern (one line description)
+
+### What this pattern solves
+
+Give detailed information about the problem that this pattern addresses, include when it
+should/should not be used.
+
+### How it works
+
+Give a high level overview of how this pattern works technically with sufficient links for the
+relevant dependencies for folks to read more.
+
+### Code examples
+
+Explain how this pattern is used in code.
+
+File.kt:
+```kotlin
+fun someFunction() {
+ // Use this code
+}
+```
+
+### Relevant links
+
+Add relevant links to other files that implement this pattern, design docs, or other important
+info.
+
+Link 1: [More info](some_example_link) \
+Link 2: [More info](some_example_link) \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
index d6302e640ba7..30ff6691f503 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
@@ -80,4 +80,6 @@ adb shell dumpsys activity service SystemUIService WMShell
# Run a specific command
adb shell dumpsys activity service SystemUIService WMShell help
adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ...
-``` \ No newline at end of file
+```
+
+More detail can be found in [Debugging in the Shell](debugging.md) section. \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
index 8a80333facc4..98af930c4486 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
@@ -5,7 +5,7 @@
## Unit tests
New WM Shell unit tests can be added to the
-[Shell/tests/unittest](frameworks/base/libs/WindowManager/Shell/tests/unittest) directory, and can
+[Shell/tests/unittest](/libs/WindowManager/Shell/tests/unittest) directory, and can
be run via command line using `atest`:
```shell
atest WMShellUnitTests
@@ -25,10 +25,24 @@ Flicker tests are tests that perform actions and make assertions on the state in
and SurfaceFlinger traces captured during the run.
New WM Shell Flicker tests can be added to the
-[Shell/tests/flicker](frameworks/base/libs/WindowManager/Shell/tests/flicker) directory, and can
-be run via command line using `atest`:
+[Shell/tests/flicker](/libs/WindowManager/Shell/tests/flicker) directory, and can be run via command line using `atest`:
```shell
-atest WMShellFlickerTests
+# Bubbles
+atest WMShellFlickerTestsBubbles
+
+# PIP
+atest WMShellFlickerTestsPip1
+atest WMShellFlickerTestsPip2
+atest WMShellFlickerTestsPip3
+atest WMShellFlickerTestsPipApps
+atest WMShellFlickerTestsPipAppsCSuite
+
+# Splitscreen
+atest WMShellFlickerTestsSplitScreenGroup1
+atest WMShellFlickerTestsSplitScreenGroup2
+
+# Other
+atest WMShellFlickerTestsOther
```
**Note**: Currently Flicker tests can only be run from the commandline and not via SysUI Studio
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
index eac748894432..9d015357b60b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
@@ -43,7 +43,7 @@ the product.
## Dagger setup
-The threading-related components are provided by the [WMShellConcurrencyModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java),
+The threading-related components are provided by the [WMShellConcurrencyModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java),
for example, the Executors and Handlers for the various threads that are used. You can request
an executor of the necessary type by using the appropriate annotation for each of the threads (ie.
`@ShellMainThread Executor`) when injecting into your Shell component.
@@ -76,7 +76,7 @@ To get the SysUI main thread, you can use the `@Main` annotation.
want to dedupe multiple messages
- In such cases inject `@ShellMainThread Handler` or use view.getHandler() which should be OK
assuming that the view root was initialized on the main Shell thread
-- **Never use Looper.getMainLooper()**
+- <u>**Never</u> use Looper.getMainLooper()**
- It's likely going to be wrong, you can inject `@Main ShellExecutor` to get the SysUI main thread
### Testing
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 fdfb6f3680b2..7da1b23dd5b1 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
@@ -35,7 +35,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
+import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
import android.content.ClipDescription;
import android.content.ComponentCallbacks2;
import android.content.Context;
@@ -51,12 +53,12 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
+import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
@@ -71,14 +73,18 @@ import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.function.Consumer;
+import java.util.function.Function;
/**
* Handles the global drag and drop handling for the Shell.
*/
public class DragAndDropController implements RemoteCallable<DragAndDropController>,
+ GlobalDragListener.GlobalDragListenerCallback,
DisplayController.OnDisplaysChangedListener,
View.OnDragListener, ComponentCallbacks2 {
@@ -90,6 +96,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
private final DisplayController mDisplayController;
private final DragAndDropEventLogger mLogger;
private final IconProvider mIconProvider;
+ private final GlobalDragListener mGlobalDragListener;
+ private final Transitions mTransitions;
private SplitScreenController mSplitScreen;
private ShellExecutor mMainExecutor;
private ArrayList<DragAndDropListener> mListeners = new ArrayList<>();
@@ -97,39 +105,40 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
// Map of displayId -> per-display info
private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
+ // The current display if a drag is in progress
+ private int mActiveDragDisplay = -1;
+
/**
- * Listener called during drag events, currently just onDragStarted.
+ * Listener called during drag events.
*/
public interface DragAndDropListener {
/** Called when a drag has started. */
- void onDragStarted();
- }
+ default void onDragStarted() {}
- /**
- * Creates {@link DragAndDropController}. Returns {@code null} if the feature is disabled.
- */
- public static DragAndDropController create(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellCommandHandler shellCommandHandler,
- DisplayController displayController,
- UiEventLogger uiEventLogger,
- IconProvider iconProvider,
- ShellExecutor mainExecutor) {
- if (!context.getResources().getBoolean(R.bool.config_enableShellDragDrop)) {
- return null;
+ /** Called when a drag has ended. */
+ default void onDragEnded() {}
+
+ /**
+ * Called when an unhandled drag has occurred. The impl must return true if it decides to
+ * handled the unhandled drag, and it must also call `onFinishCallback` to complete the
+ * drag.
+ */
+ default boolean onUnhandledDrag(@NonNull PendingIntent launchIntent,
+ @NonNull SurfaceControl dragSurface,
+ @NonNull Consumer<Boolean> onFinishCallback) {
+ return false;
}
- return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
- displayController, uiEventLogger, iconProvider, mainExecutor);
}
- DragAndDropController(Context context,
+ public DragAndDropController(Context context,
ShellInit shellInit,
ShellController shellController,
ShellCommandHandler shellCommandHandler,
DisplayController displayController,
UiEventLogger uiEventLogger,
IconProvider iconProvider,
+ GlobalDragListener globalDragListener,
+ Transitions transitions,
ShellExecutor mainExecutor) {
mContext = context;
mShellController = shellController;
@@ -137,6 +146,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
mDisplayController = displayController;
mLogger = new DragAndDropEventLogger(uiEventLogger);
mIconProvider = iconProvider;
+ mGlobalDragListener = globalDragListener;
+ mTransitions = transitions;
mMainExecutor = mainExecutor;
shellInit.addInitCallback(this::onInit, this);
}
@@ -154,6 +165,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP,
this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
+ mGlobalDragListener.setListener(this);
}
private ExternalInterfaceBinder createExternalInterface() {
@@ -187,10 +199,18 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
mListeners.remove(listener);
}
- private void notifyDragStarted() {
+ /**
+ * Notifies all listeners and returns whether any listener handled the callback.
+ */
+ private boolean notifyListeners(Function<DragAndDropListener, Boolean> callback) {
for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onDragStarted();
+ boolean handled = callback.apply(mListeners.get(i));
+ if (handled) {
+ // Return once the callback reports it has handled it
+ return true;
+ }
}
+ return false;
}
@Override
@@ -276,6 +296,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
}
if (event.getAction() == ACTION_DRAG_STARTED) {
+ mActiveDragDisplay = displayId;
pd.isHandlingDrag = DragUtils.canHandleDrag(event);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s",
@@ -294,14 +315,17 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
return false;
}
// TODO(b/290391688): Also update the session data with task stack changes
- InstanceId loggerSessionId = mLogger.logStart(event);
- pd.activeDragCount++;
- pd.dragSession = new DragSession(mContext, ActivityTaskManager.getInstance(),
+ pd.dragSession = new DragSession(ActivityTaskManager.getInstance(),
mDisplayController.getDisplayLayout(displayId), event.getClipData());
pd.dragSession.update();
- pd.dragLayout.prepare(pd.dragSession, loggerSessionId);
+ pd.activeDragCount++;
+ pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession));
setDropTargetWindowVisibility(pd, View.VISIBLE);
- notifyDragStarted();
+ notifyListeners(l -> {
+ l.onDragStarted();
+ // Return false to continue dispatch to next listener
+ return false;
+ });
break;
case ACTION_DRAG_ENTERED:
pd.dragLayout.show();
@@ -335,11 +359,43 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
});
}
mLogger.logEnd();
+ mActiveDragDisplay = -1;
+ notifyListeners(l -> {
+ l.onDragEnded();
+ // Return false to continue dispatch to next listener
+ return false;
+ });
break;
}
return true;
}
+ @Override
+ public void onCrossWindowDrop(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
+ // Bring the task forward when an item is dropped on it
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(taskInfo.token, true /* onTop */);
+ mTransitions.startTransition(WindowManager.TRANSIT_TO_FRONT, wct, null);
+ }
+
+ @Override
+ public void onUnhandledDrop(@NonNull DragEvent dragEvent,
+ @NonNull Consumer<Boolean> onFinishCallback) {
+ final PendingIntent launchIntent = DragUtils.getLaunchIntent(dragEvent);
+ if (launchIntent == null) {
+ // No intent to launch, report that this is unhandled by the listener
+ onFinishCallback.accept(false);
+ return;
+ }
+
+ final boolean handled = notifyListeners(
+ l -> l.onUnhandledDrag(launchIntent, dragEvent.getDragSurface(), onFinishCallback));
+ if (!handled) {
+ // Nobody handled this, we still have to notify WM
+ onFinishCallback.accept(false);
+ }
+ }
+
/**
* Handles dropping on the drop target.
*/
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
index 2a7dd5aeb341..75b126c47690 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java
@@ -53,17 +53,21 @@ public class DragAndDropEventLogger {
/**
* 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);
+ public InstanceId logStart(DragSession session) {
+ mInstanceId = session.appData != null
+ ? session.appData.getParcelableExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID,
+ InstanceId.class)
+ : null;
if (mInstanceId == null) {
mInstanceId = mIdSequence.newInstanceId();
}
- mActivityInfo = item.getActivityInfo();
- log(getStartEnum(description), mActivityInfo);
+ mActivityInfo = session.activityInfo;
+ if (session.appData != null) {
+ log(getStartEnum(session.getClipDescription()), mActivityInfo);
+ } else {
+ // TODO(b/255649902): Update this once we have a new enum
+ log(DragAndDropUiEventEnum.GLOBAL_APP_DRAG_START_ACTIVITY, mActivityInfo);
+ }
return mInstanceId;
}
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 a31a773a76a0..eb82da8a8e9f 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
@@ -29,6 +29,8 @@ import static android.content.Intent.EXTRA_PACKAGE_NAME;
import static android.content.Intent.EXTRA_SHORTCUT_ID;
import static android.content.Intent.EXTRA_TASK_ID;
import static android.content.Intent.EXTRA_USER;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -52,9 +54,11 @@ import android.content.pm.LauncherApps;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.Log;
import android.util.Slog;
import androidx.annotation.IntDef;
@@ -63,8 +67,10 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
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.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.lang.annotation.Retention;
@@ -104,7 +110,9 @@ public class DragAndDropPolicy {
void start(DragSession session, InstanceId loggerSessionId) {
mLoggerSessionId = loggerSessionId;
mSession = session;
- RectF disallowHitRegion = (RectF) mSession.dragData.getExtra(EXTRA_DISALLOW_HIT_REGION);
+ RectF disallowHitRegion = mSession.appData != null
+ ? (RectF) mSession.appData.getExtra(EXTRA_DISALLOW_HIT_REGION)
+ : null;
if (disallowHitRegion == null) {
mDisallowHitRegion.setEmpty();
} else {
@@ -223,7 +231,7 @@ public class DragAndDropPolicy {
}
@VisibleForTesting
- void handleDrop(Target target, ClipData data) {
+ void handleDrop(Target target) {
if (target == null || !mTargets.contains(target)) {
return;
}
@@ -238,41 +246,77 @@ public class DragAndDropPolicy {
mSplitScreen.onDroppedToSplit(position, mLoggerSessionId);
}
- final ClipDescription description = data.getDescription();
- final Intent dragData = mSession.dragData;
- startClipDescription(description, dragData, position);
+ if (mSession.appData != null) {
+ launchApp(mSession, position);
+ } else {
+ launchIntent(mSession, position);
+ }
}
- private void startClipDescription(ClipDescription description, Intent intent,
- @SplitPosition int position) {
+ /**
+ * Launches an app provided by SysUI.
+ */
+ private void launchApp(DragSession session, @SplitPosition int position) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching app data at position=%d",
+ position);
+ final ClipDescription description = session.getClipDescription();
final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
final Bundle opts = baseActivityOpts.toBundle();
- if (intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
- opts.putAll(intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
+ if (session.appData.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
+ opts.putAll(session.appData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
}
// Put BAL flags to avoid activity start aborted.
opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
- final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
+ final UserHandle user = session.appData.getParcelableExtra(EXTRA_USER);
if (isTask) {
- final int taskId = intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
+ final int taskId = session.appData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
mStarter.startTask(taskId, position, opts);
} else if (isShortcut) {
- final String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
- final String id = intent.getStringExtra(EXTRA_SHORTCUT_ID);
+ final String packageName = session.appData.getStringExtra(EXTRA_PACKAGE_NAME);
+ final String id = session.appData.getStringExtra(EXTRA_SHORTCUT_ID);
mStarter.startShortcut(packageName, id, position, opts, user);
} else {
- final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
+ final PendingIntent launchIntent =
+ session.appData.getParcelableExtra(EXTRA_PENDING_INTENT);
+ if (Build.IS_DEBUGGABLE) {
+ if (!user.equals(launchIntent.getCreatorUserHandle())) {
+ Log.e(TAG, "Expected app intent's EXTRA_USER to match pending intent user");
+ }
+ }
mStarter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
position, opts);
}
}
/**
+ * Launches an intent sender provided by an application.
+ */
+ private void launchIntent(DragSession session, @SplitPosition int position) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching intent at position=%d",
+ position);
+ final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
+ baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
+ // TODO(b/255649902): Rework this so that SplitScreenController can always use the options
+ // instead of a fillInIntent since it's assuming that the PendingIntent is mutable
+ baseActivityOpts.setPendingIntentLaunchFlags(FLAG_ACTIVITY_NEW_TASK
+ | FLAG_ACTIVITY_MULTIPLE_TASK);
+
+ final Bundle opts = baseActivityOpts.toBundle();
+ // Put BAL flags to avoid activity start aborted.
+ opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
+ opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
+
+ mStarter.startIntent(session.launchableIntent,
+ session.launchableIntent.getCreatorUserHandle().getIdentifier(),
+ null /* fillIntent */, position, opts);
+ }
+
+ /**
* Interface for actually committing the task launches.
*/
public interface 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 445ba897c173..59d696918448 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
@@ -22,6 +22,8 @@ import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -38,12 +40,15 @@ import android.app.ActivityManager;
import android.app.StatusBarManager;
import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.view.DragEvent;
import android.view.SurfaceControl;
+import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.widget.LinearLayout;
@@ -65,7 +70,8 @@ import java.util.ArrayList;
/**
* Coordinates the visible drop targets for the current drag within a single display.
*/
-public class DragLayout extends LinearLayout {
+public class DragLayout extends LinearLayout
+ implements ViewTreeObserver.OnComputeInternalInsetsListener {
// While dragging the status bar is hidden.
private static final int HIDE_STATUS_BAR_FLAGS = StatusBarManager.DISABLE_NOTIFICATION_ICONS
@@ -90,7 +96,9 @@ public class DragLayout extends LinearLayout {
private int mDisplayMargin;
private int mDividerSize;
+ private int mLaunchIntentEdgeMargin;
private Insets mInsets = Insets.NONE;
+ private Region mTouchableRegion;
private boolean mIsShowing;
private boolean mHasDropped;
@@ -106,10 +114,11 @@ public class DragLayout extends LinearLayout {
mStatusBarManager = context.getSystemService(StatusBarManager.class);
mLastConfiguration.setTo(context.getResources().getConfiguration());
- mDisplayMargin = context.getResources().getDimensionPixelSize(
- R.dimen.drop_layout_display_margin);
- mDividerSize = context.getResources().getDimensionPixelSize(
- R.dimen.split_divider_bar_width);
+ final Resources res = context.getResources();
+ mDisplayMargin = res.getDimensionPixelSize(R.dimen.drop_layout_display_margin);
+ mDividerSize = res.getDimensionPixelSize(R.dimen.split_divider_bar_width);
+ mLaunchIntentEdgeMargin =
+ res.getDimensionPixelSize(R.dimen.drag_launchable_intent_edge_margin);
// Always use LTR because we assume dropZoneView1 is on the left and 2 is on the right when
// showing the highlight.
@@ -131,6 +140,66 @@ public class DragLayout extends LinearLayout {
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mTouchableRegion = Region.obtain();
+ getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ mTouchableRegion.recycle();
+ }
+
+ @Override
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inOutInfo) {
+ if (mSession != null && mSession.launchableIntent != null) {
+ inOutInfo.touchableRegion.set(mTouchableRegion);
+ inOutInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ updateTouchableRegion();
+ }
+
+ /**
+ * Updates the touchable region, this should be called after any configuration changes have
+ * been applied.
+ */
+ private void updateTouchableRegion() {
+ mTouchableRegion.setEmpty();
+ if (mSession != null && mSession.launchableIntent != null) {
+ final int width = getMeasuredWidth();
+ final int height = getMeasuredHeight();
+ if (mIsLeftRightSplit) {
+ mTouchableRegion.union(
+ new Rect(0, 0, mInsets.left + mLaunchIntentEdgeMargin, height));
+ mTouchableRegion.union(
+ new Rect(width - mInsets.right - mLaunchIntentEdgeMargin, 0, width,
+ height));
+ } else {
+ mTouchableRegion.union(
+ new Rect(0, 0, width, mInsets.top + mLaunchIntentEdgeMargin));
+ mTouchableRegion.union(
+ new Rect(0, height - mInsets.bottom - mLaunchIntentEdgeMargin, width,
+ height));
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Updating drag layout width=%d height=%d touchable region=%s",
+ width, height, mTouchableRegion);
+
+ // Reapply insets to update the touchable region
+ requestApplyInsets();
+ }
+ }
+
+
+ @Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
mInsets = insets.getInsets(Type.tappableElement() | Type.displayCutout());
recomputeDropTargets();
@@ -164,6 +233,7 @@ public class DragLayout extends LinearLayout {
mDropZoneView2.onThemeChange();
}
mLastConfiguration.setTo(newConfig);
+ requestLayout();
}
private void updateContainerMarginsForSingleTask() {
@@ -242,6 +312,7 @@ public class DragLayout extends LinearLayout {
mSplitScreenController.getStageBounds(topOrLeftBounds, bottomOrRightBounds);
updateDropZoneSizes(topOrLeftBounds, bottomOrRightBounds);
}
+ requestLayout();
}
private void updateDropZoneSizesForSingleTask() {
@@ -392,7 +463,7 @@ public class DragLayout extends LinearLayout {
mHasDropped = true;
// Process the drop
- mPolicy.handleDrop(mCurrentTarget, event.getClipData());
+ mPolicy.handleDrop(mCurrentTarget);
// Start animating the drop UI out with the drag surface
hide(event, dropCompleteCallback);
@@ -503,5 +574,7 @@ public class DragLayout extends LinearLayout {
pw.println(innerPrefix + "mIsShowing=" + mIsShowing);
pw.println(innerPrefix + "mHasDropped=" + mHasDropped);
pw.println(innerPrefix + "mCurrentTarget=" + mCurrentTarget);
+ pw.println(innerPrefix + "mInsets=" + mInsets);
+ pw.println(innerPrefix + "mTouchableRegion=" + mTouchableRegion);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index 353d702e5bc4..8f1bc59af1ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -21,12 +21,15 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.content.ClipData;
-import android.content.Context;
+import android.content.ClipDescription;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import androidx.annotation.Nullable;
+
import com.android.wm.shell.common.DisplayLayout;
import java.util.List;
@@ -39,7 +42,18 @@ public class DragSession {
private final ClipData mInitialDragData;
final DisplayLayout displayLayout;
- Intent dragData;
+ // The activity info associated with the activity in the appData or the launchableIntent
+ @Nullable
+ ActivityInfo activityInfo;
+ // The intent bundle that includes data about an app-type drag that is started by
+ // Launcher/SysUI. Only one of appDragData OR launchableIntent will be non-null for a session.
+ @Nullable
+ Intent appData;
+ // A launchable intent that is specified in the ClipData directly.
+ // Only one of appDragData OR launchableIntent will be non-null for a session.
+ @Nullable
+ PendingIntent launchableIntent;
+ // Stores the current running task at the time that the drag was initiated
ActivityManager.RunningTaskInfo runningTaskInfo;
@WindowConfiguration.WindowingMode
int runningTaskWinMode = WINDOWING_MODE_UNDEFINED;
@@ -47,7 +61,7 @@ public class DragSession {
int runningTaskActType = ACTIVITY_TYPE_STANDARD;
boolean dragItemSupportsSplitscreen;
- DragSession(Context context, ActivityTaskManager activityTaskManager,
+ DragSession(ActivityTaskManager activityTaskManager,
DisplayLayout dispLayout, ClipData data) {
mActivityTaskManager = activityTaskManager;
mInitialDragData = data;
@@ -55,6 +69,14 @@ public class DragSession {
}
/**
+ * Returns the clip description associated with the drag.
+ * @return
+ */
+ ClipDescription getClipDescription() {
+ return mInitialDragData.getDescription();
+ }
+
+ /**
* Updates the session data based on the current state of the system.
*/
void update() {
@@ -67,9 +89,11 @@ public class DragSession {
runningTaskActType = task.getActivityType();
}
- final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
- dragItemSupportsSplitscreen = info == null
- || ActivityInfo.isResizeableMode(info.resizeMode);
- dragData = mInitialDragData.getItemAt(0).getIntent();
+ activityInfo = mInitialDragData.getItemAt(0).getActivityInfo();
+ // TODO: This should technically check & respect config_supportsNonResizableMultiWindow
+ dragItemSupportsSplitscreen = activityInfo == null
+ || ActivityInfo.isResizeableMode(activityInfo.resizeMode);
+ appData = mInitialDragData.getItemAt(0).getIntent();
+ launchableIntent = DragUtils.getLaunchIntent(mInitialDragData);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
index 7c0883d2538f..24f8e186bf76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
@@ -20,9 +20,14 @@ 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.app.PendingIntent;
+import android.content.ClipData;
import android.content.ClipDescription;
import android.view.DragEvent;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
/** Collection of utility classes for handling drag and drop. */
public class DragUtils {
private static final String TAG = "DragUtils";
@@ -31,8 +36,21 @@ public class DragUtils {
* Returns whether we can handle this particular drag.
*/
public static boolean canHandleDrag(DragEvent event) {
- return event.getClipData().getItemCount() > 0
- && (isAppDrag(event.getClipDescription()));
+ if (event.getClipData().getItemCount() <= 0) {
+ // No clip data, ignore this drag
+ return false;
+ }
+ if (isAppDrag(event.getClipDescription())) {
+ // Clip data contains an app drag initiated from SysUI, handle it
+ return true;
+ }
+ if (com.android.window.flags.Flags.delegateUnhandledDrags()
+ && getLaunchIntent(event) != null) {
+ // Clip data contains a launchable intent drag, handle it
+ return true;
+ }
+ // Otherwise ignore
+ return false;
}
/**
@@ -45,6 +63,31 @@ public class DragUtils {
}
/**
+ * Returns a launchable intent in the given `DragEvent` or `null` if there is none.
+ */
+ @Nullable
+ public static PendingIntent getLaunchIntent(@NonNull DragEvent dragEvent) {
+ return getLaunchIntent(dragEvent.getClipData());
+ }
+
+ /**
+ * Returns a launchable intent in the given `ClipData` or `null` if there is none.
+ */
+ @Nullable
+ public static PendingIntent getLaunchIntent(@NonNull ClipData data) {
+ for (int i = 0; i < data.getItemCount(); i++) {
+ final ClipData.Item item = data.getItemAt(i);
+ if (item.getIntentSender() != null) {
+ final PendingIntent intent = new PendingIntent(item.getIntentSender().getTarget());
+ if (intent != null && intent.isActivity()) {
+ return intent;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
* Returns a list of the mime types provided in the clip description.
*/
public static String getMimeTypesConcatenated(ClipDescription description) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
new file mode 100644
index 000000000000..8826141fb406
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.draganddrop
+
+import android.app.ActivityManager
+import android.os.RemoteException
+import android.util.Log
+import android.view.DragEvent
+import android.view.IWindowManager
+import android.window.IGlobalDragListener
+import android.window.IUnhandledDragCallback
+import androidx.annotation.VisibleForTesting
+import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import java.util.function.Consumer
+
+/**
+ * Manages the listener and callbacks for unhandled global drags.
+ * This is only used by DragAndDropController and should not be used directly by other classes.
+ */
+class GlobalDragListener(
+ private val wmService: IWindowManager,
+ private val mainExecutor: ShellExecutor
+) {
+ private var callback: GlobalDragListenerCallback? = null
+
+ private val globalDragListener: IGlobalDragListener =
+ object : IGlobalDragListener.Stub() {
+ override fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {
+ mainExecutor.execute() {
+ this@GlobalDragListener.onCrossWindowDrop(taskInfo)
+ }
+ }
+
+ override fun onUnhandledDrop(event: DragEvent, callback: IUnhandledDragCallback) {
+ mainExecutor.execute() {
+ this@GlobalDragListener.onUnhandledDrop(event, callback)
+ }
+ }
+ }
+
+ /**
+ * Callbacks for global drag events.
+ */
+ interface GlobalDragListenerCallback {
+ /**
+ * Called when a global drag is successfully handled by another window.
+ */
+ fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {}
+
+ /**
+ * Called when a global drag is unhandled (ie. dropped outside of all visible windows, or
+ * dropped on a window that does not want to handle it).
+ *
+ * The implementer _must_ call onFinishedCallback, and if it consumes the drop, then it is
+ * also responsible for releasing up the drag surface provided via the drag event.
+ */
+ fun onUnhandledDrop(dragEvent: DragEvent, onFinishedCallback: Consumer<Boolean>) {}
+ }
+
+ /**
+ * Sets a listener for callbacks when an unhandled drag happens.
+ */
+ fun setListener(listener: GlobalDragListenerCallback?) {
+ val updateWm = (callback == null && listener != null)
+ || (callback != null && listener == null)
+ callback = listener
+ if (updateWm) {
+ try {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "%s unhandled drag listener",
+ if (callback != null) "Registering" else "Unregistering")
+ wmService.setGlobalDragListener(
+ if (callback != null) globalDragListener else null)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Failed to set unhandled drag listener")
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "onCrossWindowDrop: %s", taskInfo)
+ callback?.onCrossWindowDrop(taskInfo)
+ }
+
+ @VisibleForTesting
+ fun onUnhandledDrop(dragEvent: DragEvent, wmCallback: IUnhandledDragCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "onUnhandledDrop: %s", dragEvent)
+ if (callback == null) {
+ wmCallback.notifyUnhandledDropComplete(false)
+ return
+ }
+
+ callback?.onUnhandledDrop(dragEvent) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Notifying onUnhandledDrop complete: %b", it)
+ wmCallback.notifyUnhandledDropComplete(it)
+ }
+ }
+
+ companion object {
+ private val TAG = GlobalDragListener::class.java.simpleName
+ }
+}
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
index a80241e0ac5c..f2bdcae31956 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.freeform;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM;
import android.app.ActivityManager.RunningTaskInfo;
@@ -151,6 +153,9 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
@Override
public void onFocusTaskChanged(RunningTaskInfo taskInfo) {
+ if (taskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ return;
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
"Freeform Task Focus Changed: #%d focused=%b",
taskInfo.taskId, taskInfo.isFocused);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 6b6a7bc42046..ffcc526eac40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -112,7 +112,6 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
onChangeTransitionReady(change, startT, finishT);
break;
}
- mWindowDecorViewModel.onTransitionReady(transition, info, change);
}
mTransitionToTaskInfo.put(transition, taskInfoList);
}
@@ -153,8 +152,6 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
@Override
public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
- mWindowDecorViewModel.onTransitionMerged(merged, playing);
-
final List<ActivityManager.RunningTaskInfo> infoOfMerged =
mTransitionToTaskInfo.get(merged);
if (infoOfMerged == null) {
@@ -178,7 +175,6 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
final List<ActivityManager.RunningTaskInfo> taskInfo =
mTransitionToTaskInfo.getOrDefault(transition, Collections.emptyList());
mTransitionToTaskInfo.remove(transition);
- mWindowDecorViewModel.onTransitionFinished(transition);
for (int i = 0; i < taskInfo.size(); ++i) {
mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index e63bbc07bc41..73de231fb63a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -27,7 +27,7 @@ import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
import static android.view.WindowManager.TRANSIT_SLEEP;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS
index ec09827fa4d1..afddfab99a2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS
@@ -1,3 +1,2 @@
# WM shell sub-module pip owner
hwwang@google.com
-mateuszc@google.com
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 3635165d76ce..bd186ba22588 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
@@ -25,7 +25,6 @@ import static android.util.RotationUtils.rotateBounds;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
@@ -64,6 +63,7 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.util.Rational;
import android.view.Choreographer;
import android.view.Display;
import android.view.Surface;
@@ -86,6 +86,8 @@ import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipMenuController;
+import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.phone.PipMotionHelper;
@@ -111,7 +113,7 @@ import java.util.function.IntConsumer;
* see also {@link PipMotionHelper}.
*/
public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
- DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener {
+ DisplayController.OnDisplaysChangedListener {
private static final String TAG = PipTaskOrganizer.class.getSimpleName();
/**
@@ -125,6 +127,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
SystemProperties.getInt(
"persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 400);
+ private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.005f;
+
private final Context mContext;
private final SyncTransactionQueue mSyncTransactionQueue;
private final PipBoundsState mPipBoundsState;
@@ -140,6 +144,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private final int mCrossFadeAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional<SplitScreenController> mSplitScreenOptional;
+ @Nullable private final PipPerfHintController mPipPerfHintController;
protected final ShellTaskOrganizer mTaskOrganizer;
protected final ShellExecutor mMainExecutor;
@@ -157,10 +162,30 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
new PipAnimationController.PipAnimationCallback() {
private boolean mIsCancelled;
+ @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
+ private void onHighPerfSessionTimeout(
+ PipPerfHintController.PipHighPerfSession session) {}
+
+ private void cleanUpHighPerfSessionMaybe() {
+ if (mPipHighPerfSession != null) {
+ // Close the high perf session once pointer interactions are over;
+ mPipHighPerfSession.close();
+ mPipHighPerfSession = null;
+ }
+ }
+
@Override
public void onPipAnimationStart(TaskInfo taskInfo,
PipAnimationController.PipTransitionAnimator animator) {
+ if (mPipPerfHintController != null) {
+ // Start a high perf session with a timeout callback.
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout,
+ "PipTaskOrganizer::mPipAnimationCallback");
+ }
+
final int direction = animator.getTransitionDirection();
mIsCancelled = false;
sendOnPipTransitionStarted(direction);
@@ -169,6 +194,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@Override
public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
PipAnimationController.PipTransitionAnimator animator) {
+ // Close the high perf session if needed.
+ cleanUpHighPerfSessionMaybe();
+
final int direction = animator.getTransitionDirection();
if (mIsCancelled) {
sendOnPipTransitionFinished(direction);
@@ -334,6 +362,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@Nullable
SurfaceControl mPipOverlay;
+ /**
+ * The app bounds used for the buffer size of the
+ * {@link com.android.wm.shell.pip.PipContentOverlay.PipAppIconOverlay}.
+ *
+ * Note that this is empty if the overlay is removed or if it's some other type of overlay
+ * defined in {@link PipContentOverlay}.
+ */
+ @NonNull
+ final Rect mAppBounds = new Rect();
+
public PipTaskOrganizer(Context context,
@NonNull SyncTransactionQueue syncTransactionQueue,
@NonNull PipTransitionState pipTransitionState,
@@ -346,6 +384,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@NonNull PipTransitionController pipTransitionController,
@NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenOptional,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -371,6 +410,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
mSplitScreenOptional = splitScreenOptional;
+ mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
mTaskOrganizer = shellTaskOrganizer;
mMainExecutor = mainExecutor;
@@ -379,7 +419,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mMainExecutor.execute(() -> {
mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
});
- mTaskOrganizer.addFocusListener(this);
mPipTransitionController.setPipOrganizer(this);
displayController.addDisplayWindowListener(this);
pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
@@ -464,15 +503,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
* Expect {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)} afterwards.
*/
public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
- SurfaceControl overlay) {
+ SurfaceControl overlay, Rect appBounds) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "stopSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
+ "stopSwipePipToHome: %s, stat=%s", componentName, mPipTransitionState);
// do nothing if there is no startSwipePipToHome being called before
if (!mPipTransitionState.getInSwipePipToHomeTransition()) {
return;
}
mPipBoundsState.setBounds(destinationBounds);
- mPipOverlay = overlay;
+ setContentOverlay(overlay, appBounds);
if (ENABLE_SHELL_TRANSITIONS && overlay != null) {
// With Shell transition, the overlay was attached to the remote transition leash, which
// will be removed when the current transition is finished, so we need to reparent it
@@ -589,9 +628,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
if (requestEnterSplit && mSplitScreenOptional.isPresent()) {
wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
- mSplitScreenOptional.get().prepareEnterSplitScreen(wct, mTaskInfo,
- isPipToTopLeft()
- ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ mSplitScreenOptional.get().onPipExpandToSplit(wct, mTaskInfo);
mPipTransitionController.startExitTransition(
TRANSIT_EXIT_PIP_TO_SPLIT, wct, destinationBounds);
return;
@@ -733,6 +770,37 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mPictureInPictureParams.getTitle());
mPipParamsChangedForwarder.notifySubtitleChanged(
mPictureInPictureParams.getSubtitle());
+
+ if (mPictureInPictureParams.hasSourceBoundsHint()
+ && mPictureInPictureParams.hasSetAspectRatio()) {
+ Rational sourceRectHintAspectRatio = new Rational(
+ mPictureInPictureParams.getSourceRectHint().width(),
+ mPictureInPictureParams.getSourceRectHint().height());
+ if (sourceRectHintAspectRatio.compareTo(
+ mPictureInPictureParams.getAspectRatio()) != 0) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "Aspect ratio of source rect hint (%d/%d) does not match the provided "
+ + "aspect ratio value (%d/%d). Consider matching them for "
+ + "improved animation. Future releases might override the "
+ + "value to match.",
+ mPictureInPictureParams.getSourceRectHint().width(),
+ mPictureInPictureParams.getSourceRectHint().height(),
+ mPictureInPictureParams.getAspectRatio().getNumerator(),
+ mPictureInPictureParams.getAspectRatio().getDenominator());
+ }
+ if (Math.abs(sourceRectHintAspectRatio.floatValue()
+ - mPictureInPictureParams.getAspectRatioFloat())
+ > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "Aspect ratio of source rect hint (%f) does not match the provided "
+ + "aspect ratio value (%f) and is above threshold of %f. "
+ + "Consider matching them for improved animation. Future "
+ + "releases might override the value to match.",
+ sourceRectHintAspectRatio.floatValue(),
+ mPictureInPictureParams.getAspectRatioFloat(),
+ PIP_ASPECT_RATIO_MISMATCH_THRESHOLD);
+ }
+ }
}
mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo);
@@ -1017,11 +1085,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
@Override
- public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
- mPipMenuController.onFocusTaskChanged(taskInfo);
- }
-
- @Override
public boolean supportCompatUI() {
// PIP doesn't support compat.
return false;
@@ -1305,7 +1368,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
* Handles all changes to the PictureInPictureParams.
*/
protected void applyNewPictureInPictureParams(@NonNull PictureInPictureParams params) {
- if (mDeferredTaskInfo != null || PipUtils.aspectRatioChanged(params.getAspectRatioFloat(),
+ if (PipUtils.aspectRatioChanged(params.getAspectRatioFloat(),
mPictureInPictureParams.getAspectRatioFloat())) {
if (mPipBoundsAlgorithm.isValidPictureInPictureAspectRatio(
params.getAspectRatioFloat())) {
@@ -1318,8 +1381,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
TAG, params.hasSetAspectRatio(), params.getAspectRatioFloat());
}
}
- if (mDeferredTaskInfo != null
- || PipUtils.remoteActionsChanged(params.getActions(),
+ if (PipUtils.remoteActionsChanged(params.getActions(),
mPictureInPictureParams.getActions())
|| !PipUtils.remoteActionsMatch(params.getCloseAction(),
mPictureInPictureParams.getCloseAction())) {
@@ -1882,13 +1944,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
private void removeContentOverlay(SurfaceControl surface, Runnable callback) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "removeContentOverlay: %s, state=%s, surface=%s",
+ mTaskInfo, mPipTransitionState, surface);
if (mPipOverlay != null) {
if (mPipOverlay != surface) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: trying to remove overlay (%s) which is not local reference (%s)",
TAG, surface, mPipOverlay);
}
- mPipOverlay = null;
+ clearContentOverlay();
}
if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
// Avoid double removal, which is fatal.
@@ -1905,6 +1970,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (callback != null) callback.run();
}
+ void clearContentOverlay() {
+ mPipOverlay = null;
+ mAppBounds.setEmpty();
+ }
+
+ void setContentOverlay(@Nullable SurfaceControl leash, @NonNull Rect appBounds) {
+ mPipOverlay = leash;
+ if (mPipOverlay != null) {
+ mAppBounds.set(appBounds);
+ } else {
+ mAppBounds.setEmpty();
+ }
+ }
+
private void resetShadowRadius() {
if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
// mLeash is undefined when in PipTransitionState.UNDEFINED
@@ -1950,9 +2029,13 @@ 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 + "mPipOverlay=" + mPipOverlay);
pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState());
pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
mPipTransitionController.dump(pw, innerPrefix);
+ if (mPipPerfHintController != null) {
+ mPipPerfHintController.dump(pw, innerPrefix);
+ }
}
@Override
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 f5f15d81ea44..6a1a62ea30a1 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
@@ -68,13 +68,14 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.CounterRotatorHelper;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -141,8 +142,6 @@ public class PipTransition extends PipTransitionController {
/** Whether the PIP window has fade out for fixed rotation. */
private boolean mHasFadeOut;
- private Rect mInitBounds = new Rect();
-
/** Used for setting transform to a transaction from animator. */
private final PipAnimationController.PipTransactionHandler mTransactionConsumer =
new PipAnimationController.PipTransactionHandler() {
@@ -287,12 +286,6 @@ public class PipTransition extends PipTransitionController {
// For transition that we don't animate, but contains the PIP leash, we need to update the
// PIP surface, otherwise it will be reset after the transition.
if (currentPipTaskChange != null) {
- // Set the "end" bounds of pip. The default setup uses the start bounds. Since this is
- // changing the *finish*Transaction, we need to use the end bounds. This will also
- // make sure that the fade-in animation (below) uses the end bounds as well.
- if (!currentPipTaskChange.getEndAbsBounds().isEmpty()) {
- mPipBoundsState.setBounds(currentPipTaskChange.getEndAbsBounds());
- }
updatePipForUnhandledTransition(currentPipTaskChange, startTransaction,
finishTransaction);
}
@@ -465,12 +458,13 @@ public class PipTransition extends PipTransitionController {
mSurfaceTransactionHelper.crop(tx, leash, destinationBounds)
.resetScale(tx, leash, destinationBounds)
.round(tx, leash, true /* applyCornerRadius */);
- if (mPipOrganizer.mPipOverlay != null && !mInitBounds.isEmpty()) {
+ final Rect appBounds = mPipOrganizer.mAppBounds;
+ if (mPipOrganizer.mPipOverlay != null && !appBounds.isEmpty()) {
// Resetting the scale for pinned task while re-adjusting its crop,
// also scales the overlay. So we need to update the overlay leash too.
Rect overlayBounds = new Rect(destinationBounds);
final int overlaySize = PipContentOverlay.PipAppIconOverlay
- .getOverlaySize(mInitBounds, destinationBounds);
+ .getOverlaySize(appBounds, destinationBounds);
overlayBounds.offsetTo(
(destinationBounds.width() - overlaySize) / 2,
@@ -479,7 +473,6 @@ public class PipTransition extends PipTransitionController {
mPipOrganizer.mPipOverlay, overlayBounds);
}
}
- mInitBounds.setEmpty();
wct.setBoundsChangeTransaction(taskInfo.token, tx);
}
final int displayRotation = taskInfo.getConfiguration().windowConfiguration
@@ -617,7 +610,7 @@ public class PipTransition extends PipTransitionController {
// if overlay is present remove it immediately, as exit transition came before it faded out
if (mPipOrganizer.mPipOverlay != null) {
startTransaction.remove(mPipOrganizer.mPipOverlay);
- clearPipOverlay();
+ mPipOrganizer.clearContentOverlay();
}
if (pipChange == null) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -951,9 +944,6 @@ public class PipTransition extends PipTransitionController {
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
final Rect currentBounds = pipChange.getStartAbsBounds();
- // Cache the start bounds for overlay manipulations as a part of finishCallback.
- mInitBounds.set(currentBounds);
-
int rotationDelta = deltaRotation(startRotation, endRotation);
Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
taskInfo.pictureInPictureParams, currentBounds, destinationBounds);
@@ -992,10 +982,12 @@ public class PipTransition extends PipTransitionController {
0 /* startingAngle */, rotationDelta);
if (sourceHintRect == null) {
// We use content overlay when there is no source rect hint to enter PiP use bounds
- // animation.
+ // animation. We also temporarily disallow app icon overlay and use color overlay
+ // instead when in fixed rotation enter PiP in button nav with no sourceRectHint.
+ // TODO(b/319286295): Fix App Icon Overlay animation in fixed rotation in btn nav.
// TODO(b/272819817): cleanup the null-check and extra logging.
final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null;
- if (hasTopActivityInfo) {
+ if (hasTopActivityInfo && mFixedRotationState != FIXED_ROTATION_TRANSITION) {
animator.setAppIconContentOverlay(
mContext, currentBounds, destinationBounds, taskInfo.topActivityInfo,
mPipBoundsState.getLauncherState().getAppIconSizePx());
@@ -1022,7 +1014,7 @@ public class PipTransition extends PipTransitionController {
} else {
throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
}
- mPipOrganizer.mPipOverlay = animator.getContentOverlayLeash();
+ mPipOrganizer.setContentOverlay(animator.getContentOverlayLeash(), currentBounds);
animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration);
@@ -1045,6 +1037,7 @@ public class PipTransition extends PipTransitionController {
private void computeEnterPipRotatedBounds(int rotationDelta, int startRotation, int endRotation,
TaskInfo taskInfo, Rect outDestinationBounds, @Nullable Rect outSourceHintRect) {
mPipDisplayLayoutState.rotateTo(endRotation);
+ mPipBoundsState.updateMinMaxSize(mPipBoundsState.getAspectRatio());
final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
@@ -1073,10 +1066,6 @@ public class PipTransition extends PipTransitionController {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation);
}
- Rect appBounds = pipTaskInfo.configuration.windowConfiguration.getAppBounds();
- if (mFixedRotationState == FIXED_ROTATION_CALLBACK && appBounds != null) {
- mInitBounds.set(appBounds);
- }
final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mPipOverlay;
if (swipePipToHomeOverlay != null) {
// Launcher fade in the overlay on top of the fullscreen Task. It is possible we
@@ -1106,7 +1095,7 @@ public class PipTransition extends PipTransitionController {
sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
if (swipePipToHomeOverlay != null) {
mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay,
- this::clearPipOverlay /* callback */, false /* withStartDelay */);
+ null /* callback */, false /* withStartDelay */);
}
mPipTransitionState.setInSwipePipToHomeTransition(false);
}
@@ -1250,10 +1239,6 @@ public class PipTransition extends PipTransitionController {
mPipMenuController.updateMenuBounds(destinationBounds);
}
- private void clearPipOverlay() {
- mPipOrganizer.mPipOverlay = null;
- }
-
@Override
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
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 04911c0bc064..32442f740a52 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
@@ -23,12 +23,16 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI
import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
import android.annotation.Nullable;
+import android.app.ActivityTaskManager;
+import android.app.Flags;
import android.app.PictureInPictureParams;
+import android.app.PictureInPictureUiState;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.os.IBinder;
+import android.os.RemoteException;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -37,16 +41,20 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
/**
* Responsible supplying PiP Transitions.
@@ -116,6 +124,17 @@ public abstract class PipTransitionController implements Transitions.TransitionH
}
/**
+ * Called when the Shell wants to start resizing Pip transition/animation.
+ *
+ * @param onFinishResizeCallback callback guaranteed to execute when animation ends and
+ * client completes any potential draws upon WM state updates.
+ */
+ public void startResizeTransition(WindowContainerTransaction wct,
+ Consumer<Rect> onFinishResizeCallback) {
+ // Default implementation does nothing.
+ }
+
+ /**
* Called when the transition animation can't continue (eg. task is removed during
* animation)
*/
@@ -168,6 +187,17 @@ public abstract class PipTransitionController implements Transitions.TransitionH
final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
callback.onPipTransitionStarted(direction, pipBounds);
}
+ if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
+ try {
+ ActivityTaskManager.getService().onPictureInPictureUiStateChanged(
+ new PictureInPictureUiState.Builder()
+ .setTransitioningToPip(true)
+ .build());
+ } catch (RemoteException | IllegalStateException e) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "Failed to set alert PiP state change.");
+ }
+ }
}
protected void sendOnPipTransitionFinished(
@@ -176,6 +206,17 @@ public abstract class PipTransitionController implements Transitions.TransitionH
final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
callback.onPipTransitionFinished(direction);
}
+ if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
+ try {
+ ActivityTaskManager.getService().onPictureInPictureUiStateChanged(
+ new PictureInPictureUiState.Builder()
+ .setTransitioningToPip(false)
+ .build());
+ } catch (RemoteException | IllegalStateException e) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "Failed to set alert PiP state change.");
+ }
+ }
}
protected void sendOnPipTransitionCancelled(
@@ -264,6 +305,12 @@ public abstract class PipTransitionController implements Transitions.TransitionH
public void end() {
}
+ /** Starts the {@link android.window.SystemPerformanceHinter.HighPerfSession}. */
+ public void startHighPerfSession() {}
+
+ /** Closes the {@link android.window.SystemPerformanceHinter.HighPerfSession}. */
+ public void closeHighPerfSession() {}
+
/**
* Callback interface for PiP transitions (both from and to PiP mode)
*/
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 760652625f9e..0169e8c40f45 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
@@ -19,12 +19,9 @@ package com.android.wm.shell.pip.phone;
import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.RemoteAction;
import android.content.Context;
-import android.graphics.Matrix;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.os.Debug;
import android.os.Handler;
import android.os.RemoteException;
@@ -41,16 +38,13 @@ import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.common.pip.PipMediaController.ActionListener;
+import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.PipMenuController;
-import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.splitscreen.SplitScreenController;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import java.util.Optional;
/**
* Manages the PiP menu view which can show menu options or a scrim.
@@ -99,30 +93,18 @@ public class PhonePipMenuController implements PipMenuController {
* Called when the PIP requested to show the menu.
*/
void onPipShowMenu();
-
- /**
- * Called when the PIP requested to enter Split.
- */
- void onEnterSplit();
}
- private final Matrix mMoveTransform = new Matrix();
- private final Rect mTmpSourceBounds = new Rect();
- private final RectF mTmpSourceRectF = new RectF();
- private final RectF mTmpDestinationRectF = new RectF();
private final Context mContext;
private final PipBoundsState mPipBoundsState;
private final PipMediaController mMediaController;
private final ShellExecutor mMainExecutor;
private final Handler mMainHandler;
- private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
- mSurfaceControlTransactionFactory;
private final float[] mTmpTransform = new float[9];
private final ArrayList<Listener> mListeners = new ArrayList<>();
private final SystemWindows mSystemWindows;
- private final Optional<SplitScreenController> mSplitScreenController;
private final PipUiEventLogger mPipUiEventLogger;
private List<RemoteAction> mAppActions;
@@ -145,7 +127,6 @@ public class PhonePipMenuController implements PipMenuController {
public PhonePipMenuController(Context context, PipBoundsState pipBoundsState,
PipMediaController mediaController, SystemWindows systemWindows,
- Optional<SplitScreenController> splitScreenOptional,
PipUiEventLogger pipUiEventLogger,
ShellExecutor mainExecutor, Handler mainHandler) {
mContext = context;
@@ -154,11 +135,7 @@ public class PhonePipMenuController implements PipMenuController {
mSystemWindows = systemWindows;
mMainExecutor = mainExecutor;
mMainHandler = mainHandler;
- mSplitScreenController = splitScreenOptional;
mPipUiEventLogger = pipUiEventLogger;
-
- mSurfaceControlTransactionFactory =
- new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
}
public boolean isMenuVisible() {
@@ -190,7 +167,7 @@ public class PhonePipMenuController implements PipMenuController {
detachPipMenuView();
}
mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler,
- mSplitScreenController, mPipUiEventLogger);
+ mPipUiEventLogger);
mPipMenuView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -251,13 +228,6 @@ public class PhonePipMenuController implements PipMenuController {
updateMenuLayout(destinationBounds);
}
- @Override
- public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
- if (mPipMenuView != null) {
- mPipMenuView.onFocusTaskChanged(taskInfo);
- }
- }
-
/**
* Tries to grab a surface control from {@link PipMenuView}. If this isn't available for some
* reason (ie. the window isn't ready yet, thus {@link android.view.ViewRootImpl} is
@@ -485,10 +455,6 @@ public class PhonePipMenuController implements PipMenuController {
mListeners.forEach(Listener::onPipDismiss);
}
- void onEnterSplit() {
- mListeners.forEach(Listener::onEnterSplit);
- }
-
/**
* @return the best set of actions to show in the PiP menu.
*/
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 63f20fd8e997..84afed18b8d4 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
@@ -48,7 +48,6 @@ import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.Pair;
-import android.util.Size;
import android.view.DisplayInfo;
import android.view.InsetsState;
import android.view.SurfaceControl;
@@ -75,6 +74,8 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.IPip;
+import com.android.wm.shell.common.pip.IPipAnimationListener;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
@@ -85,8 +86,6 @@ import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
-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;
@@ -244,6 +243,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// The same rotation may have been set by auto PiP-able or fixed rotation. So notify
// the change with fromRotation=false to apply the rotated destination bounds from
// PipTaskOrganizer#onMovementBoundsChanged.
+ // We need to update the bounds scale in case this was from fixed rotation, as the
+ // current proportion was computed using the previous orientation max size and is wrong.
+ mPipBoundsState.updateBoundsScale();
updateMovementBounds(null, false /* fromRotation */,
false /* fromImeAdjustment */, false /* fromShelfAdjustment */, t);
return;
@@ -974,16 +976,31 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG,
hotseatKeepClearArea);
onDisplayRotationChangedNotInPip(mContext, launcherRotation);
+ // cache current min/max size
+ Point minSize = mPipBoundsState.getMinSize();
+ Point maxSize = mPipBoundsState.getMaxSize();
+ final float aspectRatioFloat;
+ if (pictureInPictureParams.hasSetAspectRatio()) {
+ aspectRatioFloat = pictureInPictureParams.getAspectRatioFloat();
+ } else {
+ aspectRatioFloat = mPipBoundsAlgorithm.getDefaultAspectRatio();
+ }
+ mPipBoundsState.updateMinMaxSize(aspectRatioFloat);
final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo,
pictureInPictureParams);
+ // restore min/max size, as this is referenced later in OnDisplayChangingListener and needs
+ // to reflect the pre-rotation state for it to work
+ mPipBoundsState.setMinSize(minSize.x, minSize.y);
+ mPipBoundsState.setMaxSize(maxSize.x, maxSize.y);
// sync mPipBoundsState with the newly calculated bounds.
mPipBoundsState.setNormalBounds(entryBounds);
return entryBounds;
}
private void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
- SurfaceControl overlay) {
- mPipTaskOrganizer.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay);
+ SurfaceControl overlay, Rect appBounds) {
+ mPipTaskOrganizer.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay,
+ appBounds);
}
private void abortSwipePipToHome(int taskId, ComponentName componentName) {
@@ -1038,22 +1055,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
/** Save the state to restore to on re-entry. */
public void saveReentryState(Rect pipBounds) {
float snapFraction = mPipBoundsAlgorithm.getSnapFraction(pipBounds);
-
- if (!mPipBoundsState.hasUserResizedPip()) {
- mPipBoundsState.saveReentryState(null /* bounds */, snapFraction);
- return;
- }
-
- Size reentrySize = new Size(pipBounds.width(), pipBounds.height());
-
- // TODO: b/279937014 Investigate why userResizeBounds are empty with shell transitions on
- // fallback to using the userResizeBounds if userResizeBounds are not empty
- if (!mTouchHandler.getUserResizeBounds().isEmpty()) {
- Rect userResizeBounds = mTouchHandler.getUserResizeBounds();
- reentrySize = new Size(userResizeBounds.width(), userResizeBounds.height());
- }
-
- mPipBoundsState.saveReentryState(reentrySize, snapFraction);
+ mPipBoundsState.saveReentryState(snapFraction);
}
@Override
@@ -1143,6 +1145,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// Update the display layout
mPipDisplayLayoutState.rotateTo(toRotation);
+ mTouchHandler.updateMinMaxSize(mPipBoundsState.getAspectRatio());
+
+ postChangeStackBounds.set(0, 0,
+ (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()),
+ (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale()));
// Calculate the stack bounds in the new orientation based on same fraction along the
// rotated movement bounds.
@@ -1280,13 +1287,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void stopSwipePipToHome(int taskId, ComponentName componentName,
- Rect destinationBounds, SurfaceControl overlay) {
+ Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
if (overlay != null) {
overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome");
}
executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
(controller) -> controller.stopSwipePipToHome(
- taskId, componentName, destinationBounds, overlay));
+ taskId, componentName, destinationBounds, overlay, appBounds));
}
@Override
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 4e75847b6bc0..f92938989637 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
@@ -131,7 +131,8 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
});
mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
@Override
- public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject) {
// Show the dismiss target, in case the initial touch event occurred within
// the magnetic field radius.
if (mEnableDismissDragToEdge) {
@@ -141,6 +142,7 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject,
float velX, float velY, boolean wasFlungOut) {
if (wasFlungOut) {
mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */);
@@ -151,7 +153,8 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
}
@Override
- public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject) {
if (mEnableDismissDragToEdge) {
mMainExecutor.executeDelayed(() -> {
mMotionHelper.notifyDismissalPending();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java
index 06446573840c..321b739920f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java
@@ -32,7 +32,6 @@ public class PipMenuIconsAlgorithm {
protected ViewGroup mViewRoot;
protected ViewGroup mTopEndContainer;
protected View mDragHandle;
- protected View mEnterSplitButton;
protected View mSettingsButton;
protected View mDismissButton;
@@ -43,11 +42,10 @@ public class PipMenuIconsAlgorithm {
* Bind the necessary views.
*/
public void bindViews(ViewGroup viewRoot, ViewGroup topEndContainer, View dragHandle,
- View enterSplitButton, View settingsButton, View dismissButton) {
+ View settingsButton, View dismissButton) {
mViewRoot = viewRoot;
mTopEndContainer = topEndContainer;
mDragHandle = dragHandle;
- mEnterSplitButton = enterSplitButton;
mSettingsButton = settingsButton;
mDismissButton = dismissButton;
}
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 63cef9e41f98..15342be0e8b7 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
@@ -16,7 +16,6 @@
package com.android.wm.shell.pip.phone;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
@@ -35,10 +34,8 @@ import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.PendingIntent;
import android.app.RemoteAction;
-import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -69,14 +66,12 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.splitscreen.SplitScreenController;
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.Optional;
/**
* Translucent window that gets started on top of a task in PIP to allow the user to control it.
@@ -114,7 +109,6 @@ public class PipMenuView extends FrameLayout {
private boolean mAllowMenuTimeout = true;
private boolean mAllowTouches = true;
private int mDismissFadeOutDurationMs;
- private boolean mFocusedTaskAllowSplitScreen;
private final List<RemoteAction> mActions = new ArrayList<>();
private RemoteAction mCloseAction;
@@ -127,7 +121,6 @@ public class PipMenuView extends FrameLayout {
private AnimatorSet mMenuContainerAnimator;
private final PhonePipMenuController mController;
- private final Optional<SplitScreenController> mSplitScreenControllerOptional;
private final PipUiEventLogger mPipUiEventLogger;
private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
@@ -152,7 +145,6 @@ public class PipMenuView extends FrameLayout {
protected View mViewRoot;
protected View mSettingsButton;
protected View mDismissButton;
- protected View mEnterSplitButton;
protected View mTopEndContainer;
protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
@@ -161,14 +153,12 @@ public class PipMenuView extends FrameLayout {
public PipMenuView(Context context, PhonePipMenuController controller,
ShellExecutor mainExecutor, Handler mainHandler,
- Optional<SplitScreenController> splitScreenController,
PipUiEventLogger pipUiEventLogger) {
super(context, null, 0);
mContext = context;
mController = controller;
mMainExecutor = mainExecutor;
mMainHandler = mainHandler;
- mSplitScreenControllerOptional = splitScreenController;
mPipUiEventLogger = pipUiEventLogger;
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -200,17 +190,6 @@ public class PipMenuView extends FrameLayout {
}
});
- mEnterSplitButton = findViewById(R.id.enter_split);
- mEnterSplitButton.setAlpha(0);
- mEnterSplitButton.setOnClickListener(v -> {
- if (mEnterSplitButton.getAlpha() != 0) {
- enterSplit();
- }
- });
-
- // this disables the ripples
- mEnterSplitButton.setEnabled(false);
-
findViewById(R.id.resize_handle).setAlpha(0);
mActionsGroup = findViewById(R.id.actions_group);
@@ -218,8 +197,7 @@ public class PipMenuView extends FrameLayout {
R.dimen.pip_between_action_padding_land);
mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(mContext);
mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer,
- findViewById(R.id.resize_handle), mEnterSplitButton, mSettingsButton,
- mDismissButton);
+ findViewById(R.id.resize_handle), mSettingsButton, mDismissButton);
mDismissFadeOutDurationMs = context.getResources()
.getInteger(R.integer.config_pipExitAnimationDuration);
@@ -281,22 +259,10 @@ public class PipMenuView extends FrameLayout {
return super.dispatchGenericMotionEvent(event);
}
- public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
- final boolean isSplitScreen = mSplitScreenControllerOptional.isPresent()
- && mSplitScreenControllerOptional.get().isTaskInSplitScreenForeground(
- taskInfo.taskId);
- mFocusedTaskAllowSplitScreen = isSplitScreen
- || (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && taskInfo.supportsMultiWindow
- && taskInfo.topActivityType != WindowConfiguration.ACTIVITY_TYPE_HOME);
- }
-
void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout,
boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) {
mAllowMenuTimeout = allowMenuTimeout;
mDidLastShowMenuResize = resizeMenuOnShow;
- final boolean enableEnterSplit =
- mContext.getResources().getBoolean(R.bool.config_pipEnableEnterSplitButton);
if (mMenuState != menuState) {
// Disallow touches if the menu needs to resize while showing, and we are transitioning
// to/from a full menu state.
@@ -315,14 +281,8 @@ public class PipMenuView extends FrameLayout {
mSettingsButton.getAlpha(), 1f);
ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
mDismissButton.getAlpha(), 1f);
- ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA,
- mEnterSplitButton.getAlpha(),
- enableEnterSplit && mFocusedTaskAllowSplitScreen ? 1f : 0f);
if (menuState == MENU_STATE_FULL) {
- mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
- enterSplitAnim);
- } else {
- mMenuContainerAnimator.playTogether(enterSplitAnim);
+ mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
}
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
mMenuContainerAnimator.setDuration(ANIMATION_HIDE_DURATION_MS);
@@ -375,7 +335,6 @@ public class PipMenuView extends FrameLayout {
mMenuContainer.setAlpha(0f);
mSettingsButton.setAlpha(0f);
mDismissButton.setAlpha(0f);
- mEnterSplitButton.setAlpha(0f);
}
void pokeMenu() {
@@ -415,10 +374,7 @@ public class PipMenuView extends FrameLayout {
mSettingsButton.getAlpha(), 0f);
ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
mDismissButton.getAlpha(), 0f);
- ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA,
- mEnterSplitButton.getAlpha(), 0f);
- mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
- enterSplitAnim);
+ mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
mMenuContainerAnimator.setDuration(getFadeOutDuration(animationType));
mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
@@ -439,7 +395,7 @@ public class PipMenuView extends FrameLayout {
/**
* @return Estimated minimum {@link Size} to hold the actions.
- * See also {@link #updateActionViews(Rect)}
+ * See also {@link #updateActionViews(int, Rect)}
*/
Size getEstimatedMinMenuSize() {
final int pipActionSize = getResources().getDimensionPixelSize(R.dimen.pip_action_size);
@@ -608,13 +564,6 @@ public class PipMenuView extends FrameLayout {
}
}
- private void enterSplit() {
- // Do not notify menu visibility when hiding the menu, the controller will do this when it
- // handles the message
- hideMenu(mController::onEnterSplit, false /* notifyMenuVisibility */, true /* resize */,
- ANIM_TYPE_HIDE);
- }
-
private void showSettings() {
final Pair<ComponentName, Integer> topPipActivityInfo =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index c708b86e88c5..df67707e2014 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -42,6 +42,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
@@ -50,6 +51,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
+import java.util.Optional;
import java.util.function.Consumer;
/**
@@ -84,6 +86,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
/** Coordinator instance for resolving conflicts with other floating content. */
private FloatingContentCoordinator mFloatingContentCoordinator;
+ @Nullable private final PipPerfHintController mPipPerfHintController;
+ @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
/**
* PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()}
* using physics animations.
@@ -169,13 +174,15 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController,
- FloatingContentCoordinator floatingContentCoordinator) {
+ FloatingContentCoordinator floatingContentCoordinator,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
mContext = context;
mPipTaskOrganizer = pipTaskOrganizer;
mPipBoundsState = pipBoundsState;
mMenuController = menuController;
mSnapAlgorithm = snapAlgorithm;
mFloatingContentCoordinator = floatingContentCoordinator;
+ mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
mResizePipUpdateListener = (target, values) -> {
if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
@@ -386,6 +393,16 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
movetoTarget(velX, velY, postBoundsUpdateCallback, true /* isStash */);
}
+ private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
+
+ private void cleanUpHighPerfSessionMaybe() {
+ if (mPipHighPerfSession != null) {
+ // Close the high perf session once pointer interactions are over;
+ mPipHighPerfSession.close();
+ mPipHighPerfSession = null;
+ }
+ }
+
private void movetoTarget(
float velocityX,
float velocityY,
@@ -591,6 +608,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
(int) toY + getBounds().height()));
if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
+ if (mPipPerfHintController != null) {
+ // Start a high perf session with a timeout callback.
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout, "startBoundsAnimator");
+ }
if (postBoundsUpdateCallback != null) {
mTemporaryBoundsPhysicsAnimator
.addUpdateListener(mResizePipUpdateListener)
@@ -633,6 +655,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
mSpringingToTouch = false;
mDismissalPending = false;
+ cleanUpHighPerfSessionMaybe();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index f175775ce8b2..89d3dd63a08e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -15,19 +15,14 @@
*/
package com.android.wm.shell.pip.phone;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
-import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.Looper;
import android.view.BatchedInputEventReceiver;
@@ -41,11 +36,11 @@ import android.view.ViewConfiguration;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.policy.TaskResizingAlgorithm;
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipAnimationController;
@@ -53,7 +48,6 @@ import com.android.wm.shell.pip.PipTaskOrganizer;
import java.io.PrintWriter;
import java.util.function.Consumer;
-import java.util.function.Function;
/**
* Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
@@ -77,7 +71,6 @@ public class PipResizeGestureHandler {
private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
private final int mDisplayId;
private final ShellExecutor mMainExecutor;
- private final Region mTmpRegion = new Region();
private final PointF mDownPoint = new PointF();
private final PointF mDownSecondPoint = new PointF();
@@ -88,24 +81,15 @@ public class PipResizeGestureHandler {
private final Rect mLastResizeBounds = new Rect();
private final Rect mUserResizeBounds = new Rect();
private final Rect mDownBounds = new Rect();
- private final Rect mDragCornerSize = new Rect();
- private final Rect mTmpTopLeftCorner = new Rect();
- private final Rect mTmpTopRightCorner = new Rect();
- private final Rect mTmpBottomLeftCorner = new Rect();
- private final Rect mTmpBottomRightCorner = new Rect();
- private final Rect mDisplayBounds = new Rect();
- private final Function<Rect, Rect> mMovementBoundsSupplier;
private final Runnable mUpdateMovementBoundsRunnable;
private final Consumer<Rect> mUpdateResizeBoundsCallback;
- private int mDelta;
private float mTouchSlop;
private boolean mAllowGesture;
private boolean mIsAttached;
private boolean mIsEnabled;
private boolean mEnablePinchResize;
- private boolean mEnableDragCornerResize;
private boolean mIsSysUiStateValid;
private boolean mThresholdCrossed;
private boolean mOngoingPinchToResize = false;
@@ -116,6 +100,12 @@ public class PipResizeGestureHandler {
private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver;
+ @Nullable
+ private final PipPerfHintController mPipPerfHintController;
+
+ @Nullable
+ private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
private int mCtrlType;
private int mOhmOffset;
@@ -123,19 +113,19 @@ public class PipResizeGestureHandler {
PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
PipTouchState pipTouchState, PipTaskOrganizer pipTaskOrganizer,
PipDismissTargetHandler pipDismissTargetHandler,
- Function<Rect, Rect> movementBoundsSupplier, Runnable updateMovementBoundsRunnable,
+ Runnable updateMovementBoundsRunnable,
PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController,
- ShellExecutor mainExecutor) {
+ ShellExecutor mainExecutor, @Nullable PipPerfHintController pipPerfHintController) {
mContext = context;
mDisplayId = context.getDisplayId();
mMainExecutor = mainExecutor;
+ mPipPerfHintController = pipPerfHintController;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipBoundsState = pipBoundsState;
mMotionHelper = motionHelper;
mPipTouchState = pipTouchState;
mPipTaskOrganizer = pipTaskOrganizer;
mPipDismissTargetHandler = pipDismissTargetHandler;
- mMovementBoundsSupplier = movementBoundsSupplier;
mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
mPhonePipMenuController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
@@ -171,20 +161,9 @@ public class PipResizeGestureHandler {
}
private void reloadResources() {
- final Resources res = mContext.getResources();
- mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
- mEnableDragCornerResize = res.getBoolean(R.bool.config_pipEnableDragCornerResize);
mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
}
- private void resetDragCorners() {
- mDragCornerSize.set(0, 0, mDelta, mDelta);
- mTmpTopLeftCorner.set(mDragCornerSize);
- mTmpTopRightCorner.set(mDragCornerSize);
- mTmpBottomLeftCorner.set(mDragCornerSize);
- mTmpBottomRightCorner.set(mDragCornerSize);
- }
-
private void disposeInputChannel() {
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
@@ -232,7 +211,7 @@ public class PipResizeGestureHandler {
@VisibleForTesting
void onInputEvent(InputEvent ev) {
- if (!mEnableDragCornerResize && !mEnablePinchResize) {
+ if (!mEnablePinchResize) {
// No need to handle anything if neither form of resizing is enabled.
return;
}
@@ -260,8 +239,6 @@ public class PipResizeGestureHandler {
if (mEnablePinchResize && mOngoingPinchToResize) {
onPinchResize(mv);
- } else if (mEnableDragCornerResize) {
- onDragCornerResize(mv);
}
}
}
@@ -273,48 +250,6 @@ public class PipResizeGestureHandler {
return mCtrlType != CTRL_NONE || mOngoingPinchToResize;
}
- /**
- * Check whether the current x,y coordinate is within the region in which drag-resize should
- * start.
- * This consists of 4 small squares on the 4 corners of the PIP window, a quarter of which
- * overlaps with the PIP window while the rest goes outside of the PIP window.
- * _ _ _ _
- * |_|_|_________|_|_|
- * |_|_| |_|_|
- * | PIP |
- * | WINDOW |
- * _|_ _|_
- * |_|_|_________|_|_|
- * |_|_| |_|_|
- */
- public boolean isWithinDragResizeRegion(int x, int y) {
- if (!mEnableDragCornerResize) {
- return false;
- }
-
- final Rect currentPipBounds = mPipBoundsState.getBounds();
- if (currentPipBounds == null) {
- return false;
- }
- resetDragCorners();
- mTmpTopLeftCorner.offset(currentPipBounds.left - mDelta / 2,
- currentPipBounds.top - mDelta / 2);
- mTmpTopRightCorner.offset(currentPipBounds.right - mDelta / 2,
- currentPipBounds.top - mDelta / 2);
- mTmpBottomLeftCorner.offset(currentPipBounds.left - mDelta / 2,
- currentPipBounds.bottom - mDelta / 2);
- mTmpBottomRightCorner.offset(currentPipBounds.right - mDelta / 2,
- currentPipBounds.bottom - mDelta / 2);
-
- mTmpRegion.setEmpty();
- mTmpRegion.op(mTmpTopLeftCorner, Region.Op.UNION);
- mTmpRegion.op(mTmpTopRightCorner, Region.Op.UNION);
- mTmpRegion.op(mTmpBottomLeftCorner, Region.Op.UNION);
- mTmpRegion.op(mTmpBottomRightCorner, Region.Op.UNION);
-
- return mTmpRegion.contains(x, y);
- }
-
public boolean isUsingPinchToZoom() {
return mEnablePinchResize;
}
@@ -325,66 +260,31 @@ public class PipResizeGestureHandler {
public boolean willStartResizeGesture(MotionEvent ev) {
if (isInValidSysUiState()) {
- switch (ev.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- if (isWithinDragResizeRegion((int) ev.getRawX(), (int) ev.getRawY())) {
- return true;
- }
- break;
-
- case MotionEvent.ACTION_POINTER_DOWN:
- if (mEnablePinchResize && ev.getPointerCount() == 2) {
- onPinchResize(ev);
- mOngoingPinchToResize = mAllowGesture;
- return mAllowGesture;
- }
- break;
-
- default:
- break;
+ if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
+ if (mEnablePinchResize && ev.getPointerCount() == 2) {
+ onPinchResize(ev);
+ mOngoingPinchToResize = mAllowGesture;
+ return mAllowGesture;
+ }
}
}
return false;
}
- private void setCtrlType(int x, int y) {
- final Rect currentPipBounds = mPipBoundsState.getBounds();
-
- Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds);
+ private boolean isInValidSysUiState() {
+ return mIsSysUiStateValid;
+ }
- mDisplayBounds.set(movementBounds.left,
- movementBounds.top,
- movementBounds.right + currentPipBounds.width(),
- movementBounds.bottom + currentPipBounds.height());
+ private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
- if (mTmpTopLeftCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
- && currentPipBounds.left != mDisplayBounds.left) {
- mCtrlType |= CTRL_LEFT;
- mCtrlType |= CTRL_TOP;
- }
- if (mTmpTopRightCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
- && currentPipBounds.right != mDisplayBounds.right) {
- mCtrlType |= CTRL_RIGHT;
- mCtrlType |= CTRL_TOP;
- }
- if (mTmpBottomRightCorner.contains(x, y)
- && currentPipBounds.bottom != mDisplayBounds.bottom
- && currentPipBounds.right != mDisplayBounds.right) {
- mCtrlType |= CTRL_RIGHT;
- mCtrlType |= CTRL_BOTTOM;
- }
- if (mTmpBottomLeftCorner.contains(x, y)
- && currentPipBounds.bottom != mDisplayBounds.bottom
- && currentPipBounds.left != mDisplayBounds.left) {
- mCtrlType |= CTRL_LEFT;
- mCtrlType |= CTRL_BOTTOM;
+ private void cleanUpHighPerfSessionMaybe() {
+ if (mPipHighPerfSession != null) {
+ // Close the high perf session once pointer interactions are over;
+ mPipHighPerfSession.close();
+ mPipHighPerfSession = null;
}
}
- private boolean isInValidSysUiState() {
- return mIsSysUiStateValid;
- }
-
@VisibleForTesting
void onPinchResize(MotionEvent ev) {
int action = ev.getActionMasked();
@@ -394,6 +294,7 @@ public class PipResizeGestureHandler {
mSecondIndex = -1;
mAllowGesture = false;
finishResize();
+ cleanUpHighPerfSessionMaybe();
}
if (ev.getPointerCount() != 2) {
@@ -415,6 +316,12 @@ public class PipResizeGestureHandler {
mLastPoint.set(mDownPoint);
mLastSecondPoint.set(mLastSecondPoint);
mLastResizeBounds.set(mDownBounds);
+
+ // start the high perf session as the second pointer gets detected
+ if (mPipPerfHintController != null) {
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout, "onPinchResize");
+ }
}
}
@@ -457,59 +364,6 @@ public class PipResizeGestureHandler {
}
}
- private void onDragCornerResize(MotionEvent ev) {
- int action = ev.getActionMasked();
- float x = ev.getX();
- float y = ev.getY() - mOhmOffset;
- if (action == MotionEvent.ACTION_DOWN) {
- mLastResizeBounds.setEmpty();
- mAllowGesture = isInValidSysUiState() && isWithinDragResizeRegion((int) x, (int) y);
- if (mAllowGesture) {
- setCtrlType((int) x, (int) y);
- mDownPoint.set(x, y);
- mDownBounds.set(mPipBoundsState.getBounds());
- }
- } else if (mAllowGesture) {
- switch (action) {
- case MotionEvent.ACTION_POINTER_DOWN:
- // We do not support multi touch for resizing via drag
- mAllowGesture = false;
- break;
- case MotionEvent.ACTION_MOVE:
- // Capture inputs
- if (!mThresholdCrossed
- && Math.hypot(x - mDownPoint.x, y - mDownPoint.y) > mTouchSlop) {
- mThresholdCrossed = true;
- // Reset the down to begin resizing from this point
- mDownPoint.set(x, y);
- mInputMonitor.pilferPointers();
- }
- if (mThresholdCrossed) {
- if (mPhonePipMenuController.isMenuVisible()) {
- mPhonePipMenuController.hideMenu(ANIM_TYPE_NONE,
- false /* resize */);
- }
- final Rect currentPipBounds = mPipBoundsState.getBounds();
- mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y,
- mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x,
- mMinSize.y, mMaxSize, true,
- mDownBounds.width() > mDownBounds.height()));
- mPipBoundsAlgorithm.transformBoundsToAspectRatio(mLastResizeBounds,
- mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */,
- true /* useCurrentSize */);
- mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds,
- null);
- mPipBoundsState.setHasUserResizedPip(true);
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- finishResize();
- break;
- }
- }
- }
-
private void snapToMovementBoundsEdge(Rect bounds, Rect movementBounds) {
final int leftEdge = bounds.left;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchGesture.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchGesture.java
index 1a3cc8b1c1d2..fcac2c6b90ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchGesture.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchGesture.java
@@ -39,4 +39,9 @@ public abstract class PipTouchGesture {
public boolean onUp(PipTouchState touchState) {
return false;
}
+
+ /**
+ * Cleans up the high performance hint session if needed.
+ */
+ public void cleanUpHighPerfSessionMaybe() {}
}
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 452a41696fcf..c1adfffce074 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
@@ -25,8 +25,10 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI
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;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
@@ -52,16 +54,18 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDoubleTapHelper;
+import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
+import java.util.Optional;
/**
* Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
@@ -82,6 +86,7 @@ public class PipTouchHandler {
private final PipDismissTargetHandler mPipDismissTargetHandler;
private final PipTaskOrganizer mPipTaskOrganizer;
private final ShellExecutor mMainExecutor;
+ @Nullable private final PipPerfHintController mPipPerfHintController;
private PipResizeGestureHandler mPipResizeGestureHandler;
@@ -148,11 +153,6 @@ public class PipTouchHandler {
}
@Override
- public void onEnterSplit() {
- mMotionHelper.expandIntoSplit();
- }
-
- @Override
public void onPipDismiss() {
mTouchState.removeDoubleTapTimeoutCallback();
mMotionHelper.dismissPip();
@@ -176,9 +176,11 @@ public class PipTouchHandler {
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
- ShellExecutor mainExecutor) {
+ ShellExecutor mainExecutor,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
mContext = context;
mMainExecutor = mainExecutor;
+ mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipBoundsState = pipBoundsState;
@@ -211,8 +213,8 @@ public class PipTouchHandler {
mPipResizeGestureHandler =
new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
mMotionHelper, mTouchState, pipTaskOrganizer, mPipDismissTargetHandler,
- this::getMovementBounds, this::updateMovementBounds, pipUiEventLogger,
- menuController, mainExecutor);
+ this::updateMovementBounds, pipUiEventLogger,
+ menuController, mainExecutor, mPipPerfHintController);
mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState,
mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(),
this::onAccessibilityShowMenu, this::updateMovementBounds,
@@ -465,17 +467,11 @@ public class PipTouchHandler {
}
private void updatePinchResizeSizeConstraints(float aspectRatio) {
- final int minWidth, minHeight, maxWidth, maxHeight;
-
- minWidth = mSizeSpecSource.getMinSize(aspectRatio).getWidth();
- minHeight = mSizeSpecSource.getMinSize(aspectRatio).getHeight();
- maxWidth = mSizeSpecSource.getMaxSize(aspectRatio).getWidth();
- maxHeight = mSizeSpecSource.getMaxSize(aspectRatio).getHeight();
-
- mPipResizeGestureHandler.updateMinSize(minWidth, minHeight);
- mPipResizeGestureHandler.updateMaxSize(maxWidth, maxHeight);
- mPipBoundsState.setMaxSize(maxWidth, maxHeight);
- mPipBoundsState.setMinSize(minWidth, minHeight);
+ mPipBoundsState.updateMinMaxSize(aspectRatio);
+ mPipResizeGestureHandler.updateMinSize(mPipBoundsState.getMinSize().x,
+ mPipBoundsState.getMinSize().y);
+ mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getMaxSize().x,
+ mPipBoundsState.getMaxSize().y);
}
/**
@@ -524,6 +520,7 @@ public class PipTouchHandler {
}
if (mPipResizeGestureHandler.hasOngoingGesture()) {
+ mGesture.cleanUpHighPerfSessionMaybe();
mPipDismissTargetHandler.hideDismissTargetMaybe();
return true;
}
@@ -546,7 +543,7 @@ public class PipTouchHandler {
// Ignore the motion event When the entry animation is waiting to be started
if (!mTouchState.isUserInteracting() && mPipTaskOrganizer.isEntryScheduled()) {
- ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE,
"%s: Waiting to start the entry animation, skip the motion event.", TAG);
return true;
}
@@ -793,12 +790,31 @@ public class PipTouchHandler {
private final PointF mDelta = new PointF();
private boolean mShouldHideMenuAfterFling;
+ @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
+ private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
+
+ @Override
+ public void cleanUpHighPerfSessionMaybe() {
+ if (mPipHighPerfSession != null) {
+ // Close the high perf session once pointer interactions are over;
+ mPipHighPerfSession.close();
+ mPipHighPerfSession = null;
+ }
+ }
+
@Override
public void onDown(PipTouchState touchState) {
if (!touchState.isUserInteracting()) {
return;
}
+ if (mPipPerfHintController != null) {
+ // Cache the PiP high perf session to close it upon touch up.
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout, "DefaultPipTouchGesture#onDown");
+ }
+
Rect bounds = getPossiblyMotionBounds();
mDelta.set(0f, 0f);
mStartPosition.set(bounds.left, bounds.top);
@@ -941,6 +957,7 @@ public class PipTouchHandler {
mTouchState.scheduleDoubleTapTimeoutCallback();
}
}
+ cleanUpHighPerfSessionMaybe();
return true;
}
@@ -1032,7 +1049,7 @@ public class PipTouchHandler {
}
final Size estimatedMinMenuSize = mMenuController.getEstimatedMinMenuSize();
if (estimatedMinMenuSize == null) {
- ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE,
"%s: Failed to get estimated menu size", TAG);
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 5ee3734e371d..c5dc0edf1a4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -110,6 +110,7 @@ public class TvPipBoundsState extends PipBoundsState {
@Override
public void onConfigurationChanged() {
+ super.onConfigurationChanged();
updateDefaultGravity();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index cd3d38b6500c..3d286461ef79 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -223,10 +223,9 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
mShellController = shellController;
mDisplayController = displayController;
- DisplayLayout layout = new DisplayLayout(context, context.getDisplay());
-
mTvPipBoundsState = tvPipBoundsState;
+ DisplayLayout layout = new DisplayLayout(context, context.getDisplay());
mPipDisplayLayoutState = pipDisplayLayoutState;
mPipDisplayLayoutState.setDisplayLayout(layout);
mPipDisplayLayoutState.setDisplayId(context.getDisplayId());
@@ -291,6 +290,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
mPipNotificationController.onConfigurationChanged();
mTvPipBoundsAlgorithm.onConfigurationChanged(mContext);
mTvPipBoundsState.onConfigurationChanged();
+ mPipDisplayLayoutState.onConfigurationChanged();
int defaultGravityX = mTvPipBoundsState.getDefaultGravity()
& Gravity.HORIZONTAL_GRAVITY_MASK;
@@ -299,6 +299,24 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
}
+ @Override
+ public void onDensityOrFontScaleChanged() {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onDensityOrFontScaleChanged()", TAG);
+ updatePinnedStackBounds();
+ mTvPipMenuController.reloadMenu();
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onDisplayConfigurationChanged(), displayId %d, saved display id %d",
+ TAG, displayId, mPipDisplayLayoutState.getDisplayId());
+ mPipDisplayLayoutState.setDisplayLayout(
+ new DisplayLayout(mContext, mContext.getDisplay()));
+ mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
+ }
+
private void reloadResources() {
final Resources res = mContext.getResources();
mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index c6803f7beebd..62156fc7443b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -40,7 +40,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.List;
@@ -63,6 +63,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private TvPipMenuView mPipMenuView;
private TvPipBackgroundView mPipBackgroundView;
+ private boolean mIsReloading;
+
@TvPipMenuMode
private int mCurrentMenuMode = MODE_NO_MENU;
@TvPipMenuMode
@@ -134,6 +136,18 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mTvPipActionsProvider = tvPipActionsProvider;
}
+ void reloadMenu() {
+ if (mLeash == null) {
+ return;
+ }
+ mPrevMenuMode = mCurrentMenuMode;
+ detachPipMenu();
+ mCurrentMenuMode = MODE_NO_MENU;
+ attachPipMenu(/* showEduText */ false);
+ mPipMenuView.onCloseEduTextAnimationEnd();
+ mIsReloading = true;
+ }
+
@Override
public void attach(SurfaceControl leash) {
if (mDelegate == null) {
@@ -141,10 +155,10 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
mLeash = leash;
- attachPipMenu();
+ attachPipMenu(/* showEduText */ true);
}
- private void attachPipMenu() {
+ private void attachPipMenu(boolean showEduText) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: attachPipMenu()", TAG);
@@ -155,13 +169,17 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
attachPipBackgroundView();
attachPipMenuView();
- int pipEduTextHeight = mContext.getResources()
- .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
int pipMenuBorderWidth = mContext.getResources()
.getDimensionPixelSize(R.dimen.pip_menu_border_width);
mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-pipMenuBorderWidth,
-pipMenuBorderWidth, -pipMenuBorderWidth, -pipMenuBorderWidth));
- mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -pipEduTextHeight));
+ if (showEduText) {
+ int pipEduTextHeight = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
+ mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -pipEduTextHeight));
+ } else {
+ mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
+ }
}
private void attachPipMenuView() {
@@ -223,6 +241,10 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mMainHandler.post(() -> {
if (mPipMenuView != null) {
mPipMenuView.onPipTransitionFinished(enterTransition);
+ if (mIsReloading) {
+ requestMenuMode(mPrevMenuMode);
+ mIsReloading = false;
+ }
}
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
index 202d36f0dfbd..adc03cf5c4d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java
@@ -18,7 +18,6 @@ package com.android.wm.shell.pip.tv;
import static android.view.Gravity.BOTTOM;
import static android.view.Gravity.CENTER;
-import static android.view.View.GONE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE;
@@ -36,7 +35,6 @@ import android.text.TextUtils;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
-import android.widget.FrameLayout.LayoutParams;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -267,7 +265,6 @@ class TvPipMenuEduTextDrawer extends FrameLayout {
}
public void onCloseEduTextAnimationEnd() {
- setVisibility(GONE);
mListener.onCloseEduTextAnimationEnd();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 57439a59ccca..b259e8d584a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -89,6 +89,8 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
private final int mPipMenuOuterSpace;
private final int mPipMenuBorderWidth;
+ private final int mButtonStartEndOffset;
+
private final int mPipMenuFadeAnimationDuration;
private final int mResizeAnimationDuration;
@@ -147,6 +149,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
mPipMenuOuterSpace = res.getDimensionPixelSize(R.dimen.pip_menu_outer_space);
mPipMenuBorderWidth = res.getDimensionPixelSize(R.dimen.pip_menu_border_width);
mArrowElevation = res.getDimensionPixelSize(R.dimen.pip_menu_arrow_elevation);
+ mButtonStartEndOffset = res.getDimensionPixelSize(R.dimen.pip_menu_button_start_end_offset);
initMoveArrows();
@@ -204,6 +207,16 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
v.setElevation(mArrowElevation);
}
+ private void setButtonPadding(boolean vertical) {
+ if (vertical) {
+ mActionButtonsRecyclerView.setPadding(
+ 0, mButtonStartEndOffset, 0, mButtonStartEndOffset);
+ } else {
+ mActionButtonsRecyclerView.setPadding(
+ mButtonStartEndOffset, 0, mButtonStartEndOffset, 0);
+ }
+ }
+
void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
if (targetBounds == null) {
return;
@@ -244,6 +257,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
} else {
mButtonLayoutManager.setOrientation(vertical
? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
+ setButtonPadding(vertical);
}
}
@@ -262,9 +276,10 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
mEduTextDrawer.init();
}
+ boolean vertical = mCurrentPipBounds.height() > mCurrentPipBounds.width();
mButtonLayoutManager.setOrientation(
- mCurrentPipBounds.height() > mCurrentPipBounds.width()
- ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
+ vertical ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
+ setButtonPadding(vertical);
if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU
&& mActionButtonsRecyclerView.getAlpha() != 1f) {
mActionButtonsRecyclerView.animate()
@@ -468,6 +483,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
@Override
public void onCloseEduTextAnimationEnd() {
+ mEduTextDrawer.setVisibility(GONE);
mPipFrameView.setVisibility(GONE);
mEduTextContainer.setVisibility(GONE);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index 1c94625ddde9..54e162bba2f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -54,6 +54,7 @@ public class TvPipNotificationController implements TvPipActionsProvider.Listene
// Referenced in com.android.systemui.util.NotificationChannels.
public static final String NOTIFICATION_CHANNEL = "TVPIP";
private static final String NOTIFICATION_TAG = "TvPip";
+ private static final String EXTRA_COMPONENT_NAME = "TvPipComponentName";
private final Context mContext;
private final PackageManager mPackageManager;
@@ -176,6 +177,7 @@ public class TvPipNotificationController implements TvPipActionsProvider.Listene
Bundle extras = new Bundle();
extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, mMediaSessionToken);
+ extras.putParcelable(EXTRA_COMPONENT_NAME, PipUtils.getTopPipActivity(mContext).first);
mNotificationBuilder.setExtras(extras);
PendingIntent closeIntent = mTvPipActionsProvider.getCloseAction().getPendingIntent();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index 21223c9ac362..614ef2ab9831 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -28,10 +28,11 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipMenuController;
+import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
@@ -59,6 +60,7 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
@NonNull TvPipTransition tvPipTransition,
@NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenOptional,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -66,8 +68,8 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
super(context, syncTransactionQueue, pipTransitionState, pipBoundsState,
pipDisplayLayoutState, boundsHandler, pipMenuController, pipAnimationController,
surfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
- splitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer,
- mainExecutor);
+ splitScreenOptional, pipPerfHintControllerOptional, displayController,
+ pipUiEventLogger, shellTaskOrganizer, mainExecutor);
mTvPipTransition = tvPipTransition;
}
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 571c839adf11..c2f4d72a1ddf 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
@@ -25,10 +25,10 @@ import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.transitTypeToString;
+import static com.android.wm.shell.common.pip.PipMenuController.ALPHA_NO_CHANGE;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
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 static com.android.wm.shell.pip.PipMenuController.ALPHA_NO_CHANGE;
import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP;
import static com.android.wm.shell.pip.PipTransitionState.ENTERING_PIP;
import static com.android.wm.shell.pip.PipTransitionState.EXITING_PIP;
@@ -71,9 +71,9 @@ import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
index 6dabb3bf6f9a..79d1793819f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
@@ -1,4 +1,3 @@
# WM shell sub-module pip owner
hwwang@google.com
-mateuszc@google.com
gabiyev@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
new file mode 100644
index 000000000000..24077a35d41c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
@@ -0,0 +1,277 @@
+/*
+ * 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.pip2;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.Choreographer;
+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.
+ */
+public class PipSurfaceTransactionHelper {
+ /** for {@link #scale(SurfaceControl.Transaction, SurfaceControl, Rect, Rect)} operation */
+ private final Matrix mTmpTransform = new Matrix();
+ private final float[] mTmpFloat9 = new float[9];
+ private final RectF mTmpSourceRectF = new RectF();
+ private final RectF mTmpDestinationRectF = new RectF();
+ private final Rect mTmpDestinationRect = new Rect();
+
+ private int mCornerRadius;
+ private int mShadowRadius;
+
+ public PipSurfaceTransactionHelper(Context context) {
+ onDensityOrFontScaleChanged(context);
+ }
+
+ /**
+ * Called when display size or font size of settings changed
+ *
+ * @param context the current context
+ */
+ public void onDensityOrFontScaleChanged(Context context) {
+ mCornerRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
+ mShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius);
+ }
+
+ /**
+ * Operates the alpha on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper alpha(SurfaceControl.Transaction tx, SurfaceControl leash,
+ float alpha) {
+ tx.setAlpha(leash, alpha);
+ return this;
+ }
+
+ /**
+ * Operates the crop (and position) on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper crop(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect destinationBounds) {
+ tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height())
+ .setPosition(leash, destinationBounds.left, destinationBounds.top);
+ return this;
+ }
+
+ /**
+ * Operates the scale (setMatrix) on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, Rect destinationBounds) {
+ mTmpDestinationRectF.set(destinationBounds);
+ return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */);
+ }
+
+ /**
+ * Operates the scale (setMatrix) on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, RectF destinationBounds) {
+ return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */);
+ }
+
+ /**
+ * Operates the scale (setMatrix) on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, Rect destinationBounds, float degrees) {
+ mTmpDestinationRectF.set(destinationBounds);
+ return scale(tx, leash, sourceBounds, mTmpDestinationRectF, degrees);
+ }
+
+ /**
+ * Operates the scale (setMatrix) on a given transaction and leash, along with a rotation.
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, RectF destinationBounds, float degrees) {
+ mTmpSourceRectF.set(sourceBounds);
+ // We want the matrix to position the surface relative to the screen coordinates so offset
+ // the source to 0,0
+ mTmpSourceRectF.offsetTo(0, 0);
+ mTmpDestinationRectF.set(destinationBounds);
+ mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+ mTmpTransform.postRotate(degrees,
+ mTmpDestinationRectF.centerX(), mTmpDestinationRectF.centerY());
+ tx.setMatrix(leash, mTmpTransform, mTmpFloat9);
+ return this;
+ }
+
+ /**
+ * Operates the scale (setMatrix) on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx,
+ SurfaceControl leash, Rect sourceRectHint,
+ Rect sourceBounds, Rect destinationBounds, Rect insets,
+ boolean isInPipDirection, float fraction) {
+ mTmpDestinationRect.set(sourceBounds);
+ // Similar to {@link #scale}, we want to position the surface relative to the screen
+ // coordinates so offset the bounds to 0,0
+ mTmpDestinationRect.offsetTo(0, 0);
+ mTmpDestinationRect.inset(insets);
+ // Scale to the bounds no smaller than the destination and offset such that the top/left
+ // of the scaled inset source rect aligns with the top/left of the destination bounds
+ final float scale;
+ if (isInPipDirection
+ && sourceRectHint != null && sourceRectHint.width() < sourceBounds.width()) {
+ // scale by sourceRectHint if it's not edge-to-edge, for entering PiP transition only.
+ final float endScale = sourceBounds.width() <= sourceBounds.height()
+ ? (float) destinationBounds.width() / sourceRectHint.width()
+ : (float) destinationBounds.height() / sourceRectHint.height();
+ final float startScale = sourceBounds.width() <= sourceBounds.height()
+ ? (float) destinationBounds.width() / sourceBounds.width()
+ : (float) destinationBounds.height() / sourceBounds.height();
+ scale = (1 - fraction) * startScale + fraction * endScale;
+ } else {
+ scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
+ (float) destinationBounds.height() / sourceBounds.height());
+ }
+ final float left = destinationBounds.left - insets.left * scale;
+ final float top = destinationBounds.top - insets.top * scale;
+ mTmpTransform.setScale(scale, scale);
+ tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
+ .setCrop(leash, mTmpDestinationRect)
+ .setPosition(leash, left, top);
+ return this;
+ }
+
+ /**
+ * Operates the rotation according to the given degrees and scale (setMatrix) according to the
+ * source bounds and rotated destination bounds. The crop will be the unscaled source bounds.
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper rotateAndScaleWithCrop(SurfaceControl.Transaction tx,
+ SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, Rect insets,
+ float degrees, float positionX, float positionY, boolean isExpanding,
+ boolean clockwise) {
+ mTmpDestinationRect.set(sourceBounds);
+ mTmpDestinationRect.inset(insets);
+ final int srcW = mTmpDestinationRect.width();
+ final int srcH = mTmpDestinationRect.height();
+ final int destW = destinationBounds.width();
+ final int destH = destinationBounds.height();
+ // Scale by the short side so there won't be empty area if the aspect ratio of source and
+ // destination are different.
+ final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH;
+ final Rect crop = mTmpDestinationRect;
+ crop.set(0, 0, Transitions.SHELL_TRANSITIONS_ROTATION ? destH
+ : destW, Transitions.SHELL_TRANSITIONS_ROTATION ? destW : destH);
+ // Inverse scale for crop to fit in screen coordinates.
+ crop.scale(1 / scale);
+ crop.offset(insets.left, insets.top);
+ if (isExpanding) {
+ // Expand bounds (shrink insets) in source orientation.
+ positionX -= insets.left * scale;
+ positionY -= insets.top * scale;
+ } else {
+ // Shrink bounds (expand insets) in destination orientation.
+ if (clockwise) {
+ positionX -= insets.top * scale;
+ positionY += insets.left * scale;
+ } else {
+ positionX += insets.top * scale;
+ positionY -= insets.left * scale;
+ }
+ }
+ mTmpTransform.setScale(scale, scale);
+ mTmpTransform.postRotate(degrees);
+ mTmpTransform.postTranslate(positionX, positionY);
+ tx.setMatrix(leash, mTmpTransform, mTmpFloat9).setCrop(leash, crop);
+ return this;
+ }
+
+ /**
+ * Resets the scale (setMatrix) on a given transaction and leash if there's any
+ *
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper resetScale(SurfaceControl.Transaction tx,
+ SurfaceControl leash,
+ Rect destinationBounds) {
+ tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, mTmpFloat9)
+ .setPosition(leash, destinationBounds.left, destinationBounds.top);
+ return this;
+ }
+
+ /**
+ * Operates the round corner radius on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash,
+ boolean applyCornerRadius) {
+ tx.setCornerRadius(leash, applyCornerRadius ? mCornerRadius : 0);
+ return this;
+ }
+
+ /**
+ * Operates the round corner radius on a given transaction and leash, scaled by bounds
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect fromBounds, Rect toBounds) {
+ final float scale = (float) (Math.hypot(fromBounds.width(), fromBounds.height())
+ / Math.hypot(toBounds.width(), toBounds.height()));
+ tx.setCornerRadius(leash, mCornerRadius * scale);
+ return this;
+ }
+
+ /**
+ * Operates the shadow radius on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ public PipSurfaceTransactionHelper shadow(SurfaceControl.Transaction tx, SurfaceControl leash,
+ boolean applyShadowRadius) {
+ tx.setShadowRadius(leash, applyShadowRadius ? mShadowRadius : 0);
+ return this;
+ }
+
+ /**
+ * Interface to standardize {@link SurfaceControl.Transaction} generation across PiP.
+ */
+ public interface SurfaceControlTransactionFactory {
+ /**
+ * @return a new transaction to operate on.
+ */
+ SurfaceControl.Transaction getTransaction();
+ }
+
+ /**
+ * Implementation of {@link SurfaceControlTransactionFactory} that returns
+ * {@link SurfaceControl.Transaction} with VsyncId being set.
+ */
+ public static class VsyncSurfaceControlTransactionFactory
+ implements SurfaceControlTransactionFactory {
+ @Override
+ public SurfaceControl.Transaction getTransaction() {
+ final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ tx.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+ return tx;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
new file mode 100644
index 000000000000..6e36a32ac931
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
@@ -0,0 +1,577 @@
+/*
+ * 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.pip2.phone;
+
+import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
+
+import android.annotation.Nullable;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Size;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipMediaController.ActionListener;
+import com.android.wm.shell.common.pip.PipMenuController;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages the PiP menu view which can show menu options or a scrim.
+ *
+ * The current media session provides actions whenever there are no valid actions provided by the
+ * current PiP activity. Otherwise, those actions always take precedence.
+ */
+public class PhonePipMenuController implements PipMenuController {
+
+ private static final String TAG = "PhonePipMenuController";
+ private static final boolean DEBUG = false;
+
+ public static final int MENU_STATE_NONE = 0;
+ public static final int MENU_STATE_FULL = 1;
+
+ /**
+ * A listener interface to receive notification on changes in PIP.
+ */
+ public interface Listener {
+ /**
+ * Called when the PIP menu visibility change has started.
+ *
+ * @param menuState the new, about-to-change state of the menu
+ * @param resize whether or not to resize the PiP with the state change
+ */
+ void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback);
+
+ /**
+ * Called when the PIP menu state has finished changing/animating.
+ *
+ * @param menuState the new state of the menu.
+ */
+ void onPipMenuStateChangeFinish(int menuState);
+
+ /**
+ * Called when the PIP requested to be expanded.
+ */
+ void onPipExpand();
+
+ /**
+ * Called when the PIP requested to be dismissed.
+ */
+ void onPipDismiss();
+
+ /**
+ * Called when the PIP requested to show the menu.
+ */
+ void onPipShowMenu();
+ }
+
+ private final Context mContext;
+ private final PipBoundsState mPipBoundsState;
+ private final PipMediaController mMediaController;
+ private final ShellExecutor mMainExecutor;
+ private final Handler mMainHandler;
+
+ private final ArrayList<Listener> mListeners = new ArrayList<>();
+ private final SystemWindows mSystemWindows;
+ private final PipUiEventLogger mPipUiEventLogger;
+
+ private List<RemoteAction> mAppActions;
+ private RemoteAction mCloseAction;
+ private List<RemoteAction> mMediaActions;
+
+ private int mMenuState;
+
+ private PipMenuView mPipMenuView;
+
+ private SurfaceControl mLeash;
+
+ private ActionListener mMediaActionListener = new ActionListener() {
+ @Override
+ public void onMediaActionsChanged(List<RemoteAction> mediaActions) {
+ mMediaActions = new ArrayList<>(mediaActions);
+ updateMenuActions();
+ }
+ };
+
+ public PhonePipMenuController(Context context, PipBoundsState pipBoundsState,
+ PipMediaController mediaController, SystemWindows systemWindows,
+ PipUiEventLogger pipUiEventLogger,
+ ShellExecutor mainExecutor, Handler mainHandler) {
+ mContext = context;
+ mPipBoundsState = pipBoundsState;
+ mMediaController = mediaController;
+ mSystemWindows = systemWindows;
+ mMainExecutor = mainExecutor;
+ mMainHandler = mainHandler;
+ mPipUiEventLogger = pipUiEventLogger;
+ }
+
+ public boolean isMenuVisible() {
+ return mPipMenuView != null && mMenuState != MENU_STATE_NONE;
+ }
+
+ /**
+ * Attach the menu when the PiP task first appears.
+ */
+ @Override
+ public void attach(SurfaceControl leash) {
+ mLeash = leash;
+ attachPipMenuView();
+ }
+
+ /**
+ * Detach the menu when the PiP task is gone.
+ */
+ @Override
+ public void detach() {
+ hideMenu();
+ detachPipMenuView();
+ mLeash = null;
+ }
+
+ void attachPipMenuView() {
+ // In case detach was not called (e.g. PIP unexpectedly closed)
+ if (mPipMenuView != null) {
+ detachPipMenuView();
+ }
+ mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler,
+ mPipUiEventLogger);
+ mPipMenuView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ v.getViewRootImpl().addSurfaceChangedCallback(
+ new ViewRootImpl.SurfaceChangedCallback() {
+ @Override
+ public void surfaceCreated(SurfaceControl.Transaction t) {
+ final SurfaceControl sc = getSurfaceControl();
+ if (sc != null) {
+ t.reparent(sc, mLeash);
+ // make menu on top of the surface
+ t.setLayer(sc, Integer.MAX_VALUE);
+ }
+ }
+
+ @Override
+ public void surfaceReplaced(SurfaceControl.Transaction t) {
+ }
+
+ @Override
+ public void surfaceDestroyed() {
+ }
+ });
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ });
+
+ mSystemWindows.addView(mPipMenuView,
+ getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
+ 0, SHELL_ROOT_LAYER_PIP);
+ setShellRootAccessibilityWindow();
+
+ // Make sure the initial actions are set
+ updateMenuActions();
+ }
+
+ private void detachPipMenuView() {
+ if (mPipMenuView == null) {
+ return;
+ }
+
+ mSystemWindows.removeView(mPipMenuView);
+ mPipMenuView = null;
+ }
+
+ /**
+ * Updates the layout parameters of the menu.
+ * @param destinationBounds New Menu bounds.
+ */
+ @Override
+ public void updateMenuBounds(Rect destinationBounds) {
+ mSystemWindows.updateViewLayout(mPipMenuView,
+ getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, destinationBounds.width(),
+ destinationBounds.height()));
+ updateMenuLayout(destinationBounds);
+ }
+
+ /**
+ * Tries to grab a surface control from {@link PipMenuView}. If this isn't available for some
+ * reason (ie. the window isn't ready yet, thus {@link ViewRootImpl} is
+ * {@code null}), it will get the leash that the WindowlessWM has assigned to it.
+ */
+ public SurfaceControl getSurfaceControl() {
+ return mSystemWindows.getViewSurface(mPipMenuView);
+ }
+
+ /**
+ * Adds a new menu activity listener.
+ */
+ public void addListener(Listener listener) {
+ if (!mListeners.contains(listener)) {
+ mListeners.add(listener);
+ }
+ }
+
+ @Nullable
+ Size getEstimatedMinMenuSize() {
+ return mPipMenuView == null ? null : mPipMenuView.getEstimatedMinMenuSize();
+ }
+
+ /**
+ * When other components requests the menu controller directly to show the menu, we must
+ * first fire off the request to the other listeners who will then propagate the call
+ * back to the controller with the right parameters.
+ */
+ @Override
+ public void showMenu() {
+ mListeners.forEach(Listener::onPipShowMenu);
+ }
+
+ /**
+ * Similar to {@link #showMenu(int, Rect, boolean, boolean, boolean)} but only show the menu
+ * upon PiP window transition is finished.
+ */
+ public void showMenuWithPossibleDelay(int menuState, Rect stackBounds, boolean allowMenuTimeout,
+ boolean willResizeMenu, boolean showResizeHandle) {
+ if (willResizeMenu) {
+ // hide all visible controls including close button and etc. first, this is to ensure
+ // menu is totally invisible during the transition to eliminate unpleasant artifacts
+ fadeOutMenu();
+ }
+ showMenuInternal(menuState, stackBounds, allowMenuTimeout, willResizeMenu,
+ willResizeMenu /* withDelay=willResizeMenu here */, showResizeHandle);
+ }
+
+ /**
+ * Shows the menu activity immediately.
+ */
+ public void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout,
+ boolean willResizeMenu, boolean showResizeHandle) {
+ showMenuInternal(menuState, stackBounds, allowMenuTimeout, willResizeMenu,
+ false /* withDelay */, showResizeHandle);
+ }
+
+ private void showMenuInternal(int menuState, Rect stackBounds, boolean allowMenuTimeout,
+ boolean willResizeMenu, boolean withDelay, boolean showResizeHandle) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showMenu() state=%s"
+ + " isMenuVisible=%s"
+ + " allowMenuTimeout=%s"
+ + " willResizeMenu=%s"
+ + " withDelay=%s"
+ + " showResizeHandle=%s"
+ + " callers=\n%s", TAG, menuState, isMenuVisible(), allowMenuTimeout,
+ willResizeMenu, withDelay, showResizeHandle, Debug.getCallers(5, " "));
+ }
+
+ if (!checkPipMenuState()) {
+ return;
+ }
+
+ // Sync the menu bounds before showing it in case it is out of sync.
+ movePipMenu(null /* pipLeash */, null /* transaction */, stackBounds,
+ PipMenuController.ALPHA_NO_CHANGE);
+ updateMenuBounds(stackBounds);
+
+ mPipMenuView.showMenu(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay,
+ showResizeHandle);
+ }
+
+ /**
+ * Move the PiP menu, which does a translation and possibly a scale transformation.
+ */
+ @Override
+ public void movePipMenu(@Nullable SurfaceControl pipLeash,
+ @Nullable SurfaceControl.Transaction t,
+ Rect destinationBounds, float alpha) {
+ if (destinationBounds.isEmpty()) {
+ return;
+ }
+
+ if (!checkPipMenuState()) {
+ return;
+ }
+
+ // TODO(b/286307861) transaction should be applied outside of PiP menu controller
+ if (pipLeash != null && t != null) {
+ t.apply();
+ }
+ }
+
+ /**
+ * Does an immediate window crop of the PiP menu.
+ */
+ @Override
+ public void resizePipMenu(@Nullable SurfaceControl pipLeash,
+ @Nullable SurfaceControl.Transaction t,
+ Rect destinationBounds) {
+ if (destinationBounds.isEmpty()) {
+ return;
+ }
+
+ if (!checkPipMenuState()) {
+ return;
+ }
+
+ // TODO(b/286307861) transaction should be applied outside of PiP menu controller
+ if (pipLeash != null && t != null) {
+ t.apply();
+ }
+ }
+
+ private boolean checkPipMenuState() {
+ if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Not going to move PiP, either menu or its parent is not created.", TAG);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Pokes the menu, indicating that the user is interacting with it.
+ */
+ public void pokeMenu() {
+ final boolean isMenuVisible = isMenuVisible();
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: pokeMenu() isMenuVisible=%b", TAG, isMenuVisible);
+ }
+ if (isMenuVisible) {
+ mPipMenuView.pokeMenu();
+ }
+ }
+
+ private void fadeOutMenu() {
+ final boolean isMenuVisible = isMenuVisible();
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: fadeOutMenu() isMenuVisible=%b", TAG, isMenuVisible);
+ }
+ if (isMenuVisible) {
+ mPipMenuView.fadeOutMenu();
+ }
+ }
+
+ /**
+ * Hides the menu view.
+ */
+ public void hideMenu() {
+ final boolean isMenuVisible = isMenuVisible();
+ if (isMenuVisible) {
+ mPipMenuView.hideMenu();
+ }
+ }
+
+ /**
+ * Hides the menu view.
+ *
+ * @param animationType the animation type to use upon hiding the menu
+ * @param resize whether or not to resize the PiP with the state change
+ */
+ public void hideMenu(@PipMenuView.AnimationType int animationType, boolean resize) {
+ final boolean isMenuVisible = isMenuVisible();
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: hideMenu() state=%s"
+ + " isMenuVisible=%s"
+ + " animationType=%s"
+ + " resize=%s"
+ + " callers=\n%s", TAG, mMenuState, isMenuVisible,
+ animationType, resize,
+ Debug.getCallers(5, " "));
+ }
+ if (isMenuVisible) {
+ mPipMenuView.hideMenu(resize, animationType);
+ }
+ }
+
+ /**
+ * Hides the menu activity.
+ */
+ public void hideMenu(Runnable onStartCallback, Runnable onEndCallback) {
+ if (isMenuVisible()) {
+ // If the menu is visible in either the closed or full state, then hide the menu and
+ // trigger the animation trigger afterwards
+ if (onStartCallback != null) {
+ onStartCallback.run();
+ }
+ mPipMenuView.hideMenu(onEndCallback);
+ }
+ }
+
+ /**
+ * Sets the menu actions to the actions provided by the current PiP menu.
+ */
+ @Override
+ public void setAppActions(List<RemoteAction> appActions,
+ RemoteAction closeAction) {
+ mAppActions = appActions;
+ mCloseAction = closeAction;
+ updateMenuActions();
+ }
+
+ void onPipExpand() {
+ mListeners.forEach(Listener::onPipExpand);
+ }
+
+ void onPipDismiss() {
+ mListeners.forEach(Listener::onPipDismiss);
+ }
+
+ /**
+ * @return the best set of actions to show in the PiP menu.
+ */
+ private List<RemoteAction> resolveMenuActions() {
+ if (isValidActions(mAppActions)) {
+ return mAppActions;
+ }
+ return mMediaActions;
+ }
+
+ /**
+ * Updates the PiP menu with the best set of actions provided.
+ */
+ private void updateMenuActions() {
+ if (mPipMenuView != null) {
+ mPipMenuView.setActions(mPipBoundsState.getBounds(),
+ resolveMenuActions(), mCloseAction);
+ }
+ }
+
+ /**
+ * Returns whether the set of actions are valid.
+ */
+ private static boolean isValidActions(List<?> actions) {
+ return actions != null && actions.size() > 0;
+ }
+
+ /**
+ * Handles changes in menu visibility.
+ */
+ void onMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onMenuStateChangeStart() mMenuState=%s"
+ + " menuState=%s resize=%s"
+ + " callers=\n%s", TAG, mMenuState, menuState, resize,
+ Debug.getCallers(5, " "));
+ }
+
+ if (menuState != mMenuState) {
+ mListeners.forEach(l -> l.onPipMenuStateChangeStart(menuState, resize, callback));
+ if (menuState == MENU_STATE_FULL) {
+ // Once visible, start listening for media action changes. This call will trigger
+ // the menu actions to be updated again.
+ mMediaController.addActionListener(mMediaActionListener);
+ } else {
+ // Once hidden, stop listening for media action changes. This call will trigger
+ // the menu actions to be updated again.
+ mMediaController.removeActionListener(mMediaActionListener);
+ }
+
+ try {
+ WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
+ mSystemWindows.getFocusGrantToken(mPipMenuView),
+ menuState != MENU_STATE_NONE /* grantFocus */);
+ } catch (RemoteException e) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Unable to update focus as menu appears/disappears, %s", TAG, e);
+ }
+ }
+ }
+
+ void onMenuStateChangeFinish(int menuState) {
+ if (menuState != mMenuState) {
+ mListeners.forEach(l -> l.onPipMenuStateChangeFinish(menuState));
+ }
+ mMenuState = menuState;
+ setShellRootAccessibilityWindow();
+ }
+
+ private void setShellRootAccessibilityWindow() {
+ switch (mMenuState) {
+ case MENU_STATE_NONE:
+ mSystemWindows.setShellRootAccessibilityWindow(0, SHELL_ROOT_LAYER_PIP, null);
+ break;
+ default:
+ mSystemWindows.setShellRootAccessibilityWindow(0, SHELL_ROOT_LAYER_PIP,
+ mPipMenuView);
+ break;
+ }
+ }
+
+ /**
+ * Handles a pointer event sent from pip input consumer.
+ */
+ void handlePointerEvent(MotionEvent ev) {
+ if (mPipMenuView == null) {
+ return;
+ }
+
+ if (ev.isTouchEvent()) {
+ mPipMenuView.dispatchTouchEvent(ev);
+ } else {
+ mPipMenuView.dispatchGenericMotionEvent(ev);
+ }
+ }
+
+ /**
+ * Tell the PIP Menu to recalculate its layout given its current position on the display.
+ */
+ public void updateMenuLayout(Rect bounds) {
+ final boolean isMenuVisible = isMenuVisible();
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateMenuLayout() state=%s"
+ + " isMenuVisible=%s"
+ + " callers=\n%s", TAG, mMenuState, isMenuVisible,
+ Debug.getCallers(5, " "));
+ }
+ if (isMenuVisible) {
+ mPipMenuView.updateMenuLayout(bounds);
+ }
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mMenuState=" + mMenuState);
+ pw.println(innerPrefix + "mPipMenuView=" + mPipMenuView);
+ pw.println(innerPrefix + "mListeners=" + mListeners.size());
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 186cb615f4ec..e73a85003881 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -18,14 +18,31 @@ package com.android.wm.shell.pip2.phone;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
+
+import android.app.PictureInPictureParams;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.view.InsetsState;
+import android.view.SurfaceControl;
+
+import androidx.annotation.BinderThread;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.IPip;
+import com.android.wm.shell.common.pip.IPipAnimationListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -37,32 +54,54 @@ import com.android.wm.shell.sysui.ShellInit;
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
public class PipController implements ConfigurationChangeListener,
- DisplayController.OnDisplaysChangedListener {
+ DisplayController.OnDisplaysChangedListener, RemoteCallable<PipController> {
private static final String TAG = PipController.class.getSimpleName();
private Context mContext;
private ShellController mShellController;
private DisplayController mDisplayController;
private DisplayInsetsController mDisplayInsetsController;
+ private PipBoundsState mPipBoundsState;
+ private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipDisplayLayoutState mPipDisplayLayoutState;
+ private PipScheduler mPipScheduler;
+ private ShellExecutor mMainExecutor;
private PipController(Context context,
ShellInit shellInit,
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
- PipDisplayLayoutState pipDisplayLayoutState) {
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipScheduler pipScheduler,
+ ShellExecutor mainExecutor) {
mContext = context;
mShellController = shellController;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
+ mPipBoundsState = pipBoundsState;
+ mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipDisplayLayoutState = pipDisplayLayoutState;
+ mPipScheduler = pipScheduler;
+ mMainExecutor = mainExecutor;
if (PipUtils.isPip2ExperimentEnabled()) {
shellInit.addInitCallback(this::onInit, this);
}
}
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
private void onInit() {
// Ensure that we have the display info in case we get calls to update the bounds before the
// listener calls back
@@ -80,6 +119,10 @@ public class PipController implements ConfigurationChangeListener,
.getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
}
});
+
+ // Allow other outside processes to bind to PiP controller using the key below.
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+ this::createExternalInterface, this);
}
/**
@@ -90,16 +133,24 @@ public class PipController implements ConfigurationChangeListener,
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
- PipDisplayLayoutState pipDisplayLayoutState) {
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipScheduler pipScheduler,
+ ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Device doesn't support Pip feature", TAG);
return null;
}
return new PipController(context, shellInit, shellController, displayController,
- displayInsetsController, pipDisplayLayoutState);
+ displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState,
+ pipScheduler, mainExecutor);
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IPipImpl(this);
+ }
@Override
public void onConfigurationChanged(Configuration newConfiguration) {
@@ -130,4 +181,86 @@ public class PipController implements ConfigurationChangeListener,
private void onDisplayChanged(DisplayLayout layout) {
mPipDisplayLayoutState.setDisplayLayout(layout);
}
+
+ private Rect getSwipePipToHomeBounds(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams pictureInPictureParams,
+ int launcherRotation, Rect hotseatKeepClearArea) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "getSwipePipToHomeBounds: %s", componentName);
+ mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, pictureInPictureParams,
+ mPipBoundsAlgorithm);
+ return mPipBoundsAlgorithm.getEntryDestinationBounds();
+ }
+
+ private void onSwipePipToHomeAnimationStart(int taskId, ComponentName componentName,
+ Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onSwipePipToHomeAnimationStart: %s", componentName);
+ mPipScheduler.setInSwipePipToHomeTransition(true);
+ // TODO: cache the overlay if provided for reparenting later.
+ }
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class IPipImpl extends IPip.Stub implements ExternalInterfaceBinder {
+ private PipController mController;
+
+ IPipImpl(PipController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ @Override
+ public void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams pictureInPictureParams, int launcherRotation,
+ Rect keepClearArea) {
+ Rect[] result = new Rect[1];
+ executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome",
+ (controller) -> {
+ result[0] = controller.getSwipePipToHomeBounds(componentName, activityInfo,
+ pictureInPictureParams, launcherRotation, keepClearArea);
+ }, true /* blocking */);
+ return result[0];
+ }
+
+ @Override
+ public void stopSwipePipToHome(int taskId, ComponentName componentName,
+ Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
+ if (overlay != null) {
+ overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome");
+ }
+ executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
+ (controller) -> controller.onSwipePipToHomeAnimationStart(
+ taskId, componentName, destinationBounds, overlay, appBounds));
+ }
+
+ @Override
+ public void abortSwipePipToHome(int taskId, ComponentName componentName) {}
+
+ @Override
+ public void setShelfHeight(boolean visible, int height) {}
+
+ @Override
+ public void setLauncherKeepClearAreaHeight(boolean visible, int height) {}
+
+ @Override
+ public void setLauncherAppIconSize(int iconSizePx) {}
+
+ @Override
+ public void setPipAnimationListener(IPipAnimationListener listener) {
+ // TODO: set a proper animation listener to update the Launcher state as needed.
+ }
+
+ @Override
+ public void setPipAnimationTypeToAlpha() {}
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuActionView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuActionView.java
new file mode 100644
index 000000000000..7252675dc52d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuActionView.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pip2.phone;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Container layout wraps single action image view drawn in PiP menu and can restrict the size of
+ * action image view (see pip_menu_action.xml).
+ */
+public class PipMenuActionView extends FrameLayout {
+ private ImageView mImageView;
+ private View mCustomCloseBackground;
+
+ public PipMenuActionView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mImageView = findViewById(R.id.image);
+ mCustomCloseBackground = findViewById(R.id.custom_close_bg);
+ }
+
+ /** pass through to internal {@link #mImageView} */
+ public void setImageDrawable(Drawable drawable) {
+ mImageView.setImageDrawable(drawable);
+ }
+
+ /** pass through to internal {@link #mCustomCloseBackground} */
+ public void setCustomCloseBackgroundVisibility(@Visibility int visibility) {
+ mCustomCloseBackground.setVisibility(visibility);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuIconsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuIconsAlgorithm.java
new file mode 100644
index 000000000000..ecb6ad690e26
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuIconsAlgorithm.java
@@ -0,0 +1,71 @@
+/*
+ * 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.pip2.phone;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+/**
+ * Helper class to calculate and place the menu icons on the PIP Menu.
+ */
+public class PipMenuIconsAlgorithm {
+
+ private static final String TAG = "PipMenuIconsAlgorithm";
+
+ protected ViewGroup mViewRoot;
+ protected ViewGroup mTopEndContainer;
+ protected View mDragHandle;
+ protected View mSettingsButton;
+ protected View mDismissButton;
+
+ protected PipMenuIconsAlgorithm(Context context) {
+ }
+
+ /**
+ * Bind the necessary views.
+ */
+ public void bindViews(ViewGroup viewRoot, ViewGroup topEndContainer, View dragHandle,
+ View settingsButton, View dismissButton) {
+ mViewRoot = viewRoot;
+ mTopEndContainer = topEndContainer;
+ mDragHandle = dragHandle;
+ mSettingsButton = settingsButton;
+ mDismissButton = dismissButton;
+ }
+
+ /**
+ * Updates the position of the drag handle based on where the PIP window is on the screen.
+ */
+ public void onBoundsChanged(Rect bounds) {
+ // On phones, the menu icons are always static and will never move based on the PIP window
+ // position. No need to do anything here.
+ }
+
+ /**
+ * Set the gravity on the given view.
+ */
+ protected static void setLayoutGravity(View v, int gravity) {
+ if (v.getLayoutParams() instanceof FrameLayout.LayoutParams) {
+ FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) v.getLayoutParams();
+ params.gravity = gravity;
+ v.setLayoutParams(params);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java
new file mode 100644
index 000000000000..42b8e9f5a3ad
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java
@@ -0,0 +1,600 @@
+/*
+ * 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.pip2.phone;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
+import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
+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_FULL;
+import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.util.Pair;
+import android.util.Size;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Translucent window that gets started on top of a task in PIP to allow the user to control it.
+ */
+public class PipMenuView extends FrameLayout {
+
+ private static final String TAG = "PipMenuView";
+
+ private static final int ANIMATION_NONE_DURATION_MS = 0;
+ private static final int ANIMATION_HIDE_DURATION_MS = 125;
+
+ /** No animation performed during menu hide. */
+ public static final int ANIM_TYPE_NONE = 0;
+ /** Fade out the menu until it's invisible. Used when the PIP window remains visible. */
+ public static final int ANIM_TYPE_HIDE = 1;
+ /** Fade out the menu in sync with the PIP window. */
+ public static final int ANIM_TYPE_DISMISS = 2;
+
+ @IntDef(prefix = { "ANIM_TYPE_" }, value = {
+ ANIM_TYPE_NONE,
+ ANIM_TYPE_HIDE,
+ ANIM_TYPE_DISMISS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AnimationType {}
+
+ private static final int INITIAL_DISMISS_DELAY = 3500;
+ private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
+ private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30;
+
+ private static final float MENU_BACKGROUND_ALPHA = 0.54f;
+ private static final float DISABLED_ACTION_ALPHA = 0.54f;
+
+ private int mMenuState;
+ private boolean mAllowMenuTimeout = true;
+ private boolean mAllowTouches = true;
+ private int mDismissFadeOutDurationMs;
+ private final List<RemoteAction> mActions = new ArrayList<>();
+ private RemoteAction mCloseAction;
+
+ private AccessibilityManager mAccessibilityManager;
+ private Drawable mBackgroundDrawable;
+ private View mMenuContainer;
+ private LinearLayout mActionsGroup;
+ private int mBetweenActionPaddingLand;
+
+ private AnimatorSet mMenuContainerAnimator;
+ private final PhonePipMenuController mController;
+ private final PipUiEventLogger mPipUiEventLogger;
+
+ private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final float alpha = (float) animation.getAnimatedValue();
+ mBackgroundDrawable.setAlpha((int) (MENU_BACKGROUND_ALPHA * alpha * 255));
+ }
+ };
+
+ private ShellExecutor mMainExecutor;
+ private Handler mMainHandler;
+
+ /**
+ * Whether the most recent showing of the menu caused a PIP resize, such as when PIP is too
+ * small and it is resized on menu show to fit the actions.
+ */
+ private boolean mDidLastShowMenuResize;
+ private final Runnable mHideMenuRunnable = this::hideMenu;
+
+ protected View mViewRoot;
+ protected View mSettingsButton;
+ protected View mDismissButton;
+ protected View mTopEndContainer;
+ protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
+
+ // How long the shell will wait for the app to close the PiP if a custom action is set.
+ private final int mPipForceCloseDelay;
+
+ public PipMenuView(Context context, PhonePipMenuController controller,
+ ShellExecutor mainExecutor, Handler mainHandler, PipUiEventLogger pipUiEventLogger) {
+ super(context, null, 0);
+ mContext = context;
+ mController = controller;
+ mMainExecutor = mainExecutor;
+ mMainHandler = mainHandler;
+ mPipUiEventLogger = pipUiEventLogger;
+
+ mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+ inflate(context, R.layout.pip_menu, this);
+
+ mPipForceCloseDelay = context.getResources().getInteger(
+ R.integer.config_pipForceCloseDelay);
+
+ mBackgroundDrawable = mContext.getDrawable(R.drawable.pip_menu_background);
+ mBackgroundDrawable.setAlpha(0);
+ mViewRoot = findViewById(R.id.background);
+ mViewRoot.setBackground(mBackgroundDrawable);
+ mMenuContainer = findViewById(R.id.menu_container);
+ mMenuContainer.setAlpha(0);
+ mTopEndContainer = findViewById(R.id.top_end_container);
+ mSettingsButton = findViewById(R.id.settings);
+ mSettingsButton.setAlpha(0);
+ mSettingsButton.setOnClickListener((v) -> {
+ if (v.getAlpha() != 0) {
+ showSettings();
+ }
+ });
+ mDismissButton = findViewById(R.id.dismiss);
+ mDismissButton.setAlpha(0);
+ mDismissButton.setOnClickListener(v -> dismissPip());
+ findViewById(R.id.expand_button).setOnClickListener(v -> {
+ if (mMenuContainer.getAlpha() != 0) {
+ expandPip();
+ }
+ });
+
+ findViewById(R.id.resize_handle).setAlpha(0);
+
+ mActionsGroup = findViewById(R.id.actions_group);
+ mBetweenActionPaddingLand = getResources().getDimensionPixelSize(
+ R.dimen.pip_between_action_padding_land);
+ mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(mContext);
+ mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer,
+ findViewById(R.id.resize_handle), mSettingsButton, mDismissButton);
+ mDismissFadeOutDurationMs = context.getResources()
+ .getInteger(R.integer.config_pipExitAnimationDuration);
+
+ initAccessibility();
+ }
+
+ private void initAccessibility() {
+ this.setAccessibilityDelegate(new AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ String label = getResources().getString(R.string.pip_menu_title);
+ info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, label));
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (action == ACTION_CLICK && mMenuState != MENU_STATE_FULL) {
+ mController.showMenu();
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+ });
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_ESCAPE) {
+ hideMenu();
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return true;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (!mAllowTouches) {
+ return false;
+ }
+
+ if (mAllowMenuTimeout) {
+ repostDelayedHide(POST_INTERACTION_DISMISS_DELAY);
+ }
+
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent event) {
+ if (mAllowMenuTimeout) {
+ repostDelayedHide(POST_INTERACTION_DISMISS_DELAY);
+ }
+
+ return super.dispatchGenericMotionEvent(event);
+ }
+
+ void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout,
+ boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) {
+ mAllowMenuTimeout = allowMenuTimeout;
+ mDidLastShowMenuResize = resizeMenuOnShow;
+ if (mMenuState != menuState) {
+ // Disallow touches if the menu needs to resize while showing, and we are transitioning
+ // to/from a full menu state.
+ boolean disallowTouchesUntilAnimationEnd = resizeMenuOnShow
+ && (mMenuState == MENU_STATE_FULL || menuState == MENU_STATE_FULL);
+ mAllowTouches = !disallowTouchesUntilAnimationEnd;
+ cancelDelayedHide();
+ if (mMenuContainerAnimator != null) {
+ mMenuContainerAnimator.cancel();
+ }
+ mMenuContainerAnimator = new AnimatorSet();
+ ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
+ mMenuContainer.getAlpha(), 1f);
+ menuAnim.addUpdateListener(mMenuBgUpdateListener);
+ ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
+ mSettingsButton.getAlpha(), 1f);
+ ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
+ mDismissButton.getAlpha(), 1f);
+ if (menuState == MENU_STATE_FULL) {
+ mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
+ }
+ mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
+ mMenuContainerAnimator.setDuration(ANIMATION_HIDE_DURATION_MS);
+ mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAllowTouches = true;
+ notifyMenuStateChangeFinish(menuState);
+ if (allowMenuTimeout) {
+ repostDelayedHide(INITIAL_DISMISS_DELAY);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mAllowTouches = true;
+ }
+ });
+ if (withDelay) {
+ // starts the menu container animation after window expansion is completed
+ notifyMenuStateChangeStart(menuState, resizeMenuOnShow, () -> {
+ if (mMenuContainerAnimator == null) {
+ return;
+ }
+ mMenuContainerAnimator.setStartDelay(MENU_SHOW_ON_EXPAND_START_DELAY);
+ setVisibility(VISIBLE);
+ mMenuContainerAnimator.start();
+ });
+ } else {
+ notifyMenuStateChangeStart(menuState, resizeMenuOnShow, null);
+ setVisibility(VISIBLE);
+ mMenuContainerAnimator.start();
+ }
+ updateActionViews(menuState, stackBounds);
+ } else {
+ // If we are already visible, then just start the delayed dismiss and unregister any
+ // existing input consumers from the previous drag
+ if (allowMenuTimeout) {
+ repostDelayedHide(POST_INTERACTION_DISMISS_DELAY);
+ }
+ }
+ }
+
+ /**
+ * Different from {@link #hideMenu()}, this function does not try to finish this menu activity
+ * and instead, it fades out the controls by setting the alpha to 0 directly without menu
+ * visibility callbacks invoked.
+ */
+ void fadeOutMenu() {
+ mMenuContainer.setAlpha(0f);
+ mSettingsButton.setAlpha(0f);
+ mDismissButton.setAlpha(0f);
+ }
+
+ void pokeMenu() {
+ cancelDelayedHide();
+ }
+
+ void updateMenuLayout(Rect bounds) {
+ mPipMenuIconsAlgorithm.onBoundsChanged(bounds);
+ }
+
+ void hideMenu() {
+ hideMenu(null);
+ }
+
+ void hideMenu(Runnable animationEndCallback) {
+ hideMenu(animationEndCallback, true /* notifyMenuVisibility */, mDidLastShowMenuResize,
+ ANIM_TYPE_HIDE);
+ }
+
+ void hideMenu(boolean resize, @AnimationType int animationType) {
+ hideMenu(null /* animationFinishedRunnable */, true /* notifyMenuVisibility */, resize,
+ animationType);
+ }
+
+ void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility,
+ boolean resize, @AnimationType int animationType) {
+ if (mMenuState != MENU_STATE_NONE) {
+ cancelDelayedHide();
+ if (notifyMenuVisibility) {
+ notifyMenuStateChangeStart(MENU_STATE_NONE, resize, null);
+ }
+ mMenuContainerAnimator = new AnimatorSet();
+ ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
+ mMenuContainer.getAlpha(), 0f);
+ menuAnim.addUpdateListener(mMenuBgUpdateListener);
+ ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
+ mSettingsButton.getAlpha(), 0f);
+ ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
+ mDismissButton.getAlpha(), 0f);
+ mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
+ mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
+ mMenuContainerAnimator.setDuration(getFadeOutDuration(animationType));
+ mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setVisibility(GONE);
+ if (notifyMenuVisibility) {
+ notifyMenuStateChangeFinish(MENU_STATE_NONE);
+ }
+ if (animationFinishedRunnable != null) {
+ animationFinishedRunnable.run();
+ }
+ }
+ });
+ mMenuContainerAnimator.start();
+ }
+ }
+
+ /**
+ * @return Estimated minimum {@link Size} to hold the actions.
+ * See also {@link #updateActionViews(int, Rect)}
+ */
+ Size getEstimatedMinMenuSize() {
+ final int pipActionSize = getResources().getDimensionPixelSize(R.dimen.pip_action_size);
+ // the minimum width would be (2 * pipActionSize) since we have settings and dismiss button
+ // on the top action container.
+ final int width = Math.max(2, mActions.size()) * pipActionSize;
+ final int height = getResources().getDimensionPixelSize(R.dimen.pip_expand_action_size)
+ + getResources().getDimensionPixelSize(R.dimen.pip_action_padding)
+ + getResources().getDimensionPixelSize(R.dimen.pip_expand_container_edge_margin);
+ return new Size(width, height);
+ }
+
+ void setActions(Rect stackBounds, @Nullable List<RemoteAction> actions,
+ @Nullable RemoteAction closeAction) {
+ mActions.clear();
+ if (actions != null && !actions.isEmpty()) {
+ mActions.addAll(actions);
+ }
+ mCloseAction = closeAction;
+ if (mMenuState == MENU_STATE_FULL) {
+ updateActionViews(mMenuState, stackBounds);
+ }
+ }
+
+ private void updateActionViews(int menuState, Rect stackBounds) {
+ ViewGroup expandContainer = findViewById(R.id.expand_container);
+ ViewGroup actionsContainer = findViewById(R.id.actions_container);
+ actionsContainer.setOnTouchListener((v, ev) -> {
+ // Do nothing, prevent click through to parent
+ return true;
+ });
+
+ // Update the expand button only if it should show with the menu
+ expandContainer.setVisibility(menuState == MENU_STATE_FULL
+ ? View.VISIBLE
+ : View.INVISIBLE);
+
+ LayoutParams expandedLp =
+ (LayoutParams) expandContainer.getLayoutParams();
+ 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
+ // account for the existence of the action container
+ expandedLp.topMargin = 0;
+ expandedLp.bottomMargin = 0;
+ } else {
+ actionsContainer.setVisibility(View.VISIBLE);
+ if (mActionsGroup != null) {
+ // Ensure we have as many buttons as actions
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ while (mActionsGroup.getChildCount() < mActions.size()) {
+ final PipMenuActionView actionView = (PipMenuActionView) inflater.inflate(
+ R.layout.pip_menu_action, mActionsGroup, false);
+ mActionsGroup.addView(actionView);
+ }
+
+ // Update the visibility of all views
+ for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
+ mActionsGroup.getChildAt(i).setVisibility(i < mActions.size()
+ ? View.VISIBLE
+ : View.GONE);
+ }
+
+ // Recreate the layout
+ final boolean isLandscapePip = stackBounds != null
+ && (stackBounds.width() > stackBounds.height());
+ for (int i = 0; i < mActions.size(); i++) {
+ final RemoteAction action = mActions.get(i);
+ final PipMenuActionView actionView =
+ (PipMenuActionView) mActionsGroup.getChildAt(i);
+ final boolean isCloseAction = mCloseAction != null && Objects.equals(
+ mCloseAction.getActionIntent(), action.getActionIntent());
+
+ final int iconType = action.getIcon().getType();
+ if (iconType == Icon.TYPE_URI || iconType == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
+ // Disallow loading icon from content URI
+ actionView.setImageDrawable(null);
+ } else {
+ // TODO: Check if the action drawable has changed before we reload it
+ action.getIcon().loadDrawableAsync(mContext, d -> {
+ if (d != null) {
+ d.setTint(Color.WHITE);
+ actionView.setImageDrawable(d);
+ }
+ }, mMainHandler);
+ }
+ actionView.setCustomCloseBackgroundVisibility(
+ isCloseAction ? View.VISIBLE : View.GONE);
+ actionView.setContentDescription(action.getContentDescription());
+ if (action.isEnabled()) {
+ actionView.setOnClickListener(
+ v -> onActionViewClicked(action.getActionIntent(), isCloseAction));
+ }
+ actionView.setEnabled(action.isEnabled());
+ actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
+
+ // Update the margin between actions
+ LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
+ actionView.getLayoutParams();
+ lp.leftMargin = (isLandscapePip && i > 0) ? mBetweenActionPaddingLand : 0;
+ }
+ }
+
+ // Update the expand container margin to adjust the center of the expand button to
+ // account for the existence of the action container
+ expandedLp.topMargin = getResources().getDimensionPixelSize(
+ R.dimen.pip_action_padding);
+ expandedLp.bottomMargin = getResources().getDimensionPixelSize(
+ R.dimen.pip_expand_container_edge_margin);
+ }
+ expandContainer.requestLayout();
+ }
+
+ private void notifyMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
+ mController.onMenuStateChangeStart(menuState, resize, callback);
+ }
+
+ private void notifyMenuStateChangeFinish(int menuState) {
+ mMenuState = menuState;
+ mController.onMenuStateChangeFinish(menuState);
+ }
+
+ private void expandPip() {
+ // Do not notify menu visibility when hiding the menu, the controller will do this when it
+ // handles the message
+ hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* resize */,
+ ANIM_TYPE_HIDE);
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN);
+ }
+
+ private void dismissPip() {
+ if (mMenuState != MENU_STATE_NONE) {
+ // Do not call hideMenu() directly. Instead, let the menu controller handle it just as
+ // any other dismissal that will update the touch state and fade out the PIP task
+ // and the menu view at the same time.
+ mController.onPipDismiss();
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_TAP_TO_REMOVE);
+ }
+ }
+
+ /**
+ * Execute the {@link PendingIntent} attached to the {@link PipMenuActionView}.
+ * If the given {@link PendingIntent} matches {@link #mCloseAction}, we need to make sure
+ * the PiP is removed after a certain timeout in case the app does not respond in a
+ * timely manner.
+ */
+ private void onActionViewClicked(@NonNull PendingIntent intent, boolean isCloseAction) {
+ try {
+ intent.send();
+ } catch (PendingIntent.CanceledException e) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to send action, %s", TAG, e);
+ }
+ if (isCloseAction) {
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_CUSTOM_CLOSE);
+ mAllowTouches = false;
+ mMainExecutor.executeDelayed(() -> {
+ hideMenu();
+ // TODO: it's unsafe to call onPipDismiss with a delay here since
+ // we may have a different PiP by the time this runnable is executed.
+ mController.onPipDismiss();
+ mAllowTouches = true;
+ }, mPipForceCloseDelay);
+ }
+ }
+
+ private void showSettings() {
+ final Pair<ComponentName, Integer> topPipActivityInfo =
+ PipUtils.getTopPipActivity(mContext);
+ if (topPipActivityInfo.first != null) {
+ final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
+ Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
+ settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivityAsUser(settingsIntent, UserHandle.of(topPipActivityInfo.second));
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_SHOW_SETTINGS);
+ }
+ }
+
+ private void cancelDelayedHide() {
+ mMainExecutor.removeCallbacks(mHideMenuRunnable);
+ }
+
+ private void repostDelayedHide(int delay) {
+ int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
+ FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS);
+ mMainExecutor.removeCallbacks(mHideMenuRunnable);
+ mMainExecutor.executeDelayed(mHideMenuRunnable, recommendedTimeout);
+ }
+
+ private long getFadeOutDuration(@AnimationType int animationType) {
+ switch (animationType) {
+ case ANIM_TYPE_NONE:
+ return ANIMATION_NONE_DURATION_MS;
+ case ANIM_TYPE_HIDE:
+ return ANIMATION_HIDE_DURATION_MS;
+ case ANIM_TYPE_DISMISS:
+ return mDismissFadeOutDurationMs;
+ default:
+ throw new IllegalStateException("Invalid animation type " + animationType);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 0448d94669ce..895c793007a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -24,10 +24,12 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Rect;
import android.view.SurfaceControl;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
@@ -36,6 +38,10 @@ import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.PipTransitionController;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.function.Consumer;
+
/**
* Scheduler for Shell initiated PiP transitions and animations.
*/
@@ -57,14 +63,41 @@ public class PipScheduler {
@Nullable
private SurfaceControl mPinnedTaskLeash;
+ // true if Launcher has started swipe PiP to home animation
+ private boolean mInSwipePipToHomeTransition;
+
+ /**
+ * Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell.
+ * This is used for a broadcast receiver to resolve intents. This should be removed once
+ * there is an equivalent of PipTouchHandler and PipResizeGestureHandler for PiP2.
+ */
+ private static final int PIP_EXIT_VIA_EXPAND_CODE = 0;
+ private static final int PIP_DOUBLE_TAP = 1;
+
+ @IntDef(value = {
+ PIP_EXIT_VIA_EXPAND_CODE,
+ PIP_DOUBLE_TAP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface PipUserJourneyCode {}
+
/**
- * A temporary broadcast receiver to initiate exit PiP via expand.
- * This will later be modified to be triggered by the PiP menu.
+ * A temporary broadcast receiver to initiate PiP CUJs.
*/
private class PipSchedulerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- scheduleExitPipViaExpand();
+ int userJourneyCode = intent.getIntExtra("cuj_code_extra", 0);
+ switch (userJourneyCode) {
+ case PIP_EXIT_VIA_EXPAND_CODE:
+ scheduleExitPipViaExpand();
+ break;
+ case PIP_DOUBLE_TAP:
+ scheduleDoubleTapToResize();
+ break;
+ default:
+ throw new IllegalStateException("unexpected CUJ code=" + userJourneyCode);
+ }
}
}
@@ -96,7 +129,7 @@ public class PipScheduler {
@Nullable
private WindowContainerTransaction getExitPipViaExpandTransaction() {
- if (mPipTaskToken == null || mPinnedTaskLeash == null) {
+ if (mPipTaskToken == null) {
return null;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -121,6 +154,31 @@ public class PipScheduler {
}
}
+ /**
+ * Schedules resize PiP via double tap.
+ */
+ public void scheduleDoubleTapToResize() {}
+
+ /**
+ * Animates resizing of the pinned stack given the duration.
+ */
+ public void scheduleAnimateResizePip(Rect toBounds, Consumer<Rect> onFinishResizeCallback) {
+ if (mPipTaskToken == null) {
+ return;
+ }
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(mPipTaskToken, toBounds);
+ mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback);
+ }
+
+ void setInSwipePipToHomeTransition(boolean inSwipePipToHome) {
+ mInSwipePipToHomeTransition = true;
+ }
+
+ boolean isInSwipePipToHomeTransition() {
+ return mInSwipePipToHomeTransition;
+ }
+
void onExitPip() {
mPipTaskToken = null;
mPinnedTaskLeash = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 6200ea583a48..dfb04758c851 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -18,12 +18,16 @@ package com.android.wm.shell.pip2.phone;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PIP;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
+import android.content.Context;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -34,30 +38,39 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import java.util.function.Consumer;
+
/**
* Implementation of transitions for PiP on phone.
*/
public class PipTransition extends PipTransitionController {
private static final String TAG = PipTransition.class.getSimpleName();
+ private final Context mContext;
private final PipScheduler mPipScheduler;
@Nullable
private WindowContainerToken mPipTaskToken;
@Nullable
- private IBinder mAutoEnterButtonNavTransition;
+ private IBinder mEnterTransition;
@Nullable
private IBinder mExitViaExpandTransition;
+ @Nullable
+ private IBinder mResizeTransition;
+
+ private Consumer<Rect> mFinishResizeCallback;
public PipTransition(
+ Context context,
@NonNull ShellInit shellInit,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
@@ -68,6 +81,7 @@ public class PipTransition extends PipTransitionController {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
+ mContext = context;
mPipScheduler = pipScheduler;
mPipScheduler.setPipTransitionController(this);
}
@@ -81,7 +95,7 @@ public class PipTransition extends PipTransitionController {
@Override
public void startExitTransition(int type, WindowContainerTransaction out,
- @android.annotation.Nullable Rect destinationBounds) {
+ @Nullable Rect destinationBounds) {
if (out == null) {
return;
}
@@ -91,12 +105,22 @@ public class PipTransition extends PipTransitionController {
}
}
+ @Override
+ public void startResizeTransition(WindowContainerTransaction wct,
+ Consumer<Rect> onFinishResizeCallback) {
+ if (wct == null) {
+ return;
+ }
+ mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this);
+ mFinishResizeCallback = onFinishResizeCallback;
+ }
+
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
- if (isAutoEnterInButtonNavigation(request)) {
- mAutoEnterButtonNavTransition = transition;
+ if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) {
+ mEnterTransition = transition;
return getEnterPipTransaction(transition, request);
}
return null;
@@ -105,9 +129,9 @@ public class PipTransition extends PipTransitionController {
@Override
public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request,
@NonNull WindowContainerTransaction outWct) {
- if (isAutoEnterInButtonNavigation(request)) {
+ if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) {
outWct.merge(getEnterPipTransaction(transition, request), true /* transfer */);
- mAutoEnterButtonNavTransition = transition;
+ mEnterTransition = transition;
}
}
@@ -120,6 +144,151 @@ public class PipTransition extends PipTransitionController {
public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
@Nullable SurfaceControl.Transaction finishT) {}
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (transition == mEnterTransition) {
+ mEnterTransition = null;
+ if (mPipScheduler.isInSwipePipToHomeTransition()) {
+ // If this is the second transition as a part of swipe PiP to home cuj,
+ // handle this transition as a special case with no-op animation.
+ return handleSwipePipToHomeTransition(info, startTransaction, finishTransaction,
+ finishCallback);
+ }
+ if (isLegacyEnter(info)) {
+ // If this is a legacy-enter-pip (auto-enter is off and PiP activity went to pause),
+ // then we should run an ALPHA type (cross-fade) animation.
+ return startAlphaTypeEnterAnimation(info, startTransaction, finishTransaction,
+ finishCallback);
+ }
+ return startBoundsTypeEnterAnimation(info, startTransaction, finishTransaction,
+ finishCallback);
+ } else if (transition == mExitViaExpandTransition) {
+ mExitViaExpandTransition = null;
+ return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
+ } else if (transition == mResizeTransition) {
+ mResizeTransition = null;
+ return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback);
+ }
+ return false;
+ }
+
+ private boolean startResizeAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ TransitionInfo.Change pipChange = getPipChange(info);
+ if (pipChange == null) {
+ return false;
+ }
+ SurfaceControl pipLeash = pipChange.getLeash();
+ Rect destinationBounds = pipChange.getEndAbsBounds();
+
+ // Even though the final bounds and crop are applied with finishTransaction since
+ // this is a visible change, we still need to handle the app draw coming in. Snapshot
+ // covering app draw during collection will be removed by startTransaction. So we make
+ // the crop equal to the final bounds and then scale the leash back to starting bounds.
+ startTransaction.setWindowCrop(pipLeash, pipChange.getEndAbsBounds().width(),
+ pipChange.getEndAbsBounds().height());
+ startTransaction.setScale(pipLeash,
+ (float) mPipBoundsState.getBounds().width() / destinationBounds.width(),
+ (float) mPipBoundsState.getBounds().height() / destinationBounds.height());
+ startTransaction.apply();
+
+ finishTransaction.setScale(pipLeash,
+ (float) mPipBoundsState.getBounds().width() / destinationBounds.width(),
+ (float) mPipBoundsState.getBounds().height() / destinationBounds.height());
+
+ // We are done with the transition, but will continue animating leash to final bounds.
+ finishCallback.onTransitionFinished(null);
+
+ // Animate the pip leash with the new buffer
+ final int duration = mContext.getResources().getInteger(
+ R.integer.config_pipResizeAnimationDuration);
+ // TODO: b/275910498 Couple this routine with a new implementation of the PiP animator.
+ startResizeAnimation(pipLeash, mPipBoundsState.getBounds(), destinationBounds, duration);
+ return true;
+ }
+
+ private boolean handleSwipePipToHomeTransition(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ TransitionInfo.Change pipChange = getPipChange(info);
+ if (pipChange == null) {
+ return false;
+ }
+ mPipScheduler.setInSwipePipToHomeTransition(false);
+ mPipTaskToken = pipChange.getContainer();
+
+ // cache the PiP task token and leash
+ mPipScheduler.setPipTaskToken(mPipTaskToken);
+
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
+
+ private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ TransitionInfo.Change pipChange = getPipChange(info);
+ if (pipChange == null) {
+ return false;
+ }
+ mPipTaskToken = pipChange.getContainer();
+
+ // cache the PiP task token and leash
+ mPipScheduler.setPipTaskToken(mPipTaskToken);
+
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
+
+ private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ TransitionInfo.Change pipChange = getPipChange(info);
+ if (pipChange == null) {
+ return false;
+ }
+ mPipTaskToken = pipChange.getContainer();
+
+ // cache the PiP task token and leash
+ mPipScheduler.setPipTaskToken(mPipTaskToken);
+
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
+
+ private boolean startExpandAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ onExitPip();
+ return true;
+ }
+
+ @Nullable
+ private TransitionInfo.Change getPipChange(TransitionInfo info) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
+ return change;
+ }
+ }
+ return null;
+ }
+
private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
// cache the original task token to check for multi-activity case later
@@ -153,48 +322,24 @@ public class PipTransition extends PipTransitionController {
&& pipTask.pictureInPictureParams.isAutoEnterEnabled();
}
- @Override
- public boolean startAnimation(@NonNull IBinder transition,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (transition == mAutoEnterButtonNavTransition) {
- mAutoEnterButtonNavTransition = null;
- TransitionInfo.Change pipChange = getPipChange(info);
- if (pipChange == null) {
- return false;
- }
- mPipTaskToken = pipChange.getContainer();
-
- // cache the PiP task token and leash
- mPipScheduler.setPipTaskToken(mPipTaskToken);
- mPipScheduler.setPinnedTaskLeash(pipChange.getLeash());
-
- startTransaction.apply();
- finishCallback.onTransitionFinished(null);
- return true;
- } else if (transition == mExitViaExpandTransition) {
- mExitViaExpandTransition = null;
- startTransaction.apply();
- finishCallback.onTransitionFinished(null);
- onExitPip();
- return true;
- }
- return false;
+ private boolean isEnterPictureInPictureModeRequest(@NonNull TransitionRequestInfo requestInfo) {
+ return requestInfo.getType() == TRANSIT_PIP;
}
- @Nullable
- private TransitionInfo.Change getPipChange(TransitionInfo info) {
- for (TransitionInfo.Change change : info.getChanges()) {
- if (change.getTaskInfo() != null
- && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
- return change;
- }
- }
- return null;
+ private boolean isLegacyEnter(@NonNull TransitionInfo info) {
+ TransitionInfo.Change pipChange = getPipChange(info);
+ // If the only change in the changes list is a TO_FRONT mode PiP task,
+ // then this is legacy-enter PiP.
+ return pipChange != null && pipChange.getMode() == TRANSIT_TO_FRONT
+ && info.getChanges().size() == 1;
}
+ /**
+ * TODO: b/275910498 Use a new implementation of the PiP animator here.
+ */
+ private void startResizeAnimation(SurfaceControl leash, Rect startBounds,
+ Rect endBounds, int duration) {}
+
private void onExitPip() {
mPipTaskToken = null;
mPipScheduler.onExitPip();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/OWNERS
new file mode 100644
index 000000000000..3f3308cfc75a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/OWNERS
@@ -0,0 +1 @@
+include platform/development:/tools/winscope/OWNERS
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 05e4af3302af..ad29d15019c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -26,6 +26,8 @@ import com.android.internal.protolog.common.IProtoLogGroup;
public enum ShellProtoLogGroup implements IProtoLogGroup {
// NOTE: Since we enable these from the same WM ShellCommand, these names should not conflict
// with those in the framework ProtoLogGroup
+ WM_SHELL(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ Consts.TAG_WM_SHELL),
WM_SHELL_INIT(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM_SHELL),
WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
deleted file mode 100644
index 93ffb3dc8115..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
+++ /dev/null
@@ -1,120 +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.protolog;
-
-import android.annotation.Nullable;
-
-import com.android.internal.protolog.BaseProtoLogImpl;
-import com.android.internal.protolog.ProtoLogViewerConfigReader;
-import com.android.internal.protolog.common.IProtoLogGroup;
-
-import java.io.File;
-import java.io.PrintWriter;
-
-
-/**
- * A service for the ProtoLog logging system.
- */
-public class ShellProtoLogImpl extends BaseProtoLogImpl {
- private static final String TAG = "ProtoLogImpl";
- private static final int BUFFER_CAPACITY = 1024 * 1024;
- // TODO: find a proper location to save the protolog message file
- private static final String LOG_FILENAME = "/data/misc/wmtrace/shell_log.winscope";
- private static final String VIEWER_CONFIG_FILENAME = "/system_ext/etc/wmshell.protolog.json.gz";
-
- private static ShellProtoLogImpl sServiceInstance = null;
-
- static {
- addLogGroupEnum(ShellProtoLogGroup.values());
- }
-
- /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void d(IProtoLogGroup group, int messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance()
- .log(LogLevel.DEBUG, group, messageHash, paramsMask, messageString, args);
- }
-
- /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void v(IProtoLogGroup group, int messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance().log(LogLevel.VERBOSE, group, messageHash, paramsMask, messageString,
- args);
- }
-
- /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void i(IProtoLogGroup group, int messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance().log(LogLevel.INFO, group, messageHash, paramsMask, messageString, args);
- }
-
- /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void w(IProtoLogGroup group, int messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance().log(LogLevel.WARN, group, messageHash, paramsMask, messageString, args);
- }
-
- /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void e(IProtoLogGroup group, int messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance()
- .log(LogLevel.ERROR, group, messageHash, paramsMask, messageString, args);
- }
-
- /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void wtf(IProtoLogGroup group, int messageHash, int paramsMask,
- @Nullable String messageString,
- Object... args) {
- getSingleInstance().log(LogLevel.WTF, group, messageHash, paramsMask, messageString, args);
- }
-
- /** Returns true iff logging is enabled for the given {@code IProtoLogGroup}. */
- public static boolean isEnabled(IProtoLogGroup group) {
- return group.isLogToLogcat()
- || (group.isLogToProto() && getSingleInstance().isProtoEnabled());
- }
-
- /**
- * Returns the single instance of the ProtoLogImpl singleton class.
- */
- public static synchronized ShellProtoLogImpl getSingleInstance() {
- if (sServiceInstance == null) {
- sServiceInstance = new ShellProtoLogImpl();
- }
- return sServiceInstance;
- }
-
- public int startTextLogging(String[] groups, PrintWriter pw) {
- mViewerConfig.loadViewerConfig(pw, VIEWER_CONFIG_FILENAME);
- return setLogging(true /* setTextLogging */, true, pw, groups);
- }
-
- public int stopTextLogging(String[] groups, PrintWriter pw) {
- return setLogging(true /* setTextLogging */, false, pw, groups);
- }
-
- private ShellProtoLogImpl() {
- super(new File(LOG_FILENAME), VIEWER_CONFIG_FILENAME, BUFFER_CAPACITY,
- new ProtoLogViewerConfigReader());
- }
-}
-
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index e4213569b526..1c54754e9953 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -163,14 +163,14 @@ public class RecentTasksController implements TaskStackListenerCallback,
/**
* Adds a split pair. This call does not validate the taskIds, only that they are not the same.
*/
- public void addSplitPair(int taskId1, int taskId2, SplitBounds splitBounds) {
+ public boolean addSplitPair(int taskId1, int taskId2, SplitBounds splitBounds) {
if (taskId1 == taskId2) {
- return;
+ return false;
}
if (mSplitTasks.get(taskId1, INVALID_TASK_ID) == taskId2
&& mTaskSplitBoundsMap.get(taskId1).equals(splitBounds)) {
// If the two tasks are already paired and the bounds are the same, then skip updating
- return;
+ return false;
}
// Remove any previous pairs
removeSplitPair(taskId1);
@@ -185,6 +185,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
notifyRecentTasksChanged();
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENT_TASKS, "Add split pair: %d, %d, %s",
taskId1, taskId2, splitBounds);
+ return true;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index d023cea6d19d..b5ea1b1b43ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -19,10 +19,12 @@ package com.android.wm.shell.recents;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
@@ -58,10 +60,10 @@ import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.function.Consumer;
@@ -591,7 +593,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
cancel("transit_sleep");
return;
}
- if (mKeyguardLocked || (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
+ if (mKeyguardLocked || (info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.merge: keyguard is locked", mInstanceId);
// We will not accept new changes if we are swiping over the keyguard.
@@ -928,7 +930,14 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
Slog.e(TAG, "Duplicate call to finish");
return;
}
- if (!toHome) {
+
+ boolean returningToApp = !toHome
+ && !mWillFinishToHome
+ && mPausingTasks != null
+ && mState == STATE_NORMAL;
+ if (returningToApp && allAppsAreTranslucent(mPausingTasks)) {
+ mHomeTransitionObserver.notifyHomeVisibilityChanged(true);
+ } else if (!toHome) {
// For some transitions, we may have notified home activity that it became visible.
// We need to notify the observer that we are no longer going home.
mHomeTransitionObserver.notifyHomeVisibilityChanged(false);
@@ -947,7 +956,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
else wct.restoreTransientOrder(mRecentsTask);
}
- if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
+ if (returningToApp) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app");
// The gesture is returning to the pausing-task(s) rather than continuing with
// recents, so end the transition by moving the app back to the top (and also
@@ -1020,7 +1029,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsController.finishInner: no valid PiP leash;"
+ "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d",
- mPipTransaction.toString(), mPipTask.toString(), mPipTaskId);
+ mPipTransaction, mPipTask, mPipTaskId);
} else {
t.show(pipLeash);
PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t);
@@ -1047,6 +1056,18 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
}
+ private boolean allAppsAreTranslucent(ArrayList<TaskState> tasks) {
+ if (tasks == null || tasks.isEmpty()) {
+ return false;
+ }
+ for (int i = tasks.size() - 1; i >= 0; --i) {
+ if (!tasks.get(i).mIsTranslucent) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct,
SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) {
if (!sendUserLeaveHint && task.isLeaf()) {
@@ -1117,6 +1138,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
/** The surface/leash of the task provided by Core. */
SurfaceControl mTaskSurface;
+ /** True when the task is translucent. */
+ final boolean mIsTranslucent;
+
/** The (local) animation-leash created for this task. Only non-null for leafs. */
@Nullable
SurfaceControl mLeash;
@@ -1125,6 +1149,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
mToken = change.getContainer();
mTaskInfo = change.getTaskInfo();
mTaskSurface = change.getLeash();
+ mIsTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0;
mLeash = leash;
}
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 253acc49071a..0ca244c4b96a 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
@@ -158,5 +158,10 @@ interface ISplitScreen {
* does not expect split to currently be running.
*/
RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
+
+ /**
+ * Reverse the split.
+ */
+ oneway void switchSplitPosition() = 22;
}
-// Last id = 21 \ No newline at end of file
+// Last id = 22 \ No newline at end of file
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 e52235fda80f..64e26dbd70be 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,11 +16,14 @@
package com.android.wm.shell.splitscreen;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
+
import android.content.Context;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -50,6 +53,8 @@ class MainStage extends StageTaskListener {
void activate(WindowContainerTransaction wct, boolean includingTopTask) {
if (mIsActive) return;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "activate: main stage includingTopTask=%b",
+ includingTopTask);
if (includingTopTask) {
reparentTopTask(wct);
@@ -64,6 +69,8 @@ class MainStage extends StageTaskListener {
void deactivate(WindowContainerTransaction wct, boolean toTop) {
if (!mIsActive) return;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "deactivate: main stage toTop=%b rootTaskInfo=%s",
+ toTop, mRootTaskInfo);
mIsActive = false;
if (mRootTaskInfo == null) return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS
index 7237d2bde39f..37ccd15587e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS
@@ -1,2 +1,4 @@
# WM shell sub-modules splitscreen owner
chenghsiuchang@google.com
+jeremysim@google.com
+peanutbutter@google.com
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 9903113c5453..f5fbae55960a 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,12 +16,15 @@
package com.android.wm.shell.splitscreen;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
+
import android.app.ActivityManager;
import android.content.Context;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -47,6 +50,8 @@ class SideStage extends StageTaskListener {
}
boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove all side stage tasks: childCount=%d toTop=%b",
+ mChildrenTaskInfo.size(), toTop);
if (mChildrenTaskInfo.size() == 0) return false;
wct.reparentTasks(
mRootTaskInfo.token,
@@ -59,6 +64,8 @@ class SideStage extends StageTaskListener {
boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove side stage task: task=%d exists=%b", taskId,
+ task != null);
if (task == null) return false;
wct.reparent(task.token, newParent, false /* onTop */);
return true;
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 7b5709769369..952e2d4b3b9a 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
@@ -25,20 +25,20 @@ 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.MultiInstanceHelper.getComponent;
+import static com.android.wm.shell.common.MultiInstanceHelper.getShortcutComponent;
+import static com.android.wm.shell.common.MultiInstanceHelper.samePackage;
import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
-import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -47,6 +47,7 @@ import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.os.Bundle;
@@ -68,6 +69,8 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
@@ -81,6 +84,7 @@ import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.LaunchAdjacentController;
+import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -133,7 +137,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
public static final int EXIT_REASON_RECREATE_SPLIT = 10;
public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 11;
- public static final int EXIT_REASON_ENTER_DESKTOP = 12;
+ public static final int EXIT_REASON_DESKTOP_MODE = 12;
@IntDef(value = {
EXIT_REASON_UNKNOWN,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -147,7 +151,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
EXIT_REASON_CHILD_TASK_ENTER_PIP,
EXIT_REASON_RECREATE_SPLIT,
EXIT_REASON_FULLSCREEN_SHORTCUT,
- EXIT_REASON_ENTER_DESKTOP
+ EXIT_REASON_DESKTOP_MODE
})
@Retention(RetentionPolicy.SOURCE)
@interface ExitReason{}
@@ -171,13 +175,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final ShellTaskOrganizer mTaskOrganizer;
private final SyncTransactionQueue mSyncQueue;
private final Context mContext;
+ private final LauncherApps mLauncherApps;
private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
private final ShellExecutor mMainExecutor;
private final SplitScreenImpl mImpl = new SplitScreenImpl();
private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
private final DisplayInsetsController mDisplayInsetsController;
- private final Optional<DragAndDropController> mDragAndDropController;
+ private final DragAndDropController mDragAndDropController;
private final Transitions mTransitions;
private final TransactionPool mTransactionPool;
private final IconProvider mIconProvider;
@@ -185,8 +190,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final LaunchAdjacentController mLaunchAdjacentController;
private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
private final Optional<DesktopTasksController> mDesktopTasksController;
+ private final MultiInstanceHelper mMultiInstanceHelpher;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
- private final String[] mAppsSupportMultiInstances;
@VisibleForTesting
StageCoordinator mStageCoordinator;
@@ -196,6 +201,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private SurfaceControl mGoingToRecentsTasksLayer;
private SurfaceControl mStartingSplitTasksLayer;
+ /**
+ * @param stageCoordinator if null, a stage coordinator will be created when this controller is
+ * initialized. Can be non-null for testing purposes.
+ */
public SplitScreenController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
@@ -206,7 +215,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropController,
+ DragAndDropController dragAndDropController,
Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider,
@@ -214,12 +223,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
LaunchAdjacentController launchAdjacentController,
Optional<WindowDecorViewModel> windowDecorViewModel,
Optional<DesktopTasksController> desktopTasksController,
+ @Nullable StageCoordinator stageCoordinator,
+ MultiInstanceHelper multiInstanceHelper,
ShellExecutor mainExecutor) {
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
mContext = context;
+ mLauncherApps = context.getSystemService(LauncherApps.class);
mRootTDAOrganizer = rootTDAOrganizer;
mMainExecutor = mainExecutor;
mDisplayController = displayController;
@@ -233,63 +245,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mLaunchAdjacentController = launchAdjacentController;
mWindowDecorViewModel = windowDecorViewModel;
mDesktopTasksController = desktopTasksController;
+ mStageCoordinator = stageCoordinator;
+ mMultiInstanceHelpher = multiInstanceHelper;
mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
// override for this controller from the base module
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
shellInit.addInitCallback(this::onInit, this);
}
-
- // TODO(255224696): Remove the config once having a way for client apps to opt-in
- // multi-instances split.
- mAppsSupportMultiInstances = mContext.getResources()
- .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
- }
-
- @VisibleForTesting
- SplitScreenController(Context context,
- ShellInit shellInit,
- ShellCommandHandler shellCommandHandler,
- ShellController shellController,
- ShellTaskOrganizer shellTaskOrganizer,
- SyncTransactionQueue syncQueue,
- RootTaskDisplayAreaOrganizer rootTDAOrganizer,
- DisplayController displayController,
- DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController,
- DragAndDropController dragAndDropController,
- Transitions transitions,
- TransactionPool transactionPool,
- IconProvider iconProvider,
- RecentTasksController recentTasks,
- LaunchAdjacentController launchAdjacentController,
- WindowDecorViewModel windowDecorViewModel,
- DesktopTasksController desktopTasksController,
- ShellExecutor mainExecutor,
- StageCoordinator stageCoordinator) {
- mShellCommandHandler = shellCommandHandler;
- mShellController = shellController;
- mTaskOrganizer = shellTaskOrganizer;
- mSyncQueue = syncQueue;
- mContext = context;
- mRootTDAOrganizer = rootTDAOrganizer;
- mMainExecutor = mainExecutor;
- mDisplayController = displayController;
- mDisplayImeController = displayImeController;
- mDisplayInsetsController = displayInsetsController;
- mDragAndDropController = Optional.of(dragAndDropController);
- mTransitions = transitions;
- mTransactionPool = transactionPool;
- mIconProvider = iconProvider;
- mRecentTasksOptional = Optional.of(recentTasks);
- mLaunchAdjacentController = launchAdjacentController;
- mWindowDecorViewModel = Optional.of(windowDecorViewModel);
- mDesktopTasksController = Optional.of(desktopTasksController);
- mStageCoordinator = stageCoordinator;
- mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
- shellInit.addInitCallback(this::onInit, this);
- mAppsSupportMultiInstances = mContext.getResources()
- .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
}
public SplitScreen asSplitScreen() {
@@ -316,7 +279,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
// TODO: Multi-display
mStageCoordinator = createStageCoordinator();
}
- mDragAndDropController.ifPresent(controller -> controller.setSplitScreenController(this));
+ if (mDragAndDropController != null) {
+ mDragAndDropController.setSplitScreenController(this);
+ }
mWindowDecorViewModel.ifPresent(viewModel -> viewModel.setSplitScreenController(this));
mDesktopTasksController.ifPresent(controller -> controller.setSplitScreenController(this));
}
@@ -440,6 +405,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
/**
+ * Performs previous child eviction and such to prepare for the pip task expending into one of
+ * the split stages
+ *
+ * @param taskInfo TaskInfo of the pip task
+ */
+ public void onPipExpandToSplit(WindowContainerTransaction wct,
+ ActivityManager.RunningTaskInfo taskInfo) {
+ mStageCoordinator.onPipExpandToSplit(wct, taskInfo);
+ }
+
+ /**
* Doing necessary window transaction for other transition handler need to exit split in
* transition.
*/
@@ -500,12 +476,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
public void goToFullscreenFromSplit() {
- mStageCoordinator.goToFullscreenFromSplit();
+ if (mStageCoordinator.isSplitActive()) {
+ mStageCoordinator.goToFullscreenFromSplit();
+ }
}
/** Move the specified task to fullscreen, regardless of focus state. */
- public void moveTaskToFullscreen(int taskId) {
- mStageCoordinator.moveTaskToFullscreen(taskId);
+ public void moveTaskToFullscreen(int taskId, int exitReason) {
+ mStageCoordinator.moveTaskToFullscreen(taskId, exitReason);
}
public boolean isLaunchToSplit(TaskInfo taskInfo) {
@@ -588,7 +566,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
if (samePackage(packageName, getPackageName(reverseSplitPosition(position)),
user.getIdentifier(), getUserId(reverseSplitPosition(position)))) {
- if (supportMultiInstancesSplit(packageName)) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(
+ getShortcutComponent(packageName, shortcutId, user, mLauncherApps))) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else if (isSplitScreenVisible()) {
@@ -609,7 +588,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
activityOptions.toBundle(), user);
}
- void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
+ void startShortcutAndTaskWithLegacyTransition(@NonNull ShortcutInfo shortcutInfo,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
@SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
@@ -621,7 +600,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final int userId1 = shortcutInfo.getUserId();
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(shortcutInfo.getPackage())) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(shortcutInfo.getActivity())) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
@@ -640,7 +619,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
instanceId);
}
- void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
+ void startShortcutAndTask(@NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options1,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
@PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
InstanceId instanceId) {
@@ -653,7 +632,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final int userId1 = shortcutInfo.getUserId();
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(shortcutInfo.getActivity())) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
@@ -692,7 +671,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent))) {
fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
@@ -722,7 +701,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
boolean setSecondIntentMultipleTask = false;
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent))) {
setSecondIntentMultipleTask = true;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
@@ -757,7 +736,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent1))) {
fillInIntent1 = new Intent();
fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
fillInIntent2 = new Intent();
@@ -794,7 +773,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic();
boolean setSecondIntentMultipleTask = false;
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent1))) {
fillInIntent1 = new Intent();
fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
setSecondIntentMultipleTask = true;
@@ -829,6 +808,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "startIntent(): intent=%s user=%d fillInIntent=%s position=%d", intent, userId1,
+ fillInIntent, position);
// Flag this as a no-user-action launch to prevent sending user leaving event to the current
// top activity since it's going to be put into another side of the split. This prevents the
// current top activity from going into pip mode due to user leaving event.
@@ -847,6 +829,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
.map(recentTasks -> recentTasks.findTaskInBackground(component, userId1))
.orElse(null);
if (taskInfo != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Found suitable background task=%s", taskInfo);
if (ENABLE_SHELL_TRANSITIONS) {
mStageCoordinator.startTask(taskInfo.taskId, position, options);
} else {
@@ -856,7 +840,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return;
}
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(intent))) {
// Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
// the split and there is no reusable background task.
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -915,19 +899,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return taskInfo != null ? taskInfo.userId : -1;
}
- @VisibleForTesting
- boolean supportMultiInstancesSplit(String packageName) {
- if (packageName != null) {
- for (int i = 0; i < mAppsSupportMultiInstances.length; i++) {
- if (mAppsSupportMultiInstances[i].equals(packageName)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
/**
* Determines whether the widgetIntent needs to be modified if multiple tasks of its
* corresponding package/app are supported. There are 4 possible paths:
@@ -1038,6 +1009,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.onDroppedToSplit(position, dragSessionId);
}
+ void switchSplitPosition(String reason) {
+ if (isSplitScreenVisible()) {
+ mStageCoordinator.switchSplitPosition(reason);
+ }
+ }
+
/**
* Return the {@param exitReason} as a string.
*/
@@ -1065,8 +1042,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return "CHILD_TASK_ENTER_PIP";
case EXIT_REASON_RECREATE_SPLIT:
return "RECREATE_SPLIT";
- case EXIT_REASON_ENTER_DESKTOP:
- return "ENTER_DESKTOP";
+ case EXIT_REASON_DESKTOP_MODE:
+ return "DESKTOP_MODE";
default:
return "unknown reason, reason int = " + exitReason;
}
@@ -1402,5 +1379,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
true /* blocking */);
return out[0];
}
+
+ @Override
+ public void switchSplitPosition() {
+ executeRemoteCallWithTaskPermission(mController, "switchSplitPosition",
+ (controller) -> controller.switchSplitPosition("remoteCall"));
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
index 7fd03a9a306b..7f16c5e3592e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -43,6 +43,8 @@ public class SplitScreenShellCommandHandler implements
return runRemoveFromSideStage(args, pw);
case "setSideStagePosition":
return runSetSideStagePosition(args, pw);
+ case "switchSplitPosition":
+ return runSwitchSplitPosition();
default:
pw.println("Invalid command: " + args[0]);
return false;
@@ -84,6 +86,11 @@ public class SplitScreenShellCommandHandler implements
return true;
}
+ private boolean runSwitchSplitPosition() {
+ mController.switchSplitPosition("shellCommand");
+ return true;
+ }
+
@Override
public void printShellCommandHelp(PrintWriter pw, String prefix) {
pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>");
@@ -92,5 +99,7 @@ public class SplitScreenShellCommandHandler implements
pw.println(prefix + " Remove a task with given id in split-screen mode.");
pw.println(prefix + "setSideStagePosition <SideStagePosition>");
pw.println(prefix + " Sets the position of the side-stage.");
+ pw.println(prefix + "switchSplitPosition");
+ pw.println(prefix + " Reverses the split.");
}
}
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 2c0ba92524ad..1a53a1d10dd2 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
@@ -25,6 +25,8 @@ 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.common.split.SplitScreenConstants.FADE_DURATION;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
@@ -48,9 +50,9 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.OneShotRemoteHandler;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
@@ -101,6 +103,7 @@ class SplitScreenTransitions {
@NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
@NonNull WindowContainerToken topRoot) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playAnimation: transition=%d", info.getDebugId());
initTransition(transition, finishTransaction, finishCallback);
final TransitSession pendingTransition = getPendingTransition(transition);
@@ -123,10 +126,12 @@ class SplitScreenTransitions {
playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot, topRoot);
}
- /** Internal funcation of playAnimation. */
+ /** Internal function of playAnimation. */
private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
@NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playInternalAnimation: transition=%d",
+ info.getDebugId());
// Play some place-holder fade animations
final boolean isEnter = isPendingEnter(transition);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -220,6 +225,8 @@ class SplitScreenTransitions {
@NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull WindowContainerToken toTopRoot, @NonNull SplitDecorManager toTopDecor,
@NonNull WindowContainerToken topRoot) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playDragDismissAnimation: transition=%d",
+ info.getDebugId());
initTransition(transition, finishTransaction, finishCallback);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -259,6 +266,7 @@ class SplitScreenTransitions {
@NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
@NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playResizeAnimation: transition=%d", info.getDebugId());
initTransition(transition, finishTransaction, finishCallback);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -312,13 +320,15 @@ class SplitScreenTransitions {
@Nullable
private TransitSession getPendingTransition(IBinder transition) {
if (isPendingEnter(transition)) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved enter transition");
return mPendingEnter;
} else if (isPendingDismiss(transition)) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved dismiss transition");
return mPendingDismiss;
} else if (isPendingResize(transition)) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved resize transition");
return mPendingResize;
}
-
return null;
}
@@ -339,7 +349,7 @@ class SplitScreenTransitions {
Transitions.TransitionHandler handler,
int extraTransitType, boolean resizeAnim) {
if (mPendingEnter != null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition "
+ " skip to start enter split transition since it already exist. ");
return null;
}
@@ -355,8 +365,10 @@ class SplitScreenTransitions {
mPendingEnter = new EnterSession(
transition, remoteTransition, extraTransitType, resizeAnim);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Enter split screen");
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setEnterTransition: transitType=%d resize=%b",
+ extraTransitType, resizeAnim);
}
/** Starts a transition to dismiss split. */
@@ -364,7 +376,7 @@ class SplitScreenTransitions {
Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop,
@SplitScreenController.ExitReason int reason) {
if (mPendingDismiss != null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition "
+ " skip to start dismiss split transition since it already exist. reason to "
+ " dismiss = %s", exitReasonToString(reason));
return null;
@@ -381,32 +393,35 @@ class SplitScreenTransitions {
@SplitScreenController.ExitReason int reason) {
mPendingDismiss = new DismissSession(transition, reason, dismissTop);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Dismiss due to %s. toTop=%s",
exitReasonToString(reason), stageTypeToString(dismissTop));
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setDismissTransition: reason=%s dismissTop=%s",
+ exitReasonToString(reason), stageTypeToString(dismissTop));
}
IBinder startResizeTransition(WindowContainerTransaction wct,
Transitions.TransitionHandler handler,
- @Nullable TransitionFinishedCallback finishCallback) {
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishCallback,
+ @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " splitTransition deduced Resize split screen.");
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setResizeTransition: hasPendingResize=%b",
+ mPendingResize != null);
if (mPendingResize != null) {
+ mainDecor.cancelRunningAnimations();
+ sideDecor.cancelRunningAnimations();
mPendingResize.cancel(null);
mAnimations.clear();
onFinish(null /* wct */);
}
IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler);
- setResizeTransition(transition, finishCallback);
+ mPendingResize = new TransitSession(transition, consumedCallback, finishCallback);
return transition;
}
- void setResizeTransition(@NonNull IBinder transition,
- @Nullable TransitionFinishedCallback finishCallback) {
- mPendingResize = new TransitSession(transition, null /* consumedCb */, finishCallback);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
- + " deduced Resize split screen");
- }
-
void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
if (mergeTarget != mAnimatingTransition) return;
@@ -442,13 +457,18 @@ class SplitScreenTransitions {
mPendingEnter.onConsumed(aborted);
mPendingEnter = null;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for enter transition");
} else if (isPendingDismiss(transition)) {
mPendingDismiss.onConsumed(aborted);
mPendingDismiss = null;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for dismiss transition");
} else if (isPendingResize(transition)) {
mPendingResize.onConsumed(aborted);
mPendingResize = null;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for resize transition");
}
+
+ // TODO: handle transition consumed for active remote handler
}
void onFinish(WindowContainerTransaction wct) {
@@ -458,12 +478,15 @@ class SplitScreenTransitions {
if (isPendingEnter(mAnimatingTransition)) {
mPendingEnter.onFinished(wct, mFinishTransaction);
mPendingEnter = null;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for enter transition");
} else if (isPendingDismiss(mAnimatingTransition)) {
mPendingDismiss.onFinished(wct, mFinishTransaction);
mPendingDismiss = null;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for dismiss transition");
} else if (isPendingResize(mAnimatingTransition)) {
mPendingResize.onFinished(wct, mFinishTransaction);
mPendingResize = null;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for resize transition");
}
mActiveRemoteHandler = null;
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
index f4ab2266179a..a0bf843444df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -25,7 +25,7 @@ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED_
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__FULLSCREEN_SHORTCUT;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ENTER_DESKTOP;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DESKTOP_MODE;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ROOT_TASK_VANISHED;
@@ -43,7 +43,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
-import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ENTER_DESKTOP;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RECREATE_SPLIT;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
@@ -194,8 +194,8 @@ public class SplitscreenEventLogger {
return SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
case EXIT_REASON_FULLSCREEN_SHORTCUT:
return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
- case EXIT_REASON_ENTER_DESKTOP:
- return SPLITSCREEN_UICHANGED__EXIT_REASON__ENTER_DESKTOP;
+ case EXIT_REASON_DESKTOP_MODE:
+ return SPLITSCREEN_UICHANGED__EXIT_REASON__DESKTOP_MODE;
case EXIT_REASON_UNKNOWN:
// Fall through
default:
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 99b18d7d860d..ec907fd9bd12 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
@@ -45,6 +45,9 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
+import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
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;
@@ -53,6 +56,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASO
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
@@ -65,8 +69,6 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonT
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
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.util.TransitionUtil.isClosingType;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -136,13 +138,14 @@ import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.common.split.SplitWindowManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
+import com.android.wm.shell.splitscreen.SplitScreenController.SplitEnterReason;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.SplitBounds;
-import com.android.wm.shell.util.TransitionUtil;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import dalvik.annotation.optimization.NeverCompile;
@@ -314,6 +317,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Creating main/side root task");
mMainStage = new MainStage(
mContext,
mTaskOrganizer,
@@ -455,6 +459,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition,
WindowContainerTransaction wct) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "moveToStage: task=%d position=%d", task.taskId,
+ stagePosition);
prepareEnterSplitScreen(wct, task, stagePosition, false /* resizeAnim */);
if (ENABLE_SHELL_TRANSITIONS) {
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct,
@@ -475,6 +481,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
boolean removeFromSideStage(int taskId) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "removeFromSideStage: task=%d", taskId);
final WindowContainerTransaction wct = new WindowContainerTransaction();
/**
@@ -499,11 +506,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo, splitPosition,
taskBounds);
}
- if (enteredSplitSelect) mTaskOrganizer.applyTransaction(wct);
+ if (enteredSplitSelect) {
+ mTaskOrganizer.applyTransaction(wct);
+ }
}
void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
Bundle options, UserHandle user) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startShortcut: pkg=%s id=%s position=%d user=%d",
+ packageName, shortcutId, position, user.getIdentifier());
final boolean isEnteringSplit = !isSplitActive();
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -565,6 +576,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Use this method to launch an existing Task via a taskId */
void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startTask: task=%d position=%d", taskId, position);
mSplitRequest = new SplitRequest(taskId, position);
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
@@ -596,6 +608,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Launches an activity into split. */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntent: intent=%s position=%d", intent.getIntent(),
+ position);
mSplitRequest = new SplitRequest(intent.getIntent(), position);
if (!ENABLE_SHELL_TRANSITIONS) {
startIntentLegacy(intent, fillInIntent, position, options);
@@ -691,6 +705,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2,
@SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "startTasks: task1=%d task2=%d position=%d snapPosition=%d",
+ taskId1, taskId2, splitPosition, snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId2 == INVALID_TASK_ID) {
if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
@@ -719,6 +736,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
@SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "startIntentAndTask: intent=%s task1=%d position=%d snapPosition=%d",
+ pendingIntent.getIntent(), taskId, splitPosition, snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId == INVALID_TASK_ID) {
options1 = options1 != null ? options1 : new Bundle();
@@ -741,6 +761,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
@PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
InstanceId instanceId) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "startShortcutAndTask: shortcut=%s task1=%d position=%d snapPosition=%d",
+ shortcutInfo, taskId, splitPosition, snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId == INVALID_TASK_ID) {
options1 = options1 != null ? options1 : new Bundle();
@@ -798,10 +821,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void startIntents(PendingIntent pendingIntent1, Intent fillInIntent1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
- PendingIntent pendingIntent2, Intent fillInIntent2,
+ @Nullable PendingIntent pendingIntent2, Intent fillInIntent2,
@Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
@SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "startIntents: intent1=%s intent2=%s position=%d snapPosition=%d",
+ pendingIntent1.getIntent(),
+ (pendingIntent2 != null ? pendingIntent2.getIntent() : "null"),
+ splitPosition, snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (pendingIntent2 == null) {
options1 = options1 != null ? options1 : new Bundle();
@@ -1303,6 +1331,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
void switchSplitPosition(String reason) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "switchSplitPosition");
final SurfaceControl.Transaction t = mTransactionPool.acquire();
mTempRect1.setEmpty();
final StageTaskListener topLeftStage =
@@ -1344,7 +1373,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
});
});
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
+ ProtoLog.v(WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
mSplitLayout.isLeftRightSplit());
@@ -1377,11 +1406,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!mMainStage.isActive()) {
return;
}
-
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onKeyguardVisibilityChanged: showing=%b", showing);
setDividerVisibility(!mKeyguardShowing, null);
}
void onFinishedWakingUp() {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinishedWakingUp");
if (!mMainStage.isActive()) {
return;
}
@@ -1422,6 +1452,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: topTaskId=%d reason=%s active=%b",
+ toTopTaskId, exitReasonToString(exitReason), mMainStage.isActive());
if (!mMainStage.isActive()) return;
StageTaskListener childrenToTop = null;
@@ -1440,6 +1472,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void exitSplitScreen(@Nullable StageTaskListener childrenToTop,
@ExitReason int exitReason) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: mainStageToTop=%b reason=%s active=%b",
+ childrenToTop == mMainStage, exitReasonToString(exitReason), mMainStage.isActive());
if (!mMainStage.isActive()) return;
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -1448,6 +1482,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void applyExitSplitScreen(@Nullable StageTaskListener childrenToTop,
WindowContainerTransaction wct, @ExitReason int exitReason) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "applyExitSplitScreen: reason=%s",
+ exitReasonToString(exitReason));
if (!mMainStage.isActive() || mIsExiting) return;
onSplitScreenExit();
@@ -1503,7 +1539,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
});
- Slog.i(TAG, "applyExitSplitScreen, reason = " + exitReasonToString(exitReason));
// Log the exit
if (childrenToTop != null) {
logExitToStage(exitReason, childrenToTop == mMainStage);
@@ -1528,6 +1563,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* Exits the split screen by finishing one of the tasks.
*/
protected void exitStage(@SplitPosition int stageToClose) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitStage: stageToClose=%d", stageToClose);
mSplitLayout.flingDividerToDismiss(stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT,
EXIT_REASON_APP_FINISHED);
}
@@ -1541,12 +1577,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
try {
activityTaskManagerService.setFocusedTask(getTaskId(stageToFocus));
} catch (RemoteException | NullPointerException e) {
- ProtoLog.e(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ ProtoLog.e(WM_SHELL_SPLIT_SCREEN,
"Unable to update focus on the chosen stage: %s", e.getMessage());
}
}
private void clearRequestIfPresented() {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearRequestIfPresented");
if (mSideStageListener.mVisible && mSideStageListener.mHasChildren
&& mMainStageListener.mVisible && mSideStageListener.mHasChildren) {
mSplitRequest = null;
@@ -1573,6 +1610,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// The device is folded
case EXIT_REASON_FULLSCREEN_SHORTCUT:
// User has used a keyboard shortcut to go back to fullscreen from split
+ case EXIT_REASON_DESKTOP_MODE:
+ // One of the children enters desktop mode
return true;
default:
return false;
@@ -1582,6 +1621,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void clearSplitPairedInRecents(@ExitReason int exitReason) {
if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) return;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearSplitPairedInRecents: reason=%s",
+ exitReasonToString(exitReason));
mRecentTasks.ifPresent(recentTasks -> {
// Notify recents if we are exiting in a way that breaks the pair, and disable further
// updates to splits in the recents until we enter split again
@@ -1598,11 +1639,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void prepareExitSplitScreen(@StageType int stageToTop,
@NonNull WindowContainerTransaction wct) {
if (!mMainStage.isActive()) return;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareExitSplitScreen: stageToTop=%d", stageToTop);
mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
}
private void prepareEnterSplitScreen(WindowContainerTransaction wct) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareEnterSplitScreen");
prepareEnterSplitScreen(wct, null /* taskInfo */, SPLIT_POSITION_UNDEFINED,
!mIsDropEntering);
}
@@ -1614,6 +1657,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void prepareEnterSplitScreen(WindowContainerTransaction wct,
@Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
boolean resizeAnim) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareEnterSplitScreen: position=%d resize=%b",
+ startPosition, resizeAnim);
onSplitScreenEnter();
// Preemptively reset the reparenting behavior if we know that we are entering, as starting
// split tasks with activity trampolines can inadvertently trigger the task to be
@@ -1630,6 +1675,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void prepareBringSplit(WindowContainerTransaction wct,
@Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
boolean resizeAnim) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareBringSplit: task=%d isSplitVisible=%b",
+ taskInfo != null ? taskInfo.taskId : -1, isSplitScreenVisible());
if (taskInfo != null) {
wct.startTask(taskInfo.taskId,
resolveStartStage(STAGE_TYPE_UNDEFINED, startPosition, null, wct));
@@ -1650,6 +1697,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void prepareActiveSplit(WindowContainerTransaction wct,
@Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
boolean resizeAnim) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareActiveSplit: task=%d isSplitVisible=%b",
+ taskInfo != null ? taskInfo.taskId : -1, isSplitScreenVisible());
if (!ENABLE_SHELL_TRANSITIONS) {
// Legacy transition we need to create divider here, shell transition case we will
// create it on #finishEnterSplitScreen
@@ -1668,6 +1717,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
private void prepareSplitLayout(WindowContainerTransaction wct, boolean resizeAnim) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareSplitLayout: resize=%b", resizeAnim);
if (resizeAnim) {
mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
} else {
@@ -1687,6 +1737,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "finishEnterSplitScreen");
mSplitLayout.update(finishT, true /* resetImePosition */);
mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash,
getMainStageBounds());
@@ -1836,12 +1887,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
leftTopTaskId, rightBottomTaskId, mSplitLayout.calculateCurrentSnapPosition());
if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
// Update the pair for the top tasks
- recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId, splitBounds);
+ boolean added = recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId,
+ splitBounds);
+ if (added) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "updateRecentTasksSplitPair: adding split pair ltTask=%d rbTask=%d",
+ leftTopTaskId, rightBottomTaskId);
+ }
}
});
}
private void sendSplitVisibilityChanged() {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "sendSplitVisibilityChanged: dividerVisible=%b",
+ mDividerVisible);
for (int i = mListeners.size() - 1; i >= 0; --i) {
final SplitScreen.SplitScreenListener l = mListeners.get(i);
l.onSplitVisibilityChanged(mDividerVisible);
@@ -1856,6 +1915,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
throw new IllegalArgumentException(this + "\n Unknown task appeared: " + taskInfo);
}
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: task=%s", taskInfo);
mRootTaskInfo = taskInfo;
mRootTaskLeash = leash;
@@ -1881,6 +1941,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mSplitLayout != null
&& mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
&& mMainStage.isActive()) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: task=%d updating",
+ taskInfo.taskId);
// Clear the divider remote animating flag as the divider will be re-rendered to apply
// the new rotation config. Don't reset the IME state since those updates are not in
// sync with task info changes.
@@ -1893,6 +1955,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
@CallSuper
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: task=%s", taskInfo);
if (mRootTaskInfo == null) {
throw new IllegalArgumentException(this + "\n Unknown task vanished: " + taskInfo);
}
@@ -1912,6 +1975,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@VisibleForTesting
void onRootTaskAppeared() {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskAppeared: rootTask=%s mainRoot=%b sideRoot=%b",
+ mRootTaskInfo, mMainStageListener.mHasRootTask, mSideStageListener.mHasRootTask);
// Wait unit all root tasks appeared.
if (mRootTaskInfo == null
|| !mMainStageListener.mHasRootTask
@@ -1938,6 +2003,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* #onStageHasChildrenChanged because this would be called every time child task appeared.
* NOTICE: This only be called on legacy transition. */
private void onChildTaskAppeared(StageListenerImpl stageListener, int taskId) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onChildTaskAppeared: isMainStage=%b task=%d",
+ stageListener == mMainStageListener, taskId);
// Handle entering split screen while there is a split pair running in the background.
if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive()
&& mSplitRequest == null) {
@@ -1961,6 +2028,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
private void onRootTaskVanished() {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskVanished");
final WindowContainerTransaction wct = new WindowContainerTransaction();
mLaunchAdjacentController.clearLaunchAdjacentRoot();
applyExitSplitScreen(null /* childrenToTop */, wct, EXIT_REASON_ROOT_TASK_VANISHED);
@@ -1991,6 +2059,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return;
}
+ // TODO Protolog
+
// Check if it needs to dismiss split screen when both stage invisible.
if (!mainStageVisible && mExitSplitScreenOnHide) {
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
@@ -2021,14 +2091,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return;
}
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "Request to %s divider bar from %s.",
- (visible ? "show" : "hide"), Debug.getCaller());
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "setDividerVisibility: visible=%b keyguardShowing=%b dividerAnimating=%b caller=%s",
+ visible, mKeyguardShowing, mIsDividerRemoteAnimating, Debug.getCaller());
// Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard
// dismissing animation.
if (visible && mKeyguardShowing) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
" Defer showing divider bar due to keyguard showing.");
return;
}
@@ -2037,7 +2107,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
sendSplitVisibilityChanged();
if (mIsDividerRemoteAnimating) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
" Skip animating divider bar due to it's remote animating.");
return;
}
@@ -2051,12 +2121,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void applyDividerVisibility(@Nullable SurfaceControl.Transaction t) {
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
if (dividerLeash == null) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
" Skip animating divider bar due to divider leash not ready.");
return;
}
if (mIsDividerRemoteAnimating) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
" Skip animating divider bar due to it's remote animating.");
return;
}
@@ -2120,6 +2190,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Callback when split roots have child or haven't under it.
* NOTICE: This only be called on legacy transition. */
private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onStageHasChildrenChanged: isMainStage=%b",
+ stageListener == mMainStageListener);
final boolean hasChildren = stageListener.mHasChildren;
final boolean isSideStage = stageListener == mSideStageListener;
if (!hasChildren && !mIsExiting && mMainStage.isActive()) {
@@ -2171,13 +2243,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
@Override
- public void onSnappedToDismiss(boolean bottomOrRight, int reason) {
+ public void onSnappedToDismiss(boolean bottomOrRight, @ExitReason int exitReason) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onSnappedToDismiss: bottomOrRight=%b reason=%s",
+ bottomOrRight, exitReasonToString(exitReason));
final boolean mainStageToTop =
bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
: mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
final StageTaskListener toTopStage = mainStageToTop ? mMainStage : mSideStage;
if (!ENABLE_SHELL_TRANSITIONS) {
- exitSplitScreen(toTopStage, reason);
+ exitSplitScreen(toTopStage, exitReason);
return;
}
@@ -2220,6 +2294,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onLayoutSizeChanged(SplitLayout layout) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onLayoutSizeChanged");
// Reset this flag every time onLayoutSizeChanged.
mShowDecorImmediately = false;
@@ -2237,8 +2312,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
sendOnBoundsChanged();
if (ENABLE_SHELL_TRANSITIONS) {
mSplitLayout.setDividerInteractive(false, false, "onSplitResizeStart");
- mSplitTransitions.startResizeTransition(wct, this, (finishWct, t) ->
- mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish"));
+ mSplitTransitions.startResizeTransition(wct, this, (aborted) -> {
+ mSplitLayout.setDividerInteractive(true, false, "onSplitResizeConsumed");
+ }, (finishWct, t) -> {
+ mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish");
+ }, mMainStage.getSplitDecorManager(), mSideStage.getSplitDecorManager());
} else {
// Only need screenshot for legacy case because shell transition should screenshot
// itself during transition.
@@ -2276,8 +2354,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
- return layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo,
+ boolean updated = layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo,
bottomRightStage.mRootTaskInfo);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "updateWindowBounds: topLeftStage=%s bottomRightStage=%s",
+ layout.getBounds1(), layout.getBounds2());
+ return updated;
}
void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t,
@@ -2289,6 +2370,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
(layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash,
bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer,
applyResizingOffset);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "updateSurfaceBounds: topLeftStage=%s bottomRightStage=%s",
+ layout.getBounds1(), layout.getBounds2());
}
@Override
@@ -2327,6 +2411,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setLayoutOffsetTarget: x=%d y=%d",
+ offsetX, offsetY);
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
final StageTaskListener bottomRightStage =
@@ -2341,6 +2427,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (displayId != DEFAULT_DISPLAY) {
return;
}
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onDisplayAdded: display=%d", displayId);
mDisplayController.addDisplayChangingController(this::onDisplayChange);
}
@@ -2355,8 +2442,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void onDisplayChange(int displayId, int fromRotation, int toRotation,
@Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) {
- if (displayId != DEFAULT_DISPLAY || !mMainStage.isActive()) return;
+ if (displayId != DEFAULT_DISPLAY || !mMainStage.isActive()) {
+ return;
+ }
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "onDisplayChange: display=%d fromRot=%d toRot=%d config=%s",
+ displayId, fromRotation, toRotation,
+ newDisplayAreaInfo != null ? newDisplayAreaInfo.configuration : null);
mSplitLayout.rotateTo(toRotation);
if (newDisplayAreaInfo != null) {
mSplitLayout.updateConfiguration(newDisplayAreaInfo.configuration);
@@ -2367,6 +2460,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@VisibleForTesting
void onFoldedStateChanged(boolean folded) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFoldedStateChanged: folded=%b", folded);
mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
if (!folded) return;
@@ -2437,6 +2531,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
if (triggerTask == null) {
if (isSplitActive()) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d display rotation",
+ request.getDebugId());
// Check if the display is rotating.
final TransitionRequestInfo.DisplayChange displayChange =
request.getDisplayChange();
@@ -2465,6 +2561,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
if (isSplitActive()) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d split active",
+ request.getDebugId());
// 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"
@@ -2539,6 +2637,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return null;
} else {
if (isOpening && getStageOfTask(triggerTask) != null) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d enter split",
+ request.getDebugId());
// One task is appearing into split, prepare to enter split screen.
out = new WindowContainerTransaction();
prepareEnterSplitScreen(out);
@@ -2555,6 +2655,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
*/
public void addEnterOrExitIfNeeded(@Nullable TransitionRequestInfo request,
@NonNull WindowContainerTransaction outWCT) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "addEnterOrExitIfNeeded: transition=%d",
+ request.getDebugId());
final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
if (triggerTask != null && triggerTask.displayId != mDisplayId) {
// Skip handling task on the other display.
@@ -2589,6 +2691,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "mergeAnimation: transition=%d", info.getDebugId());
mSplitTransitions.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
}
@@ -2600,6 +2703,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
@Nullable SurfaceControl.Transaction finishT) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed");
mSplitTransitions.onTransitionConsumed(transition, aborted, finishT);
}
@@ -2615,6 +2719,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// If we're not in split-mode, just abort so something else can handle it.
if (!mMainStage.isActive()) return false;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startAnimation: transition=%d", info.getDebugId());
mSplitLayout.setFreezeDividerWindow(false);
final StageChangeRecord record = new StageChangeRecord();
final int transitType = info.getType();
@@ -2725,6 +2830,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info,
startTransaction, finishTransaction, finishCallback)) {
if (mSplitTransitions.isPendingResize(transition)) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "startAnimation: transition=%d display change", info.getDebugId());
// Only need to update in resize because divider exist before transition.
mSplitLayout.update(startTransaction, true /* resetImePosition */);
startTransaction.apply();
@@ -2795,6 +2902,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startPendingAnimation: transition=%d",
+ info.getDebugId());
boolean shouldAnimate = true;
if (mSplitTransitions.isPendingEnter(transition)) {
shouldAnimate = startPendingEnterAnimation(transition,
@@ -2828,6 +2937,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Called to clean-up state and do house-keeping after the animation is done. */
public void onTransitionAnimationComplete() {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionAnimationComplete");
// If still playing, let it finish.
if (!mMainStage.isActive() && !mIsExiting) {
// Update divider state after animation so that it is still around and positioned
@@ -2840,6 +2950,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@NonNull SplitScreenTransitions.EnterSession enterTransition,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction finishT) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startPendingEnterAnimation: enterTransition=%s",
+ enterTransition);
// First, verify that we actually have opened apps in both splits.
TransitionInfo.Change mainChild = null;
TransitionInfo.Change sideChild = null;
@@ -2957,17 +3069,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
public void goToFullscreenFromSplit() {
- boolean leftOrTop;
- if (mSideStage.isFocused()) {
- leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "goToFullscreenFromSplit");
+ // If main stage is focused, toEnd = true if
+ // mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT. Otherwise toEnd = false
+ // If side stage is focused, toEnd = true if
+ // mSideStagePosition = SPLIT_POSITION_TOP_OR_LEFT. Otherwise toEnd = false
+ final boolean toEnd;
+ if (mMainStage.isFocused()) {
+ toEnd = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
} else {
- leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ toEnd = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
}
- mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
+ mSplitLayout.flingDividerToDismiss(toEnd, EXIT_REASON_FULLSCREEN_SHORTCUT);
}
/** Move the specified task to fullscreen, regardless of focus state. */
- public void moveTaskToFullscreen(int taskId) {
+ public void moveTaskToFullscreen(int taskId, int exitReason) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "moveTaskToFullscreen");
boolean leftOrTop;
if (mMainStage.containsTask(taskId)) {
leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
@@ -2976,10 +3094,30 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
} else {
return;
}
- mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
+ mSplitLayout.flingDividerToDismiss(!leftOrTop, exitReason);
}
+ /**
+ * Performs previous child eviction and such to prepare for the pip task expending into one of
+ * the split stages
+ *
+ * @param taskInfo TaskInfo of the pip task
+ */
+ public void onPipExpandToSplit(WindowContainerTransaction wct,
+ ActivityManager.RunningTaskInfo taskInfo) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onPipExpandToSplit: task=%s", taskInfo);
+ prepareEnterSplitScreen(wct, taskInfo, getActivateSplitPosition(taskInfo),
+ false /*resizeAnim*/);
+
+ if (!isSplitScreenVisible() || mSplitRequest == null) {
+ return;
+ }
+
+ boolean replacingMainStage = getMainStagePosition() == mSplitRequest.mActivatePosition;
+ (replacingMainStage ? mMainStage : mSideStage).evictOtherChildren(wct, taskInfo.taskId);
+ }
+
boolean isLaunchToSplit(TaskInfo taskInfo) {
return getActivateSplitPosition(taskInfo) != SPLIT_POSITION_UNDEFINED;
}
@@ -3015,6 +3153,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction finishT) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "prepareDismissAnimation: transition=%d toStage=%d reason=%s",
+ info.getDebugId(), toStage, exitReasonToString(dismissReason));
// 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
@@ -3101,6 +3242,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@NonNull SplitScreenTransitions.DismissSession dismissTransition,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction finishT) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "startPendingDismissAnimation: transition=%d dismissTransition=%s",
+ info.getDebugId(), dismissTransition);
prepareDismissAnimation(dismissTransition.mDismissTop, dismissTransition.mReason, info,
t, finishT);
if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) {
@@ -3121,6 +3265,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Call this when starting the open-recents animation while split-screen is active. */
public void onRecentsInSplitAnimationStart(TransitionInfo info) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationStart: transition=%d",
+ info.getDebugId());
if (isSplitScreenVisible()) {
// Cache tasks on live tile.
for (int i = 0; i < info.getChanges().size(); ++i) {
@@ -3153,6 +3299,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Call this when the recents animation during split-screen finishes. */
public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct,
SurfaceControl.Transaction finishT) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationFinish");
mPausingTasks.clear();
// Check if the recent transition is finished by returning to the current
// split, so we can restore the divider bar.
@@ -3178,6 +3325,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Call this when the recents animation finishes by doing pair-to-pair switch. */
public void onRecentsPairToPairAnimationFinish(WindowContainerTransaction finishWct) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsPairToPairAnimationFinish");
// Pair-to-pair switch happened so here should evict the live tile from its stage.
// Otherwise, the task will remain in stage, and occluding the new task when next time
// user entering recents.
@@ -3259,6 +3407,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* handled.
*/
private void setSplitsVisible(boolean visible) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setSplitsVisible: visible=%b", visible);
mMainStageListener.mVisible = mSideStageListener.mVisible = visible;
mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible;
}
@@ -3267,6 +3416,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* Sets drag info to be logged when splitscreen is next entered.
*/
public void onDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onDroppedToSplit: position=%d", position);
if (!isSplitScreenVisible()) {
mIsDropEntering = true;
mSkipEvictingMainStageChildren = true;
@@ -3283,7 +3433,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/**
* Sets info to be logged when splitscreen is next entered.
*/
- public void onRequestToSplit(InstanceId sessionId, int enterReason) {
+ public void onRequestToSplit(InstanceId sessionId, @SplitEnterReason int enterReason) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRequestToSplit: reason=%d", enterReason);
if (!isSplitScreenVisible() && !ENABLE_SHELL_TRANSITIONS) {
// If split running background, exit split first.
// Skip this on shell transition due to we could evict existing tasks on transition
@@ -3359,6 +3510,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onNoLongerSupportMultiWindow: task=%s", taskInfo);
if (mMainStage.isActive()) {
final boolean isMainStage = mMainStageListener == this;
if (!ENABLE_SHELL_TRANSITIONS) {
@@ -3368,15 +3520,24 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return;
}
- final int stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ // If visible, we preserve the app and keep it running. If an app becomes
+ // unsupported in the bg, break split without putting anything on top
+ boolean splitScreenVisible = isSplitScreenVisible();
+ int stageType = STAGE_TYPE_UNDEFINED;
+ if (splitScreenVisible) {
+ stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ }
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(stageType, wct);
+ clearSplitPairedInRecents(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow",
"app package " + taskInfo.baseActivity.getPackageName()
+ " does not support splitscreen, or is a controlled activity type"));
- mSplitUnsupportedToast.show();
+ if (splitScreenVisible) {
+ mSplitUnsupportedToast.show();
+ }
}
}
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 af7bf360f036..f33ab33dafcc 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
@@ -25,6 +25,7 @@ import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.annotation.CallSuper;
@@ -44,6 +45,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -175,6 +177,9 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
@CallSuper
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: task=%d taskParent=%d rootTask=%d",
+ taskInfo.taskId, taskInfo.parentTaskId,
+ mRootTaskInfo != null ? mRootTaskInfo.taskId : -1);
if (mRootTaskInfo == null) {
mRootLeash = leash;
mRootTaskInfo = taskInfo;
@@ -225,6 +230,9 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
|| !ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
|| !ArrayUtils.contains(CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
taskInfo.getWindowingMode())) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "onTaskInfoChanged: task=%d no longer supports multiwindow",
+ taskInfo.taskId);
// Leave split screen if the task no longer supports multi window or have
// uncontrolled task.
mCallbacks.onNoLongerSupportMultiWindow(taskInfo);
@@ -251,6 +259,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
@CallSuper
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: task=%d", taskInfo.taskId);
final int taskId = taskInfo.taskId;
if (mRootTaskInfo.taskId == taskId) {
mCallbacks.onRootTaskVanished();
@@ -333,6 +342,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "addTask: task=%d", task.taskId);
// Clear overridden bounds and windowing mode to make sure the child task can inherit
// windowing mode and bounds from split root.
wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
@@ -342,6 +352,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "reorderChild: task=%d onTop=%b", taskId, onTop);
if (!containsTask(taskId)) {
return;
}
@@ -357,6 +368,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
/** Collects all the current child tasks and prepares transaction to evict them to display. */
void evictAllChildren(WindowContainerTransaction wct) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evicting all children");
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
@@ -367,11 +379,13 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
if (taskId == taskInfo.taskId) continue;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict other child: task=%d", taskId);
wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
}
}
void evictNonOpeningChildren(RemoteAnimationTarget[] apps, WindowContainerTransaction wct) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "evictNonOpeningChildren");
final SparseArray<ActivityManager.RunningTaskInfo> toBeEvict = mChildrenTaskInfo.clone();
for (int i = 0; i < apps.length; i++) {
if (apps[i].mode == MODE_OPENING) {
@@ -380,6 +394,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
for (int i = toBeEvict.size() - 1; i >= 0; i--) {
final ActivityManager.RunningTaskInfo taskInfo = toBeEvict.valueAt(i);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict non-opening child: task=%d", taskInfo.taskId);
wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
}
}
@@ -388,12 +403,15 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
if (!taskInfo.isVisible) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict invisible child: task=%d",
+ taskInfo.taskId);
wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
}
}
}
void evictChildren(WindowContainerTransaction wct, int taskId) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Evict child: task=%d", taskId);
final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.get(taskId);
if (taskInfo != null) {
wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index c10142588bde..e330f3ab65ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -28,6 +28,7 @@ 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.LaunchAdjacentController;
+import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
@@ -74,20 +75,20 @@ public class TvSplitScreenController extends SplitScreenController {
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Optional<DragAndDropController> dragAndDropController,
Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
+ MultiInstanceHelper multiInstanceHelper,
ShellExecutor mainExecutor,
Handler mainHandler,
SystemWindows systemWindows) {
super(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer,
syncQueue, rootTDAOrganizer, displayController, displayImeController,
- displayInsetsController, dragAndDropController, transitions, transactionPool,
+ displayInsetsController, null, transitions, transactionPool,
iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
- Optional.empty(), mainExecutor);
+ Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, mainExecutor);
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index e86b62dee86d..6325c686a682 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -390,7 +390,7 @@ public class SplashScreenExitAnimationUtils {
SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
TransactionPool transactionPool, Rect firstWindowFrame,
int mainWindowShiftLength, float roundedCornerRadius) {
- mFromYDelta = fromYDelta - roundedCornerRadius;
+ mFromYDelta = fromYDelta - Math.max(firstWindowFrame.top, roundedCornerRadius);
mToYDelta = toYDelta;
mOccludeHoleView = occludeHoleView;
mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
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 ae21c4bf5450..da2965c05ee4 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
@@ -73,6 +73,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.palette.Palette;
import com.android.internal.graphics.palette.Quantizer;
import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
+import com.android.internal.policy.PhoneWindow;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
@@ -245,16 +246,19 @@ public class SplashscreenContentDrawer {
} else {
windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
}
- params.layoutInDisplayCutoutMode = a.getInt(
- R.styleable.Window_windowLayoutInDisplayCutoutMode,
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS);
- params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
- a.recycle();
final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
? windowInfo.targetActivityInfo
: taskInfo.topActivityInfo;
+ params.layoutInDisplayCutoutMode = a.getInt(
+ R.styleable.Window_windowLayoutInDisplayCutoutMode,
+ PhoneWindow.isEdgeToEdgeEnforced(activityInfo.applicationInfo, false /* local */, a)
+ ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ : params.layoutInDisplayCutoutMode);
+ params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
+ a.recycle();
+
final int displayId = taskInfo.displayId;
// Assumes it's safe to show starting windows of launched apps while
// the keyguard is being hidden. This is okay because starting windows never show
@@ -277,11 +281,6 @@ public class SplashscreenContentDrawer {
params.token = appToken;
params.packageName = activityInfo.packageName;
params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-
- if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
- params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
- }
-
params.setTitle("Splash Screen " + title);
return params;
}
@@ -418,7 +417,7 @@ public class SplashscreenContentDrawer {
final SplashViewBuilder builder = new SplashViewBuilder(context, ai);
final SplashScreenView view = builder
.setWindowBGColor(themeBGColor)
- .chooseStyle(STARTING_WINDOW_TYPE_SPLASH_SCREEN)
+ .chooseStyle(STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN)
.build();
view.setNotCopyable();
return view;
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 e6418f35a0b1..1a0c011205fb 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
@@ -135,7 +135,6 @@ public class TaskSnapshotWindow {
} catch (RemoteException e) {
snapshotSurface.clearWindowSynced();
}
- window.setOuter(snapshotSurface);
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0,
@@ -161,7 +160,7 @@ public class TaskSnapshotWindow {
ShellExecutor splashScreenExecutor) {
mSplashScreenExecutor = splashScreenExecutor;
mSession = WindowManagerGlobal.getWindowSession();
- mWindow = new Window();
+ mWindow = new Window(this);
mWindow.setSession(mSession);
int backgroundColor = taskDescription.getBackgroundColor();
mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
@@ -204,9 +203,9 @@ public class TaskSnapshotWindow {
}
static class Window extends BaseIWindow {
- private WeakReference<TaskSnapshotWindow> mOuter;
+ private final WeakReference<TaskSnapshotWindow> mOuter;
- public void setOuter(TaskSnapshotWindow outer) {
+ Window(TaskSnapshotWindow outer) {
mOuter = new WeakReference<>(outer);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 0eb7c2d98e0a..a7843e218a8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -293,11 +293,7 @@ public class ShellController {
private class ShellInterfaceImpl implements ShellInterface {
@Override
public void onInit() {
- try {
- mMainExecutor.executeBlocking(() -> ShellController.this.handleInit());
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to initialize the Shell in 2s", e);
- }
+ mMainExecutor.execute(ShellController.this::handleInit);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 93d763608b5f..196e04edbb10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -480,7 +480,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
WindowContainerTransaction wct = new WindowContainerTransaction();
if (mCaptionInsets != null) {
wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
- WindowInsets.Type.captionBar(), mCaptionInsets);
+ WindowInsets.Type.captionBar(), mCaptionInsets, null /* boundingRects */);
} else {
wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
WindowInsets.Type.captionBar());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 34c015f05c68..198ec82b5f21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -37,8 +37,8 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.VisibleForTesting;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Objects;
@@ -330,7 +330,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
continue;
}
if (isHide) {
- if (pending.mType == TRANSIT_TO_BACK) {
+ if (pending != null && pending.mType == TRANSIT_TO_BACK) {
// TO_BACK is only used when setting the task view visibility immediately,
// so in that case we can also hide the surface immediately
startTransaction.hide(chg.getLeash());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
index 628ce27fe514..b03daaafd70c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java
@@ -27,8 +27,8 @@ import android.window.WindowContainerToken;
import androidx.annotation.NonNull;
-import com.android.wm.shell.util.CounterRotator;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.CounterRotator;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 9f20f49b4094..8746b8c8d55c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -22,17 +22,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_PIP;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
-import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
-import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
-import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -56,12 +48,11 @@ import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentsTransitionHandler;
-import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.splitscreen.StageCoordinator;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Map;
@@ -84,7 +75,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
private UnfoldTransitionHandler mUnfoldHandler;
private ActivityEmbeddingController mActivityEmbeddingController;
- private class MixedTransition {
+ abstract static class MixedTransition {
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
/** Both the display and split-state (enter/exit) is changing */
@@ -124,6 +115,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
int mAnimType = ANIM_TYPE_DEFAULT;
final IBinder mTransition;
+ protected final Transitions mPlayer;
+ protected final DefaultMixedHandler mMixedHandler;
+ protected final PipTransitionController mPipHandler;
+ protected final StageCoordinator mSplitHandler;
+ protected final KeyguardTransitionHandler mKeyguardHandler;
+
Transitions.TransitionHandler mLeftoversHandler = null;
TransitionInfo mInfo = null;
WindowContainerTransaction mFinishWCT = null;
@@ -144,12 +141,35 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
*/
int mInFlightSubAnimations = 0;
- MixedTransition(int type, IBinder transition) {
+ MixedTransition(int type, IBinder transition, Transitions player,
+ DefaultMixedHandler mixedHandler, PipTransitionController pipHandler,
+ StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler) {
mType = type;
mTransition = transition;
- }
-
- boolean startSubAnimation(Transitions.TransitionHandler handler, TransitionInfo info,
+ mPlayer = player;
+ mMixedHandler = mixedHandler;
+ mPipHandler = pipHandler;
+ mSplitHandler = splitHandler;
+ mKeyguardHandler = keyguardHandler;
+ }
+
+ abstract boolean startAnimation(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback);
+
+ abstract void mergeAnimation(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback);
+
+ abstract void onTransitionConsumed(
+ @NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishT);
+
+ protected boolean startSubAnimation(
+ Transitions.TransitionHandler handler, TransitionInfo info,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
if (mInfo != null) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -164,7 +184,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
return true;
}
- void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) {
+ private void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) {
mInFlightSubAnimations--;
if (mInfo != null) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -175,7 +195,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
joinFinishArgs(wct);
if (mInFlightSubAnimations == 0) {
- mActiveTransitions.remove(MixedTransition.this);
mFinishCB.onTransitionFinished(mFinishWCT);
}
}
@@ -236,8 +255,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
throw new IllegalStateException("Unexpected remote transition in"
+ "pip-enter-from-split request");
}
- mActiveTransitions.add(new MixedTransition(MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT,
- transition));
+ mActiveTransitions.add(createDefaultMixedTransition(
+ MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition));
WindowContainerTransaction out = new WindowContainerTransaction();
mPipHandler.augmentRequest(transition, request, out);
@@ -248,7 +267,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
mActivityEmbeddingController != null)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
" Got a PiP-enter request from an Activity Embedding split");
- mActiveTransitions.add(new MixedTransition(
+ mActiveTransitions.add(createDefaultMixedTransition(
MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition));
// Postpone transition splitting to later.
WindowContainerTransaction out = new WindowContainerTransaction();
@@ -267,7 +286,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
if (handler == null) {
return null;
}
- final MixedTransition mixed = new MixedTransition(
+ final MixedTransition mixed = createDefaultMixedTransition(
MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE, transition);
mixed.mLeftoversHandler = handler.first;
mActiveTransitions.add(mixed);
@@ -293,7 +312,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
mPlayer.getRemoteTransitionHandler(),
new WindowContainerTransaction());
}
- final MixedTransition mixed = new MixedTransition(
+ final MixedTransition mixed = createRecentsMixedTransition(
MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
mixed.mLeftoversHandler = handler.first;
mActiveTransitions.add(mixed);
@@ -302,16 +321,20 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
final WindowContainerTransaction wct =
mUnfoldHandler.handleRequest(transition, request);
if (wct != null) {
- final MixedTransition mixed = new MixedTransition(
- MixedTransition.TYPE_UNFOLD, transition);
- mixed.mLeftoversHandler = mUnfoldHandler;
- mActiveTransitions.add(mixed);
+ mActiveTransitions.add(createDefaultMixedTransition(
+ MixedTransition.TYPE_UNFOLD, transition));
}
return wct;
}
return null;
}
+ private DefaultMixedTransition createDefaultMixedTransition(int type, IBinder transition) {
+ return new DefaultMixedTransition(
+ type, transition, mPlayer, this, mPipHandler, mSplitHandler, mKeyguardHandler,
+ mUnfoldHandler, mActivityEmbeddingController);
+ }
+
@Override
public Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT) {
if (mRecentsHandler != null) {
@@ -331,31 +354,30 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
private void setRecentsTransitionDuringSplit(IBinder transition) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ "Split-Screen is foreground, so treat it as Mixed.");
- final MixedTransition mixed = new MixedTransition(
- MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
- mixed.mLeftoversHandler = mRecentsHandler;
- mActiveTransitions.add(mixed);
+ mActiveTransitions.add(createRecentsMixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition));
}
private void setRecentsTransitionDuringKeyguard(IBinder transition) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ "keyguard is visible, so treat it as Mixed.");
- final MixedTransition mixed = new MixedTransition(
- MixedTransition.TYPE_RECENTS_DURING_KEYGUARD, transition);
- mixed.mLeftoversHandler = mRecentsHandler;
- mActiveTransitions.add(mixed);
+ mActiveTransitions.add(createRecentsMixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_KEYGUARD, transition));
}
private void setRecentsTransitionDuringDesktop(IBinder transition) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ "desktop mode is active, so treat it as Mixed.");
- final MixedTransition mixed = new MixedTransition(
- MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition);
- mixed.mLeftoversHandler = mRecentsHandler;
- mActiveTransitions.add(mixed);
+ mActiveTransitions.add(createRecentsMixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition));
}
- private TransitionInfo subCopy(@NonNull TransitionInfo info,
+ private MixedTransition createRecentsMixedTransition(int type, IBinder transition) {
+ return new RecentsMixedTransition(type, transition, mPlayer, this, mPipHandler,
+ mSplitHandler, mKeyguardHandler, mRecentsHandler, mDesktopTasksController);
+ }
+
+ static TransitionInfo subCopy(@NonNull TransitionInfo info,
@WindowManager.TransitionType int newType, boolean withChanges) {
final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0);
out.setTrack(info.getTrack());
@@ -372,15 +394,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
return out;
}
- private boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
- return change.getTaskInfo() != null
- && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
- }
-
- private boolean isWallpaper(@NonNull TransitionInfo.Change change) {
- return (change.getFlags() & FLAG_IS_WALLPAPER) != 0;
- }
-
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@@ -399,10 +412,15 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
if (KeyguardTransitionHandler.handles(info)) {
if (mixed != null && mixed.mType != MixedTransition.TYPE_KEYGUARD) {
final MixedTransition keyguardMixed =
- new MixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
+ createDefaultMixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
mActiveTransitions.add(keyguardMixed);
- final boolean hasAnimateKeyguard = animateKeyguard(keyguardMixed, info,
- startTransaction, finishTransaction, finishCallback);
+ Transitions.TransitionFinishCallback callback = wct -> {
+ mActiveTransitions.remove(keyguardMixed);
+ finishCallback.onTransitionFinished(wct);
+ };
+ final boolean hasAnimateKeyguard = animateKeyguard(
+ keyguardMixed, info, startTransaction, finishTransaction, callback,
+ mKeyguardHandler, mPipHandler);
if (hasAnimateKeyguard) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Converting mixed transition into a keyguard transition");
@@ -420,277 +438,18 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
if (mixed == null) return false;
- if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
- return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
- return animateEnterPipFromActivityEmbedding(mixed, info, startTransaction,
- finishTransaction, finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
- return false;
- } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
- final boolean handledToPip = animateOpenIntentWithRemoteAndPip(mixed, info,
- startTransaction, finishTransaction, finishCallback);
- // Consume the transition on remote handler if the leftover handler already handle this
- // transition. And if it cannot, the transition will be handled by remote handler, so
- // don't consume here.
- // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip
- if (handledToPip && mixed.mHasRequestToRemote
- && mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
- mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null);
- }
- return handledToPip;
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- // Pip auto-entering info might be appended to recent transition like pressing
- // home-key in 3-button navigation. This offers split handler the opportunity to
- // handle split to pip animation.
- if (mPipHandler.isEnteringPip(change, info.getType())
- && mSplitHandler.getSplitItemPosition(change.getLastParent())
- != SPLIT_POSITION_UNDEFINED) {
- return animateEnterPipFromSplit(mixed, info, startTransaction,
- finishTransaction, finishCallback);
- }
- }
-
- return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
- return animateKeyguard(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_KEYGUARD) {
- return animateRecentsDuringKeyguard(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
- return animateRecentsDuringDesktop(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
- return animateUnfold(mixed, info, startTransaction, finishTransaction, finishCallback);
- } else {
- mActiveTransitions.remove(mixed);
- throw new IllegalStateException("Starting mixed animation without a known mixed type? "
- + mixed.mType);
- }
- }
-
- private boolean animateEnterPipFromActivityEmbedding(@NonNull MixedTransition mixed,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
- + "entering PIP from an Activity Embedding window");
- // Split into two transitions (wct)
- TransitionInfo.Change pipChange = null;
- final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- TransitionInfo.Change change = info.getChanges().get(i);
- if (mPipHandler.isEnteringPip(change, info.getType())) {
- if (pipChange != null) {
- throw new IllegalStateException("More than 1 pip-entering changes in one"
- + " transition? " + info);
- }
- pipChange = change;
- // going backwards, so remove-by-index is fine.
- everythingElse.getChanges().remove(i);
- }
- }
-
- final Transitions.TransitionFinishCallback finishCB = (wct) -> {
- --mixed.mInFlightSubAnimations;
- mixed.joinFinishArgs(wct);
- if (mixed.mInFlightSubAnimations > 0) return;
- mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(mixed.mFinishWCT);
- };
-
- if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) {
- // Fallback to dispatching to other handlers.
- return false;
- }
-
- // PIP window should always be on the highest Z order.
- if (pipChange != null) {
- mixed.mInFlightSubAnimations = 2;
- mPipHandler.startEnterAnimation(
- pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
- finishTransaction,
- finishCB);
- } else {
- mixed.mInFlightSubAnimations = 1;
- }
-
- mActivityEmbeddingController.startAnimation(mixed.mTransition, everythingElse,
- startTransaction, finishTransaction, finishCB);
- return true;
- }
-
- private boolean animateOpenIntentWithRemoteAndPip(@NonNull MixedTransition mixed,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- TransitionInfo.Change pipChange = null;
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- TransitionInfo.Change change = info.getChanges().get(i);
- if (mPipHandler.isEnteringPip(change, info.getType())) {
- if (pipChange != null) {
- throw new IllegalStateException("More than 1 pip-entering changes in one"
- + " transition? " + info);
- }
- pipChange = change;
- info.getChanges().remove(i);
- }
- }
- Transitions.TransitionFinishCallback finishCB = (wct) -> {
- --mixed.mInFlightSubAnimations;
- mixed.joinFinishArgs(wct);
- if (mixed.mInFlightSubAnimations > 0) return;
- mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(mixed.mFinishWCT);
- };
- if (pipChange == null) {
- if (mixed.mLeftoversHandler != null) {
- mixed.mInFlightSubAnimations = 1;
- if (mixed.mLeftoversHandler.startAnimation(mixed.mTransition,
- info, startTransaction, finishTransaction, finishCB)) {
- return true;
- }
- }
- mActiveTransitions.remove(mixed);
- return false;
- }
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate"
- + " animation because remote-animation likely doesn't support it");
- // Split the transition into 2 parts: the pip part and the rest.
- mixed.mInFlightSubAnimations = 2;
- // make a new startTransaction because pip's startEnterAnimation "consumes" it so
- // we need a separate one to send over to launcher.
- SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
-
- mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB);
-
- // Dispatch the rest of the transition normally.
- if (mixed.mLeftoversHandler != null
- && mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info,
- startTransaction, finishTransaction, finishCB)) {
- return true;
- }
- mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, info,
- startTransaction, finishTransaction, finishCB, this);
- return true;
- }
-
- private boolean animateEnterPipFromSplit(@NonNull final MixedTransition mixed,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
- + "entering PIP while Split-Screen is foreground.");
- TransitionInfo.Change pipChange = null;
- TransitionInfo.Change wallpaper = null;
- final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
- boolean homeIsOpening = false;
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- TransitionInfo.Change change = info.getChanges().get(i);
- if (mPipHandler.isEnteringPip(change, info.getType())) {
- if (pipChange != null) {
- throw new IllegalStateException("More than 1 pip-entering changes in one"
- + " transition? " + info);
- }
- pipChange = change;
- // going backwards, so remove-by-index is fine.
- everythingElse.getChanges().remove(i);
- } else if (isHomeOpening(change)) {
- homeIsOpening = true;
- } else if (isWallpaper(change)) {
- wallpaper = change;
- }
- }
- if (pipChange == null) {
- // um, something probably went wrong.
- mActiveTransitions.remove(mixed);
- return false;
- }
- final boolean isGoingHome = homeIsOpening;
- Transitions.TransitionFinishCallback finishCB = (wct) -> {
- --mixed.mInFlightSubAnimations;
- mixed.joinFinishArgs(wct);
- if (mixed.mInFlightSubAnimations > 0) return;
- mActiveTransitions.remove(mixed);
- if (isGoingHome) {
- mSplitHandler.onTransitionAnimationComplete();
- }
- finishCallback.onTransitionFinished(mixed.mFinishWCT);
+ final MixedTransition chosenTransition = mixed;
+ Transitions.TransitionFinishCallback callback = wct -> {
+ mActiveTransitions.remove(chosenTransition);
+ finishCallback.onTransitionFinished(wct);
};
- if (isGoingHome || mSplitHandler.getSplitItemPosition(pipChange.getLastParent())
- != SPLIT_POSITION_UNDEFINED) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed "
- + "since entering-PiP caused us to leave split and return home.");
- // We need to split the transition into 2 parts: the pip part (animated by pip)
- // and the dismiss-part (animated by launcher).
- mixed.mInFlightSubAnimations = 2;
- // immediately make the wallpaper visible (so that we don't see it pop-in during
- // the time it takes to start recents animation (which is remote).
- if (wallpaper != null) {
- startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f);
- }
- // make a new startTransaction because pip's startEnterAnimation "consumes" it so
- // we need a separate one to send over to launcher.
- SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
- @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
- if (mSplitHandler.isSplitScreenVisible()) {
- // The non-going home case, we could be pip-ing one of the split stages and keep
- // showing the other
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- TransitionInfo.Change change = info.getChanges().get(i);
- if (change == pipChange) {
- // Ignore the change/task that's going into Pip
- continue;
- }
- @SplitScreen.StageType int splitItemStage =
- mSplitHandler.getSplitItemStage(change.getLastParent());
- if (splitItemStage != STAGE_TYPE_UNDEFINED) {
- topStageToKeep = splitItemStage;
- break;
- }
- }
- }
- // Let split update internal state for dismiss.
- mSplitHandler.prepareDismissAnimation(topStageToKeep,
- EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
- finishTransaction);
-
- // We are trying to accommodate launcher's close animation which can't handle the
- // divider-bar, so if split-handler is closing the divider-bar, just hide it and remove
- // from transition info.
- for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) {
- if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR) != 0) {
- everythingElse.getChanges().remove(i);
- break;
- }
- }
- mPipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
- mPipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
- finishCB);
- // Dispatch the rest of the transition normally. This will most-likely be taken by
- // recents or default handler.
- mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, everythingElse,
- otherStartT, finishTransaction, finishCB, this);
- } else {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just "
- + "forward animation to Pip-Handler.");
- // This happens if the pip-ing activity is in a multi-activity task (and thus a
- // new pip task is spawned). In this case, we don't actually exit split so we can
- // just let pip transition handle the animation verbatim.
- mixed.mInFlightSubAnimations = 1;
- mPipHandler.startAnimation(mixed.mTransition, info, startTransaction, finishTransaction,
- finishCB);
+ boolean handled = chosenTransition.startAnimation(
+ transition, info, startTransaction, finishTransaction, callback);
+ if (!handled) {
+ mActiveTransitions.remove(chosenTransition);
}
- return true;
+ return handled;
}
private void unlinkMissingParents(TransitionInfo from) {
@@ -724,10 +483,14 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
Transitions.TransitionFinishCallback finishCallback) {
- final MixedTransition mixed = new MixedTransition(
+ final MixedTransition mixed = createDefaultMixedTransition(
MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition);
mActiveTransitions.add(mixed);
- return animateEnterPipFromSplit(mixed, info, startT, finishT, finishCallback);
+ Transitions.TransitionFinishCallback callback = wct -> {
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(wct);
+ };
+ return mixed.startAnimation(transition, info, startT, finishT, callback);
}
/**
@@ -750,7 +513,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
}
if (displayPart.getChanges().isEmpty()) return false;
unlinkMissingParents(everythingElse);
- final MixedTransition mixed = new MixedTransition(
+ final MixedTransition mixed = createDefaultMixedTransition(
MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition);
mActiveTransitions.add(mixed);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change "
@@ -779,116 +542,22 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
return true;
}
- private boolean animateRecentsDuringSplit(@NonNull final MixedTransition mixed,
+ private static boolean animateKeyguard(@NonNull final MixedTransition mixed,
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- // Split-screen is only interested in the recents transition finishing (and merging), so
- // just wrap finish and start recents animation directly.
- Transitions.TransitionFinishCallback finishCB = (wct) -> {
- mixed.mInFlightSubAnimations = 0;
- mActiveTransitions.remove(mixed);
- // If pair-to-pair switching, the post-recents clean-up isn't needed.
- wct = wct != null ? wct : new WindowContainerTransaction();
- if (mixed.mAnimType != MixedTransition.ANIM_TYPE_PAIR_TO_PAIR) {
- mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
- } else {
- // notify pair-to-pair recents animation finish
- mSplitHandler.onRecentsPairToPairAnimationFinish(wct);
- }
- mSplitHandler.onTransitionAnimationComplete();
- finishCallback.onTransitionFinished(wct);
- };
- mixed.mInFlightSubAnimations = 1;
- mSplitHandler.onRecentsInSplitAnimationStart(info);
- final boolean handled = mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info,
- startTransaction, finishTransaction, finishCB);
- if (!handled) {
- mSplitHandler.onRecentsInSplitAnimationCanceled();
- mActiveTransitions.remove(mixed);
- }
- return handled;
- }
-
- private boolean animateKeyguard(@NonNull final MixedTransition mixed,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull KeyguardTransitionHandler keyguardHandler,
+ PipTransitionController pipHandler) {
if (mixed.mFinishT == null) {
mixed.mFinishT = finishTransaction;
mixed.mFinishCB = finishCallback;
}
// Sync pip state.
- if (mPipHandler != null) {
- mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
+ if (pipHandler != null) {
+ pipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
}
- return mixed.startSubAnimation(mKeyguardHandler, info, startTransaction, finishTransaction);
- }
-
- private boolean animateRecentsDuringKeyguard(@NonNull final MixedTransition mixed,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (mixed.mInfo == null) {
- mixed.mInfo = info;
- mixed.mFinishT = finishTransaction;
- mixed.mFinishCB = finishCallback;
- }
- return mixed.startSubAnimation(mRecentsHandler, info, startTransaction, finishTransaction);
- }
-
- private boolean animateRecentsDuringDesktop(@NonNull final MixedTransition mixed,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- Transitions.TransitionFinishCallback finishCB = wct -> {
- mixed.mInFlightSubAnimations--;
- if (mixed.mInFlightSubAnimations == 0) {
- mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(wct);
- }
- };
-
- mixed.mInFlightSubAnimations++;
- boolean consumed = mRecentsHandler.startAnimation(
- mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
- if (!consumed) {
- mixed.mInFlightSubAnimations--;
- return false;
- }
- if (mDesktopTasksController != null) {
- mDesktopTasksController.syncSurfaceState(info, finishTransaction);
- return true;
- }
-
- return false;
- }
-
- private boolean animateUnfold(@NonNull final MixedTransition mixed,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- final Transitions.TransitionFinishCallback finishCB = (wct) -> {
- mixed.mInFlightSubAnimations--;
- if (mixed.mInFlightSubAnimations > 0) return;
- mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(wct);
- };
- mixed.mInFlightSubAnimations = 1;
- // Sync pip state.
- if (mPipHandler != null) {
- mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
- }
- if (mSplitHandler != null && mSplitHandler.isSplitActive()) {
- mSplitHandler.updateSurfaces(startTransaction);
- }
- return mUnfoldHandler.startAnimation(
- mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
+ return mixed.startSubAnimation(keyguardHandler, info, startTransaction, finishTransaction);
}
/** Use to when split use intent to enter, check if this enter transition should be mixed or
@@ -932,65 +601,13 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
for (int i = 0; i < mActiveTransitions.size(); ++i) {
if (mActiveTransitions.get(i).mTransition != mergeTarget) continue;
+
MixedTransition mixed = mActiveTransitions.get(i);
if (mixed.mInFlightSubAnimations <= 0) {
// Already done, so no need to end it.
return;
}
- if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
- // queue since no actual animation.
- } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
- if (mixed.mAnimType == MixedTransition.ANIM_TYPE_GOING_HOME) {
- boolean ended = mSplitHandler.end();
- // If split couldn't end (because it is remote), then don't end everything else
- // since we have to play out the animation anyways.
- if (!ended) return;
- mPipHandler.end();
- if (mixed.mLeftoversHandler != null) {
- mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
- finishCallback);
- }
- } else {
- mPipHandler.end();
- }
- } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
- mPipHandler.end();
- mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
- mPipHandler.end();
- if (mixed.mLeftoversHandler != null) {
- mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
- finishCallback);
- }
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
- if (mSplitHandler.isPendingEnter(transition)) {
- // Recents -> enter-split means that we are switching from one pair to
- // another pair.
- mixed.mAnimType = MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
- }
- mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
- mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_KEYGUARD) {
- if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
- handoverTransitionLeashes(mixed, info, t, mixed.mFinishT);
- if (animateKeyguard(mixed, info, t, mixed.mFinishT, mixed.mFinishCB)) {
- finishCallback.onTransitionFinished(null);
- }
- }
- mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
- mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
- mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
- } else {
- throw new IllegalStateException("Playing a mixed transition with unknown type? "
- + mixed.mType);
- }
+ mixed.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
}
}
@@ -1003,46 +620,30 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
mixed = mActiveTransitions.remove(i);
break;
}
- if (mixed == null) return;
- if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
- mPipHandler.onTransitionConsumed(transition, aborted, finishT);
- } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
- mPipHandler.onTransitionConsumed(transition, aborted, finishT);
- mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
- mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
- } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
- mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
- } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
- mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
- mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
- } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
- mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
- }
- if (mixed.mHasRequestToRemote) {
- mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
+ if (mixed != null) {
+ mixed.onTransitionConsumed(transition, aborted, finishT);
}
}
/**
- * Update an incoming {@link TransitionInfo} with the leashes from an ongoing
- * {@link MixedTransition} so that it can take over some parts of the animation without
+ * Update an incoming {@link TransitionInfo} with the leashes from an existing
+ * {@link TransitionInfo} so that it can take over some parts of the animation without
* reparenting to new transition roots.
*/
- private static void handoverTransitionLeashes(@NonNull MixedTransition mixed,
- @NonNull TransitionInfo info,
+ static void handoverTransitionLeashes(
+ @NonNull TransitionInfo from,
+ @NonNull TransitionInfo to,
@NonNull SurfaceControl.Transaction startT,
@NonNull SurfaceControl.Transaction finishT) {
// Show the roots in case they contain new changes not present in the original transition.
- for (int j = info.getRootCount() - 1; j >= 0; --j) {
- startT.show(info.getRoot(j).getLeash());
+ for (int j = to.getRootCount() - 1; j >= 0; --j) {
+ startT.show(to.getRoot(j).getLeash());
}
// Find all of the leashes from the original transition.
Map<WindowContainerToken, TransitionInfo.Change> originalChanges = new ArrayMap<>();
- for (TransitionInfo.Change oldChange : mixed.mInfo.getChanges()) {
+ for (TransitionInfo.Change oldChange : from.getChanges()) {
if (oldChange.getContainer() != null) {
originalChanges.put(oldChange.getContainer(), oldChange);
}
@@ -1050,9 +651,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
// Merge the animation leashes by re-using the original ones if we see the same container
// in the new transition and the old.
- for (TransitionInfo.Change newChange : info.getChanges()) {
+ for (TransitionInfo.Change newChange : to.getChanges()) {
if (originalChanges.containsKey(newChange.getContainer())) {
- final TransitionInfo.Change oldChange = originalChanges.get(newChange.getContainer());
+ final TransitionInfo.Change oldChange = originalChanges.get(
+ newChange.getContainer());
startT.reparent(newChange.getLeash(), null);
newChange.setLeash(oldChange.getLeash());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
new file mode 100644
index 000000000000..e9cd73b0df5e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+
+import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+import com.android.wm.shell.unfold.UnfoldTransitionHandler;
+
+class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
+ private final UnfoldTransitionHandler mUnfoldHandler;
+ private final ActivityEmbeddingController mActivityEmbeddingController;
+
+ DefaultMixedTransition(int type, IBinder transition, Transitions player,
+ DefaultMixedHandler mixedHandler, PipTransitionController pipHandler,
+ StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler,
+ UnfoldTransitionHandler unfoldHandler,
+ ActivityEmbeddingController activityEmbeddingController) {
+ super(type, transition, player, mixedHandler, pipHandler, splitHandler, keyguardHandler);
+ mUnfoldHandler = unfoldHandler;
+ mActivityEmbeddingController = activityEmbeddingController;
+
+ switch (type) {
+ case TYPE_UNFOLD:
+ mLeftoversHandler = mUnfoldHandler;
+ break;
+ case TYPE_DISPLAY_AND_SPLIT_CHANGE:
+ case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+ case TYPE_ENTER_PIP_FROM_SPLIT:
+ case TYPE_KEYGUARD:
+ case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+ default:
+ break;
+ }
+ }
+
+ @Override
+ boolean startAnimation(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ return switch (mType) {
+ case TYPE_DISPLAY_AND_SPLIT_CHANGE -> false;
+ case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING ->
+ animateEnterPipFromActivityEmbedding(
+ info, startTransaction, finishTransaction, finishCallback);
+ case TYPE_ENTER_PIP_FROM_SPLIT ->
+ animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
+ finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+ case TYPE_KEYGUARD ->
+ animateKeyguard(this, info, startTransaction, finishTransaction, finishCallback,
+ mKeyguardHandler, mPipHandler);
+ case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE ->
+ animateOpenIntentWithRemoteAndPip(transition, info, startTransaction,
+ finishTransaction, finishCallback);
+ case TYPE_UNFOLD ->
+ animateUnfold(info, startTransaction, finishTransaction, finishCallback);
+ default -> throw new IllegalStateException(
+ "Starting default mixed animation with unknown or illegal type: " + mType);
+ };
+ }
+
+ private boolean animateEnterPipFromActivityEmbedding(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for entering PIP from"
+ + " an Activity Embedding window #%d", info.getDebugId());
+ // Split into two transitions (wct)
+ TransitionInfo.Change pipChange = null;
+ final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (mPipHandler.isEnteringPip(change, info.getType())) {
+ if (pipChange != null) {
+ throw new IllegalStateException("More than 1 pip-entering changes in one"
+ + " transition? " + info);
+ }
+ pipChange = change;
+ // going backwards, so remove-by-index is fine.
+ everythingElse.getChanges().remove(i);
+ }
+ }
+
+ final Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ --mInFlightSubAnimations;
+ joinFinishArgs(wct);
+ if (mInFlightSubAnimations > 0) return;
+ finishCallback.onTransitionFinished(mFinishWCT);
+ };
+
+ if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) {
+ // Fallback to dispatching to other handlers.
+ return false;
+ }
+
+ // PIP window should always be on the highest Z order.
+ if (pipChange != null) {
+ mInFlightSubAnimations = 2;
+ mPipHandler.startEnterAnimation(
+ pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
+ finishTransaction,
+ finishCB);
+ } else {
+ mInFlightSubAnimations = 1;
+ }
+
+ mActivityEmbeddingController.startAnimation(
+ mTransition, everythingElse, startTransaction, finishTransaction, finishCB);
+ return true;
+ }
+
+ private boolean animateOpenIntentWithRemoteAndPip(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for opening an intent"
+ + " with a remote transition and PIP #%d", info.getDebugId());
+ boolean handledToPip = tryAnimateOpenIntentWithRemoteAndPip(
+ info, startTransaction, finishTransaction, finishCallback);
+ // Consume the transition on remote handler if the leftover handler already handle this
+ // transition. And if it cannot, the transition will be handled by remote handler, so don't
+ // consume here.
+ // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip
+ if (handledToPip && mHasRequestToRemote
+ && mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
+ mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null);
+ }
+ return handledToPip;
+ }
+
+ private boolean tryAnimateOpenIntentWithRemoteAndPip(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ TransitionInfo.Change pipChange = null;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (mPipHandler.isEnteringPip(change, info.getType())) {
+ if (pipChange != null) {
+ throw new IllegalStateException("More than 1 pip-entering changes in one"
+ + " transition? " + info);
+ }
+ pipChange = change;
+ info.getChanges().remove(i);
+ }
+ }
+ Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ --mInFlightSubAnimations;
+ joinFinishArgs(wct);
+ if (mInFlightSubAnimations > 0) return;
+ finishCallback.onTransitionFinished(mFinishWCT);
+ };
+ if (pipChange == null) {
+ if (mLeftoversHandler != null) {
+ mInFlightSubAnimations = 1;
+ if (mLeftoversHandler.startAnimation(
+ mTransition, info, startTransaction, finishTransaction, finishCB)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Splitting PIP into a separate"
+ + " animation because remote-animation likely doesn't support it #%d",
+ info.getDebugId());
+ // Split the transition into 2 parts: the pip part and the rest.
+ mInFlightSubAnimations = 2;
+ // make a new startTransaction because pip's startEnterAnimation "consumes" it so
+ // we need a separate one to send over to launcher.
+ SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
+
+ mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB);
+
+ // Dispatch the rest of the transition normally.
+ if (mLeftoversHandler != null
+ && mLeftoversHandler.startAnimation(mTransition, info,
+ startTransaction, finishTransaction, finishCB)) {
+ return true;
+ }
+ mLeftoversHandler = mPlayer.dispatchTransition(
+ mTransition, info, startTransaction, finishTransaction, finishCB, mMixedHandler);
+ return true;
+ }
+
+ private boolean animateUnfold(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for unfolding #%d",
+ info.getDebugId());
+
+ final Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ mInFlightSubAnimations--;
+ if (mInFlightSubAnimations > 0) return;
+ finishCallback.onTransitionFinished(wct);
+ };
+ mInFlightSubAnimations = 1;
+ // Sync pip state.
+ if (mPipHandler != null) {
+ mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
+ }
+ if (mSplitHandler != null && mSplitHandler.isSplitActive()) {
+ mSplitHandler.updateSurfaces(startTransaction);
+ }
+ return mUnfoldHandler.startAnimation(
+ mTransition, info, startTransaction, finishTransaction, finishCB);
+ }
+
+ @Override
+ void mergeAnimation(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ switch (mType) {
+ case TYPE_DISPLAY_AND_SPLIT_CHANGE:
+ // queue since no actual animation.
+ return;
+ case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+ mPipHandler.end();
+ mActivityEmbeddingController.mergeAnimation(
+ transition, info, t, mergeTarget, finishCallback);
+ return;
+ case TYPE_ENTER_PIP_FROM_SPLIT:
+ if (mAnimType == ANIM_TYPE_GOING_HOME) {
+ boolean ended = mSplitHandler.end();
+ // If split couldn't end (because it is remote), then don't end everything else
+ // since we have to play out the animation anyways.
+ if (!ended) return;
+ mPipHandler.end();
+ if (mLeftoversHandler != null) {
+ mLeftoversHandler.mergeAnimation(
+ transition, info, t, mergeTarget, finishCallback);
+ }
+ } else {
+ mPipHandler.end();
+ }
+ return;
+ case TYPE_KEYGUARD:
+ mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ return;
+ case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+ mPipHandler.end();
+ if (mLeftoversHandler != null) {
+ mLeftoversHandler.mergeAnimation(
+ transition, info, t, mergeTarget, finishCallback);
+ }
+ return;
+ case TYPE_UNFOLD:
+ mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ return;
+ default:
+ throw new IllegalStateException("Playing a default mixed transition with unknown or"
+ + " illegal type: " + mType);
+ }
+ }
+
+ @Override
+ void onTransitionConsumed(
+ @NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishT) {
+ switch (mType) {
+ case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+ mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ case TYPE_ENTER_PIP_FROM_SPLIT:
+ mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ case TYPE_KEYGUARD:
+ mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+ mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ case TYPE_UNFOLD:
+ mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ default:
+ break;
+ }
+
+ if (mHasRequestToRemote) {
+ mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
+ }
+ }
+}
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 193a4fb5b503..74e85f8dd468 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
@@ -109,8 +109,8 @@ 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.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
@@ -334,6 +334,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
boolean isDisplayRotationAnimationStarted = false;
final boolean isDreamTransition = isDreamTransition(info);
final boolean isOnlyTranslucent = isOnlyTranslucent(info);
+ final boolean isActivityLevel = isActivityLevelOnly(info);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -470,10 +471,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
final float cornerRadius;
- if (a.hasRoundedCorners() && isTask) {
- // hasRoundedCorners is currently only enabled for tasks
+ if (a.hasRoundedCorners()) {
+ final int displayId = isTask ? change.getTaskInfo().displayId
+ : info.getRoot(TransitionUtil.rootIndexFor(change, info))
+ .getDisplayId();
final Context displayContext =
- mDisplayController.getDisplayContext(change.getTaskInfo().displayId);
+ mDisplayController.getDisplayContext(displayId);
cornerRadius = displayContext == null ? 0
: ScreenDecorationsUtils.getWindowCornerRadius(displayContext);
} else {
@@ -500,8 +503,35 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
: new Rect(change.getEndAbsBounds());
clipRect.offsetTo(0, 0);
+ final TransitionInfo.Root animRoot = TransitionUtil.getRootFor(change, info);
+ final Point animRelOffset = new Point(
+ change.getEndAbsBounds().left - animRoot.getOffset().x,
+ change.getEndAbsBounds().top - animRoot.getOffset().y);
+ if (change.getActivityComponent() != null && !isActivityLevel) {
+ // At this point, this is an independent activity change in a non-activity
+ // transition. This means that an activity transition got erroneously combined
+ // with another ongoing transition. This then means that the animation root may
+ // not tightly fit the activities, so we have to put them in a separate crop.
+ final int layer = Transitions.calculateAnimLayer(change, i,
+ info.getChanges().size(), info.getType());
+ final SurfaceControl leash = new SurfaceControl.Builder()
+ .setName("Transition ActivityWrap: "
+ + change.getActivityComponent().toShortString())
+ .setParent(animRoot.getLeash())
+ .setContainerLayer().build();
+ startTransaction.setCrop(leash, clipRect);
+ startTransaction.setPosition(leash, animRelOffset.x, animRelOffset.y);
+ startTransaction.setLayer(leash, layer);
+ startTransaction.show(leash);
+ startTransaction.reparent(change.getLeash(), leash);
+ startTransaction.setPosition(change.getLeash(), 0, 0);
+ animRelOffset.set(0, 0);
+ finishTransaction.reparent(leash, null);
+ leash.release();
+ }
+
buildSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
- mTransactionPool, mMainExecutor, change.getEndRelOffset(), cornerRadius,
+ mTransactionPool, mMainExecutor, animRelOffset, cornerRadius,
clipRect);
if (info.getAnimationOptions() != null) {
@@ -610,6 +640,18 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
return (translucentOpen + translucentClose) > 0;
}
+ /**
+ * Does `info` only contain activity-level changes? This kinda assumes that if so, they are
+ * all in one task.
+ */
+ private static boolean isActivityLevelOnly(@NonNull TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getActivityComponent() == null) return false;
+ }
+ return true;
+ }
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index 473deba3b7d2..cb2944c120e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static com.android.wm.shell.transition.Transitions.TransitionObserver;
@@ -31,11 +32,12 @@ import android.window.TransitionInfo;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/**
* The {@link TransitionObserver} that observes for transitions involving the home
- * activity. It reports transitions to the caller via {@link IHomeTransitionListener}.
+ * activity on the {@link android.view.Display#DEFAULT_DISPLAY} only.
+ * It reports transitions to the caller via {@link IHomeTransitionListener}.
*/
public class HomeTransitionObserver implements TransitionObserver,
RemoteCallable<HomeTransitionObserver> {
@@ -58,6 +60,7 @@ public class HomeTransitionObserver implements TransitionObserver,
for (TransitionInfo.Change change : info.getChanges()) {
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (taskInfo == null
+ || taskInfo.displayId != DEFAULT_DISPLAY
|| taskInfo.taskId == -1
|| !taskInfo.isRunning) {
continue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
index 18716c68da27..72fba3bb7de4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
@@ -20,12 +20,13 @@ import android.window.RemoteTransition;
import android.window.TransitionFilter;
/**
- * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks.
+ * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks
+ * on the default display.
*/
-interface IHomeTransitionListener {
+oneway interface IHomeTransitionListener {
/**
- * Called when a transition changes the visibility of the home activity.
+ * Called when a transition changes the visibility of the home activity on the default display.
*/
void onHomeVisibilityChanged(in boolean isVisible);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
new file mode 100644
index 000000000000..0974cd13f249
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
+import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
+
+import android.annotation.NonNull;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+
+public class MixedTransitionHelper {
+ static boolean animateEnterPipFromSplit(
+ @NonNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull Transitions player, @NonNull DefaultMixedHandler mixedHandler,
+ @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+ + "entering PIP while Split-Screen is foreground.");
+ TransitionInfo.Change pipChange = null;
+ TransitionInfo.Change wallpaper = null;
+ final TransitionInfo everythingElse =
+ subCopy(info, TRANSIT_TO_BACK, true /* changes */);
+ boolean homeIsOpening = false;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (pipHandler.isEnteringPip(change, info.getType())) {
+ if (pipChange != null) {
+ throw new IllegalStateException("More than 1 pip-entering changes in one"
+ + " transition? " + info);
+ }
+ pipChange = change;
+ // going backwards, so remove-by-index is fine.
+ everythingElse.getChanges().remove(i);
+ } else if (isHomeOpening(change)) {
+ homeIsOpening = true;
+ } else if (isWallpaper(change)) {
+ wallpaper = change;
+ }
+ }
+ if (pipChange == null) {
+ // um, something probably went wrong.
+ return false;
+ }
+ final boolean isGoingHome = homeIsOpening;
+ Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ --mixed.mInFlightSubAnimations;
+ mixed.joinFinishArgs(wct);
+ if (mixed.mInFlightSubAnimations > 0) return;
+ if (isGoingHome) {
+ splitHandler.onTransitionAnimationComplete();
+ }
+ finishCallback.onTransitionFinished(mixed.mFinishWCT);
+ };
+ if (isGoingHome || splitHandler.getSplitItemPosition(pipChange.getLastParent())
+ != SPLIT_POSITION_UNDEFINED) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed "
+ + "since entering-PiP caused us to leave split and return home.");
+ // We need to split the transition into 2 parts: the pip part (animated by pip)
+ // and the dismiss-part (animated by launcher).
+ mixed.mInFlightSubAnimations = 2;
+ // immediately make the wallpaper visible (so that we don't see it pop-in during
+ // the time it takes to start recents animation (which is remote).
+ if (wallpaper != null) {
+ startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f);
+ }
+ // make a new startTransaction because pip's startEnterAnimation "consumes" it so
+ // we need a separate one to send over to launcher.
+ SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
+ @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
+ if (splitHandler.isSplitScreenVisible()) {
+ // The non-going home case, we could be pip-ing one of the split stages and keep
+ // showing the other
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (change == pipChange) {
+ // Ignore the change/task that's going into Pip
+ continue;
+ }
+ @SplitScreen.StageType int splitItemStage =
+ splitHandler.getSplitItemStage(change.getLastParent());
+ if (splitItemStage != STAGE_TYPE_UNDEFINED) {
+ topStageToKeep = splitItemStage;
+ break;
+ }
+ }
+ }
+ // Let split update internal state for dismiss.
+ splitHandler.prepareDismissAnimation(topStageToKeep,
+ EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
+ finishTransaction);
+
+ // We are trying to accommodate launcher's close animation which can't handle the
+ // divider-bar, so if split-handler is closing the divider-bar, just hide it and
+ // remove from transition info.
+ for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) {
+ if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR)
+ != 0) {
+ everythingElse.getChanges().remove(i);
+ break;
+ }
+ }
+
+ pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
+ pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
+ finishCB);
+ // Dispatch the rest of the transition normally. This will most-likely be taken by
+ // recents or default handler.
+ mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse,
+ otherStartT, finishTransaction, finishCB, mixedHandler);
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just "
+ + "forward animation to Pip-Handler.");
+ // This happens if the pip-ing activity is in a multi-activity task (and thus a
+ // new pip task is spawned). In this case, we don't actually exit split so we can
+ // just let pip transition handle the animation verbatim.
+ mixed.mInFlightSubAnimations = 1;
+ pipHandler.startAnimation(
+ mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
+ }
+ return true;
+ }
+
+ private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
+ return change.getTaskInfo() != null
+ && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
+ }
+
+ private static boolean isWallpaper(@NonNull TransitionInfo.Change change) {
+ return (change.getFlags() & FLAG_IS_WALLPAPER) != 0;
+ }
+
+ static boolean animateKeyguard(
+ @NonNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull KeyguardTransitionHandler keyguardHandler,
+ PipTransitionController pipHandler) {
+ if (mixed.mFinishT == null) {
+ mixed.mFinishT = finishTransaction;
+ mixed.mFinishCB = finishCallback;
+ }
+ // Sync pip state.
+ if (pipHandler != null) {
+ pipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
+ }
+ return mixed.startSubAnimation(keyguardHandler, info, startTransaction, finishTransaction);
+ }
+}
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 4355ed2bd3bf..94519a0d118c 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
@@ -74,6 +74,9 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
@Override
public void onTransitionFinished(WindowContainerTransaction wct,
SurfaceControl.Transaction sct) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Finished one-shot remote transition %s for (#%d).", mRemote,
+ info.getDebugId());
if (mRemote.asBinder() != null) {
mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
}
@@ -82,8 +85,8 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
}
mMainExecutor.execute(() -> {
finishCallback.onTransitionFinished(wct);
+ mRemote = null;
});
- mRemote = null;
}
};
Transitions.setRunningRemoteTransitionDelegate(mRemote.getAppThread());
@@ -115,17 +118,24 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Merging registered One-shot remote"
+ + " transition %s for (#%d).", mRemote, info.getDebugId());
IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
@Override
public void onTransitionFinished(WindowContainerTransaction wct,
SurfaceControl.Transaction sct) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Finished merging one-shot remote transition %s for (#%d).", mRemote,
+ info.getDebugId());
// We have merged, since we sent the transaction over binder, the one in this
// process won't be cleared if the remote applied it. We don't actually know if the
// remote applied the transaction, but applying twice will break surfaceflinger
// so just assume the worst-case and clear the local transaction.
t.clear();
- mMainExecutor.execute(() -> finishCallback.onTransitionFinished(wct));
- mRemote = null;
+ mMainExecutor.execute(() -> {
+ finishCallback.onTransitionFinished(wct);
+ mRemote = null;
+ });
}
};
try {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
new file mode 100644
index 000000000000..4ea71490798c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.transition.DefaultMixedHandler.handoverTransitionLeashes;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+
+class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition {
+ private final RecentsTransitionHandler mRecentsHandler;
+ private final DesktopTasksController mDesktopTasksController;
+
+ RecentsMixedTransition(int type, IBinder transition, Transitions player,
+ DefaultMixedHandler mixedHandler, PipTransitionController pipHandler,
+ StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler,
+ RecentsTransitionHandler recentsHandler,
+ DesktopTasksController desktopTasksController) {
+ super(type, transition, player, mixedHandler, pipHandler, splitHandler, keyguardHandler);
+ mRecentsHandler = recentsHandler;
+ mDesktopTasksController = desktopTasksController;
+ mLeftoversHandler = mRecentsHandler;
+ }
+
+ @Override
+ boolean startAnimation(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ return switch (mType) {
+ case TYPE_RECENTS_DURING_DESKTOP ->
+ animateRecentsDuringDesktop(
+ info, startTransaction, finishTransaction, finishCallback);
+ case TYPE_RECENTS_DURING_KEYGUARD ->
+ animateRecentsDuringKeyguard(
+ info, startTransaction, finishTransaction, finishCallback);
+ case TYPE_RECENTS_DURING_SPLIT ->
+ animateRecentsDuringSplit(
+ info, startTransaction, finishTransaction, finishCallback);
+ default -> throw new IllegalStateException(
+ "Starting Recents mixed animation with unknown or illegal type: " + mType);
+ };
+ }
+
+ private boolean animateRecentsDuringDesktop(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition for Recents during"
+ + " Desktop #%d", info.getDebugId());
+
+ if (mInfo == null) {
+ mInfo = info;
+ mFinishT = finishTransaction;
+ mFinishCB = finishCallback;
+ }
+ Transitions.TransitionFinishCallback finishCB = wct -> {
+ mInFlightSubAnimations--;
+ if (mInFlightSubAnimations == 0) {
+ finishCallback.onTransitionFinished(wct);
+ }
+ };
+
+ mInFlightSubAnimations++;
+ boolean consumed = mRecentsHandler.startAnimation(
+ mTransition, info, startTransaction, finishTransaction, finishCB);
+ if (!consumed) {
+ mInFlightSubAnimations--;
+ return false;
+ }
+ if (mDesktopTasksController != null) {
+ mDesktopTasksController.syncSurfaceState(info, finishTransaction);
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean animateRecentsDuringKeyguard(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for Recents during"
+ + " Keyguard #%d", info.getDebugId());
+
+ if (mInfo == null) {
+ mInfo = info;
+ mFinishT = finishTransaction;
+ mFinishCB = finishCallback;
+ }
+ return startSubAnimation(mRecentsHandler, info, startTransaction, finishTransaction);
+ }
+
+ private boolean animateRecentsDuringSplit(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for Recents during"
+ + " split screen #%d", info.getDebugId());
+
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ // Pip auto-entering info might be appended to recent transition like pressing
+ // home-key in 3-button navigation. This offers split handler the opportunity to
+ // handle split to pip animation.
+ if (mPipHandler.isEnteringPip(change, info.getType())
+ && mSplitHandler.getSplitItemPosition(change.getLastParent())
+ != SPLIT_POSITION_UNDEFINED) {
+ return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
+ finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+ }
+ }
+
+ // Split-screen is only interested in the recents transition finishing (and merging), so
+ // just wrap finish and start recents animation directly.
+ Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ mInFlightSubAnimations = 0;
+ // If pair-to-pair switching, the post-recents clean-up isn't needed.
+ wct = wct != null ? wct : new WindowContainerTransaction();
+ if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) {
+ mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
+ } else {
+ // notify pair-to-pair recents animation finish
+ mSplitHandler.onRecentsPairToPairAnimationFinish(wct);
+ }
+ mSplitHandler.onTransitionAnimationComplete();
+ finishCallback.onTransitionFinished(wct);
+ };
+ mInFlightSubAnimations = 1;
+ mSplitHandler.onRecentsInSplitAnimationStart(info);
+ final boolean handled = mLeftoversHandler.startAnimation(
+ mTransition, info, startTransaction, finishTransaction, finishCB);
+ if (!handled) {
+ mSplitHandler.onRecentsInSplitAnimationCanceled();
+ }
+ return handled;
+ }
+
+ @Override
+ void mergeAnimation(
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ switch (mType) {
+ case TYPE_RECENTS_DURING_DESKTOP:
+ mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ return;
+ case TYPE_RECENTS_DURING_KEYGUARD:
+ if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
+ handoverTransitionLeashes(mInfo, info, t, mFinishT);
+ if (animateKeyguard(
+ this, info, t, mFinishT, mFinishCB, mKeyguardHandler, mPipHandler)) {
+ finishCallback.onTransitionFinished(null);
+ }
+ }
+ mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
+ return;
+ case TYPE_RECENTS_DURING_SPLIT:
+ if (mSplitHandler.isPendingEnter(transition)) {
+ // Recents -> enter-split means that we are switching from one pair to
+ // another pair.
+ mAnimType = DefaultMixedHandler.MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
+ }
+ mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ return;
+ default:
+ throw new IllegalStateException("Playing a Recents mixed transition with unknown or"
+ + " illegal type: " + mType);
+ }
+ }
+
+ @Override
+ void onTransitionConsumed(
+ @NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishT) {
+ switch (mType) {
+ case TYPE_RECENTS_DURING_DESKTOP:
+ case TYPE_RECENTS_DURING_SPLIT:
+ mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+ break;
+ default:
+ break;
+ }
+
+ if (mHasRequestToRemote) {
+ mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
+ }
+ }
+}
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 293b66084d28..4c4c5806ea55 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
@@ -39,7 +39,7 @@ import androidx.annotation.BinderThread;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index b012d359931a..1be85d05c16e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -56,7 +56,7 @@ import com.android.internal.R;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.shared.TransitionUtil;
/** The helper class that provides methods for adding styles to transition animations. */
public class TransitionAnimationHelper {
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 af69b5272ad5..888105d4a20a 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
@@ -31,15 +31,14 @@ import static android.view.WindowManager.fixScale;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
-import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
-import static com.android.wm.shell.util.TransitionUtil.isClosingType;
-import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -80,10 +79,13 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.transition.tracing.LegacyTransitionTracer;
+import com.android.wm.shell.transition.tracing.PerfettoTransitionTracer;
+import com.android.wm.shell.transition.tracing.TransitionTracer;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -172,6 +174,9 @@ public class Transitions implements RemoteCallable<Transitions>,
/** Transition to animate task to desktop. */
public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15;
+ /** Transition to resize PiP task. */
+ public static final int TRANSIT_RESIZE_PIP = TRANSIT_FIRST_CUSTOM + 16;
+
private final ShellTaskOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
@@ -184,7 +189,7 @@ public class Transitions implements RemoteCallable<Transitions>,
private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
private final SleepHandler mSleepHandler = new SleepHandler();
- private final Tracer mTracer = new Tracer();
+ private final TransitionTracer mTransitionTracer;
private boolean mIsRegistered = false;
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
@@ -307,6 +312,12 @@ public class Transitions implements RemoteCallable<Transitions>,
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
shellInit.addInitCallback(this::onInit, this);
mHomeTransitionObserver = observer;
+
+ if (android.tracing.Flags.perfettoTransitionTracing()) {
+ mTransitionTracer = new PerfettoTransitionTracer();
+ } else {
+ mTransitionTracer = new LegacyTransitionTracer();
+ }
}
private void onInit() {
@@ -481,11 +492,9 @@ public class Transitions implements RemoteCallable<Transitions>,
final SurfaceControl leash = change.getLeash();
final int mode = info.getChanges().get(i).getMode();
- if (mode == TRANSIT_TO_FRONT
- && ((change.getStartAbsBounds().height() != change.getEndAbsBounds().height()
- || change.getStartAbsBounds().width() != change.getEndAbsBounds().width()))) {
- // When the window is moved to front with a different size, make sure the crop is
- // updated to prevent it from using the old crop.
+ if (mode == TRANSIT_TO_FRONT) {
+ // When the window is moved to front, make sure the crop is updated to prevent it
+ // from using the old crop.
t.setWindowCrop(leash, change.getEndAbsBounds().width(),
change.getEndAbsBounds().height());
}
@@ -520,6 +529,44 @@ public class Transitions implements RemoteCallable<Transitions>,
}
}
+ static int calculateAnimLayer(@NonNull TransitionInfo.Change change, int i,
+ int numChanges, @WindowManager.TransitionType int transitType) {
+ // Put animating stuff above this line and put static stuff below it.
+ final int zSplitLine = numChanges + 1;
+ final boolean isOpening = isOpeningType(transitType);
+ final boolean isClosing = isClosingType(transitType);
+ final int mode = change.getMode();
+ // Put all the OPEN/SHOW on top
+ if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
+ if (isOpening
+ // This is for when an activity launches while a different transition is
+ // collecting.
+ || change.hasFlags(FLAG_MOVED_TO_TOP)) {
+ // put on top
+ return zSplitLine + numChanges - i;
+ } else {
+ // put on bottom
+ return zSplitLine - i;
+ }
+ } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
+ if (isOpening) {
+ // put on bottom and leave visible
+ return zSplitLine - i;
+ } else {
+ // put on top
+ return zSplitLine + numChanges - i;
+ }
+ } else { // CHANGE or other
+ if (isClosing || TransitionUtil.isOrderOnly(change)) {
+ // Put below CLOSE mode (in the "static" section).
+ return zSplitLine - i;
+ } else {
+ // Put above CLOSE mode.
+ return zSplitLine + numChanges - i;
+ }
+ }
+ }
+
/**
* Reparents all participants into a shared parent and orders them based on: the global transit
* type, their transit mode, and their destination z-order.
@@ -527,19 +574,14 @@ public class Transitions implements RemoteCallable<Transitions>,
private static void setupAnimHierarchy(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
final int type = info.getType();
- final boolean isOpening = isOpeningType(type);
- final boolean isClosing = isClosingType(type);
for (int i = 0; i < info.getRootCount(); ++i) {
t.show(info.getRoot(i).getLeash());
}
final int numChanges = info.getChanges().size();
- // Put animating stuff above this line and put static stuff below it.
- final int zSplitLine = numChanges + 1;
// changes should be ordered top-to-bottom in z
for (int i = numChanges - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final SurfaceControl leash = change.getLeash();
- final int mode = change.getMode();
// Don't reparent anything that isn't independent within its parents
if (!TransitionInfo.isIndependent(change, info)) {
@@ -548,50 +590,14 @@ public class Transitions implements RemoteCallable<Transitions>,
boolean hasParent = change.getParent() != null;
- final int rootIdx = TransitionUtil.rootIndexFor(change, info);
+ final TransitionInfo.Root root = TransitionUtil.getRootFor(change, info);
if (!hasParent) {
- t.reparent(leash, info.getRoot(rootIdx).getLeash());
+ t.reparent(leash, root.getLeash());
t.setPosition(leash,
- change.getStartAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
- change.getStartAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
- }
- final int layer;
- // Put all the OPEN/SHOW on top
- if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
- // Wallpaper is always at the bottom, opening wallpaper on top of closing one.
- if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
- layer = -zSplitLine + numChanges - i;
- } else {
- layer = -zSplitLine - i;
- }
- } else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
- if (isOpening
- // This is for when an activity launches while a different transition is
- // collecting.
- || change.hasFlags(FLAG_MOVED_TO_TOP)) {
- // put on top
- layer = zSplitLine + numChanges - i;
- } else {
- // put on bottom
- layer = zSplitLine - i;
- }
- } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
- if (isOpening) {
- // put on bottom and leave visible
- layer = zSplitLine - i;
- } else {
- // put on top
- layer = zSplitLine + numChanges - i;
- }
- } else { // CHANGE or other
- if (isClosing || TransitionUtil.isOrderOnly(change)) {
- // Put below CLOSE mode (in the "static" section).
- layer = zSplitLine - i;
- } else {
- // Put above CLOSE mode.
- layer = zSplitLine + numChanges - i;
- }
+ change.getStartAbsBounds().left - root.getOffset().x,
+ change.getStartAbsBounds().top - root.getOffset().y);
}
+ final int layer = calculateAnimLayer(change, i, numChanges, type);
t.setLayer(leash, layer);
}
}
@@ -757,6 +763,10 @@ public class Transitions implements RemoteCallable<Transitions>,
}
if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
allOccluded = false;
+ } else if (change.hasAllFlags(TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION)) {
+ // Remove the change because it should be invisible in the animation.
+ info.getChanges().remove(i);
+ continue;
}
// The change has already animated by back gesture, don't need to play transition
// animation on it.
@@ -864,7 +874,7 @@ public class Transitions implements RemoteCallable<Transitions>,
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
+ " %s is still animating. Notify the animating transition"
+ " in case they can be merged", ready, playing);
- mTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
+ mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
playing.mToken, (wct) -> onMerged(playing, ready));
}
@@ -898,7 +908,7 @@ public class Transitions implements RemoteCallable<Transitions>,
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken);
}
- mTracer.logMerged(merged.mInfo.getDebugId(), playing.mInfo.getDebugId());
+ mTransitionTracer.logMerged(merged.mInfo.getDebugId(), playing.mInfo.getDebugId());
// See if we should merge another transition.
processReadyQueue(track);
}
@@ -919,7 +929,7 @@ public class Transitions implements RemoteCallable<Transitions>,
active.mStartT, active.mFinishT, (wct) -> onFinish(active, wct));
if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
- mTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
+ mTransitionTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
return;
}
}
@@ -944,7 +954,7 @@ public class Transitions implements RemoteCallable<Transitions>,
if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
mHandlers.get(i));
- mTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
+ mTransitionTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
return mHandlers.get(i);
}
}
@@ -974,7 +984,7 @@ public class Transitions implements RemoteCallable<Transitions>,
final Track track = mTracks.get(transition.getTrack());
transition.mAborted = true;
- mTracer.logAborted(transition.mInfo.getDebugId());
+ mTransitionTracer.logAborted(transition.mInfo.getDebugId());
if (transition.mHandler != null) {
// Notifies to clean-up the aborted transition.
@@ -1154,7 +1164,11 @@ public class Transitions implements RemoteCallable<Transitions>,
mPendingTransitions.add(0, active);
}
- /** Start a new transition directly. */
+ /**
+ * Start a new transition directly.
+ * @param handler if null, the transition will be dispatched to the registered set of transition
+ * handlers to be handled
+ */
public IBinder startTransition(@WindowManager.TransitionType int type,
@NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition "
@@ -1470,7 +1484,7 @@ public class Transitions implements RemoteCallable<Transitions>,
public void setHomeTransitionListener(IHomeTransitionListener listener) {
executeRemoteCallWithTaskPermission(mTransitions, "setHomeTransitionListener",
(transitions) -> {
- transitions.mHomeTransitionObserver.setHomeTransitionListener(mTransitions,
+ transitions.mHomeTransitionObserver.setHomeTransitionListener(transitions,
listener);
});
}
@@ -1502,12 +1516,18 @@ public class Transitions implements RemoteCallable<Transitions>,
}
}
-
@Override
public boolean onShellCommand(String[] args, PrintWriter pw) {
switch (args[0]) {
case "tracing": {
- mTracer.onShellCommand(Arrays.copyOfRange(args, 1, args.length), pw);
+ if (!android.tracing.Flags.perfettoTransitionTracing()) {
+ ((LegacyTransitionTracer) mTransitionTracer)
+ .onShellCommand(Arrays.copyOfRange(args, 1, args.length), pw);
+ } else {
+ pw.println("Command not supported. Use the Perfetto command instead to start "
+ + "and stop this trace instead.");
+ return false;
+ }
return true;
}
default: {
@@ -1520,8 +1540,10 @@ public class Transitions implements RemoteCallable<Transitions>,
@Override
public void printShellCommandHelp(PrintWriter pw, String prefix) {
- pw.println(prefix + "tracing");
- mTracer.printShellCommandHelp(pw, prefix + " ");
+ if (!android.tracing.Flags.perfettoTransitionTracing()) {
+ pw.println(prefix + "tracing");
+ ((LegacyTransitionTracer) mTransitionTracer).printShellCommandHelp(pw, prefix + " ");
+ }
}
private void dump(@NonNull PrintWriter pw, String prefix) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java
index 5919aad133c7..9c848869e0f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.transition;
+package com.android.wm.shell.transition.tracing;
import static android.os.Build.IS_USER;
@@ -29,6 +29,7 @@ import android.util.Log;
import com.android.internal.util.TraceBuffer;
import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.transition.Transitions;
import com.google.protobuf.nano.MessageNano;
@@ -45,7 +46,8 @@ import java.util.concurrent.TimeUnit;
/**
* Helper class to collect and dump transition traces.
*/
-public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
+public class LegacyTransitionTracer
+ implements ShellCommandHandler.ShellCommandActionHandler, TransitionTracer {
private static final int ALWAYS_ON_TRACING_CAPACITY = 15 * 1024; // 15 KB
private static final int ACTIVE_TRACING_BUFFER_CAPACITY = 5000 * 1024; // 5 MB
@@ -60,33 +62,33 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
private final TraceBuffer.ProtoProvider mProtoProvider =
new TraceBuffer.ProtoProvider<MessageNano,
- com.android.wm.shell.nano.WmShellTransitionTraceProto,
- com.android.wm.shell.nano.Transition>() {
- @Override
- public int getItemSize(MessageNano proto) {
- return proto.getCachedSize();
- }
-
- @Override
- public byte[] getBytes(MessageNano proto) {
- return MessageNano.toByteArray(proto);
- }
-
- @Override
- public void write(
- com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto,
- Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os)
+ com.android.wm.shell.nano.WmShellTransitionTraceProto,
+ com.android.wm.shell.nano.Transition>() {
+ @Override
+ public int getItemSize(MessageNano proto) {
+ return proto.getCachedSize();
+ }
+
+ @Override
+ public byte[] getBytes(MessageNano proto) {
+ return MessageNano.toByteArray(proto);
+ }
+
+ @Override
+ public void write(
+ com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto,
+ Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os)
throws IOException {
- encapsulatingProto.transitions = buffer.toArray(
- new com.android.wm.shell.nano.Transition[0]);
- os.write(getBytes(encapsulatingProto));
- }
- };
+ encapsulatingProto.transitions = buffer.toArray(
+ new com.android.wm.shell.nano.Transition[0]);
+ os.write(getBytes(encapsulatingProto));
+ }
+ };
private final TraceBuffer<MessageNano,
com.android.wm.shell.nano.WmShellTransitionTraceProto,
- com.android.wm.shell.nano.Transition> mTraceBuffer
- = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider,
- (proto) -> handleOnEntryRemovedFromTrace(proto));
+ com.android.wm.shell.nano.Transition> mTraceBuffer =
+ new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider,
+ this::handleOnEntryRemovedFromTrace);
private final Map<Object, Runnable> mRemovedFromTraceCallbacks = new HashMap<>();
private final Map<Transitions.TransitionHandler, Integer> mHandlerIds = new HashMap<>();
@@ -99,6 +101,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
* @param transitionId The id of the transition being dispatched.
* @param handler The handler the transition is being dispatched to.
*/
+ @Override
public void logDispatched(int transitionId, Transitions.TransitionHandler handler) {
final int handlerId;
if (mHandlerIds.containsKey(handler)) {
@@ -130,6 +133,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
*
* @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
*/
+ @Override
public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
proto.id = mergeRequestedTransitionId;
@@ -145,6 +149,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
* @param mergedTransitionId The id of the transition that was merged.
* @param playingTransitionId The id of the transition the transition was merged into.
*/
+ @Override
public void logMerged(int mergedTransitionId, int playingTransitionId) {
com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
proto.id = mergedTransitionId;
@@ -159,6 +164,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
*
* @param transitionId The id of the transition that was aborted.
*/
+ @Override
public void logAborted(int transitionId) {
com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
proto.id = transitionId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS
new file mode 100644
index 000000000000..2ff4d90306a9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS
@@ -0,0 +1,4 @@
+# WM shell transition tracing owners
+# Bug component: 1157642
+natanieljr@google.com
+pablogamito@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
new file mode 100644
index 000000000000..ed4ae05cb2c9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition.tracing;
+
+import android.internal.perfetto.protos.ShellTransitionOuterClass.ShellHandlerMapping;
+import android.internal.perfetto.protos.ShellTransitionOuterClass.ShellHandlerMappings;
+import android.internal.perfetto.protos.ShellTransitionOuterClass.ShellTransition;
+import android.internal.perfetto.protos.TracePacketOuterClass.TracePacket;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.tracing.transition.TransitionDataSource;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Helper class to collect and dump transition traces.
+ */
+public class PerfettoTransitionTracer implements TransitionTracer {
+ private final AtomicInteger mActiveTraces = new AtomicInteger(0);
+ private final TransitionDataSource mDataSource = new TransitionDataSource(
+ mActiveTraces::incrementAndGet,
+ this::onFlush,
+ mActiveTraces::decrementAndGet);
+ private final Map<String, Integer> mHandlerMapping = new HashMap<>();
+
+ public PerfettoTransitionTracer() {
+ Producer.init(InitArguments.DEFAULTS);
+ mDataSource.register(DataSourceParams.DEFAULTS);
+ }
+
+ /**
+ * Adds an entry in the trace to log that a transition has been dispatched to a handler.
+ *
+ * @param transitionId The id of the transition being dispatched.
+ * @param handler The handler the transition is being dispatched to.
+ */
+ @Override
+ public void logDispatched(int transitionId, Transitions.TransitionHandler handler) {
+ if (!isTracing()) {
+ return;
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logDispatched");
+ try {
+ doLogDispatched(transitionId, handler);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogDispatched(int transitionId, Transitions.TransitionHandler handler) {
+ mDataSource.trace(ctx -> {
+ final int handlerId = getHandlerId(handler);
+
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long token = os.start(TracePacket.SHELL_TRANSITION);
+ os.write(ShellTransition.ID, transitionId);
+ os.write(ShellTransition.DISPATCH_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.write(ShellTransition.HANDLER, handlerId);
+ os.end(token);
+ });
+ }
+
+ private int getHandlerId(Transitions.TransitionHandler handler) {
+ final int handlerId;
+ synchronized (mHandlerMapping) {
+ if (mHandlerMapping.containsKey(handler.getClass().getName())) {
+ handlerId = mHandlerMapping.get(handler.getClass().getName());
+ } else {
+ // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto
+ handlerId = mHandlerMapping.size() + 1;
+ mHandlerMapping.put(handler.getClass().getName(), handlerId);
+ }
+ }
+ return handlerId;
+ }
+
+ /**
+ * Adds an entry in the trace to log that a request to merge a transition was made.
+ *
+ * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
+ */
+ @Override
+ public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
+ if (!isTracing()) {
+ return;
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logMergeRequested");
+ try {
+ doLogMergeRequested(mergeRequestedTransitionId, playingTransitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
+ mDataSource.trace(ctx -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long token = os.start(TracePacket.SHELL_TRANSITION);
+ os.write(ShellTransition.ID, mergeRequestedTransitionId);
+ os.write(ShellTransition.MERGE_REQUEST_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.write(ShellTransition.MERGE_TARGET, playingTransitionId);
+ os.end(token);
+ });
+ }
+
+ /**
+ * Adds an entry in the trace to log that a transition was merged by the handler.
+ *
+ * @param mergedTransitionId The id of the transition that was merged.
+ * @param playingTransitionId The id of the transition the transition was merged into.
+ */
+ @Override
+ public void logMerged(int mergedTransitionId, int playingTransitionId) {
+ if (!isTracing()) {
+ return;
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logMerged");
+ try {
+ doLogMerged(mergedTransitionId, playingTransitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogMerged(int mergeRequestedTransitionId, int playingTransitionId) {
+ mDataSource.trace(ctx -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long token = os.start(TracePacket.SHELL_TRANSITION);
+ os.write(ShellTransition.ID, mergeRequestedTransitionId);
+ os.write(ShellTransition.MERGE_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.write(ShellTransition.MERGE_TARGET, playingTransitionId);
+ os.end(token);
+ });
+ }
+
+ /**
+ * Adds an entry in the trace to log that a transition was aborted.
+ *
+ * @param transitionId The id of the transition that was aborted.
+ */
+ @Override
+ public void logAborted(int transitionId) {
+ if (!isTracing()) {
+ return;
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logAborted");
+ try {
+ doLogAborted(transitionId);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ private void doLogAborted(int transitionId) {
+ mDataSource.trace(ctx -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long token = os.start(TracePacket.SHELL_TRANSITION);
+ os.write(ShellTransition.ID, transitionId);
+ os.write(ShellTransition.SHELL_ABORT_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.end(token);
+ });
+ }
+
+ private boolean isTracing() {
+ return mActiveTraces.get() > 0;
+ }
+
+ private void onFlush() {
+ mDataSource.trace(ctx -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+
+ final long mappingsToken = os.start(TracePacket.SHELL_HANDLER_MAPPINGS);
+ for (Map.Entry<String, Integer> entry : mHandlerMapping.entrySet()) {
+ final String handler = entry.getKey();
+ final int handlerId = entry.getValue();
+
+ final long mappingEntryToken = os.start(ShellHandlerMappings.MAPPING);
+ os.write(ShellHandlerMapping.ID, handlerId);
+ os.write(ShellHandlerMapping.NAME, handler);
+ os.end(mappingEntryToken);
+
+ }
+ os.end(mappingsToken);
+
+ ctx.flush();
+ });
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java
new file mode 100644
index 000000000000..5857ad88e9e6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition.tracing;
+
+import com.android.wm.shell.transition.Transitions;
+
+public interface TransitionTracer {
+ /**
+ * Adds an entry in the trace to log that a transition has been dispatched to a handler.
+ *
+ * @param transitionId The id of the transition being dispatched.
+ * @param handler The handler the transition is being dispatched to.
+ */
+ void logDispatched(int transitionId, Transitions.TransitionHandler handler);
+
+ /**
+ * Adds an entry in the trace to log that a request to merge a transition was made.
+ *
+ * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
+ */
+ void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId);
+
+ /**
+ * Adds an entry in the trace to log that a transition was merged by the handler.
+ *
+ * @param mergedTransitionId The id of the transition that was merged.
+ * @param playingTransitionId The id of the transition the transition was merged into.
+ */
+ void logMerged(int mergedTransitionId, int playingTransitionId);
+
+ /**
+ * Adds an entry in the trace to log that a transition was aborted.
+ *
+ * @param transitionId The id of the transition that was aborted.
+ */
+ void logAborted(int transitionId);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
index d7cb490ed0cb..e6d35e83116b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
@@ -28,11 +28,11 @@ import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
+import dagger.Lazy;
+
import java.util.List;
import java.util.Optional;
-import dagger.Lazy;
-
/**
* Manages fold/unfold animations of tasks on foldable devices.
* When folding or unfolding a foldable device we play animations that
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 98d343b66760..c26604a84a61 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -35,6 +35,7 @@ import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
@@ -43,7 +44,6 @@ import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
-import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.List;
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
deleted file mode 100644
index 7e95814c06c2..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/CounterRotator.java
+++ /dev/null
@@ -1,83 +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.wm.shell.util;
-
-import android.graphics.Point;
-import android.util.RotationUtils;
-import android.view.SurfaceControl;
-
-/**
- * Utility class that takes care of rotating unchanging child-surfaces to match the parent rotation
- * during a transition animation. This gives the illusion that the child surfaces haven't rotated
- * relative to the screen.
- */
-public class CounterRotator {
- private SurfaceControl mSurface = 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 parentW (and H) Is the size of the rotating parent after the rotation.
- */
- public void setup(SurfaceControl.Transaction t, SurfaceControl parent, int rotateDelta,
- float parentW, float parentH) {
- if (rotateDelta == 0) return;
- mSurface = new SurfaceControl.Builder()
- .setName("Transition Unrotate")
- .setContainerLayer()
- .setParent(parent)
- .build();
- // Rotate forward to match the new rotation (rotateDelta is the forward rotation the parent
- // already took). Child surfaces will be in the old rotation relative to the new parent
- // rotation, so we need to forward-rotate the child surfaces to match.
- RotationUtils.rotateSurface(t, mSurface, rotateDelta);
- final Point tmpPt = new Point(0, 0);
- // parentW/H are the size in the END rotation, the rotation utilities expect the starting
- // size. So swap them if necessary
- if ((rotateDelta % 2) != 0) {
- final float w = parentW;
- parentW = parentH;
- parentH = w;
- }
- RotationUtils.rotatePoint(tmpPt, rotateDelta, (int) parentW, (int) parentH);
- t.setPosition(mSurface, tmpPt.x, tmpPt.y);
- t.show(mSurface);
- }
-
- /**
- * Adds a surface that needs to be counter-rotate.
- */
- public void addChild(SurfaceControl.Transaction t, SurfaceControl child) {
- if (mSurface == null) return;
- t.reparent(child, mSurface);
- }
-
- /**
- * Clean-up. Since finishTransaction should reset all change leashes, we only need to remove the
- * counter rotation surface.
- */
- public void cleanUp(SurfaceControl.Transaction finishTransaction) {
- if (mSurface == null) return;
- finishTransaction.remove(mSurface);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
index 9b48a542720c..7a50814f0275 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.util
import android.util.Log
import com.android.internal.protolog.common.IProtoLogGroup
-import com.android.wm.shell.protolog.ShellProtoLogImpl
+import com.android.internal.protolog.common.ProtoLog
/**
* Log messages using an API similar to [com.android.internal.protolog.common.ProtoLog]. Useful for
@@ -31,42 +31,42 @@ class KtProtoLog {
companion object {
/** @see [com.android.internal.protolog.common.ProtoLog.d] */
fun d(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ShellProtoLogImpl.isEnabled(group)) {
+ if (ProtoLog.isEnabled(group)) {
Log.d(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.v] */
fun v(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ShellProtoLogImpl.isEnabled(group)) {
+ if (ProtoLog.isEnabled(group)) {
Log.v(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.i] */
fun i(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ShellProtoLogImpl.isEnabled(group)) {
+ if (ProtoLog.isEnabled(group)) {
Log.i(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.w] */
fun w(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ShellProtoLogImpl.isEnabled(group)) {
+ if (ProtoLog.isEnabled(group)) {
Log.w(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.e] */
fun e(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ShellProtoLogImpl.isEnabled(group)) {
+ if (ProtoLog.isEnabled(group)) {
Log.e(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.wtf] */
fun wtf(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ShellProtoLogImpl.isEnabled(group)) {
+ if (ProtoLog.isEnabled(group)) {
Log.wtf(group.tag, String.format(messageString, *args))
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
deleted file mode 100644
index 936faa3ee6bf..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.util;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.view.RemoteAnimationTarget.MODE_CHANGING;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
-import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-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_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
-import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
-import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
-import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
-import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
-
-import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.app.ActivityManager;
-import android.app.WindowConfiguration;
-import android.graphics.Rect;
-import android.util.ArrayMap;
-import android.util.SparseBooleanArray;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.window.TransitionInfo;
-
-import java.util.function.Predicate;
-
-/** Various utility functions for transitions. */
-public class TransitionUtil {
-
- /** @return true if the transition was triggered by opening something vs closing something */
- public static boolean isOpeningType(@WindowManager.TransitionType int type) {
- return type == TRANSIT_OPEN
- || type == TRANSIT_TO_FRONT
- || type == TRANSIT_KEYGUARD_GOING_AWAY;
- }
-
- /** @return true if the transition was triggered by closing something vs opening something */
- public static boolean isClosingType(@WindowManager.TransitionType int type) {
- return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK;
- }
-
- /** Returns {@code true} if the transition is opening or closing mode. */
- public static boolean isOpenOrCloseMode(@TransitionInfo.TransitionMode int mode) {
- return mode == TRANSIT_OPEN || mode == TRANSIT_CLOSE
- || mode == TRANSIT_TO_FRONT || mode == TRANSIT_TO_BACK;
- }
-
- /** Returns {@code true} if the transition has a display change. */
- public static boolean hasDisplayChange(@NonNull TransitionInfo 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.hasFlags(FLAG_IS_DISPLAY)) {
- return true;
- }
- }
- return false;
- }
-
- /** Returns `true` if `change` is a wallpaper. */
- public static boolean isWallpaper(TransitionInfo.Change change) {
- return (change.getTaskInfo() == null)
- && change.hasFlags(FLAG_IS_WALLPAPER)
- && !change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
- }
-
- /** Returns `true` if `change` is not an app window or wallpaper. */
- public static boolean isNonApp(TransitionInfo.Change change) {
- return (change.getTaskInfo() == null)
- && !change.hasFlags(FLAG_IS_WALLPAPER)
- && !change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
- }
-
- /** Returns `true` if `change` is the divider. */
- public static boolean isDividerBar(TransitionInfo.Change change) {
- return isNonApp(change) && change.hasFlags(FLAG_IS_DIVIDER_BAR);
- }
-
- /** Returns `true` if `change` is only re-ordering. */
- public static boolean isOrderOnly(TransitionInfo.Change change) {
- return change.getMode() == TRANSIT_CHANGE
- && (change.getFlags() & FLAG_MOVED_TO_TOP) != 0
- && change.getStartAbsBounds().equals(change.getEndAbsBounds())
- && (change.getLastParent() == null
- || change.getLastParent().equals(change.getParent()));
- }
-
- /**
- * Filter that selects leaf-tasks only. THIS IS ORDER-DEPENDENT! For it to work properly, you
- * MUST call `test` in the same order that the changes appear in the TransitionInfo.
- */
- public static class LeafTaskFilter implements Predicate<TransitionInfo.Change> {
- private final SparseBooleanArray mChildTaskTargets = new SparseBooleanArray();
-
- @Override
- public boolean test(TransitionInfo.Change change) {
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo == null) return false;
- // Children always come before parent since changes are in top-to-bottom z-order.
- boolean hasChildren = mChildTaskTargets.get(taskInfo.taskId);
- if (taskInfo.hasParentTask()) {
- mChildTaskTargets.put(taskInfo.parentTaskId, true);
- }
- // If it has children, it's not a leaf.
- return !hasChildren;
- }
- }
-
-
- 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 MODE_CHANGING;
- }
- }
-
- /**
- * Very similar to Transitions#setupAnimHierarchy but specialized for leashes.
- */
- @SuppressLint("NewApi")
- private static void setupLeash(@NonNull SurfaceControl leash,
- @NonNull TransitionInfo.Change change, int layer,
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
- final boolean isOpening = TransitionUtil.isOpeningType(info.getType());
- // Put animating stuff above this line and put static stuff below it.
- int zSplitLine = info.getChanges().size();
- // changes should be ordered top-to-bottom in z
- final int mode = change.getMode();
-
- final int rootIdx = TransitionUtil.rootIndexFor(change, info);
- t.reparent(leash, info.getRoot(rootIdx).getLeash());
- final Rect absBounds =
- (mode == TRANSIT_OPEN) ? change.getEndAbsBounds() : change.getStartAbsBounds();
- t.setPosition(leash, absBounds.left - info.getRoot(rootIdx).getOffset().x,
- absBounds.top - info.getRoot(rootIdx).getOffset().y);
-
- if (isDividerBar(change)) {
- if (isOpeningType(mode)) {
- t.setAlpha(leash, 0.f);
- }
- // Set the transition leash position to 0 in case the divider leash position being
- // taking down.
- t.setPosition(leash, 0, 0);
- t.setLayer(leash, Integer.MAX_VALUE);
- return;
- }
-
- // Put all the OPEN/SHOW on top
- if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
- // Wallpaper is always at the bottom, opening wallpaper on top of closing one.
- if (mode == WindowManager.TRANSIT_OPEN || mode == WindowManager.TRANSIT_TO_FRONT) {
- t.setLayer(leash, -zSplitLine + info.getChanges().size() - layer);
- } else {
- t.setLayer(leash, -zSplitLine - layer);
- }
- } else if (TransitionUtil.isOpeningType(mode)) {
- if (isOpening) {
- t.setLayer(leash, zSplitLine + info.getChanges().size() - layer);
- if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) {
- // if transferred, it should be left visible.
- t.setAlpha(leash, 0.f);
- }
- } else {
- // put on bottom and leave it visible
- t.setLayer(leash, zSplitLine - layer);
- }
- } else if (TransitionUtil.isClosingType(mode)) {
- if (isOpening) {
- // put on bottom and leave visible
- t.setLayer(leash, zSplitLine - layer);
- } else {
- // put on top
- t.setLayer(leash, zSplitLine + info.getChanges().size() - layer);
- }
- } else { // CHANGE
- t.setLayer(leash, zSplitLine + info.getChanges().size() - layer);
- }
- }
-
- @SuppressLint("NewApi")
- private static SurfaceControl createLeash(TransitionInfo info, TransitionInfo.Change change,
- int order, SurfaceControl.Transaction t) {
- // TODO: once we can properly sync transactions across process, then get rid of this leash.
- if (change.getParent() != null && (change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
- // Special case for wallpaper atm. Normally these are left alone; but, a quirk of
- // making leashes means we have to handle them specially.
- return change.getLeash();
- }
- final int rootIdx = TransitionUtil.rootIndexFor(change, info);
- SurfaceControl leashSurface = new SurfaceControl.Builder()
- .setName(change.getLeash().toString() + "_transition-leash")
- .setContainerLayer()
- // Initial the surface visible to respect the visibility of the original surface.
- .setHidden(false)
- .setParent(info.getRoot(rootIdx).getLeash())
- .build();
- // Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
- setupLeash(leashSurface, change, info.getChanges().size() - order, info, t);
- t.reparent(change.getLeash(), leashSurface);
- t.setAlpha(change.getLeash(), 1.0f);
- t.show(change.getLeash());
- if (!isDividerBar(change)) {
- // For divider, don't modify its inner leash position when creating the outer leash
- // for the transition. In case the position being wrong after the transition finished.
- t.setPosition(change.getLeash(), 0, 0);
- }
- t.setLayer(change.getLeash(), 0);
- return leashSurface;
- }
-
- /**
- * Creates a new RemoteAnimationTarget from the provided change info
- */
- public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order,
- TransitionInfo info, SurfaceControl.Transaction t,
- @Nullable ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
- return newTarget(change, order, false /* forceTranslucent */, info, t, leashMap);
- }
-
- /**
- * Creates a new RemoteAnimationTarget from the provided change info
- */
- public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order,
- boolean forceTranslucent, TransitionInfo info, SurfaceControl.Transaction t,
- @Nullable ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
- final SurfaceControl leash = createLeash(info, change, order, t);
- if (leashMap != null) {
- leashMap.put(change.getLeash(), leash);
- }
- return newTarget(change, order, leash, forceTranslucent);
- }
-
- /**
- * Creates a new RemoteAnimationTarget from the provided change and leash
- */
- public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order,
- SurfaceControl leash) {
- return newTarget(change, order, leash, false /* forceTranslucent */);
- }
-
- /**
- * Creates a new RemoteAnimationTarget from the provided change and leash
- */
- public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order,
- SurfaceControl leash, boolean forceTranslucent) {
- if (isDividerBar(change)) {
- return getDividerTarget(change, leash);
- }
-
- int taskId;
- boolean isNotInRecents;
- ActivityManager.RunningTaskInfo taskInfo;
- WindowConfiguration windowConfiguration;
-
- taskInfo = change.getTaskInfo();
- if (taskInfo != null) {
- taskId = taskInfo.taskId;
- isNotInRecents = !taskInfo.isRunning;
- windowConfiguration = taskInfo.configuration.windowConfiguration;
- } else {
- taskId = INVALID_TASK_ID;
- isNotInRecents = true;
- windowConfiguration = new WindowConfiguration();
- }
-
- Rect localBounds = new Rect(change.getEndAbsBounds());
- localBounds.offsetTo(change.getEndRelOffset().x, change.getEndRelOffset().y);
-
- RemoteAnimationTarget target = new RemoteAnimationTarget(
- taskId,
- newModeToLegacyMode(change.getMode()),
- // TODO: once we can properly sync transactions across process,
- // then get rid of this leash.
- leash,
- forceTranslucent || (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0,
- null,
- // TODO(shell-transitions): we need to send content insets? evaluate how its used.
- new Rect(0, 0, 0, 0),
- order,
- null,
- localBounds,
- new Rect(change.getEndAbsBounds()),
- windowConfiguration,
- isNotInRecents,
- null,
- new Rect(change.getStartAbsBounds()),
- taskInfo,
- change.getAllowEnterPip(),
- INVALID_WINDOW_TYPE
- );
- target.setWillShowImeOnTarget(
- (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0);
- target.setRotationChange(change.getEndRotation() - change.getStartRotation());
- target.backgroundColor = change.getBackgroundColor();
- return target;
- }
-
- private static RemoteAnimationTarget getDividerTarget(TransitionInfo.Change change,
- SurfaceControl leash) {
- return new RemoteAnimationTarget(-1 /* taskId */, newModeToLegacyMode(change.getMode()),
- leash, false /* isTranslucent */, null /* clipRect */,
- null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
- new android.graphics.Point(0, 0) /* position */, change.getStartAbsBounds(),
- change.getStartAbsBounds(), new WindowConfiguration(), true, null /* startLeash */,
- null /* startBounds */, null /* taskInfo */, false /* allowEnterPip */,
- TYPE_DOCK_DIVIDER);
- }
-
- /**
- * Finds the "correct" root idx for a change. The change's end display is prioritized, then
- * the start display. If there is no display, it will fallback on the 0th root in the
- * transition. There MUST be at-least 1 root in the transition (ie. it's not a no-op).
- */
- public static int rootIndexFor(@NonNull TransitionInfo.Change change,
- @NonNull TransitionInfo info) {
- int rootIdx = info.findRootIndex(change.getEndDisplayId());
- if (rootIdx >= 0) return rootIdx;
- rootIdx = info.findRootIndex(change.getStartDisplayId());
- if (rootIdx >= 0) return rootIdx;
- return 0;
- }
-
- /**
- * Gets the {@link TransitionInfo.Root} for the given {@link TransitionInfo.Change}.
- * @see #rootIndexFor(TransitionInfo.Change, TransitionInfo)
- */
- @NonNull
- public static TransitionInfo.Root getRootFor(@NonNull TransitionInfo.Change change,
- @NonNull TransitionInfo info) {
- return info.getRoot(rootIndexFor(change, info));
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index cf1692018518..b2eeea7048bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -23,13 +23,11 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.os.Handler;
-import android.os.IBinder;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
-import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -54,6 +52,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
+ private final Transitions mTransitions;
private TaskOperations mTaskOperations;
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
@@ -64,29 +63,21 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
Choreographer mainChoreographer,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue,
+ Transitions transitions) {
mContext = context;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mSyncQueue = syncQueue;
+ mTransitions = transitions;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
}
}
@Override
- public void onTransitionReady(IBinder transition, TransitionInfo info,
- TransitionInfo.Change change) {}
-
- @Override
- public void onTransitionMerged(IBinder merged, IBinder playing) {}
-
- @Override
- public void onTransitionFinished(IBinder transition) {}
-
- @Override
public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) {
mTaskOperations = new TaskOperations(transitionStarter, mContext, mSyncQueue);
}
@@ -133,7 +124,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
if (decoration == null) {
createWindowDecoration(taskInfo, taskSurface, startT, finishT);
} else {
- decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
+ decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
+ false /* setTaskCropAndPosition */);
}
}
@@ -145,7 +137,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
- decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
+ decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
+ false /* setTaskCropAndPosition */);
}
@Override
@@ -191,16 +184,17 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
- final DragPositioningCallback dragPositioningCallback =
- new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController,
- 0 /* disallowedAreaForEndBoundsHeight */);
+ final FluidResizeTaskPositioner taskPositioner =
+ new FluidResizeTaskPositioner(mTaskOrganizer, mTransitions, windowDecoration,
+ mDisplayController, 0 /* disallowedAreaForEndBoundsHeight */);
final CaptionTouchEventListener touchEventListener =
- new CaptionTouchEventListener(taskInfo, dragPositioningCallback);
+ new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
- windowDecoration.setDragPositioningCallback(dragPositioningCallback);
+ windowDecoration.setDragPositioningCallback(taskPositioner);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
+ windowDecoration.setTaskDragResizer(taskPositioner);
windowDecoration.relayout(taskInfo, startT, finishT,
- false /* applyStartTransactionOnDraw */);
+ false /* applyStartTransactionOnDraw */, false /* setTaskCropAndPosition */);
setupCaptionColor(taskInfo, windowDecoration);
}
@@ -277,6 +271,9 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
if (e.findPointerIndex(mDragPointerId) == -1) {
mDragPointerId = e.getPointerId(0);
}
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ // If a decor's resize drag zone is active, don't also try to reposition it.
+ if (decoration.isHandlingDragResize()) break;
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index c12ac8b3772e..91e9601c6a27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -22,6 +22,7 @@ import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
+import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
@@ -34,6 +35,7 @@ import android.window.WindowContainerTransaction;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
/**
@@ -84,6 +86,69 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mDragPositioningCallback = dragPositioningCallback;
}
+ @Override
+ Rect calculateValidDragArea() {
+ final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.caption_left_buttons_width);
+
+ // On a smaller screen, don't require as much empty space on screen, as offscreen
+ // drags will be restricted too much.
+ final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.displayId)
+ .getResources().getConfiguration().smallestScreenWidthDp >= 600
+ ? R.dimen.freeform_required_visible_empty_space_in_header :
+ R.dimen.small_screen_required_visible_empty_space_in_header;
+ final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
+ requiredEmptySpaceId);
+
+ final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.caption_right_buttons_width);
+ final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ final int displayWidth = layout.width();
+ final Rect stableBounds = new Rect();
+ layout.getStableBounds(stableBounds);
+ return new Rect(
+ determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth),
+ stableBounds.top,
+ determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace, taskWidth,
+ displayWidth),
+ determineMaxY(requiredEmptySpace, stableBounds));
+ }
+
+
+ /**
+ * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth) {
+ // Do not let apps with < 48dp empty header space go off the left edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return 0;
+ }
+ return -taskWidth + requiredEmptySpace + rightButtonsWidth;
+ }
+
+ /**
+ * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth, int displayWidth) {
+ // Do not let apps with < 48dp empty header space go off the right edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return displayWidth - taskWidth;
+ }
+ return displayWidth - requiredEmptySpace - leftButtonsWidth;
+ }
+
+ /**
+ * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
+ return stableBounds.bottom - requiredEmptySpace;
+ }
+
+
void setDragDetector(DragDetector dragDetector) {
mDragDetector = dragDetector;
mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
@@ -92,15 +157,21 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
@Override
void relayout(RunningTaskInfo taskInfo) {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ // The crop and position of the task should only be set when a task is fluid resizing. In
+ // all other cases, it is expected that the transition handler positions and crops the task
+ // in order to allow the handler time to animate before the task before the final
+ // position and crop are set.
+ final boolean shouldSetTaskPositionAndCrop = mTaskDragResizer.isResizingOrAnimating();
// Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
// synced with the buffer transaction (that draws the View). Both will be shown on screen
// at the same, whereas applying them independently causes flickering. See b/270202228.
- relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */);
+ relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */,
+ shouldSetTaskPositionAndCrop);
}
void relayout(RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw) {
+ boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) {
final int shadowRadiusID = taskInfo.isFocused
? R.dimen.freeform_decor_shadow_focused_thickness
: R.dimen.freeform_decor_shadow_unfocused_thickness;
@@ -118,6 +189,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode());
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
+ mRelayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition;
+ mRelayoutParams.mAllowCaptionInputFallthrough = false;
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -214,6 +287,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
closeBackground.setTintList(buttonTintColor);
}
+ boolean isHandlingDragResize() {
+ return mDragResizeListener != null && mDragResizeListener.isHandlingDragResize();
+ }
+
private void closeDragResizeListener() {
if (mDragResizeListener == null) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index ab29df1f780c..fa3d8a61fd04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -21,18 +21,22 @@ 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.view.InputDevice.SOURCE_TOUCHSCREEN;
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_HOVER_ENTER;
+import static android.view.MotionEvent.ACTION_HOVER_EXIT;
+import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.statusBars;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION;
import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
@@ -43,11 +47,14 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.GestureDetector;
+import android.view.ISystemGestureExclusionListener;
+import android.view.IWindowManager;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
@@ -58,13 +65,9 @@ import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.WindowManager;
-import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -74,14 +77,13 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
-import com.android.wm.shell.recents.RecentsTransitionHandler;
-import com.android.wm.shell.recents.RecentsTransitionStateListener;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -91,6 +93,7 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
+import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import java.io.PrintWriter;
import java.util.Optional;
@@ -105,6 +108,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private static final String TAG = "DesktopModeWindowDecorViewModel";
private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
+ private final IWindowManager mWindowManager;
+ private final ShellExecutor mMainExecutor;
private final ActivityTaskManager mActivityTaskManager;
private final ShellCommandHandler mShellCommandHandler;
private final ShellTaskOrganizer mTaskOrganizer;
@@ -115,7 +120,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
private final Optional<DesktopTasksController> mDesktopTasksController;
- private final RecentsTransitionHandler mRecentsTransitionHandler;
+ private final InputManager mInputManager;
+
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -139,14 +145,31 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
new DesktopModeKeyguardChangeListener();
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final DisplayInsetsController mDisplayInsetsController;
+ private final Region mExclusionRegion = Region.obtain();
private boolean mInImmersiveMode;
+ private final ISystemGestureExclusionListener mGestureExclusionListener =
+ new ISystemGestureExclusionListener.Stub() {
+ @Override
+ public void onSystemGestureExclusionChanged(int displayId,
+ Region systemGestureExclusion, Region systemGestureExclusionUnrestricted) {
+ if (mContext.getDisplayId() != displayId) {
+ return;
+ }
+ mMainExecutor.execute(() -> {
+ mExclusionRegion.set(systemGestureExclusion);
+ });
+ }
+ };
+
public DesktopModeWindowDecorViewModel(
Context context,
+ ShellExecutor shellExecutor,
Handler mainHandler,
Choreographer mainChoreographer,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
+ IWindowManager windowManager,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
ShellController shellController,
@@ -154,15 +177,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RecentsTransitionHandler recentsTransitionHandler,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
) {
this(
context,
+ shellExecutor,
mainHandler,
mainChoreographer,
shellInit,
shellCommandHandler,
+ windowManager,
taskOrganizer,
displayController,
shellController,
@@ -170,7 +194,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
syncQueue,
transitions,
desktopTasksController,
- recentsTransitionHandler,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
@@ -180,10 +203,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
@VisibleForTesting
DesktopModeWindowDecorViewModel(
Context context,
+ ShellExecutor shellExecutor,
Handler mainHandler,
Choreographer mainChoreographer,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
+ IWindowManager windowManager,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
ShellController shellController,
@@ -191,12 +216,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RecentsTransitionHandler recentsTransitionHandler,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
mContext = context;
+ mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
@@ -207,27 +232,30 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mSyncQueue = syncQueue;
mTransitions = transitions;
mDesktopTasksController = desktopTasksController;
- mRecentsTransitionHandler = recentsTransitionHandler;
mShellCommandHandler = shellCommandHandler;
+ mWindowManager = windowManager;
mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
mInputMonitorFactory = inputMonitorFactory;
mTransactionFactory = transactionFactory;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ mInputManager = mContext.getSystemService(InputManager.class);
shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
- mRecentsTransitionHandler.addTransitionStateListener(new RecentsTransitionStateListener() {
- @Override
- public void onTransitionStarted(IBinder transition) {
- blockRelayoutOnTransitionStarted(transition);
- }
- });
mShellCommandHandler.addDumpCallback(this::dump, this);
mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
new DesktopModeOnInsetsChangedListener());
+ mDesktopTasksController.ifPresent(c -> c.setOnTaskResizeAnimationListener(
+ new DeskopModeOnTaskResizeAnimationListener()));
+ try {
+ mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener,
+ mContext.getDisplayId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register window manager callbacks", e);
+ }
}
@Override
@@ -264,48 +292,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
@Override
- public void onTransitionReady(
- @NonNull IBinder transition,
- @NonNull TransitionInfo info,
- @NonNull TransitionInfo.Change change) {
- if (change.getMode() == WindowManager.TRANSIT_CHANGE
- && (info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
- || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
- || info.getType() == Transitions.TRANSIT_MOVE_TO_DESKTOP)) {
- mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
- .addTransitionPausingRelayout(transition);
- } else if (change.getMode() == WindowManager.TRANSIT_TO_BACK
- && info.getType() == Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
- && change.getTaskInfo() != null) {
- final DesktopModeWindowDecoration decor =
- mWindowDecorByTaskId.get(change.getTaskInfo().taskId);
- if (decor != null) {
- decor.addTransitionPausingRelayout(transition);
- }
- } else if (change.getMode() == WindowManager.TRANSIT_TO_FRONT
- && ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0)
- && change.getTaskInfo() != null) {
- blockRelayoutOnTransitionStarted(transition);
- }
- }
-
- @Override
- public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
- for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
- final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
- decor.mergeTransitionPausingRelayout(merged, playing);
- }
- }
-
- @Override
- public void onTransitionFinished(@NonNull IBinder transition) {
- for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
- final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
- decor.removeTransitionPausingRelayout(transition);
- }
- }
-
- @Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
@@ -335,7 +321,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
if (decoration == null) {
createWindowDecoration(taskInfo, taskSurface, startT, finishT);
} else {
- decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
+ decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
+ false /* shouldSetTaskPositionAndCrop */);
}
}
@@ -347,7 +334,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
- decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
+ decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
+ false /* shouldSetTaskPositionAndCrop */);
}
@Override
@@ -363,19 +351,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
}
- private void blockRelayoutOnTransitionStarted(IBinder transition) {
- // Block relayout on window decorations originating from #onTaskInfoChanges until the
- // animation completes to avoid interfering with the transition animation.
- for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
- final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
- decor.incrementRelayoutBlock();
- decor.addTransitionPausingRelayout(transition);
- }
- }
-
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
- DragDetector.MotionEventHandler {
+ View.OnGenericMotionListener , DragDetector.MotionEventHandler {
+ private static final int CLOSE_MAXIMIZE_MENU_DELAY_MS = 150;
private final int mTaskId;
private final WindowContainerToken mTaskToken;
@@ -383,10 +362,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final DragDetector mDragDetector;
private final GestureDetector mGestureDetector;
+ /**
+ * Whether to pilfer the next motion event to send cancellations to the windows below.
+ * Useful when the caption window is spy and the gesture should be handle by the system
+ * instead of by the app for their custom header content.
+ */
+ private boolean mShouldPilferCaptionEvents;
private boolean mIsDragging;
+ private boolean mTouchscreenInUse;
private boolean mHasLongClicked;
- private boolean mShouldClick;
private int mDragPointerId = -1;
+ private final Runnable mCloseMaximizeWindowRunnable;
private DesktopModeTouchEventListener(
RunningTaskInfo taskInfo,
@@ -396,15 +382,25 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDragPositioningCallback = dragPositioningCallback;
mDragDetector = new DragDetector(this);
mGestureDetector = new GestureDetector(mContext, this);
+ mCloseMaximizeWindowRunnable = () -> {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ if (decoration == null) return;
+ decoration.closeMaximizeMenu();
+ };
}
@Override
public void onClick(View v) {
+ if (mIsDragging) {
+ mIsDragging = false;
+ return;
+ }
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
if (id == R.id.close_window) {
if (isTaskInSplitScreen(mTaskId)) {
- mSplitScreenController.moveTaskToFullscreen(getOtherSplitTask(mTaskId).taskId);
+ mSplitScreenController.moveTaskToFullscreen(getOtherSplitTask(mTaskId).taskId,
+ SplitScreenController.EXIT_REASON_DESKTOP_MODE);
} else {
mTaskOperations.closeTask(mTaskToken);
}
@@ -412,7 +408,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mTaskOperations.injectBackKey();
} else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
if (!decoration.isHandleMenuActive()) {
- moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
+ moveTaskToFront(decoration.mTaskInfo);
decoration.createHandleMenu();
} else {
decoration.closeHandleMenu();
@@ -423,18 +419,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
// App sometimes draws before the insets from WindowDecoration#relayout have
// been added, so they must be added here
mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
- decoration.incrementRelayoutBlock();
- mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
- closeOtherSplitTask(mTaskId);
+ mDesktopTasksController.get().moveToDesktop(mTaskId, wct);
}
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
decoration.closeHandleMenu();
if (isTaskInSplitScreen(mTaskId)) {
- mSplitScreenController.moveTaskToFullscreen(mTaskId);
+ mSplitScreenController.moveTaskToFullscreen(mTaskId,
+ SplitScreenController.EXIT_REASON_DESKTOP_MODE);
} else {
mDesktopTasksController.ifPresent(c ->
- c.moveToFullscreen(mTaskId, mWindowDecorByTaskId.get(mTaskId)));
+ c.moveToFullscreen(mTaskId));
}
} else if (id == R.id.split_screen_button) {
decoration.closeHandleMenu();
@@ -450,30 +445,25 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId));
}
} else if (id == R.id.maximize_window) {
- if (decoration.isMaximizeMenuActive()) {
- decoration.closeMaximizeMenu();
- return;
- }
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(
- taskInfo, decoration));
decoration.closeHandleMenu();
+ decoration.closeMaximizeMenu();
+ mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo));
} else if (id == R.id.maximize_menu_maximize_button) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(
- taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId)));
+ mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo));
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
} else if (id == R.id.maximize_menu_snap_left_button) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
- taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId), SnapPosition.LEFT));
+ taskInfo, SnapPosition.LEFT));
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
} else if (id == R.id.maximize_menu_snap_right_button) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
- taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId), SnapPosition.RIGHT));
+ taskInfo, SnapPosition.RIGHT));
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
}
@@ -482,39 +472,68 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
@Override
public boolean onTouch(View v, MotionEvent e) {
final int id = v.getId();
+ if ((e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN) {
+ mTouchscreenInUse = e.getActionMasked() != ACTION_UP
+ && e.getActionMasked() != ACTION_CANCEL;
+ }
if (id != R.id.caption_handle && id != R.id.desktop_mode_caption
&& id != R.id.open_menu_button && id != R.id.close_window
&& id != R.id.maximize_window) {
return false;
}
- moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
-
- if (!mHasLongClicked) {
- final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
- decoration.closeMaximizeMenu();
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ moveTaskToFront(decoration.mTaskInfo);
+
+ final int actionMasked = e.getActionMasked();
+ final boolean isDown = actionMasked == MotionEvent.ACTION_DOWN;
+ final boolean isUpOrCancel = actionMasked == MotionEvent.ACTION_CANCEL
+ || actionMasked == MotionEvent.ACTION_UP;
+ if (isDown) {
+ final boolean downInCustomizableCaptionRegion =
+ decoration.checkTouchEventInCustomizableRegion(e);
+ final boolean downInExclusionRegion = mExclusionRegion.contains(
+ (int) e.getRawX(), (int) e.getRawY());
+ final boolean isTransparentCaption =
+ TaskInfoKt.isTransparentCaptionBarAppearance(decoration.mTaskInfo);
+ // The caption window may be a spy window when the caption background is
+ // transparent, which means events will fall through to the app window. Make
+ // sure to cancel these events if they do not happen in the intersection of the
+ // customizable region and what the app reported as exclusion areas, because
+ // the drag-move or other caption gestures should take priority outside those
+ // regions.
+ mShouldPilferCaptionEvents = !(downInCustomizableCaptionRegion
+ && downInExclusionRegion && isTransparentCaption);
}
- final long eventDuration = e.getEventTime() - e.getDownTime();
- final boolean shouldLongClick = id == R.id.maximize_window && !mIsDragging
- && !mHasLongClicked && eventDuration >= ViewConfiguration.getLongPressTimeout();
- if (shouldLongClick) {
- v.performLongClick();
- mHasLongClicked = true;
- return true;
+ if (!mShouldPilferCaptionEvents) {
+ // The event will be handled by a window below.
+ return false;
+ }
+ // Otherwise pilfer so that windows below receive cancellations for this gesture, and
+ // continue normal handling as a caption gesture.
+ if (mInputManager != null) {
+ mInputManager.pilferPointers(v.getViewRootImpl().getInputToken());
+ }
+ if (isUpOrCancel) {
+ // Gesture is finished, reset state.
+ mShouldPilferCaptionEvents = false;
+ }
+ if (!mHasLongClicked && id != R.id.maximize_window) {
+ decoration.closeMaximizeMenuIfNeeded(e);
}
-
return mDragDetector.onMotionEvent(v, e);
}
@Override
public boolean onLongClick(View v) {
final int id = v.getId();
- if (id == R.id.maximize_window) {
+ if (id == R.id.maximize_window && mTouchscreenInUse) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
moveTaskToFront(decoration.mTaskInfo);
if (decoration.isMaximizeMenuActive()) {
decoration.closeMaximizeMenu();
} else {
+ mHasLongClicked = true;
decoration.createMaximizeMenu();
}
return true;
@@ -522,6 +541,35 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return false;
}
+ @Override
+ public boolean onGenericMotion(View v, MotionEvent ev) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ final int id = v.getId();
+ if (ev.getAction() == ACTION_HOVER_ENTER) {
+ if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) {
+ decoration.onMaximizeWindowHoverEnter();
+ } else if (id == R.id.maximize_window
+ || MaximizeMenu.Companion.isMaximizeMenuView(id)) {
+ // Re-hovering over any of the maximize menu views should keep the menu open by
+ // cancelling any attempts to close the menu.
+ mMainHandler.removeCallbacks(mCloseMaximizeWindowRunnable);
+ }
+ return true;
+ } else if (ev.getAction() == ACTION_HOVER_EXIT) {
+ if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) {
+ decoration.onMaximizeWindowHoverExit();
+ } else if (id == R.id.maximize_window || id == R.id.maximize_menu) {
+ // Close menu if not hovering over maximize menu or maximize button after a
+ // delay to give user a chance to re-enter view or to move from one maximize
+ // menu view to another.
+ mMainHandler.postDelayed(mCloseMaximizeWindowRunnable,
+ CLOSE_MAXIMIZE_MENU_DELAY_MS);
+ }
+ return true;
+ }
+ return false;
+ }
+
private void moveTaskToFront(RunningTaskInfo taskInfo) {
if (!taskInfo.isFocused) {
mDesktopTasksController.ifPresent(c -> c.moveTaskToFront(taskInfo));
@@ -534,7 +582,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
*/
@Override
public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
- final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ final RunningTaskInfo taskInfo = decoration.mTaskInfo;
if (DesktopModeStatus.isEnabled()
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
return false;
@@ -542,11 +591,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
if (mGestureDetector.onTouchEvent(e)) {
return true;
}
- if (e.getActionMasked() == MotionEvent.ACTION_CANCEL) {
- // If a motion event is cancelled, reset mShouldClick so a click is not accidentally
- // performed.
- mShouldClick = false;
- }
+ final int id = v.getId();
+ final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window
+ || id == R.id.open_menu_button);
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mDragPointerId = e.getPointerId(0);
@@ -554,13 +601,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
0 /* ctrlType */, e.getRawX(0),
e.getRawY(0));
mIsDragging = false;
- mShouldClick = true;
mHasLongClicked = false;
- return true;
+ // Do not consume input event if a button is touched, otherwise it would
+ // prevent the button's ripple effect from showing.
+ return !touchingButton;
}
case MotionEvent.ACTION_MOVE: {
- final DesktopModeWindowDecoration decoration =
- mWindowDecorByTaskId.get(mTaskId);
+ // If a decor's resize drag zone is active, don't also try to reposition it.
+ if (decoration.isHandlingDragResize()) break;
decoration.closeMaximizeMenu();
if (e.findPointerIndex(mDragPointerId) == -1) {
mDragPointerId = e.getPointerId(0);
@@ -570,21 +618,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
decoration.mTaskSurface,
- new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
+ e.getRawX(dragPointerIdx),
newTaskBounds));
mIsDragging = true;
- mShouldClick = false;
return true;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
final boolean wasDragging = mIsDragging;
if (!wasDragging) {
- if (mShouldClick && v != null && !mHasLongClicked) {
- v.performClick();
- mShouldClick = false;
- return true;
- }
return false;
}
if (e.findPointerIndex(mDragPointerId) == -1) {
@@ -602,19 +644,37 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
position,
new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
- newTaskBounds, mWindowDecorByTaskId.get(mTaskId)));
- mIsDragging = false;
- return true;
+ newTaskBounds));
+ if (touchingButton && !mHasLongClicked) {
+ // We need the input event to not be consumed here to end the ripple
+ // effect on the touched button. We will reset drag state in the ensuing
+ // onClick call that results.
+ return false;
+ } else {
+ mIsDragging = false;
+ return true;
+ }
}
}
return true;
}
+ /**
+ * Perform a task size toggle on release of the double-tap, assuming no drag event
+ * was handled during the double-tap.
+ * @param e The motion event that occurred during the double-tap gesture.
+ * @return true if the event should be consumed, false if not
+ */
@Override
- public boolean onDoubleTap(@NonNull MotionEvent e) {
- final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ public boolean onDoubleTapEvent(@NonNull MotionEvent e) {
+ final int action = e.getActionMasked();
+ if (mIsDragging || (action != MotionEvent.ACTION_UP
+ && action != MotionEvent.ACTION_CANCEL)) {
+ return false;
+ }
mDesktopTasksController.ifPresent(c -> {
- c.toggleDesktopTaskSize(taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId));
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ c.toggleDesktopTaskSize(decoration.mTaskInfo);
});
return true;
}
@@ -711,7 +771,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
handleCaptionThroughStatusBar(ev, relevantDecor);
}
}
- handleEventOutsideFocusedCaption(ev, relevantDecor);
+ handleEventOutsideCaption(ev, relevantDecor);
// Prevent status bar from reacting to a caption drag.
if (DesktopModeStatus.isEnabled()) {
if (mTransitionDragActive) {
@@ -720,8 +780,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
}
- // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
- private void handleEventOutsideFocusedCaption(MotionEvent ev,
+ /**
+ * If an UP/CANCEL action is received outside of the caption bounds, close the handle and
+ * maximize the menu.
+ *
+ * @param relevantDecor the window decoration of the focused task's caption. This method only
+ * handles motion events outside this caption's bounds.
+ */
+ private void handleEventOutsideCaption(MotionEvent ev,
DesktopModeWindowDecoration relevantDecor) {
// Returns if event occurs within caption
if (relevantDecor == null || relevantDecor.checkTouchEventInCaption(ev)) {
@@ -759,7 +825,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|| windowingMode == WINDOWING_MODE_MULTI_WINDOW;
}
- if (dragFromStatusBarAllowed && relevantDecor.checkTouchEventInHandle(ev)) {
+ if (dragFromStatusBarAllowed
+ && relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)) {
mTransitionDragActive = true;
}
}
@@ -775,7 +842,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mTransitionDragActive = false;
final int statusBarHeight = getStatusBarHeight(
relevantDecor.mTaskInfo.displayId);
- if (ev.getY() > 2 * statusBarHeight) {
+ if (ev.getRawY() > 2 * statusBarHeight) {
if (DesktopModeStatus.isEnabled()) {
animateToDesktop(relevantDecor, ev);
}
@@ -798,31 +865,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
if (mTransitionDragActive) {
mDesktopTasksController.ifPresent(
- c -> c.onDragPositioningMoveThroughStatusBar(
+ c -> c.updateVisualIndicator(
relevantDecor.mTaskInfo,
- relevantDecor.mTaskSurface, ev.getY()));
+ relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY()));
final int statusBarHeight = getStatusBarHeight(
relevantDecor.mTaskInfo.displayId);
- if (ev.getY() > statusBarHeight) {
+ if (ev.getRawY() > statusBarHeight) {
if (mMoveToDesktopAnimator == null) {
mMoveToDesktopAnimator = new MoveToDesktopAnimator(
mContext, mDragToDesktopAnimationStartBounds,
relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
mDesktopTasksController.ifPresent(
- c -> {
- final int taskId = relevantDecor.mTaskInfo.taskId;
- relevantDecor.incrementRelayoutBlock();
- if (isTaskInSplitScreen(taskId)) {
- final DesktopModeWindowDecoration otherDecor =
- mWindowDecorByTaskId.get(
- getOtherSplitTask(taskId).taskId);
- if (otherDecor != null) {
- otherDecor.incrementRelayoutBlock();
- }
- }
- c.startDragToDesktop(relevantDecor.mTaskInfo,
- mMoveToDesktopAnimator, relevantDecor);
- });
+ c -> c.startDragToDesktop(relevantDecor.mTaskInfo,
+ mMoveToDesktopAnimator));
}
}
if (mMoveToDesktopAnimator != null) {
@@ -844,16 +899,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
* @param scale the amount to scale to relative to the Screen Bounds
*/
private Rect calculateFreeformBounds(int displayId, float scale) {
+ // TODO(b/319819547): Account for app constraints so apps do not become letterboxed
final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
final int screenWidth = displayLayout.width();
final int screenHeight = displayLayout.height();
final float adjustmentPercentage = (1f - scale) / 2;
- final Rect endBounds = new Rect((int) (screenWidth * adjustmentPercentage),
+ return new Rect((int) (screenWidth * adjustmentPercentage),
(int) (screenHeight * adjustmentPercentage),
(int) (screenWidth * (adjustmentPercentage + scale)),
(int) (screenHeight * (adjustmentPercentage + scale)));
- return endBounds;
}
/**
@@ -895,7 +950,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
c -> {
c.onDragPositioningEndThroughStatusBar(relevantDecor.mTaskInfo,
calculateFreeformBounds(ev.getDisplayId(),
- FINAL_FREEFORM_SCALE));
+ DesktopTasksController
+ .DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
});
}
});
@@ -904,7 +960,18 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
@Nullable
private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
- if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) {
+ final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
+ if (focusedDecor == null) {
+ return null;
+ }
+ final boolean splitScreenVisible = mSplitScreenController != null
+ && mSplitScreenController.isSplitScreenVisible();
+ // It's possible that split tasks are visible but neither is focused, such as when there's
+ // a fullscreen translucent window on top of them. In that case, the relevant decor should
+ // just be that translucent focused window.
+ final boolean focusedTaskInSplit = mSplitScreenController != null
+ && mSplitScreenController.isTaskInSplitScreen(focusedDecor.mTaskInfo.taskId);
+ if (splitScreenVisible && focusedTaskInSplit) {
// We can't look at focused task here as only one task will have focus.
DesktopModeWindowDecoration splitTaskDecor = getSplitScreenDecor(ev);
return splitTaskDecor == null ? getFocusedDecor() : splitTaskDecor;
@@ -938,7 +1005,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private DesktopModeWindowDecoration getFocusedDecor() {
final int size = mWindowDecorByTaskId.size();
DesktopModeWindowDecoration focusedDecor = null;
- for (int i = 0; i < size; i++) {
+ // TODO(b/323251951): We need to iterate this in reverse to avoid potentially getting
+ // a decor for a closed task. This is a short term fix while the core issue is addressed,
+ // which involves refactoring the window decor lifecycle to be visibility based.
+ for (int i = size - 1; i >= 0; i--) {
final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
if (decor != null && decor.isFocused()) {
focusedDecor = decor;
@@ -1010,34 +1080,35 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
windowDecoration.createResizeVeil();
- final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback(
- windowDecoration);
+ final DragPositioningCallback dragPositioningCallback;
+ final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.desktop_mode_transition_area_height);
+ if (!DesktopModeStatus.isVeiledResizeEnabled()) {
+ dragPositioningCallback = new FluidResizeTaskPositioner(
+ mTaskOrganizer, mTransitions, windowDecoration, mDisplayController,
+ mDragStartListener, mTransactionFactory, transitionAreaHeight);
+ windowDecoration.setTaskDragResizer(
+ (FluidResizeTaskPositioner) dragPositioningCallback);
+ } else {
+ dragPositioningCallback = new VeiledResizeTaskPositioner(
+ mTaskOrganizer, windowDecoration, mDisplayController,
+ mDragStartListener, mTransitions, transitionAreaHeight);
+ windowDecoration.setTaskDragResizer(
+ (VeiledResizeTaskPositioner) dragPositioningCallback);
+ }
+
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
windowDecoration.setCaptionListeners(
- touchEventListener, touchEventListener, touchEventListener);
+ touchEventListener, touchEventListener, touchEventListener, touchEventListener);
windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
windowDecoration.setDragPositioningCallback(dragPositioningCallback);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT,
- false /* applyStartTransactionOnDraw */);
+ false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */);
incrementEventReceiverTasks(taskInfo.displayId);
}
- private DragPositioningCallback createDragPositioningCallback(
- @NonNull DesktopModeWindowDecoration windowDecoration) {
- final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.desktop_mode_transition_area_height);
- if (!DesktopModeStatus.isVeiledResizeEnabled()) {
- return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration,
- mDisplayController, mDragStartListener, mTransactionFactory,
- transitionAreaHeight);
- } else {
- return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration,
- mDisplayController, mDragStartListener, mTransitions,
- transitionAreaHeight);
- }
- }
private RunningTaskInfo getOtherSplitTask(int taskId) {
@SplitPosition int remainingTaskPosition = mSplitScreenController
@@ -1046,12 +1117,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return mSplitScreenController.getTaskInfo(remainingTaskPosition);
}
- private void closeOtherSplitTask(int taskId) {
- if (isTaskInSplitScreen(taskId)) {
- mTaskOperations.closeTask(getOtherSplitTask(taskId).token);
- }
- }
-
private boolean isTaskInSplitScreen(int taskId) {
return mSplitScreenController != null
&& mSplitScreenController.isTaskInSplitScreen(taskId);
@@ -1066,6 +1131,36 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
pw.println(innerPrefix + "mWindowDecorByTaskId=" + mWindowDecorByTaskId);
}
+ private class DeskopModeOnTaskResizeAnimationListener
+ implements OnTaskResizeAnimationListener {
+ @Override
+ public void onAnimationStart(int taskId, Transaction t, Rect bounds) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ t.apply();
+ return;
+ }
+ decoration.showResizeVeil(t, bounds);
+ decoration.setAnimatingTaskResize(true);
+ }
+
+ @Override
+ public void onBoundsChange(int taskId, Transaction t, Rect bounds) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) return;
+ decoration.updateResizeVeil(t, bounds);
+ }
+
+ @Override
+ public void onAnimationEnd(int taskId) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) return;
+ decoration.hideResizeVeil();
+ decoration.setAnimatingTaskResize(false);
+ }
+ }
+
+
private class DragStartListenerImpl
implements DragPositioningCallbackUtility.DragStartListener {
@Override
@@ -1138,7 +1233,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
}
}
-
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 6ec91e0e28dd..39803e2afd34 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -36,8 +36,6 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.os.IBinder;
-import android.util.Log;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
@@ -46,6 +44,7 @@ import android.view.ViewConfiguration;
import android.widget.ImageButton;
import android.window.WindowContainerTransaction;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
@@ -57,12 +56,13 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.DesktopModeAppControlsWindowDecorationViewHolder;
import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder;
import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationViewHolder;
-import java.util.HashSet;
-import java.util.Set;
+import kotlin.Unit;
+
import java.util.function.Supplier;
/**
@@ -82,6 +82,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private View.OnClickListener mOnCaptionButtonClickListener;
private View.OnTouchListener mOnCaptionTouchListener;
private View.OnLongClickListener mOnCaptionLongClickListener;
+ private View.OnGenericMotionListener mOnCaptionGenericMotionListener;
private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
private DragDetector mDragDetector;
@@ -103,8 +104,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private ExclusionRegionListener mExclusionRegionListener;
- private final Set<IBinder> mTransitionsPausingRelayout = new HashSet<>();
- private int mRelayoutBlock;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
DesktopModeWindowDecoration(
@@ -157,10 +156,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
void setCaptionListeners(
View.OnClickListener onCaptionButtonClickListener,
View.OnTouchListener onCaptionTouchListener,
- View.OnLongClickListener onLongClickListener) {
+ View.OnLongClickListener onLongClickListener,
+ View.OnGenericMotionListener onGenericMotionListener) {
mOnCaptionButtonClickListener = onCaptionButtonClickListener;
mOnCaptionTouchListener = onCaptionTouchListener;
mOnCaptionLongClickListener = onLongClickListener;
+ mOnCaptionGenericMotionListener = onGenericMotionListener;
}
void setExclusionRegionListener(ExclusionRegionListener exclusionRegionListener) {
@@ -178,63 +179,34 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
- // TaskListener callbacks and shell transitions aren't synchronized, so starting a shell
- // transition can trigger an onTaskInfoChanged call that updates the task's SurfaceControl
- // and interferes with the transition animation that is playing at the same time.
- if (mRelayoutBlock > 0) {
- return;
- }
-
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+ // The crop and position of the task should only be set when a task is fluid resizing. In
+ // all other cases, it is expected that the transition handler positions and crops the task
+ // in order to allow the handler time to animate before the task before the final
+ // position and crop are set.
+ final boolean shouldSetTaskPositionAndCrop = !DesktopModeStatus.isVeiledResizeEnabled()
+ && mTaskDragResizer.isResizingOrAnimating();
// Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
// synced with the buffer transaction (that draws the View). Both will be shown on screen
// at the same, whereas applying them independently causes flickering. See b/270202228.
- relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */);
+ relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */,
+ shouldSetTaskPositionAndCrop);
}
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw) {
- final int shadowRadiusID = taskInfo.isFocused
- ? R.dimen.freeform_decor_shadow_focused_thickness
- : R.dimen.freeform_decor_shadow_unfocused_thickness;
- final boolean isFreeform =
- taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
- final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
-
+ boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
if (isHandleMenuActive()) {
mHandleMenu.relayout(startT);
}
+ updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
+ shouldSetTaskPositionAndCrop);
+
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId(
- taskInfo.getWindowingMode());
- mRelayoutParams.reset();
- mRelayoutParams.mRunningTaskInfo = taskInfo;
- mRelayoutParams.mLayoutResId = windowDecorLayoutId;
- mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode());
- mRelayoutParams.mShadowRadiusId = shadowRadiusID;
- mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
- // The configuration used to lay out the window decoration. The system context's config is
- // used when the task density has been overridden to a custom density so that the resources
- // and views of the decoration aren't affected and match the rest of the System UI, if not
- // then just use the task's configuration. A copy is made instead of using the original
- // reference so that the configuration isn't mutated on config changes and diff checks can
- // be made in WindowDecoration#relayout using the pre/post-relayout configuration.
- // See b/301119301.
- // TODO(b/301119301): consider moving the config data needed for diffs to relayout params
- // instead of using a whole Configuration as a parameter.
- final Configuration windowDecorConfig = new Configuration();
- windowDecorConfig.setTo(DesktopTasksController.isDesktopDensityOverrideSet()
- ? mContext.getResources().getConfiguration() // Use system context.
- : mTaskInfo.configuration); // Use task configuration.
- mRelayoutParams.mWindowDecorConfig = windowDecorConfig;
-
- mRelayoutParams.mCornerRadius =
- (int) ScreenDecorationsUtils.getWindowCornerRadius(mContext);
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -259,9 +231,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mOnCaptionTouchListener,
mOnCaptionButtonClickListener,
mOnCaptionLongClickListener,
+ mOnCaptionGenericMotionListener,
mAppName,
- mAppIconBitmap
- );
+ mAppIconBitmap,
+ () -> {
+ if (!isMaximizeMenuActive()) {
+ createMaximizeMenu();
+ }
+ return Unit.INSTANCE;
+ });
} else {
throw new IllegalArgumentException("Unexpected layout resource id");
}
@@ -273,6 +251,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
closeMaximizeMenu();
}
+ final boolean isFreeform =
+ taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+ final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
if (!isDragResizeable) {
if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
// We still want to track caption bar's exclusion region on a non-resizeable task.
@@ -323,6 +304,80 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
}
+ @VisibleForTesting
+ static void updateRelayoutParams(
+ RelayoutParams relayoutParams,
+ Context context,
+ ActivityManager.RunningTaskInfo taskInfo,
+ boolean applyStartTransactionOnDraw,
+ boolean shouldSetTaskPositionAndCrop) {
+ final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
+ relayoutParams.reset();
+ relayoutParams.mRunningTaskInfo = taskInfo;
+ relayoutParams.mLayoutResId = captionLayoutId;
+ relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
+ relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId);
+
+ if (captionLayoutId == R.layout.desktop_mode_app_controls_window_decor
+ && TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
+ // App is requesting to customize the caption bar. Allow input to fall through to the
+ // windows below so that the app can respond to input events on their custom content.
+ relayoutParams.mAllowCaptionInputFallthrough = true;
+ // Report occluding elements as bounding rects to the insets system so that apps can
+ // draw in the empty space in the center:
+ // First, the "app chip" section of the caption bar (+ some extra margins).
+ final RelayoutParams.OccludingCaptionElement appChipElement =
+ new RelayoutParams.OccludingCaptionElement();
+ appChipElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_start;
+ appChipElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.START;
+ relayoutParams.mOccludingCaptionElements.add(appChipElement);
+ // Then, the right-aligned section (drag space, maximize and close buttons).
+ final RelayoutParams.OccludingCaptionElement controlsElement =
+ new RelayoutParams.OccludingCaptionElement();
+ controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_end;
+ controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END;
+ relayoutParams.mOccludingCaptionElements.add(controlsElement);
+ }
+ if (DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ taskInfo.isFocused)) {
+ relayoutParams.mShadowRadiusId = taskInfo.isFocused
+ ? R.dimen.freeform_decor_shadow_focused_thickness
+ : R.dimen.freeform_decor_shadow_unfocused_thickness;
+ }
+ relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
+ relayoutParams.mSetTaskPositionAndCrop = shouldSetTaskPositionAndCrop;
+ // The configuration used to lay out the window decoration. The system context's config is
+ // used when the task density has been overridden to a custom density so that the resources
+ // and views of the decoration aren't affected and match the rest of the System UI, if not
+ // then just use the task's configuration. A copy is made instead of using the original
+ // reference so that the configuration isn't mutated on config changes and diff checks can
+ // be made in WindowDecoration#relayout using the pre/post-relayout configuration.
+ // See b/301119301.
+ // TODO(b/301119301): consider moving the config data needed for diffs to relayout params
+ // instead of using a whole Configuration as a parameter.
+ final Configuration windowDecorConfig = new Configuration();
+ windowDecorConfig.setTo(DesktopTasksController.isDesktopDensityOverrideSet()
+ ? context.getResources().getConfiguration() // Use system context.
+ : taskInfo.configuration); // Use task configuration.
+ relayoutParams.mWindowDecorConfig = windowDecorConfig;
+
+ if (DesktopModeStatus.useRoundedCorners()) {
+ relayoutParams.mCornerRadius =
+ (int) ScreenDecorationsUtils.getWindowCornerRadius(context);
+ }
+ }
+
+ /**
+ * If task has focused window decor, return the caption id of the fullscreen caption size
+ * resource. Otherwise, return ID_NULL and caption width be set to task width.
+ */
+ private static int getCaptionWidthId(int layoutResId) {
+ if (layoutResId == R.layout.desktop_mode_focused_window_decor) {
+ return R.dimen.desktop_mode_fullscreen_decor_caption_width;
+ }
+ return Resources.ID_NULL;
+ }
+
+
private PointF calculateMaximizeMenuPosition() {
final PointF position = new PointF();
final Resources resources = mContext.getResources();
@@ -364,24 +419,21 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return mHandleMenu != null;
}
+ boolean isHandlingDragResize() {
+ return mDragResizeListener != null && mDragResizeListener.isHandlingDragResize();
+ }
+
private void loadAppInfo() {
- String packageName = mTaskInfo.realActivity.getPackageName();
PackageManager pm = mContext.getApplicationContext().getPackageManager();
- try {
- final IconProvider provider = new IconProvider(mContext);
- mAppIconDrawable = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity,
- PackageManager.ComponentInfoFlags.of(0)));
- final Resources resources = mContext.getResources();
- final BaseIconFactory factory = new BaseIconFactory(mContext,
- resources.getDisplayMetrics().densityDpi,
- resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius));
- mAppIconBitmap = factory.createScaledBitmap(mAppIconDrawable, MODE_DEFAULT);
- final ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
- PackageManager.ApplicationInfoFlags.of(0));
- mAppName = pm.getApplicationLabel(applicationInfo);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Package not found: " + packageName, e);
- }
+ final IconProvider provider = new IconProvider(mContext);
+ mAppIconDrawable = provider.getIcon(mTaskInfo.topActivityInfo);
+ final Resources resources = mContext.getResources();
+ final BaseIconFactory factory = new BaseIconFactory(mContext,
+ resources.getDisplayMetrics().densityDpi,
+ resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius));
+ mAppIconBitmap = factory.createScaledBitmap(mAppIconDrawable, MODE_DEFAULT);
+ final ApplicationInfo applicationInfo = mTaskInfo.topActivityInfo.applicationInfo;
+ mAppName = pm.getApplicationLabel(applicationInfo);
}
private void closeDragResizeListener() {
@@ -443,11 +495,72 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
/**
+ * Determine valid drag area for this task based on elements in the app chip.
+ */
+ @Override
+ Rect calculateValidDragArea() {
+ final int appTextWidth = ((DesktopModeAppControlsWindowDecorationViewHolder)
+ mWindowDecorViewHolder).getAppNameTextWidth();
+ final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.desktop_mode_app_details_width_minus_text) + appTextWidth;
+ final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.freeform_required_visible_empty_space_in_header);
+ final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.desktop_mode_right_edge_buttons_width);
+ final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ final int displayWidth = layout.width();
+ final Rect stableBounds = new Rect();
+ layout.getStableBounds(stableBounds);
+ return new Rect(
+ determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth),
+ stableBounds.top,
+ determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth, displayWidth),
+ determineMaxY(requiredEmptySpace, stableBounds));
+ }
+
+
+ /**
+ * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth) {
+ // Do not let apps with < 48dp empty header space go off the left edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return 0;
+ }
+ return -taskWidth + requiredEmptySpace + rightButtonsWidth;
+ }
+
+ /**
+ * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth, int displayWidth) {
+ // Do not let apps with < 48dp empty header space go off the right edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return displayWidth - taskWidth;
+ }
+ return displayWidth - requiredEmptySpace - leftButtonsWidth;
+ }
+
+ /**
+ * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
+ return stableBounds.bottom - requiredEmptySpace;
+ }
+
+
+ /**
* Create and display maximize menu window
*/
void createMaximizeMenu() {
mMaximizeMenu = new MaximizeMenu(mSyncQueue, mRootTaskDisplayAreaOrganizer,
- mDisplayController, mTaskInfo, mOnCaptionButtonClickListener, mContext,
+ mDisplayController, mTaskInfo, mOnCaptionButtonClickListener,
+ mOnCaptionGenericMotionListener, mOnCaptionTouchListener, mContext,
calculateMaximizeMenuPosition(), mSurfaceControlTransactionSupplier);
mMaximizeMenu.show();
}
@@ -475,7 +588,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
.setOnClickListener(mOnCaptionButtonClickListener)
.setOnTouchListener(mOnCaptionTouchListener)
.setLayoutId(mRelayoutParams.mLayoutResId)
- .setCaptionPosition(mRelayoutParams.mCaptionX, mRelayoutParams.mCaptionY)
.setWindowingButtonsVisible(DesktopModeStatus.isEnabled())
.setCaptionHeight(mResult.mCaptionHeight)
.build();
@@ -530,8 +642,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
void closeMaximizeMenuIfNeeded(MotionEvent ev) {
if (!isMaximizeMenuActive()) return;
- final PointF inputPoint = offsetCaptionLocation(ev);
- if (!mMaximizeMenu.isValidMenuInput(inputPoint)) {
+ if (!mMaximizeMenu.isValidMenuInput(ev)) {
closeMaximizeMenu();
}
}
@@ -552,35 +663,46 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId);
if (taskInfo == null) return result;
final Point positionInParent = taskInfo.positionInParent;
- result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
result.offset(-positionInParent.x, -positionInParent.y);
return result;
}
/**
- * Determine if a passed MotionEvent is in a view in caption
+ * Checks if motion event occurs in the caption handle area of a focused caption (the caption on
+ * a task in fullscreen or in multi-windowing mode). This should be used in cases where
+ * onTouchListener will not work (i.e. when caption is in status bar area).
*
* @param ev the {@link MotionEvent} to check
- * @param layoutId the id of the view
- * @return {@code true} if event is inside the specified view, {@code false} if not
+ * @return {@code true} if event is inside caption handle view, {@code false} if not
*/
- private boolean checkEventInCaptionView(MotionEvent ev, int layoutId) {
- if (mResult.mRootView == null) return false;
- final PointF inputPoint = offsetCaptionLocation(ev);
- final View view = mResult.mRootView.findViewById(layoutId);
- return view != null && pointInView(view, inputPoint.x, inputPoint.y);
- }
+ boolean checkTouchEventInFocusedCaptionHandle(MotionEvent ev) {
+ if (isHandleMenuActive() || !(mWindowDecorViewHolder
+ instanceof DesktopModeFocusedWindowDecorationViewHolder)) {
+ return false;
+ }
- boolean checkTouchEventInHandle(MotionEvent ev) {
- if (isHandleMenuActive()) return false;
- return checkEventInCaptionView(ev, R.id.caption_handle);
+ return checkTouchEventInCaption(ev);
}
/**
- * Returns true if motion event is within the caption's root view's bounds.
+ * Checks if touch event occurs in caption.
+ *
+ * @param ev the {@link MotionEvent} to check
+ * @return {@code true} if event is inside caption view, {@code false} if not
*/
boolean checkTouchEventInCaption(MotionEvent ev) {
- return checkEventInCaptionView(ev, getCaptionViewId());
+ final PointF inputPoint = offsetCaptionLocation(ev);
+ return inputPoint.x >= mResult.mCaptionX
+ && inputPoint.x <= mResult.mCaptionX + mResult.mCaptionWidth
+ && inputPoint.y >= 0
+ && inputPoint.y <= mResult.mCaptionHeight;
+ }
+
+ /**
+ * Checks whether the touch event falls inside the customizable caption region.
+ */
+ boolean checkTouchEventInCustomizableRegion(MotionEvent ev) {
+ return mResult.mCustomizableCaptionRegion.contains((int) ev.getRawX(), (int) ev.getRawY());
}
/**
@@ -593,24 +715,19 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
void checkClickEvent(MotionEvent ev) {
if (mResult.mRootView == null) return;
if (!isHandleMenuActive()) {
+ // Click if point in caption handle view
final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
final View handle = caption.findViewById(R.id.caption_handle);
- clickIfPointInView(new PointF(ev.getX(), ev.getY()), handle);
+ if (checkTouchEventInFocusedCaptionHandle(ev)) {
+ mOnCaptionButtonClickListener.onClick(handle);
+ }
} else {
mHandleMenu.checkClickEvent(ev);
closeHandleMenuIfNeeded(ev);
}
}
- private boolean clickIfPointInView(PointF inputPoint, View v) {
- if (pointInView(v, inputPoint.x, inputPoint.y)) {
- mOnCaptionButtonClickListener.onClick(v);
- return true;
- }
- return false;
- }
-
- boolean pointInView(View v, float x, float y) {
+ private boolean pointInView(View v, float x, float y) {
return v != null && v.getLeft() <= x && v.getRight() >= x
&& v.getTop() <= y && v.getBottom() >= y;
}
@@ -624,7 +741,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
super.close();
}
- private int getDesktopModeWindowDecorLayoutId(@WindowingMode int windowingMode) {
+ private static int getDesktopModeWindowDecorLayoutId(@WindowingMode int windowingMode) {
return windowingMode == WINDOWING_MODE_FREEFORM
? R.layout.desktop_mode_app_controls_window_decor
: R.layout.desktop_mode_focused_window_decor;
@@ -658,18 +775,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return exclusionRegion;
}
- /**
- * If transition exists in mTransitionsPausingRelayout, remove the transition and decrement
- * mRelayoutBlock
- */
- void removeTransitionPausingRelayout(IBinder transition) {
- if (mTransitionsPausingRelayout.remove(transition)) {
- mRelayoutBlock--;
- }
- }
-
@Override
int getCaptionHeightId(@WindowingMode int windowingMode) {
+ return getCaptionHeightIdStatic(windowingMode);
+ }
+
+ private static int getCaptionHeightIdStatic(@WindowingMode int windowingMode) {
return windowingMode == WINDOWING_MODE_FULLSCREEN
? R.dimen.desktop_mode_fullscreen_decor_caption_height
: R.dimen.desktop_mode_freeform_decor_caption_height;
@@ -684,35 +795,26 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return R.id.desktop_mode_caption;
}
- /**
- * Add transition to mTransitionsPausingRelayout
- */
- void addTransitionPausingRelayout(IBinder transition) {
- mTransitionsPausingRelayout.add(transition);
+ void setAnimatingTaskResize(boolean animatingTaskResize) {
+ if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_focused_window_decor) return;
+ ((DesktopModeAppControlsWindowDecorationViewHolder) mWindowDecorViewHolder)
+ .setAnimatingTaskResize(animatingTaskResize);
}
- /**
- * If two transitions merge and the merged transition is in mTransitionsPausingRelayout,
- * remove the merged transition from the set and add the transition it was merged into.
- */
- public void mergeTransitionPausingRelayout(IBinder merged, IBinder playing) {
- if (mTransitionsPausingRelayout.remove(merged)) {
- mTransitionsPausingRelayout.add(playing);
- }
+ void onMaximizeWindowHoverExit() {
+ ((DesktopModeAppControlsWindowDecorationViewHolder) mWindowDecorViewHolder)
+ .onMaximizeWindowHoverExit();
}
- /**
- * Increase mRelayoutBlock, stopping relayout if mRelayoutBlock is now greater than 0.
- */
- public void incrementRelayoutBlock() {
- mRelayoutBlock++;
+ void onMaximizeWindowHoverEnter() {
+ ((DesktopModeAppControlsWindowDecorationViewHolder) mWindowDecorViewHolder)
+ .onMaximizeWindowHoverEnter();
}
@Override
public String toString() {
return "{"
+ "mPositionInParent=" + mPositionInParent + ", "
- + "mRelayoutBlock=" + mRelayoutBlock + ", "
+ "taskId=" + mTaskInfo.taskId + ", "
+ "windowingMode=" + windowingModeToString(mTaskInfo.getWindowingMode()) + ", "
+ "isFocused=" + isFocused()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index cb0a6c733fe3..5afbd54088d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -26,9 +26,7 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
-import android.window.WindowContainerTransaction;
-import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
/**
@@ -130,8 +128,7 @@ public class DragPositioningCallbackUtility {
Rect taskBoundsAtDragStart, PointF repositionStartPoint, SurfaceControl.Transaction t,
float x, float y) {
updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint, x, y);
- t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left,
- repositionTaskBounds.top);
+ t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left, repositionTaskBounds.top);
}
private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
@@ -162,31 +159,30 @@ public class DragPositioningCallbackUtility {
/**
* Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
- * the bounds are outside of the stable bounds, they are shifted to place task at the top of the
- * stable bounds.
+ * the bounds are outside of the valid drag area, the task is shifted back onto the edge of the
+ * valid drag area.
*/
- static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds,
- PointF repositionStartPoint, float x, float y) {
+ static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+ PointF repositionStartPoint, float x, float y, Rect validDragArea) {
updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
x, y);
-
- // If task is outside of stable bounds (in the status bar area), shift the task down.
- if (stableBounds.top > repositionTaskBounds.top) {
- final int yShift = stableBounds.top - repositionTaskBounds.top;
- repositionTaskBounds.offset(0, yShift);
- }
+ snapTaskBoundsIfNecessary(repositionTaskBounds, validDragArea);
}
- /**
- * Apply a bounds change to a task.
- * @param windowDecoration decor of task we are changing bounds for
- * @param taskBounds new bounds of this task
- * @param taskOrganizer applies the provided WindowContainerTransaction
- */
- static void applyTaskBoundsChange(WindowContainerTransaction wct,
- WindowDecoration windowDecoration, Rect taskBounds, ShellTaskOrganizer taskOrganizer) {
- wct.setBounds(windowDecoration.mTaskInfo.token, taskBounds);
- taskOrganizer.applyTransaction(wct);
+ private static void snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
+ // If we were never supplied a valid drag area, do not restrict movement.
+ // Otherwise, we restrict deltas to keep task position inside the Rect.
+ if (validDragArea.width() == 0) return;
+ if (repositionTaskBounds.left < validDragArea.left) {
+ repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0);
+ } else if (repositionTaskBounds.left > validDragArea.right) {
+ repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0);
+ }
+ if (repositionTaskBounds.top < validDragArea.top) {
+ repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top);
+ } else if (repositionTaskBounds.top > validDragArea.bottom) {
+ repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top);
+ }
}
private static float getMinWidth(DisplayController displayController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 8511a21d4294..e83e5d1ef5a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -49,6 +49,7 @@ import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManagerGlobal;
+import android.window.InputTransferToken;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
@@ -74,7 +75,7 @@ class DragResizeInputListener implements AutoCloseable {
private final IBinder mClientToken;
- private final IBinder mFocusGrantToken;
+ private final InputTransferToken mInputTransferToken;
private final SurfaceControl mDecorationSurface;
private final InputChannel mInputChannel;
private final TaskResizeInputEventReceiver mInputEventReceiver;
@@ -121,7 +122,7 @@ class DragResizeInputListener implements AutoCloseable {
mDecorationSurface = decorationSurface;
mDisplayController = displayController;
mClientToken = new Binder();
- mFocusGrantToken = new Binder();
+ mInputTransferToken = new InputTransferToken();
mInputChannel = new InputChannel();
try {
mWindowSession.grantInputChannel(
@@ -134,7 +135,7 @@ class DragResizeInputListener implements AutoCloseable {
INPUT_FEATURE_SPY,
TYPE_APPLICATION,
null /* windowToken */,
- mFocusGrantToken,
+ mInputTransferToken,
TAG + " of " + decorationSurface.toString(),
mInputChannel);
} catch (RemoteException e) {
@@ -169,7 +170,7 @@ class DragResizeInputListener implements AutoCloseable {
INPUT_FEATURE_NO_INPUT_CHANNEL,
TYPE_INPUT_CONSUMER,
null /* windowToken */,
- mFocusGrantToken,
+ mInputTransferToken,
"TaskInputSink of " + decorationSurface,
mSinkInputChannel);
} catch (RemoteException e) {
@@ -320,6 +321,10 @@ class DragResizeInputListener implements AutoCloseable {
}
}
+ boolean isHandlingDragResize() {
+ return mInputEventReceiver.isHandlingEvents();
+ }
+
@Override
public void close() {
mInputEventReceiver.dispose();
@@ -349,6 +354,7 @@ class DragResizeInputListener implements AutoCloseable {
private boolean mShouldHandleEvents;
private int mLastCursorType = PointerIcon.TYPE_DEFAULT;
private Rect mDragStartTaskBounds;
+ private final Rect mTmpRect = new Rect();
private TaskResizeInputEventReceiver(
InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -386,6 +392,10 @@ class DragResizeInputListener implements AutoCloseable {
finishInputEvent(inputEvent, handleInputEvent(inputEvent));
}
+ boolean isHandlingEvents() {
+ return mShouldHandleEvents;
+ }
+
private boolean handleInputEvent(InputEvent inputEvent) {
if (!(inputEvent instanceof MotionEvent)) {
return false;
@@ -409,7 +419,6 @@ class DragResizeInputListener implements AutoCloseable {
mShouldHandleEvents = isInResizeHandleBounds(x, y);
}
if (mShouldHandleEvents) {
- mInputManager.pilferPointers(mInputChannel.getToken());
mDragPointerId = e.getPointerId(0);
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
@@ -427,6 +436,7 @@ class DragResizeInputListener implements AutoCloseable {
if (!mShouldHandleEvents) {
break;
}
+ mInputManager.pilferPointers(mInputChannel.getToken());
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
float rawX = e.getRawX(dragPointerIndex);
float rawY = e.getRawY(dragPointerIndex);
@@ -437,6 +447,7 @@ class DragResizeInputListener implements AutoCloseable {
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
+ mInputManager.pilferPointers(mInputChannel.getToken());
if (mShouldHandleEvents) {
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
final Rect taskBounds = mCallback.onDragPositioningEnd(
@@ -468,14 +479,15 @@ class DragResizeInputListener implements AutoCloseable {
}
private void updateInputSinkRegionForDrag(Rect taskBounds) {
+ mTmpRect.set(taskBounds);
final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId);
final Region dragTouchRegion = new Region(-taskBounds.left,
-taskBounds.top,
-taskBounds.left + layout.width(),
-taskBounds.top + layout.height());
// Remove the localized task bounds from the touch region.
- taskBounds.offsetTo(0, 0);
- dragTouchRegion.op(taskBounds, Region.Op.DIFFERENCE);
+ mTmpRect.offsetTo(0, 0);
+ dragTouchRegion.op(mTmpRect, Region.Op.DIFFERENCE);
updateSinkInputChannel(dragTouchRegion);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 3a1ea0e201b2..6bfc7cdcb33e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -16,23 +16,42 @@
package com.android.wm.shell.windowdecor;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
import android.graphics.PointF;
import android.graphics.Rect;
+import android.os.IBinder;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.transition.Transitions;
import java.util.function.Supplier;
/**
* A task positioner that resizes/relocates task contents as it is dragged.
* Utilizes {@link DragPositioningCallbackUtility} to determine new task bounds.
+ *
+ * This positioner applies the final bounds after a resize or drag using a shell transition in order
+ * to utilize the startAnimation callback to set the final task position and crop. In most cases,
+ * the transition will be aborted since the final bounds are usually the same bounds set in the
+ * final {@link #onDragPositioningMove} call. In this case, the cropping and positioning would be
+ * set by {@link WindowDecoration#relayout} due to the final bounds change; however, it is important
+ * that we send the final shell transition since we still utilize the {@link #onTransitionConsumed}
+ * callback.
*/
-class FluidResizeTaskPositioner implements DragPositioningCallback {
+class FluidResizeTaskPositioner implements DragPositioningCallback,
+ TaskDragResizer, Transitions.TransitionHandler {
private final ShellTaskOrganizer mTaskOrganizer;
+ private final Transitions mTransitions;
private final WindowDecoration mWindowDecoration;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private DisplayController mDisplayController;
@@ -45,21 +64,28 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
// finalize the bounds there using WCT#setBounds
private final int mDisallowedAreaForEndBoundsHeight;
private boolean mHasDragResized;
+ private boolean mIsResizingOrAnimatingResize;
private int mCtrlType;
+ private IBinder mDragResizeEndTransition;
@Surface.Rotation private int mRotation;
- FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
- DisplayController displayController, int disallowedAreaForEndBoundsHeight) {
- this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {},
- SurfaceControl.Transaction::new, disallowedAreaForEndBoundsHeight);
+ FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, Transitions transitions,
+ WindowDecoration windowDecoration, DisplayController displayController,
+ int disallowedAreaForEndBoundsHeight) {
+ this(taskOrganizer, transitions, windowDecoration, displayController,
+ dragStartListener -> {}, SurfaceControl.Transaction::new,
+ disallowedAreaForEndBoundsHeight);
}
- FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
+ FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
+ Transitions transitions,
+ WindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
Supplier<SurfaceControl.Transaction> supplier,
int disallowedAreaForEndBoundsHeight) {
mTaskOrganizer = taskOrganizer;
+ mTransitions = transitions;
mWindowDecoration = windowDecoration;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
@@ -103,9 +129,10 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
// This is the first bounds change since drag resize operation started.
wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */);
}
- DragPositioningCallbackUtility.applyTaskBoundsChange(wct, mWindowDecoration,
- mRepositionTaskBounds, mTaskOrganizer);
+ wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
+ mTaskOrganizer.applyTransaction(wct);
mHasDragResized = true;
+ mIsResizingOrAnimatingResize = true;
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mWindowDecoration,
@@ -129,16 +156,17 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
mWindowDecoration)) {
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
}
- mTaskOrganizer.applyTransaction(wct);
+ mDragResizeEndTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
} else if (mCtrlType == CTRL_TYPE_UNDEFINED
&& DragPositioningCallbackUtility.isBelowDisallowedArea(
mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
y)) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
+ mWindowDecoration.calculateValidDragArea());
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
- mTaskOrganizer.applyTransaction(wct);
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
mTaskBoundsAtDragStart.setEmpty();
@@ -153,4 +181,51 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
|| (mCtrlType & CTRL_TYPE_LEFT) != 0 || (mCtrlType & CTRL_TYPE_RIGHT) != 0;
}
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ for (TransitionInfo.Change change: info.getChanges()) {
+ final SurfaceControl sc = change.getLeash();
+ final Rect endBounds = change.getEndAbsBounds();
+ startTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endBounds.left, endBounds.top);
+ finishTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endBounds.left, endBounds.top);
+ }
+
+ startTransaction.apply();
+ if (transition.equals(mDragResizeEndTransition)) {
+ mIsResizingOrAnimatingResize = false;
+ mDragResizeEndTransition = null;
+ }
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
+
+ /**
+ * We should never reach this as this handler's transitions are only started from shell
+ * explicitly.
+ */
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishTransaction) {
+ if (transition.equals(mDragResizeEndTransition)) {
+ mIsResizingOrAnimatingResize = false;
+ mDragResizeEndTransition = null;
+ }
+ }
+
+ @Override
+ public boolean isResizingOrAnimating() {
+ return mIsResizingOrAnimatingResize;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index 652a2ed39c67..b37dd0d6fd2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -64,8 +64,6 @@ class HandleMenu {
private final View.OnTouchListener mOnTouchListener;
private final RunningTaskInfo mTaskInfo;
private final int mLayoutResId;
- private final int mCaptionX;
- private final int mCaptionY;
private int mMarginMenuTop;
private int mMarginMenuStart;
private int mMenuHeight;
@@ -74,16 +72,13 @@ class HandleMenu {
private HandleMenuAnimator mHandleMenuAnimator;
- HandleMenu(WindowDecoration parentDecor, int layoutResId, int captionX, int captionY,
- View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
- Bitmap appIcon, CharSequence appName, boolean shouldShowWindowingPill,
- int captionHeight) {
+ HandleMenu(WindowDecoration parentDecor, int layoutResId, View.OnClickListener onClickListener,
+ View.OnTouchListener onTouchListener, Bitmap appIcon, CharSequence appName,
+ boolean shouldShowWindowingPill, int captionHeight) {
mParentDecor = parentDecor;
mContext = mParentDecor.mDecorWindowContext;
mTaskInfo = mParentDecor.mTaskInfo;
mLayoutResId = layoutResId;
- mCaptionX = captionX;
- mCaptionY = captionY;
mOnClickListener = onClickListener;
mOnTouchListener = onTouchListener;
mAppIconBitmap = appIcon;
@@ -225,12 +220,12 @@ class HandleMenu {
if (mLayoutResId
== R.layout.desktop_mode_app_controls_window_decor) {
// Align the handle menu to the left of the caption.
- menuX = mCaptionX + mMarginMenuStart;
- menuY = mCaptionY + mMarginMenuTop;
+ menuX = mMarginMenuStart;
+ menuY = mMarginMenuTop;
} else {
// Position the handle menu at the center of the caption.
- menuX = mCaptionX + (captionWidth / 2) - (mMenuWidth / 2);
- menuY = mCaptionY + mMarginMenuStart;
+ menuX = (captionWidth / 2) - (mMenuWidth / 2);
+ menuY = mMarginMenuStart;
}
// Handle Menu position setup.
@@ -346,8 +341,6 @@ class HandleMenu {
private View.OnClickListener mOnClickListener;
private View.OnTouchListener mOnTouchListener;
private int mLayoutId;
- private int mCaptionX;
- private int mCaptionY;
private boolean mShowWindowingPill;
private int mCaptionHeight;
@@ -381,12 +374,6 @@ class HandleMenu {
return this;
}
- Builder setCaptionPosition(int captionX, int captionY) {
- mCaptionX = captionX;
- mCaptionY = captionY;
- return this;
- }
-
Builder setWindowingButtonsVisible(boolean windowingButtonsVisible) {
mShowWindowingPill = windowingButtonsVisible;
return this;
@@ -398,8 +385,8 @@ class HandleMenu {
}
HandleMenu build() {
- return new HandleMenu(mParent, mLayoutId, mCaptionX, mCaptionY, mOnClickListener,
- mOnTouchListener, mAppIcon, mName, mShowWindowingPill, mCaptionHeight);
+ return new HandleMenu(mParent, mLayoutId, mOnClickListener, mOnTouchListener,
+ mAppIcon, mName, mShowWindowingPill, mCaptionHeight);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
new file mode 100644
index 000000000000..b2f8cfdbfb7a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
+import android.content.Context
+import android.content.res.ColorStateList
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.ImageButton
+import android.widget.ProgressBar
+import androidx.core.animation.doOnEnd
+import androidx.core.animation.doOnStart
+import androidx.core.content.ContextCompat
+import com.android.wm.shell.R
+
+private const val OPEN_MAXIMIZE_MENU_DELAY_ON_HOVER_MS = 350
+private const val MAX_DRAWABLE_ALPHA = 255
+
+class MaximizeButtonView(
+ context: Context,
+ attrs: AttributeSet
+) : FrameLayout(context, attrs) {
+ lateinit var onHoverAnimationFinishedListener: () -> Unit
+ private val hoverProgressAnimatorSet = AnimatorSet()
+ var hoverDisabled = false
+
+ private val progressBar: ProgressBar
+ private val maximizeWindow: ImageButton
+
+ init {
+ LayoutInflater.from(context).inflate(R.layout.maximize_menu_button, this, true)
+
+ progressBar = requireViewById(R.id.progress_bar)
+ maximizeWindow = requireViewById(R.id.maximize_window)
+ }
+
+ fun startHoverAnimation() {
+ if (hoverDisabled) return
+ if (hoverProgressAnimatorSet.isRunning) {
+ cancelHoverAnimation()
+ }
+
+ maximizeWindow.background.alpha = 0
+
+ hoverProgressAnimatorSet.playSequentially(
+ ValueAnimator.ofInt(0, MAX_DRAWABLE_ALPHA)
+ .setDuration(50)
+ .apply {
+ addUpdateListener {
+ maximizeWindow.background.alpha = animatedValue as Int
+ }
+ },
+ ObjectAnimator.ofInt(progressBar, "progress", 100)
+ .setDuration(OPEN_MAXIMIZE_MENU_DELAY_ON_HOVER_MS.toLong())
+ .apply {
+ doOnStart {
+ progressBar.setProgress(0, false)
+ progressBar.visibility = View.VISIBLE
+ }
+ doOnEnd {
+ progressBar.visibility = View.INVISIBLE
+ onHoverAnimationFinishedListener()
+ }
+ }
+ )
+ hoverProgressAnimatorSet.start()
+ }
+
+ fun cancelHoverAnimation() {
+ hoverProgressAnimatorSet.removeAllListeners()
+ hoverProgressAnimatorSet.cancel()
+ progressBar.visibility = View.INVISIBLE
+ }
+
+ fun setAnimationTints(darkMode: Boolean) {
+ if (darkMode) {
+ progressBar.progressTintList = ColorStateList.valueOf(
+ resources.getColor(R.color.desktop_mode_maximize_menu_progress_dark))
+ maximizeWindow.background?.setTintList(ContextCompat.getColorStateList(context,
+ R.color.desktop_mode_caption_button_color_selector_dark))
+ } else {
+ progressBar.progressTintList = ColorStateList.valueOf(
+ resources.getColor(R.color.desktop_mode_maximize_menu_progress_light))
+ maximizeWindow.background?.setTintList(ContextCompat.getColorStateList(context,
+ R.color.desktop_mode_caption_button_color_selector_light))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 921708faab16..b82f7ca47ef3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -16,17 +16,20 @@
package com.android.wm.shell.windowdecor
+import android.annotation.IdRes
import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.content.res.Resources
import android.graphics.PixelFormat
import android.graphics.PointF
import android.view.LayoutInflater
+import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.SurfaceControlViewHost
-import android.view.View
import android.view.View.OnClickListener
+import android.view.View.OnGenericMotionListener
+import android.view.View.OnTouchListener
import android.view.WindowManager
import android.view.WindowlessWindowManager
import android.widget.Button
@@ -49,6 +52,8 @@ class MaximizeMenu(
private val displayController: DisplayController,
private val taskInfo: RunningTaskInfo,
private val onClickListener: OnClickListener,
+ private val onGenericMotionListener: OnGenericMotionListener,
+ private val onTouchListener: OnTouchListener,
private val decorWindowContext: Context,
private val menuPosition: PointF,
private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }
@@ -62,6 +67,8 @@ class MaximizeMenu(
private val cornerRadius = loadDimensionPixelSize(
R.dimen.desktop_mode_maximize_menu_corner_radius
).toFloat()
+ private val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width)
+ private val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
/** Position the menu relative to the caption's position. */
fun positionMenu(position: PointF, t: Transaction) {
@@ -95,8 +102,6 @@ class MaximizeMenu(
.setName("Maximize Menu")
.setContainerLayer()
.build()
- val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width)
- val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height)
val lp = WindowManager.LayoutParams(
menuWidth,
menuHeight,
@@ -142,15 +147,26 @@ class MaximizeMenu(
private fun setupMaximizeMenu() {
val maximizeMenuView = maximizeMenu?.mWindowViewHost?.view ?: return
- maximizeMenuView.requireViewById<Button>(
+ maximizeMenuView.setOnGenericMotionListener(onGenericMotionListener)
+ maximizeMenuView.setOnTouchListener(onTouchListener)
+
+ val maximizeButton = maximizeMenuView.requireViewById<Button>(
R.id.maximize_menu_maximize_button
- ).setOnClickListener(onClickListener)
- maximizeMenuView.requireViewById<Button>(
+ )
+ maximizeButton.setOnClickListener(onClickListener)
+ maximizeButton.setOnGenericMotionListener(onGenericMotionListener)
+
+ val snapRightButton = maximizeMenuView.requireViewById<Button>(
R.id.maximize_menu_snap_right_button
- ).setOnClickListener(onClickListener)
- maximizeMenuView.requireViewById<Button>(
+ )
+ snapRightButton.setOnClickListener(onClickListener)
+ snapRightButton.setOnGenericMotionListener(onGenericMotionListener)
+
+ val snapLeftButton = maximizeMenuView.requireViewById<Button>(
R.id.maximize_menu_snap_left_button
- ).setOnClickListener(onClickListener)
+ )
+ snapLeftButton.setOnClickListener(onClickListener)
+ snapLeftButton.setOnGenericMotionListener(onGenericMotionListener)
}
/**
@@ -160,14 +176,11 @@ class MaximizeMenu(
*
* @param inputPoint the input to compare against.
*/
- fun isValidMenuInput(inputPoint: PointF): Boolean {
- val menuView = maximizeMenu?.mWindowViewHost?.view ?: return true
- return !viewsLaidOut() || pointInView(menuView, inputPoint.x - menuPosition.x,
- inputPoint.y - menuPosition.y)
- }
-
- private fun pointInView(v: View, x: Float, y: Float): Boolean {
- return v.left <= x && v.right >= x && v.top <= y && v.bottom >= y
+ fun isValidMenuInput(ev: MotionEvent): Boolean {
+ val x = ev.rawX
+ val y = ev.rawY
+ return !viewsLaidOut() || (menuPosition.x <= x && menuPosition.x + menuWidth >= x &&
+ menuPosition.y <= y && menuPosition.y + menuHeight >= y)
}
/**
@@ -176,4 +189,12 @@ class MaximizeMenu(
private fun viewsLaidOut(): Boolean {
return maximizeMenu?.mWindowViewHost?.view?.isLaidOut ?: false
}
+
+ companion object {
+ fun isMaximizeMenuView(@IdRes viewId: Int): Boolean {
+ return viewId == R.id.maximize_menu || viewId == R.id.maximize_menu_maximize_button ||
+ viewId == R.id.maximize_menu_snap_left_button ||
+ viewId == R.id.maximize_menu_snap_right_button
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskResizeAnimationListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskResizeAnimationListener.kt
new file mode 100644
index 000000000000..09c62bfc9da2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskResizeAnimationListener.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.graphics.Rect
+import android.view.SurfaceControl
+
+import com.android.wm.shell.transition.Transitions.TransitionHandler
+/**
+ * Listener that allows implementations of [TransitionHandler] to notify when an
+ * animation that is resizing a task is starting, updating, and finishing the animation.
+ */
+interface OnTaskResizeAnimationListener {
+ /**
+ * Notifies that a transition animation is about to be started with the given bounds.
+ */
+ fun onAnimationStart(taskId: Int, t: SurfaceControl.Transaction, bounds: Rect)
+
+ /**
+ * Notifies that a transition animation is expanding or shrinking the task to the given bounds.
+ */
+ fun onBoundsChange(taskId: Int, t: SurfaceControl.Transaction, bounds: Rect)
+
+ /**
+ * Notifies that a transition animation is about to be finished.
+ */
+ fun onAnimationEnd(taskId: Int)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index 368231e2d7f0..b0d3b5090ef0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -125,6 +125,7 @@ public class ResizeVeil {
relayout(taskBounds, t);
if (fadeIn) {
+ cancelAnimation();
mVeilAnimator = new ValueAnimator();
mVeilAnimator.setFloatValues(0f, 1f);
mVeilAnimator.setDuration(RESIZE_ALPHA_DURATION);
@@ -210,15 +211,16 @@ public class ResizeVeil {
* Animate veil's alpha to 0, fading it out.
*/
public void hideVeil() {
- final ValueAnimator animator = new ValueAnimator();
- animator.setFloatValues(1, 0);
- animator.setDuration(RESIZE_ALPHA_DURATION);
- animator.addUpdateListener(animation -> {
+ cancelAnimation();
+ mVeilAnimator = new ValueAnimator();
+ mVeilAnimator.setFloatValues(1, 0);
+ mVeilAnimator.setDuration(RESIZE_ALPHA_DURATION);
+ mVeilAnimator.addUpdateListener(animation -> {
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
- t.setAlpha(mVeilSurface, 1 - animator.getAnimatedFraction());
+ t.setAlpha(mVeilSurface, 1 - mVeilAnimator.getAnimatedFraction());
t.apply();
});
- animator.addListener(new AnimatorListenerAdapter() {
+ mVeilAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
@@ -226,7 +228,7 @@ public class ResizeVeil {
t.apply();
}
});
- animator.start();
+ mVeilAnimator.start();
}
@ColorRes
@@ -240,10 +242,20 @@ public class ResizeVeil {
}
}
+ private void cancelAnimation() {
+ if (mVeilAnimator != null) {
+ mVeilAnimator.removeAllUpdateListeners();
+ mVeilAnimator.cancel();
+ }
+ }
+
/**
* Dispose of veil when it is no longer needed, likely on close of its container decor.
*/
void dispose() {
+ cancelAnimation();
+ mVeilAnimator = null;
+
if (mViewHost != null) {
mViewHost.release();
mViewHost = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java
new file mode 100644
index 000000000000..40421b599889
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+/**
+ * Holds the state of a drag resize.
+ */
+interface TaskDragResizer {
+
+ /**
+ * Returns true if task is currently being resized or animating the final transition after
+ * a resize is complete.
+ */
+ boolean isResizingOrAnimating();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 4b55a0caaca5..5c69d5542227 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -43,7 +43,7 @@ import java.util.function.Supplier;
* If the drag is repositioning, we update in the typical manner.
*/
public class VeiledResizeTaskPositioner implements DragPositioningCallback,
- Transitions.TransitionHandler {
+ TaskDragResizer, Transitions.TransitionHandler {
private DesktopModeWindowDecoration mDesktopWindowDecoration;
private ShellTaskOrganizer mTaskOrganizer;
@@ -59,10 +59,12 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
private final int mDisallowedAreaForEndBoundsHeight;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private int mCtrlType;
+ private boolean mIsResizingOrAnimatingResize;
@Surface.Rotation private int mRotation;
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
- DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
+ DesktopModeWindowDecoration windowDecoration,
+ DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
Transitions transitions,
int disallowedAreaForEndBoundsHeight) {
@@ -71,12 +73,13 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
}
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
- DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
+ DesktopModeWindowDecoration windowDecoration,
+ DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
int disallowedAreaForEndBoundsHeight) {
- mTaskOrganizer = taskOrganizer;
mDesktopWindowDecoration = windowDecoration;
+ mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
@@ -91,7 +94,6 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
mRepositionStartPoint.set(x, y);
if (isResizing()) {
- mDesktopWindowDecoration.showResizeVeil(mTaskBoundsAtDragStart);
if (!mDesktopWindowDecoration.mTaskInfo.isFocused) {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true);
@@ -116,7 +118,12 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType,
mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
mDisplayController, mDesktopWindowDecoration)) {
- mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
+ if (!mIsResizingOrAnimatingResize) {
+ mDesktopWindowDecoration.showResizeVeil(mRepositionTaskBounds);
+ mIsResizingOrAnimatingResize = true;
+ } else {
+ mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
+ }
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
@@ -138,23 +145,21 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
- } else {
- mTaskOrganizer.applyTransaction(wct);
- }
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
} else {
// If bounds haven't changed, perform necessary veil reset here as startAnimation
// won't be called.
- mDesktopWindowDecoration.hideResizeVeil();
+ resetVeilIfVisible();
}
} else if (DragPositioningCallbackUtility.isBelowDisallowedArea(
mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
y)) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
- DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
- mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer);
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
+ mDesktopWindowDecoration.calculateValidDragArea());
+ wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
mCtrlType = CTRL_TYPE_UNDEFINED;
@@ -168,15 +173,32 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
|| (mCtrlType & CTRL_TYPE_LEFT) != 0 || (mCtrlType & CTRL_TYPE_RIGHT) != 0;
}
+ private void resetVeilIfVisible() {
+ if (mIsResizingOrAnimatingResize) {
+ mDesktopWindowDecoration.hideResizeVeil();
+ mIsResizingOrAnimatingResize = false;
+ }
+ }
+
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ for (TransitionInfo.Change change: info.getChanges()) {
+ final SurfaceControl sc = change.getLeash();
+ final Rect endBounds = change.getEndAbsBounds();
+ startTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endBounds.left, endBounds.top);
+ finishTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endBounds.left, endBounds.top);
+ }
+
startTransaction.apply();
- mDesktopWindowDecoration.hideResizeVeil();
+ resetVeilIfVisible();
mCtrlType = CTRL_TYPE_UNDEFINED;
finishCallback.onTransitionFinished(null);
+ mIsResizingOrAnimatingResize = false;
return true;
}
@@ -190,4 +212,9 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
@NonNull TransitionRequestInfo request) {
return null;
}
+
+ @Override
+ public boolean isResizingOrAnimating() {
+ return mIsResizingOrAnimatingResize;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index ae1a3d914be3..01a6012ea314 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -17,9 +17,7 @@
package com.android.wm.shell.windowdecor;
import android.app.ActivityManager;
-import android.os.IBinder;
import android.view.SurfaceControl;
-import android.window.TransitionInfo;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -103,34 +101,4 @@ public interface WindowDecorViewModel {
* @param taskInfo the info of the task
*/
void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
-
- /**
- * Notifies that a shell transition is about to start. If the transition is of type
- * TRANSIT_ENTER_DESKTOP, it will save that transition to unpause relayout for the transitioning
- * task after the transition has ended.
- *
- * @param transition the ready transaction
- * @param info of Transition to check if relayout needs to be paused for a task
- * @param change a change in the given transition
- */
- default void onTransitionReady(IBinder transition, TransitionInfo info,
- TransitionInfo.Change change) {}
-
- /**
- * Notifies that a shell transition is about to merge with another to give the window
- * decoration a chance to prepare for this merge.
- *
- * @param merged the transaction being merged
- * @param playing the transaction being merged into
- */
- default void onTransitionMerged(IBinder merged, IBinder playing) {}
-
- /**
- * Notifies that a shell transition is about to finish to give the window decoration a chance
- * to clean up.
- *
- * @param transaction
- */
- default void onTransitionFinished(IBinder transaction) {}
-
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 634b7558c7d8..32c2d1e9b257 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowInsets.Type.statusBars;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
@@ -30,6 +31,7 @@ import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.Region;
import android.os.Binder;
import android.view.Display;
import android.view.InsetsSource;
@@ -49,7 +51,10 @@ import android.window.WindowContainerTransaction;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.Supplier;
/**
@@ -123,6 +128,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
private WindowlessWindowManager mCaptionWindowManager;
private SurfaceControlViewHost mViewHost;
private Configuration mWindowDecorConfig;
+ TaskDragResizer mTaskDragResizer;
private boolean mIsCaptionVisible;
private final Binder mOwner = new Binder();
@@ -178,6 +184,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
*/
abstract void relayout(RunningTaskInfo taskInfo);
+ /**
+ * Used by the {@link DragPositioningCallback} associated with the implementing class to
+ * enforce drags ending in a valid position. A null result means no restriction.
+ */
+ @Nullable
+ abstract Rect calculateValidDragArea();
+
void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
RelayoutResult<T> outResult) {
@@ -270,9 +283,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
- final int captionWidth = taskBounds.width();
+ outResult.mCaptionWidth = params.mCaptionWidthId != Resources.ID_NULL
+ ? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width();
+ outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2;
- startT.setWindowCrop(mCaptionContainerSurface, captionWidth, outResult.mCaptionHeight)
+ startT.setWindowCrop(mCaptionContainerSurface, outResult.mCaptionWidth,
+ outResult.mCaptionHeight)
+ .setPosition(mCaptionContainerSurface, outResult.mCaptionX, 0 /* y */)
.setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
.show(mCaptionContainerSurface);
@@ -280,15 +297,48 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused);
// Caption insets
- mCaptionInsetsRect.set(taskBounds);
if (mIsCaptionVisible) {
- mCaptionInsetsRect.bottom =
- mCaptionInsetsRect.top + outResult.mCaptionHeight + params.mCaptionY;
+ // Caption inset is the full width of the task with the |captionHeight| and
+ // positioned at the top of the task bounds, also in absolute coordinates.
+ // So just reuse the task bounds and adjust the bottom coordinate.
+ mCaptionInsetsRect.set(taskBounds);
+ mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + outResult.mCaptionHeight;
+
+ // Caption bounding rectangles: these are optional, and are used to present finer
+ // insets than traditional |Insets| to apps about where their content is occluded.
+ // These are also in absolute coordinates.
+ final Rect[] boundingRects;
+ final int numOfElements = params.mOccludingCaptionElements.size();
+ if (numOfElements == 0) {
+ boundingRects = null;
+ } else {
+ // The customizable region can at most be equal to the caption bar.
+ if (params.mAllowCaptionInputFallthrough) {
+ outResult.mCustomizableCaptionRegion.set(mCaptionInsetsRect);
+ }
+ boundingRects = new Rect[numOfElements];
+ for (int i = 0; i < numOfElements; i++) {
+ final OccludingCaptionElement element =
+ params.mOccludingCaptionElements.get(i);
+ final int elementWidthPx =
+ resources.getDimensionPixelSize(element.mWidthResId);
+ boundingRects[i] =
+ calculateBoundingRect(element, elementWidthPx, mCaptionInsetsRect);
+ // Subtract the regions used by the caption elements, the rest is
+ // customizable.
+ if (params.mAllowCaptionInputFallthrough) {
+ outResult.mCustomizableCaptionRegion.op(boundingRects[i],
+ Region.Op.DIFFERENCE);
+ }
+ }
+ }
+ // Add this caption as an inset source.
wct.addInsetsSource(mTaskInfo.token,
- mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
+ mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect,
+ boundingRects);
wct.addInsetsSource(mTaskInfo.token,
mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
- mCaptionInsetsRect);
+ mCaptionInsetsRect, null /* boundingRects */);
} else {
wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */,
WindowInsets.Type.captionBar());
@@ -303,25 +353,21 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
float shadowRadius;
final Point taskPosition = mTaskInfo.positionInParent;
if (isFullscreen) {
- // Setting the task crop to the width/height stops input events from being sent to
- // some regions of the app window. See b/300324920
- // TODO(b/296921174): investigate whether crop/position needs to be set by window
- // decorations at all when transition handlers are already taking ownership of the task
- // surface placement/crop, especially when in fullscreen where tasks cannot be
- // drag-resized by the window decoration.
- startT.setWindowCrop(mTaskSurface, null);
- finishT.setWindowCrop(mTaskSurface, null);
// Shadow is not needed for fullscreen tasks
shadowRadius = 0;
} else {
- startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
- finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
shadowRadius = loadDimension(resources, params.mShadowRadiusId);
}
+
+ if (params.mSetTaskPositionAndCrop) {
+ startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+ finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
+ .setPosition(mTaskSurface, taskPosition.x, taskPosition.y);
+ }
+
startT.setShadowRadius(mTaskSurface, shadowRadius)
.show(mTaskSurface);
- finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
- .setShadowRadius(mTaskSurface, shadowRadius);
+ finishT.setShadowRadius(mTaskSurface, shadowRadius);
if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
if (!DesktopModeStatus.isVeiledResizeEnabled()) {
// When fluid resize is enabled, add a background to freeform tasks
@@ -348,11 +394,16 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
// Caption view
mCaptionWindowManager.setConfiguration(taskConfig);
final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(captionWidth, outResult.mCaptionHeight,
+ new WindowManager.LayoutParams(outResult.mCaptionWidth, outResult.mCaptionHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
+ if (params.mAllowCaptionInputFallthrough) {
+ lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+ } else {
+ lp.inputFeatures &= ~WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+ }
if (mViewHost == null) {
mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
mCaptionWindowManager);
@@ -368,6 +419,20 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
}
+ private Rect calculateBoundingRect(@NonNull OccludingCaptionElement element,
+ int elementWidthPx, @NonNull Rect captionRect) {
+ switch (element.mAlignment) {
+ case START -> {
+ return new Rect(0, 0, elementWidthPx, captionRect.height());
+ }
+ case END -> {
+ return new Rect(captionRect.width() - elementWidthPx, 0,
+ captionRect.width(), captionRect.height());
+ }
+ }
+ throw new IllegalArgumentException("Unexpected alignment " + element.mAlignment);
+ }
+
/**
* Checks if task has entered/exited immersive mode and requires a change in caption visibility.
*/
@@ -386,6 +451,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
}
+ void setTaskDragResizer(TaskDragResizer taskDragResizer) {
+ mTaskDragResizer = taskDragResizer;
+ }
+
private void setCaptionVisibility(View rootView, boolean visible) {
if (rootView == null) {
return;
@@ -533,7 +602,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId);
final Rect captionInsets = new Rect(0, 0, 0, captionHeight);
wct.addInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, WindowInsets.Type.captionBar(),
- captionInsets);
+ captionInsets, null /* boundingRects */);
}
static class RelayoutParams {
@@ -541,35 +610,51 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
int mLayoutResId;
int mCaptionHeightId;
int mCaptionWidthId;
- int mShadowRadiusId;
+ final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>();
+ boolean mAllowCaptionInputFallthrough;
+ int mShadowRadiusId;
int mCornerRadius;
- int mCaptionX;
- int mCaptionY;
-
Configuration mWindowDecorConfig;
boolean mApplyStartTransactionOnDraw;
+ boolean mSetTaskPositionAndCrop;
void reset() {
mLayoutResId = Resources.ID_NULL;
mCaptionHeightId = Resources.ID_NULL;
mCaptionWidthId = Resources.ID_NULL;
- mShadowRadiusId = Resources.ID_NULL;
+ mOccludingCaptionElements.clear();
+ mAllowCaptionInputFallthrough = false;
+ mShadowRadiusId = Resources.ID_NULL;
mCornerRadius = 0;
- mCaptionX = 0;
- mCaptionY = 0;
-
mApplyStartTransactionOnDraw = false;
+ mSetTaskPositionAndCrop = false;
mWindowDecorConfig = null;
}
+
+ /**
+ * Describes elements within the caption bar that could occlude app content, and should be
+ * sent as bounding rectangles to the insets system.
+ */
+ static class OccludingCaptionElement {
+ int mWidthResId;
+ Alignment mAlignment;
+
+ enum Alignment {
+ START, END
+ }
+ }
}
static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
int mCaptionHeight;
+ int mCaptionWidth;
+ int mCaptionX;
+ final Region mCustomizableCaptionRegion = Region.obtain();
int mWidth;
int mHeight;
T mRootView;
@@ -578,6 +663,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mWidth = 0;
mHeight = 0;
mCaptionHeight = 0;
+ mCaptionWidth = 0;
+ mCaptionX = 0;
+ mCustomizableCaptionRegion.setEmpty();
mRootView = null;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
new file mode 100644
index 000000000000..5dd96aceaec7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor.extension
+
+import android.app.TaskInfo
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS
+import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
+
+val TaskInfo.isTransparentCaptionBarAppearance: Boolean
+ get() {
+ val appearance = taskDescription?.statusBarAppearance ?: 0
+ return (appearance and APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) != 0
+ }
+
+val TaskInfo.isLightCaptionBarAppearance: Boolean
+ get() {
+ val appearance = taskDescription?.statusBarAppearance ?: 0
+ return (appearance and APPEARANCE_LIGHT_CAPTION_BARS) != 0
+ }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 589a8134c2d3..58bbb030da01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -12,6 +12,7 @@ import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.withStyledAttributes
+import androidx.core.view.isVisible
import com.android.internal.R.attr.materialColorOnSecondaryContainer
import com.android.internal.R.attr.materialColorOnSurface
import com.android.internal.R.attr.materialColorSecondaryContainer
@@ -19,6 +20,9 @@ import com.android.internal.R.attr.materialColorSurfaceContainerHigh
import com.android.internal.R.attr.materialColorSurfaceContainerLow
import com.android.internal.R.attr.materialColorSurfaceDim
import com.android.wm.shell.R
+import com.android.wm.shell.windowdecor.MaximizeButtonView
+import com.android.wm.shell.windowdecor.extension.isLightCaptionBarAppearance
+import com.android.wm.shell.windowdecor.extension.isTransparentCaptionBarAppearance
/**
* A desktop mode window decoration used when the window is floating (i.e. freeform). It hosts
@@ -30,8 +34,10 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
onCaptionTouchListener: View.OnTouchListener,
onCaptionButtonClickListener: View.OnClickListener,
onLongClickListener: OnLongClickListener,
+ onCaptionGenericMotionListener: View.OnGenericMotionListener,
appName: CharSequence,
- appIconBitmap: Bitmap
+ appIconBitmap: Bitmap,
+ onMaximizeHoverAnimationFinishedListener: () -> Unit
) : DesktopModeWindowDecorationViewHolder(rootView) {
private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
@@ -39,9 +45,13 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
private val openMenuButton: View = rootView.requireViewById(R.id.open_menu_button)
private val closeWindowButton: ImageButton = rootView.requireViewById(R.id.close_window)
private val expandMenuButton: ImageButton = rootView.requireViewById(R.id.expand_menu_button)
+ private val maximizeButtonView: MaximizeButtonView =
+ rootView.requireViewById(R.id.maximize_button_view)
private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window)
private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name)
private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon)
+ val appNameTextWidth: Int
+ get() = appNameTextView.width
init {
captionView.setOnTouchListener(onCaptionTouchListener)
@@ -51,10 +61,13 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
closeWindowButton.setOnClickListener(onCaptionButtonClickListener)
maximizeWindowButton.setOnClickListener(onCaptionButtonClickListener)
maximizeWindowButton.setOnTouchListener(onCaptionTouchListener)
+ maximizeWindowButton.setOnGenericMotionListener(onCaptionGenericMotionListener)
maximizeWindowButton.onLongClickListener = onLongClickListener
closeWindowButton.setOnTouchListener(onCaptionTouchListener)
appNameTextView.text = appName
appIconImageView.setImageBitmap(appIconBitmap)
+ maximizeButtonView.onHoverAnimationFinishedListener =
+ onMaximizeHoverAnimationFinishedListener
}
override fun bindData(taskInfo: RunningTaskInfo) {
@@ -64,19 +77,41 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
closeWindowButton.imageTintList = ColorStateList.valueOf(color)
maximizeWindowButton.imageTintList = ColorStateList.valueOf(color)
expandMenuButton.imageTintList = ColorStateList.valueOf(color)
+ appNameTextView.isVisible = !taskInfo.isTransparentCaptionBarAppearance
appNameTextView.setTextColor(color)
appIconImageView.imageAlpha = alpha
maximizeWindowButton.imageAlpha = alpha
closeWindowButton.imageAlpha = alpha
expandMenuButton.imageAlpha = alpha
+
+ maximizeButtonView.setAnimationTints(isDarkMode())
}
override fun onHandleMenuOpened() {}
override fun onHandleMenuClosed() {}
+ fun setAnimatingTaskResize(animatingTaskResize: Boolean) {
+ // If animating a task resize, cancel any running hover animations
+ if (animatingTaskResize) {
+ maximizeButtonView.cancelHoverAnimation()
+ }
+ maximizeButtonView.hoverDisabled = animatingTaskResize
+ }
+
+ fun onMaximizeWindowHoverExit() {
+ maximizeButtonView.cancelHoverAnimation()
+ }
+
+ fun onMaximizeWindowHoverEnter() {
+ maximizeButtonView.startHoverAnimation()
+ }
+
@ColorInt
private fun getCaptionBackgroundColor(taskInfo: RunningTaskInfo): Int {
+ if (taskInfo.isTransparentCaptionBarAppearance) {
+ return Color.TRANSPARENT
+ }
val materialColorAttr: Int =
if (isDarkMode()) {
if (!taskInfo.isFocused) {
@@ -100,6 +135,10 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
@ColorInt
private fun getAppNameAndButtonColor(taskInfo: RunningTaskInfo): Int {
val materialColorAttr = when {
+ taskInfo.isTransparentCaptionBarAppearance &&
+ taskInfo.isLightCaptionBarAppearance -> materialColorOnSecondaryContainer
+ taskInfo.isTransparentCaptionBarAppearance &&
+ !taskInfo.isLightCaptionBarAppearance -> materialColorOnSurface
isDarkMode() -> materialColorOnSurface
else -> materialColorOnSecondaryContainer
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 4930cb721336..6dcae2776847 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -35,9 +35,6 @@ internal class DesktopModeFocusedWindowDecorationViewHolder(
}
override fun bindData(taskInfo: RunningTaskInfo) {
- taskInfo.taskDescription?.statusBarColor?.let { captionColor ->
- captionView.setBackgroundColor(captionColor)
- }
captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index 690b4e4be122..81bc34c876b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -17,9 +17,9 @@ internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) {
*/
abstract fun bindData(taskInfo: RunningTaskInfo)
- /** Callback when the handle menu is opened. */
- abstract fun onHandleMenuOpened()
+ /** Callback when the handle menu is opened. */
+ abstract fun onHandleMenuOpened()
- /** Callback when the handle menu is closed. */
- abstract fun onHandleMenuClosed()
+ /** Callback when the handle menu is closed. */
+ abstract fun onHandleMenuClosed()
}