Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 112be924 authored by Winson Chung's avatar Winson Chung
Browse files

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
parent 326dd9c1
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -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
+112 −13
Original line number Diff line number Diff line
@@ -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;
        }
    }
}
+28 −0
Original line number Diff line number Diff line
/*
 * 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
 No newline at end of file
+2 −0
Original line number Diff line number Diff line
@@ -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";
}
+5 −1
Original line number Diff line number Diff line
@@ -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();
    }