6/ Fix some flakey issues with dragging to split

- Ensure that the task info activity type always matches the actual task
  activity type (can be different than the window configuration type)
- Ensure that the targets hit rects respect the insets, and that dragging
  out of all targets (but still within the window) will hide the last
  target
- Work around race with launching secondary task when splitting, wait
  until the split screen task org has moved existing tasks to secondary
  split root before starting the new task, and also update the home task
  position when going home (b/172686383)
- Add a timeout to clean up drag surfaces if SysUI crashes while
  mid-drag
- Clean up some other calls to get activity type/win mode

Bug: 169894807
Test: atest DragDropControllerTests
Test: atest DragAndDropPolicyTest
Test: atest SplitScreenTests
Change-Id: I7ae11cc38b1e927d955798ef1056e17c9e435758
This commit is contained in:
Winson Chung
2020-11-05 15:57:36 -08:00
parent e3d75ac262
commit 6dd73e98d7
13 changed files with 124 additions and 46 deletions

View File

@@ -346,15 +346,9 @@ public class ShellTaskOrganizer extends TaskOrganizer {
return mTaskListeners.get(taskListenerType);
}
@WindowingMode
public static int getWindowingMode(RunningTaskInfo taskInfo) {
return taskInfo.configuration.windowConfiguration.getWindowingMode();
}
@VisibleForTesting
static @TaskListenerType int taskInfoToTaskListenerType(RunningTaskInfo runningTaskInfo) {
final int windowingMode = getWindowingMode(runningTaskInfo);
switch (windowingMode) {
switch (runningTaskInfo.getWindowingMode()) {
case WINDOWING_MODE_FULLSCREEN:
return runningTaskInfo.letterboxActivityBounds != null
? TASK_LISTENER_TYPE_LETTERBOX

View File

@@ -18,6 +18,7 @@ package com.android.wm.shell.draganddrop;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.ClipDescription.EXTRA_ACTIVITY_OPTIONS;
@@ -67,6 +68,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* The policy for handling drag and drop operations to shell.
@@ -133,20 +135,20 @@ public class DragAndDropPolicy {
// TODO(b/169894807): For now, only allow splitting to the right/bottom until we
// have split pairs
mTargets.add(new Target(TYPE_FULLSCREEN,
new Rect(0, 0, w, h / 2),
new Rect(l, t, l + iw, t + ih / 2),
new Rect(l, t, l + iw, t + ih),
new Rect(0, 0, w, h)));
mTargets.add(new Target(TYPE_SPLIT_BOTTOM,
new Rect(0, h / 2, w, h),
new Rect(l, t + ih / 2, l + iw, t + ih),
new Rect(l, t + ih / 2, l + iw, t + ih),
new Rect(0, h / 2, w, h)));
} else {
mTargets.add(new Target(TYPE_FULLSCREEN,
new Rect(0, 0, w / 2, h),
new Rect(l, t, l + iw / 2, t + ih),
new Rect(l, t, l + iw, t + ih),
new Rect(0, 0, w, h)));
mTargets.add(new Target(TYPE_SPLIT_RIGHT,
new Rect(w / 2, 0, w, h),
new Rect(l + iw / 2, t, l + iw, t + ih),
new Rect(l + iw / 2, t, l + iw, t + ih),
new Rect(w / 2, 0, w, h)));
}
@@ -162,12 +164,12 @@ public class DragAndDropPolicy {
secondarySplitBounds.intersect(new Rect(l, t, l + iw, t + ih));
if (isVerticalSplit) {
mTargets.add(new Target(TYPE_FULLSCREEN,
new Rect(0, 0, w, secondarySplitRawBounds.top),
new Rect(l, t, l + iw, secondarySplitRawBounds.top),
new Rect(l, t, l + iw, t + ih),
new Rect(0, 0, w, secondarySplitRawBounds.top)));
} else {
mTargets.add(new Target(TYPE_FULLSCREEN,
new Rect(0, 0, secondarySplitRawBounds.left, h),
new Rect(l, t, secondarySplitRawBounds.left, t + ih),
new Rect(l, t, l + iw, t + ih),
new Rect(0, 0, w, h)));
}
@@ -178,7 +180,7 @@ public class DragAndDropPolicy {
} else {
// Otherwise only show the fullscreen target
mTargets.add(new Target(TYPE_FULLSCREEN,
new Rect(0, 0, w, h),
new Rect(l, t, l + iw, t + ih),
new Rect(l, t, l + iw, t + ih),
new Rect(0, 0, w, h)));
}
@@ -210,6 +212,7 @@ public class DragAndDropPolicy {
final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
final Intent dragData = mSession.dragData;
boolean deferAppLaunchUntilSplit = false;
if (target.type == TYPE_FULLSCREEN) {
if (mSplitScreen != null && mSplitScreen.isDividerVisible()) {
// If in split, remove split and launch fullscreen
@@ -226,20 +229,40 @@ public class DragAndDropPolicy {
// Not in split, enter split now
mStarter.enterSplitScreen(mSession.runningTaskId,
target.type == TYPE_SPLIT_LEFT || target.type == TYPE_SPLIT_TOP);
deferAppLaunchUntilSplit = true;
}
}
Bundle opts = dragData.hasExtra(EXTRA_ACTIVITY_OPTIONS)
? dragData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS)
: null;
if (isTask) {
mStarter.startTask(dragData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID), opts);
} else if (isShortcut) {
mStarter.startShortcut(dragData.getStringExtra(EXTRA_PACKAGE_NAME),
dragData.getStringExtra(EXTRA_SHORTCUT_ID),
opts, dragData.getParcelableExtra(EXTRA_USER));
final Runnable startAppRunnable = () -> {
Bundle opts = dragData.hasExtra(EXTRA_ACTIVITY_OPTIONS)
? dragData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS)
: null;
if (isTask) {
mStarter.startTask(dragData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID), opts);
} else if (isShortcut) {
mStarter.startShortcut(dragData.getStringExtra(EXTRA_PACKAGE_NAME),
dragData.getStringExtra(EXTRA_SHORTCUT_ID),
opts, dragData.getParcelableExtra(EXTRA_USER));
} else {
mStarter.startIntent(dragData.getParcelableExtra(EXTRA_PENDING_INTENT), opts);
}
};
if (deferAppLaunchUntilSplit) {
// TODO(b/169894807): The enterSplitScreen() call above will trigger the current task
// into split, and we should wait for home and other tasks to be moved to
// split-secondary before trying to launch the new secondary task. This can be removed
// once we have app-pairs.
mSplitScreen.registerInSplitScreenListener(new Consumer<Boolean>() {
@Override
public void accept(Boolean inSplit) {
if (inSplit) {
startAppRunnable.run();
mSplitScreen.unregisterInSplitScreenListener(this);
}
}
});
} else {
mStarter.startIntent(dragData.getParcelableExtra(EXTRA_PENDING_INTENT), opts);
startAppRunnable.run();
}
}
@@ -274,7 +297,7 @@ public class DragAndDropPolicy {
* Updates the session data based on the current state of the system.
*/
void update() {
final ClipDescription description = mInitialDragData.getDescription();
try {
List<ActivityManager.RunningTaskInfo> tasks =
mIActivityTaskManager.getFilteredTasks(1,

View File

@@ -135,17 +135,25 @@ public class DragLayout extends View {
// visibility of the current region
DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation(
(int) event.getX(), (int) event.getY());
if (target != null && mCurrentTarget != target) {
if (mCurrentTarget != target) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current target: %s", target);
Interpolator boundsInterpolator = FAST_OUT_SLOW_IN;
if (mCurrentTarget == null) {
if (target == null) {
// Animating to no target
mDropOutline.startVisibilityAnimation(false, LINEAR);
Rect finalBounds = new Rect(mCurrentTarget.drawRegion);
finalBounds.inset(mDisplayMargin, mDisplayMargin);
mDropOutline.startBoundsAnimation(finalBounds, FAST_OUT_LINEAR_IN);
} else if (mCurrentTarget == null) {
// Animating to first target
mDropOutline.startVisibilityAnimation(true, LINEAR);
Rect initialBounds = new Rect(target.drawRegion);
initialBounds.inset(mDisplayMargin, mDisplayMargin);
mDropOutline.setRegionBounds(initialBounds);
boundsInterpolator = LINEAR_OUT_SLOW_IN;
mDropOutline.startBoundsAnimation(target.drawRegion, LINEAR_OUT_SLOW_IN);
} else {
// Bounds change
mDropOutline.startBoundsAnimation(target.drawRegion, FAST_OUT_SLOW_IN);
}
mDropOutline.startBoundsAnimation(target.drawRegion, boundsInterpolator);
mCurrentTarget = target;
}
}

View File

@@ -324,8 +324,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
if (task.configuration.windowConfiguration.getWindowingMode()
!= WINDOWING_MODE_PINNED) {
if (task.getWindowingMode() != WINDOWING_MODE_PINNED) {
return;
}
mTouchHandler.getMotionHelper().expandLeavePip(

View File

@@ -398,8 +398,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
private void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean clearedTask) {
if (task.configuration.windowConfiguration.getWindowingMode()
!= WINDOWING_MODE_PINNED) {
if (task.getWindowingMode() != WINDOWING_MODE_PINNED) {
return;
}
if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");

View File

@@ -57,6 +57,9 @@ public interface SplitScreen {
/** Registers listener that gets called whenever the existence of the divider changes. */
void registerInSplitScreenListener(Consumer<Boolean> listener);
/** Unregisters listener that gets called whenever the existence of the divider changes. */
void unregisterInSplitScreenListener(Consumer<Boolean> listener);
/** Registers listener that gets called whenever the split screen bounds changes. */
void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener);

View File

@@ -58,6 +58,7 @@ import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -85,8 +86,8 @@ public class SplitScreenController implements SplitScreen,
private final WindowManagerProxy mWindowManagerProxy;
private final TaskOrganizer mTaskOrganizer;
private final ArrayList<WeakReference<Consumer<Boolean>>> mDockedStackExistsListeners =
new ArrayList<>();
private final CopyOnWriteArrayList<WeakReference<Consumer<Boolean>>> mDockedStackExistsListeners
= new CopyOnWriteArrayList<>();
private final ArrayList<WeakReference<BiConsumer<Rect, Rect>>> mBoundsChangedListeners =
new ArrayList<>();
@@ -173,7 +174,7 @@ public class SplitScreenController implements SplitScreen,
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
if (!wasVisible || task.configuration.windowConfiguration.getWindowingMode()
if (!wasVisible || task.getWindowingMode()
!= WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
|| !mSplits.isSplitScreenSupported()) {
return;
@@ -477,6 +478,17 @@ public class SplitScreenController implements SplitScreen,
}
}
@Override
public void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
synchronized (mDockedStackExistsListeners) {
for (int i = mDockedStackExistsListeners.size() - 1; i >= 0; i--) {
if (mDockedStackExistsListeners.get(i) == listener) {
mDockedStackExistsListeners.remove(i);
}
}
}
}
@Override
public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
synchronized (mBoundsChangedListeners) {
@@ -500,9 +512,7 @@ public class SplitScreenController implements SplitScreen,
}
// Note: The set of running tasks from the system is ordered by recency.
final RunningTaskInfo topRunningTask = runningTasks.get(0);
final int activityType = topRunningTask.configuration.windowConfiguration
.getActivityType();
final int activityType = topRunningTask.getActivityType();
if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
return false;
}

View File

@@ -23,7 +23,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.wm.shell.ShellTaskOrganizer.getWindowingMode;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import android.app.ActivityManager.RunningTaskInfo;
@@ -42,6 +41,7 @@ import com.android.wm.shell.Transitions;
import com.android.wm.shell.common.SyncTransactionQueue;
import java.io.PrintWriter;
import java.util.ArrayList;
class SplitScreenTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = "SplitScreenTaskListener";
@@ -106,7 +106,7 @@ class SplitScreenTaskListener implements ShellTaskOrganizer.TaskListener {
return;
}
final int winMode = getWindowingMode(taskInfo);
final int winMode = taskInfo.getWindowingMode();
if (winMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
ProtoLog.v(WM_SHELL_TASK_ORG,
"%s onTaskAppeared Primary taskId=%d", TAG, taskInfo.taskId);
@@ -283,6 +283,21 @@ class SplitScreenTaskListener implements ShellTaskOrganizer.TaskListener {
mSplitScreenController.startEnterSplit();
}
} else if (secondaryImpliesMinimize) {
// Workaround for b/172686383, we can't rely on the sync bounds change transaction for
// the home task to finish before the last updateChildTaskSurface() call even if it's
// queued on the sync transaction queue, so ensure that the home task surface is updated
// again before we minimize
final ArrayList<RunningTaskInfo> tasks = new ArrayList<>();
mSplitScreenController.getWmProxy().getHomeAndRecentsTasks(tasks,
mSplitScreenController.getSecondaryRoot());
for (int i = 0; i < tasks.size(); i++) {
final RunningTaskInfo taskInfo = tasks.get(i);
final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId);
if (leash != null) {
updateChildTaskSurface(taskInfo, leash, false /* firstAppeared */);
}
}
// Both splits are populated but the secondary split has a home/recents stack on top,
// so enter minimized mode.
mSplitScreenController.ensureMinimizedSplit();

View File

@@ -120,7 +120,7 @@ class WindowManagerProxy {
new WindowOrganizer().applyTransaction(t);
}
private boolean getHomeAndRecentsTasks(List<ActivityManager.RunningTaskInfo> out,
boolean getHomeAndRecentsTasks(List<ActivityManager.RunningTaskInfo> out,
WindowContainerToken parent) {
boolean resizable = false;
List<ActivityManager.RunningTaskInfo> rootTasks = parent == null
@@ -209,8 +209,7 @@ class WindowManagerProxy {
continue;
}
// Only move fullscreen tasks to split secondary.
if (rootTask.configuration.windowConfiguration.getWindowingMode()
!= WINDOWING_MODE_FULLSCREEN) {
if (rootTask.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
continue;
}
// Since this iterates from bottom to top, update topHomeTask for every fullscreen task
@@ -232,7 +231,7 @@ class WindowManagerProxy {
}
boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) {
final int atype = ti.configuration.windowConfiguration.getActivityType();
final int atype = ti.getActivityType();
return atype == ACTIVITY_TYPE_HOME || atype == ACTIVITY_TYPE_RECENTS;
}

View File

@@ -35,6 +35,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
@@ -68,10 +69,13 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.function.Consumer;
/**
* Tests for the drag and drop policy.
@@ -124,6 +128,12 @@ public class DragAndDropPolicyTest {
doReturn(new Rect(50, 0, 100, 100)).when(divider)
.getNonMinimizedSplitScreenSecondaryBounds();
doAnswer((Answer<Void>) invocation -> {
Consumer<Boolean> callback = invocation.getArgument(0);
callback.accept(true);
return null;
}).when(mSplitScreen).registerInSplitScreenListener(any());
mPolicy = new DragAndDropPolicy(mContext, mIActivityTaskManager, mSplitScreen, mStarter);
mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);

View File

@@ -49,6 +49,7 @@ class DragDropController {
static final int MSG_DRAG_END_TIMEOUT = 0;
static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 1;
static final int MSG_ANIMATION_END = 2;
static final int MSG_REMOVE_DRAG_SURFACE_TIMEOUT = 3;
/**
* Drag state per operation.
@@ -384,6 +385,14 @@ class DragDropController {
}
break;
}
case MSG_REMOVE_DRAG_SURFACE_TIMEOUT: {
synchronized (mService.mGlobalLock) {
mService.mTransactionFactory.get()
.reparent((SurfaceControl) msg.obj, null).apply();
}
break;
}
}
}
}

View File

@@ -23,6 +23,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.wm.DragDropController.MSG_ANIMATION_END;
import static com.android.server.wm.DragDropController.MSG_DRAG_END_TIMEOUT;
import static com.android.server.wm.DragDropController.MSG_REMOVE_DRAG_SURFACE_TIMEOUT;
import static com.android.server.wm.DragDropController.MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -59,6 +60,7 @@ import android.view.WindowManager;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import com.android.internal.os.SomeArgs;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.view.IDragAndDropPermissions;
import com.android.server.LocalServices;
@@ -248,6 +250,9 @@ class DragState {
if (mSurfaceControl != null) {
if (!mRelinquishDragSurface) {
mTransaction.reparent(mSurfaceControl, null).apply();
} else {
mDragDropController.sendTimeoutMessage(MSG_REMOVE_DRAG_SURFACE_TIMEOUT,
mSurfaceControl);
}
mSurfaceControl = null;
}

View File

@@ -4072,6 +4072,10 @@ class Task extends WindowContainer<WindowContainer> {
info.taskDescription = new ActivityManager.TaskDescription(getTaskDescription());
info.supportsSplitScreenMultiWindow = supportsSplitScreenWindowingMode();
info.configuration.setTo(getConfiguration());
// Update to the task's current activity type and windowing mode which may differ from the
// window configuration
info.configuration.windowConfiguration.setActivityType(getActivityType());
info.configuration.windowConfiguration.setWindowingMode(getWindowingMode());
info.token = mRemoteToken.toWindowContainerToken();
//TODO (AM refactor): Just use local once updateEffectiveIntent is run during all child