From 528db439de42eb5db3694d27c6e0585ba0fbf0ef Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Mon, 11 Apr 2022 23:13:01 +0000 Subject: [PATCH] Collapse bubbles when drag and drop is initiated Dragging a notification into split screen would fail if bubbles were expanded while performing this action. I think it makes sense to collapse the stack if its open when drag and drop for an app starts. Bug: 223909931 Test: atest DragAndDropControllerTest Test: manual - have bubbles expanded - swipe down the shade, long press on a notification and drag it => bubbles are collapsed and the notif is able to be put into split screen. Change-Id: Ib5160b3d772452f1cdd704926887bd38a5fd1ef1 --- .../wm/shell/bubbles/BubbleController.java | 12 +++- .../wm/shell/dagger/WMShellModule.java | 4 +- .../draganddrop/DragAndDropController.java | 43 +++++++++++-- .../DragAndDropControllerTest.java | 61 +++++++++++++++++++ .../android/systemui/wmshell/BubblesTest.java | 2 + .../wmshell/NewNotifPipelineBubblesTest.java | 2 + .../wmshell/TestableBubbleController.java | 6 +- 7 files changed, 118 insertions(+), 12 deletions(-) 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 5ef24133ca113..806c395bf3959 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 @@ -96,6 +96,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.PinnedStackListenerForwarder; @@ -214,6 +215,8 @@ public class BubbleController { /** One handed mode controller to register transition listener. */ private Optional mOneHandedOptional; + /** Drag and drop controller to register listener for onDragStarted. */ + private DragAndDropController mDragAndDropController; /** * Creates an instance of the BubbleController. @@ -230,6 +233,7 @@ public class BubbleController { ShellTaskOrganizer organizer, DisplayController displayController, Optional oneHandedOptional, + DragAndDropController dragAndDropController, ShellExecutor mainExecutor, Handler mainHandler, TaskViewTransitions taskViewTransitions, @@ -241,8 +245,8 @@ public class BubbleController { new BubbleDataRepository(context, launcherApps, mainExecutor), statusBarService, windowManager, windowManagerShellWrapper, launcherApps, logger, taskStackListener, organizer, positioner, displayController, - oneHandedOptional, mainExecutor, mainHandler, taskViewTransitions, - syncQueue); + oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, + taskViewTransitions, syncQueue); } /** @@ -264,6 +268,7 @@ public class BubbleController { BubblePositioner positioner, DisplayController displayController, Optional oneHandedOptional, + DragAndDropController dragAndDropController, ShellExecutor mainExecutor, Handler mainHandler, TaskViewTransitions taskViewTransitions, @@ -293,6 +298,7 @@ public class BubbleController { mDisplayController = displayController; mTaskViewTransitions = taskViewTransitions; mOneHandedOptional = oneHandedOptional; + mDragAndDropController = dragAndDropController; mSyncQueue = syncQueue; } @@ -433,6 +439,7 @@ public class BubbleController { }); mOneHandedOptional.ifPresent(this::registerOneHandedState); + mDragAndDropController.addListener(this::collapseStack); } @VisibleForTesting @@ -884,7 +891,6 @@ public class BubbleController { return mBubbleData.isExpanded(); } - @VisibleForTesting public void collapseStack() { mBubbleData.setExpanded(false /* expanded */); } 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 965bd26da158f..0b196f0ad7e32 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 @@ -42,6 +42,7 @@ import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.fullscreen.FullscreenUnfoldController; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; @@ -108,6 +109,7 @@ public class WMShellModule { ShellTaskOrganizer organizer, DisplayController displayController, @DynamicOverride Optional oneHandedOptional, + DragAndDropController dragAndDropController, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler, TaskViewTransitions taskViewTransitions, @@ -116,7 +118,7 @@ public class WMShellModule { floatingContentCoordinator, statusBarService, windowManager, windowManagerShellWrapper, launcherApps, taskStackListener, uiEventLogger, organizer, displayController, oneHandedOptional, - mainExecutor, mainHandler, taskViewTransitions, syncQueue); + dragAndDropController, mainExecutor, mainHandler, taskViewTransitions, syncQueue); } // 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 11ecc9197be7a..95de2dc61a436 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,9 +35,6 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.content.ClipDescription; import android.content.Context; import android.content.res.Configuration; @@ -52,17 +49,19 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.widget.FrameLayout; +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; import com.android.wm.shell.R; -import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; +import java.util.ArrayList; import java.util.Optional; /** @@ -80,10 +79,19 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange private SplitScreenController mSplitScreen; private ShellExecutor mMainExecutor; private DragAndDropImpl mImpl; + private ArrayList mListeners = new ArrayList<>(); private final SparseArray mDisplayDropTargets = new SparseArray<>(); private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); + /** + * Listener called during drag events, currently just onDragStarted. + */ + public interface DragAndDropListener { + /** Called when a drag has started. */ + void onDragStarted(); + } + public DragAndDropController(Context context, DisplayController displayController, UiEventLogger uiEventLogger, IconProvider iconProvider, ShellExecutor mainExecutor) { mContext = context; @@ -103,6 +111,22 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange mDisplayController.addDisplayWindowListener(this); } + /** Adds a listener to be notified of drag and drop events. */ + public void addListener(DragAndDropListener listener) { + mListeners.add(listener); + } + + /** Removes a drag and drop listener. */ + public void removeListener(DragAndDropListener listener) { + mListeners.remove(listener); + } + + private void notifyListeners() { + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onDragStarted(); + } + } + @Override public void onDisplayAdded(int displayId) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Display added: %d", displayId); @@ -137,13 +161,19 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); try { wm.addView(rootView, layoutParams); - mDisplayDropTargets.put(displayId, - new PerDisplay(displayId, context, wm, rootView, dragLayout)); + addDisplayDropTarget(displayId, context, wm, rootView, dragLayout); } catch (WindowManager.InvalidDisplayException e) { Slog.w(TAG, "Unable to add view for display id: " + displayId); } } + @VisibleForTesting + void addDisplayDropTarget(int displayId, Context context, WindowManager wm, + FrameLayout rootView, DragLayout dragLayout) { + mDisplayDropTargets.put(displayId, + new PerDisplay(displayId, context, wm, rootView, dragLayout)); + } + @Override public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Display changed: %d", displayId); @@ -206,6 +236,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange pd.dragLayout.prepare(mDisplayController.getDisplayLayout(displayId), event.getClipData(), loggerSessionId); setDropTargetWindowVisibility(pd, View.VISIBLE); + notifyListeners(); break; case ACTION_DRAG_ENTERED: pd.dragLayout.show(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java index 9f745208d3ed1..aaeebef03d0f2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java @@ -16,15 +16,28 @@ package com.android.wm.shell.draganddrop; +import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.DragEvent.ACTION_DRAG_STARTED; + import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import android.content.ClipData; +import android.content.ClipDescription; import android.content.Context; +import android.content.Intent; import android.os.RemoteException; import android.view.Display; import android.view.DragEvent; import android.view.View; +import android.view.WindowManager; +import android.widget.FrameLayout; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -33,6 +46,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.splitscreen.SplitScreenController; import org.junit.Before; import org.junit.Test; @@ -40,6 +54,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Optional; + /** * Tests for the drag and drop controller. */ @@ -56,6 +72,9 @@ public class DragAndDropControllerTest { @Mock private UiEventLogger mUiEventLogger; + @Mock + private DragAndDropController.DragAndDropListener mDragAndDropListener; + private DragAndDropController mController; @Before @@ -63,6 +82,7 @@ public class DragAndDropControllerTest { MockitoAnnotations.initMocks(this); mController = new DragAndDropController(mContext, mDisplayController, mUiEventLogger, mock(IconProvider.class), mock(ShellExecutor.class)); + mController.initialize(Optional.of(mock(SplitScreenController.class))); } @Test @@ -77,4 +97,45 @@ public class DragAndDropControllerTest { mController.onDisplayAdded(nonDefaultDisplayId); assertFalse(mController.onDrag(dragLayout, mock(DragEvent.class))); } + + @Test + public void testListenerOnDragStarted() { + final View dragLayout = mock(View.class); + final Display display = mock(Display.class); + doReturn(display).when(dragLayout).getDisplay(); + doReturn(DEFAULT_DISPLAY).when(display).getDisplayId(); + + final ClipData clipData = createClipData(); + final DragEvent event = mock(DragEvent.class); + doReturn(ACTION_DRAG_STARTED).when(event).getAction(); + doReturn(clipData).when(event).getClipData(); + doReturn(clipData.getDescription()).when(event).getClipDescription(); + + mController.addListener(mDragAndDropListener); + + // Ensure there's a target so that onDrag will execute + mController.addDisplayDropTarget(0, mContext, mock(WindowManager.class), + mock(FrameLayout.class), mock(DragLayout.class)); + + // Verify the listener is called on a valid drag action. + mController.onDrag(dragLayout, event); + verify(mDragAndDropListener, times(1)).onDragStarted(); + + // Verify the listener isn't called after removal. + reset(mDragAndDropListener); + mController.removeListener(mDragAndDropListener); + mController.onDrag(dragLayout, event); + verify(mDragAndDropListener, never()).onDragStarted(); + } + + private ClipData createClipData() { + ClipDescription clipDescription = new ClipDescription(MIMETYPE_APPLICATION_SHORTCUT, + new String[] { MIMETYPE_APPLICATION_SHORTCUT }); + Intent i = new Intent(); + i.putExtra(Intent.EXTRA_PACKAGE_NAME, "pkg"); + i.putExtra(Intent.EXTRA_SHORTCUT_ID, "shortcutId"); + i.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle()); + ClipData.Item item = new ClipData.Item(i); + return new ClipData(clipDescription, item); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index ca67bd2d6fcae..193879e5c55c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -133,6 +133,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.onehanded.OneHandedController; import com.google.common.collect.ImmutableList; @@ -367,6 +368,7 @@ public class BubblesTest extends SysuiTestCase { mPositioner, mock(DisplayController.class), mOneHandedOptional, + mock(DragAndDropController.class), syncExecutor, mock(Handler.class), mTaskViewTransitions, diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java index ce7924a2a4a2f..02d8691720307 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java @@ -116,6 +116,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.onehanded.OneHandedController; import org.junit.Before; @@ -332,6 +333,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mPositioner, mock(DisplayController.class), mOneHandedOptional, + mock(DragAndDropController.class), syncExecutor, mock(Handler.class), mTaskViewTransitions, diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java index 83f5987ac9626..9646edf6756a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java @@ -35,6 +35,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.onehanded.OneHandedController; import java.util.Optional; @@ -59,6 +60,7 @@ public class TestableBubbleController extends BubbleController { BubblePositioner positioner, DisplayController displayController, Optional oneHandedOptional, + DragAndDropController dragAndDropController, ShellExecutor shellMainExecutor, Handler shellMainHandler, TaskViewTransitions taskViewTransitions, @@ -66,8 +68,8 @@ public class TestableBubbleController extends BubbleController { super(context, data, Runnable::run, floatingContentCoordinator, dataRepository, statusBarService, windowManager, windowManagerShellWrapper, launcherApps, bubbleLogger, taskStackListener, shellTaskOrganizer, positioner, displayController, - oneHandedOptional, shellMainExecutor, shellMainHandler, taskViewTransitions, - syncQueue); + oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler, + taskViewTransitions, syncQueue); setInflateSynchronously(true); initialize(); }