diff options
7 files changed, 243 insertions, 39 deletions
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 101295d246bc..11ecc9197be7 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,6 +35,9 @@ 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; @@ -54,6 +57,7 @@ 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; @@ -205,6 +209,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange break; case ACTION_DRAG_ENTERED: pd.dragLayout.show(); + pd.dragLayout.update(event); break; case ACTION_DRAG_LOCATION: pd.dragLayout.update(event); @@ -250,10 +255,6 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange // Hide the window if another drag hasn't been started while animating the drop setDropTargetWindowVisibility(pd, View.INVISIBLE); } - - // Clean up the drag surface - mTransaction.reparent(dragSurface, null); - mTransaction.apply(); }); } 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 e8bae0f94bf0..756831007c35 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 @@ -123,6 +123,13 @@ public class DragAndDropPolicy { } /** + * Returns the number of targets. + */ + int getNumTargets() { + return mTargets.size(); + } + + /** * Returns the target's regions based on the current state of the device and display. */ @NonNull 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 d395f956a41c..25fe8b9e0da3 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 @@ -17,6 +17,7 @@ package com.android.wm.shell.draganddrop; import static android.app.StatusBarManager.DISABLE_NONE; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -24,6 +25,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.StatusBarManager; @@ -44,6 +46,7 @@ import com.android.internal.logging.InstanceId; 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.DisplayLayout; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -135,6 +138,12 @@ public class DragLayout extends LinearLayout { } } + private void updateContainerMarginsForSingleTask() { + mDropZoneView1.setContainerMargin( + mDisplayMargin, mDisplayMargin, mDisplayMargin, mDisplayMargin); + mDropZoneView2.setContainerMargin(0, 0, 0, 0); + } + private void updateContainerMargins(int orientation) { final float halfMargin = mDisplayMargin / 2f; if (orientation == Configuration.ORIENTATION_LANDSCAPE) { @@ -165,11 +174,20 @@ public class DragLayout extends LinearLayout { if (!alreadyInSplit) { ActivityManager.RunningTaskInfo taskInfo1 = mPolicy.getLatestRunningTask(); if (taskInfo1 != null) { - Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo); - int bgColor1 = getResizingBackgroundColor(taskInfo1); - mDropZoneView1.setAppInfo(bgColor1, icon1); - mDropZoneView2.setAppInfo(bgColor1, icon1); - updateDropZoneSizes(null, null); // passing null splits the views evenly + final int activityType = taskInfo1.getActivityType(); + if (activityType == ACTIVITY_TYPE_STANDARD) { + Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo); + int bgColor1 = getResizingBackgroundColor(taskInfo1); + mDropZoneView1.setAppInfo(bgColor1, icon1); + mDropZoneView2.setAppInfo(bgColor1, icon1); + updateDropZoneSizes(null, null); // passing null splits the views evenly + } else { + // We use the first drop zone to show the fullscreen highlight, and don't need + // to set additional info + mDropZoneView1.setForceIgnoreBottomMargin(true); + updateDropZoneSizesForSingleTask(); + updateContainerMarginsForSingleTask(); + } } } else { // We're already in split so get taskInfo from the controller to populate icon / color. @@ -195,6 +213,21 @@ public class DragLayout extends LinearLayout { } } + private void updateDropZoneSizesForSingleTask() { + final LinearLayout.LayoutParams dropZoneView1 = + (LayoutParams) mDropZoneView1.getLayoutParams(); + final LinearLayout.LayoutParams dropZoneView2 = + (LayoutParams) mDropZoneView2.getLayoutParams(); + dropZoneView1.width = MATCH_PARENT; + dropZoneView1.height = MATCH_PARENT; + dropZoneView2.width = 0; + dropZoneView2.height = 0; + dropZoneView1.weight = 1; + dropZoneView2.weight = 0; + mDropZoneView1.setLayoutParams(dropZoneView1); + mDropZoneView2.setLayoutParams(dropZoneView2); + } + /** * Sets the size of the two drop zones based on the provided bounds. The divider sits between * the views and its size is included in the calculations. @@ -265,9 +298,12 @@ public class DragLayout extends LinearLayout { // Animating to no target animateSplitContainers(false, null /* animCompleteCallback */); } else if (mCurrentTarget == null) { - // Animating to first target - animateSplitContainers(true, null /* animCompleteCallback */); - animateHighlight(target); + if (mPolicy.getNumTargets() == 1) { + animateFullscreenContainer(true); + } else { + animateSplitContainers(true, null /* animCompleteCallback */); + animateHighlight(target); + } } else { // Switching between targets mDropZoneView1.animateSwitch(); @@ -283,6 +319,10 @@ public class DragLayout extends LinearLayout { public void hide(DragEvent event, Runnable hideCompleteCallback) { mIsShowing = false; animateSplitContainers(false, hideCompleteCallback); + // Reset the state if we previously force-ignore the bottom margin + mDropZoneView1.setForceIgnoreBottomMargin(false); + mDropZoneView2.setForceIgnoreBottomMargin(false); + updateContainerMargins(getResources().getConfiguration().orientation); mCurrentTarget = null; } @@ -297,11 +337,63 @@ public class DragLayout extends LinearLayout { // Process the drop mPolicy.handleDrop(mCurrentTarget, event.getClipData()); - // TODO(b/169894807): Coordinate with dragSurface + // Start animating the drop UI out with the drag surface hide(event, dropCompleteCallback); + hideDragSurface(dragSurface); return handledDrop; } + private void hideDragSurface(SurfaceControl dragSurface) { + final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); + final ValueAnimator dragSurfaceAnimator = ValueAnimator.ofFloat(0f, 1f); + // Currently the splash icon animation runs with the default ValueAnimator duration of + // 300ms + dragSurfaceAnimator.setDuration(300); + dragSurfaceAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + dragSurfaceAnimator.addUpdateListener(animation -> { + float t = animation.getAnimatedFraction(); + float alpha = 1f - t; + // TODO: Scale the drag surface as well once we make all the source surfaces + // consistent + tx.setAlpha(dragSurface, alpha); + tx.apply(); + }); + dragSurfaceAnimator.addListener(new AnimatorListenerAdapter() { + private boolean mCanceled = false; + + @Override + public void onAnimationCancel(Animator animation) { + cleanUpSurface(); + mCanceled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mCanceled) { + // Already handled above + return; + } + cleanUpSurface(); + } + + private void cleanUpSurface() { + // Clean up the drag surface + tx.remove(dragSurface); + tx.apply(); + } + }); + dragSurfaceAnimator.start(); + } + + private void animateFullscreenContainer(boolean visible) { + mStatusBarManager.disable(visible + ? HIDE_STATUS_BAR_FLAGS + : DISABLE_NONE); + // We're only using the first drop zone if there is one fullscreen target + mDropZoneView1.setShowingMargin(visible); + mDropZoneView1.setShowingHighlight(visible); + } + private void animateSplitContainers(boolean visible, Runnable animCompleteCallback) { mStatusBarManager.disable(visible ? HIDE_STATUS_BAR_FLAGS diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java index a3ee8aed204d..38870bcb3383 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java @@ -65,6 +65,7 @@ public class DropZoneView extends FrameLayout { private final float[] mContainerMargin = new float[4]; private float mCornerRadius; private float mBottomInset; + private boolean mIgnoreBottomMargin; private int mMarginColor; // i.e. color used for negative space like the container insets private boolean mShowingHighlight; @@ -141,6 +142,14 @@ public class DropZoneView extends FrameLayout { } } + /** Ignores the bottom margin provided by the insets. */ + public void setForceIgnoreBottomMargin(boolean ignoreBottomMargin) { + mIgnoreBottomMargin = ignoreBottomMargin; + if (mMarginPercent > 0) { + mMarginView.invalidate(); + } + } + /** Sets the bottom inset so the drop zones are above bottom navigation. */ public void setBottomInset(float bottom) { mBottomInset = bottom; @@ -257,7 +266,8 @@ public class DropZoneView extends FrameLayout { mPath.addRoundRect(mContainerMargin[0] * mMarginPercent, mContainerMargin[1] * mMarginPercent, getWidth() - (mContainerMargin[2] * mMarginPercent), - getHeight() - (mContainerMargin[3] * mMarginPercent) - mBottomInset, + getHeight() - (mContainerMargin[3] * mMarginPercent) + - (mIgnoreBottomMargin ? 0 : mBottomInset), mCornerRadius * mMarginPercent, mCornerRadius * mMarginPercent, Path.Direction.CW); diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 3f80647886f4..23b252906056 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2407,4 +2407,7 @@ <string name="add_user_supervised" translatable="false">@*android:string/supervised_user_creation_label</string> <!-- Manage users - For system user management [CHAR LIMIT=40] --> <string name="manage_users">Manage users</string> + + <!-- Toast shown when a notification does not support dragging to split [CHAR LIMIT=NONE] --> + <string name="drag_split_not_supported">This notification does not support dragging to Splitscreen.</string> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java index 06b739b33e77..c2c40d84bd36 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java @@ -17,6 +17,11 @@ package com.android.systemui.statusbar.notification.row; +import static android.widget.Toast.LENGTH_SHORT; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.annotation.NonNull; import android.app.Notification; import android.app.PendingIntent; @@ -33,13 +38,15 @@ import android.service.notification.StatusBarNotification; import android.util.Log; import android.view.DragEvent; import android.view.HapticFeedbackConstants; +import android.view.SurfaceControl; import android.view.View; import android.widget.ImageView; +import android.widget.Toast; import androidx.annotation.VisibleForTesting; -import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.animation.Interpolators; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -55,12 +62,15 @@ public class ExpandableNotificationRowDragController { private final Context mContext; private final HeadsUpManager mHeadsUpManager; + private final ShadeController mShadeController; @Inject public ExpandableNotificationRowDragController(Context context, - HeadsUpManager headsUpManager) { + HeadsUpManager headsUpManager, + ShadeController shadeController) { mContext = context; mHeadsUpManager = headsUpManager; + mShadeController = shadeController; init(); } @@ -87,6 +97,16 @@ public class ExpandableNotificationRowDragController { final PendingIntent contentIntent = notification.contentIntent != null ? notification.contentIntent : notification.fullScreenIntent; + if (contentIntent == null) { + if (!enr.isPinned()) { + // We dismiss the shade for consistency, but also because toasts currently don't + // show above the shade + dismissShade(); + } + Toast.makeText(mContext, R.string.drag_split_not_supported, LENGTH_SHORT) + .show(); + return; + } Bitmap iconBitmap = getBitmapFromDrawable( getPkgIcon(enr.getEntry().getSbn().getPackageName())); @@ -97,15 +117,30 @@ public class ExpandableNotificationRowDragController { ClipDescription clipDescription = new ClipDescription("Drag And Drop", new String[]{ClipDescription.MIMETYPE_APPLICATION_ACTIVITY}); Intent dragIntent = new Intent(); - dragIntent.putExtra("android.intent.extra.PENDING_INTENT", contentIntent); + dragIntent.putExtra(ClipDescription.EXTRA_PENDING_INTENT, contentIntent); dragIntent.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle()); ClipData.Item item = new ClipData.Item(dragIntent); ClipData dragData = new ClipData(clipDescription, item); View.DragShadowBuilder myShadow = new View.DragShadowBuilder(snapshot); view.setOnDragListener(getDraggedViewDragListener()); - view.startDragAndDrop(dragData, myShadow, null, View.DRAG_FLAG_GLOBAL); + boolean result = view.startDragAndDrop(dragData, myShadow, null, View.DRAG_FLAG_GLOBAL + | View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION); + if (result) { + view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + if (enr.isPinned()) { + mHeadsUpManager.releaseAllImmediately(); + } else { + dismissShade(); + } + } } + private void dismissShade() { + // Speed up dismissing the shade since the drag needs to be handled by + // the shell layer underneath + mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */, + false /* delayed */, 1.1f /* speedUpFactor */); + } private Drawable getPkgIcon(String pkgName) { Drawable pkgicon = null; @@ -145,16 +180,6 @@ public class ExpandableNotificationRowDragController { return (view, dragEvent) -> { switch (dragEvent.getAction()) { case DragEvent.ACTION_DRAG_STARTED: - view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - if (view instanceof ExpandableNotificationRow) { - ExpandableNotificationRow enr = (ExpandableNotificationRow) view; - if (enr.isPinned()) { - mHeadsUpManager.releaseAllImmediately(); - } else { - Dependency.get(ShadeController.class).animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); - } - } return true; case DragEvent.ACTION_DRAG_ENDED: if (dragEvent.getResult()) { @@ -162,10 +187,55 @@ public class ExpandableNotificationRowDragController { ExpandableNotificationRow enr = (ExpandableNotificationRow) view; enr.dragAndDropSuccess(); } + } else { + // Fade out the drag surface in place instead of animating back to the + // start position now that the shade is closed + fadeOutAndRemoveDragSurface(dragEvent); } + // Clear the drag listener set above + view.setOnDragListener(null); return true; } return false; }; } + + private void fadeOutAndRemoveDragSurface(DragEvent dragEvent) { + SurfaceControl dragSurface = dragEvent.getDragSurface(); + SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); + ValueAnimator returnAnimator = ValueAnimator.ofFloat(0f, 1f); + returnAnimator.setDuration(200); + returnAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + returnAnimator.addUpdateListener(animation -> { + float t = animation.getAnimatedFraction(); + float alpha = 1f - t; + tx.setAlpha(dragSurface, alpha); + tx.apply(); + }); + returnAnimator.addListener(new AnimatorListenerAdapter() { + private boolean mCanceled = false; + + @Override + public void onAnimationCancel(Animator animation) { + cleanUpSurface(); + mCanceled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mCanceled) { + // Already handled above + return; + } + cleanUpSurface(); + } + + private void cleanUpSurface() { + tx.remove(dragSurface); + tx.apply(); + tx.close(); + } + }); + returnAnimator.start(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java index 24a0ad3de196..bc54bf8ebb7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.row; import static android.view.DragEvent.ACTION_DRAG_STARTED; +import android.app.Notification; +import android.app.PendingIntent; import android.content.Context; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -36,7 +38,12 @@ import org.junit.Test; import org.junit.runner.RunWith; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -58,6 +65,7 @@ public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase { private NotificationMenuRow mMenuRow = mock(NotificationMenuRow.class); private NotificationMenuRowPlugin.MenuItem mMenuItem = mock(NotificationMenuRowPlugin.MenuItem.class); + private ShadeController mShadeController = mock(ShadeController.class); @Before public void setUp() throws Exception { @@ -69,11 +77,15 @@ public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase { mContext, mDependency, TestableLooper.get(this)); - mRow = mNotificationTestHelper.createRow(); + mRow = spy(mNotificationTestHelper.createRow()); + Notification notification = mRow.getEntry().getSbn().getNotification(); + notification.contentIntent = mock(PendingIntent.class); + doReturn(true).when(mRow).startDragAndDrop(any(), any(), any(), anyInt()); mGroupRow = mNotificationTestHelper.createGroup(4); when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem); - mController = new ExpandableNotificationRowDragController(mContext, mHeadsUpManager); + mController = new ExpandableNotificationRowDragController(mContext, mHeadsUpManager, + mShadeController); } @Test @@ -86,10 +98,6 @@ public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase { mRow.doLongClickCallback(0, 0); mRow.doDragCallback(0, 0); verify(controller).startDragAndDrop(mRow); - - // Simulate the drag start - mRow.dispatchDragEvent(DragEvent.obtain(ACTION_DRAG_STARTED, 0, 0, 0, 0, null, null, null, - null, null, false)); verify(mHeadsUpManager, times(1)).releaseAllImmediately(); } @@ -98,14 +106,27 @@ public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase { ExpandableNotificationRowDragController controller = createSpyController(); mRow.setDragController(controller); - mDependency.get(ShadeController.class).instantExpandNotificationsPanel(); + mRow.doDragCallback(0, 0); + verify(controller).startDragAndDrop(mRow); + verify(mShadeController).animateCollapsePanels(eq(0), eq(true), + eq(false), anyFloat()); + } + + @Test + public void testDoStartDrag_noLaunchIntent() throws Exception { + ExpandableNotificationRowDragController controller = createSpyController(); + mRow.setDragController(controller); + + // Clear the intents + Notification notification = mRow.getEntry().getSbn().getNotification(); + notification.contentIntent = null; + notification.fullScreenIntent = null; + mRow.doDragCallback(0, 0); verify(controller).startDragAndDrop(mRow); - // Simulate the drag start - mRow.dispatchDragEvent(DragEvent.obtain(ACTION_DRAG_STARTED, 0, 0, 0, 0, null, null, null, - null, null, false)); - verify(mDependency.get(ShadeController.class)).animateCollapsePanels(0, true); + // Verify that we never start the actual drag since there is no content + verify(mRow, never()).startDragAndDrop(any(), any(), any(), anyInt()); } private ExpandableNotificationRowDragController createSpyController() { |