Add test mechanism to check whether the shell drop target window has drawn

- In rare cases on CF, the time to draw the shell drop target can be
  longer than the entire tapl drag gesture.  Instead, we expose a
  call that Launcher can use to check whether the drop target window
  has drawn since it was last made visible which tapl can wait for
  when invoking a drag.

Bug: 234653212
Test: atest NexusLauncherTests
Test: atest DragAndDropControllerTest

Change-Id: Id152b05651f1646a12e9e7b4bb1b7e52c4a9ecea
This commit is contained in:
Winson Chung
2023-04-17 06:24:43 +00:00
parent 326dd9c115
commit 112be92452
5 changed files with 149 additions and 15 deletions

View File

@@ -189,12 +189,13 @@ public abstract class WMShellBaseModule {
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,
displayController, uiEventLogger, iconProvider, mainExecutor));
shellCommandHandler, displayController, uiEventLogger, iconProvider, mainExecutor));
}
@WMSingleton

View File

@@ -35,10 +35,14 @@ 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 static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
import android.content.ClipDescription;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.HardwareRenderer;
import android.graphics.PixelFormat;
import android.util.Slog;
import android.util.SparseArray;
@@ -50,6 +54,8 @@ import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.InstanceId;
@@ -58,25 +64,31 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
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.annotations.ExternalMainThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
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 java.io.PrintWriter;
import java.util.ArrayList;
/**
* Handles the global drag and drop handling for the Shell.
*/
public class DragAndDropController implements DisplayController.OnDisplaysChangedListener,
public class DragAndDropController implements RemoteCallable<DragAndDropController>,
DisplayController.OnDisplaysChangedListener,
View.OnDragListener, ComponentCallbacks2 {
private static final String TAG = DragAndDropController.class.getSimpleName();
private final Context mContext;
private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
private final DisplayController mDisplayController;
private final DragAndDropEventLogger mLogger;
private final IconProvider mIconProvider;
@@ -100,6 +112,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
public static DragAndDropController create(Context context,
ShellInit shellInit,
ShellController shellController,
ShellCommandHandler shellCommandHandler,
DisplayController displayController,
UiEventLogger uiEventLogger,
IconProvider iconProvider,
@@ -107,19 +120,21 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
if (!context.getResources().getBoolean(R.bool.config_enableShellDragDrop)) {
return null;
}
return new DragAndDropController(context, shellInit, shellController, displayController,
uiEventLogger, iconProvider, mainExecutor);
return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
displayController, uiEventLogger, iconProvider, mainExecutor);
}
DragAndDropController(Context context,
ShellInit shellInit,
ShellController shellController,
ShellCommandHandler shellCommandHandler,
DisplayController displayController,
UiEventLogger uiEventLogger,
IconProvider iconProvider,
ShellExecutor mainExecutor) {
mContext = context;
mShellController = shellController;
mShellCommandHandler = shellCommandHandler;
mDisplayController = displayController;
mLogger = new DragAndDropEventLogger(uiEventLogger);
mIconProvider = iconProvider;
@@ -137,6 +152,23 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
mMainExecutor.executeDelayed(() -> {
mDisplayController.addDisplayWindowListener(this);
}, 0);
mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP,
this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
}
private ExternalInterfaceBinder createExternalInterface() {
return new IDragAndDropImpl(this);
}
@Override
public Context getContext() {
return mContext;
}
@Override
public ShellExecutor getRemoteCallExecutor() {
return mMainExecutor;
}
/**
@@ -156,7 +188,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
mListeners.remove(listener);
}
private void notifyListeners() {
private void notifyDragStarted() {
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).onDragStarted();
}
@@ -273,7 +305,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
pd.dragLayout.prepare(mDisplayController.getDisplayLayout(displayId),
event.getClipData(), loggerSessionId);
setDropTargetWindowVisibility(pd, View.VISIBLE);
notifyListeners();
notifyDragStarted();
break;
case ACTION_DRAG_ENTERED:
pd.dragLayout.show();
@@ -327,13 +359,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
}
private void setDropTargetWindowVisibility(PerDisplay pd, int visibility) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"Set drop target window visibility: displayId=%d visibility=%d",
pd.displayId, visibility);
pd.rootView.setVisibility(visibility);
if (visibility == View.VISIBLE) {
pd.rootView.requestApplyInsets();
}
pd.setWindowVisibility(visibility);
}
private String getMimeTypes(ClipDescription description) {
@@ -347,6 +373,18 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
return mimeTypes;
}
/**
* Returns if any displays are currently ready to handle a drag/drop.
*/
private boolean isReadyToHandleDrag() {
for (int i = 0; i < mDisplayDropTargets.size(); i++) {
if (mDisplayDropTargets.valueAt(i).mHasDrawn) {
return true;
}
}
return false;
}
// Note: Component callbacks are always called on the main thread of the process
@ExternalMainThread
@Override
@@ -372,12 +410,53 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
// Do nothing
}
private static class PerDisplay {
/**
* Dumps information about this controller.
*/
public void dump(@NonNull PrintWriter pw, String prefix) {
pw.println(prefix + TAG);
pw.println(prefix + " listeners=" + mListeners.size());
}
/**
* The interface for calls from outside the host process.
*/
@BinderThread
private static class IDragAndDropImpl extends IDragAndDrop.Stub
implements ExternalInterfaceBinder {
private DragAndDropController mController;
public IDragAndDropImpl(DragAndDropController controller) {
mController = controller;
}
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
@Override
public void invalidate() {
mController = null;
}
@Override
public boolean isReadyToHandleDrag() {
boolean[] result = new boolean[1];
executeRemoteCallWithTaskPermission(mController, "isReadyToHandleDrag",
controller -> result[0] = controller.isReadyToHandleDrag(),
true /* blocking */
);
return result[0];
}
}
private static class PerDisplay implements HardwareRenderer.FrameDrawingCallback {
final int displayId;
final Context context;
final WindowManager wm;
final FrameLayout rootView;
final DragLayout dragLayout;
// Tracks whether the window has fully drawn since it was last made visible
boolean mHasDrawn;
boolean isHandlingDrag;
// A count of the number of active drags in progress to ensure that we only hide the window
@@ -391,5 +470,25 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
rootView = rv;
dragLayout = dl;
}
private void setWindowVisibility(int visibility) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"Set drop target window visibility: displayId=%d visibility=%d",
displayId, visibility);
rootView.setVisibility(visibility);
if (visibility == View.VISIBLE) {
rootView.requestApplyInsets();
if (!mHasDrawn && rootView.getViewRootImpl() != null) {
rootView.getViewRootImpl().registerRtFrameCallback(this);
}
} else {
mHasDrawn = false;
}
}
@Override
public void onFrameDraw(long frame) {
mHasDrawn = true;
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* 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.draganddrop;
/**
* Interface that is exposed to remote callers to manipulate drag and drop.
*/
interface IDragAndDrop {
/**
* Returns whether the shell drop target is showing and will handle a drag/drop.
*/
boolean isReadyToHandleDrag() = 1;
}
// Last id = 1

View File

@@ -42,4 +42,6 @@ public class ShellSharedConstants {
public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
// See IDesktopMode.aidl
public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
// See IDragAndDrop.aidl
public static final String KEY_EXTRA_SHELL_DRAG_AND_DROP = "extra_shell_drag_and_drop";
}

View File

@@ -48,6 +48,7 @@ import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -71,6 +72,8 @@ public class DragAndDropControllerTest extends ShellTestCase {
@Mock
private ShellController mShellController;
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
private DisplayController mDisplayController;
@Mock
private UiEventLogger mUiEventLogger;
@@ -89,7 +92,8 @@ public class DragAndDropControllerTest extends ShellTestCase {
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
mController = new DragAndDropController(mContext, mShellInit, mShellController,
mDisplayController, mUiEventLogger, mIconProvider, mMainExecutor);
mShellCommandHandler, mDisplayController, mUiEventLogger, mIconProvider,
mMainExecutor);
mController.onInit();
}