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

Commit 26626108 authored by Nergi Rahardi's avatar Nergi Rahardi Committed by Android (Google) Code Review
Browse files

Merge changes I77cb3ee2,Ie247dc30 into main

* changes:
  [DnD] Listen to topology changes in DragDropController
  Refactor VirtualDisplay creation and cleanup to its own utils
parents c471f457 7e365f70
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
@@ -28,9 +28,11 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
import android.content.ClipData;
import android.content.Context;
import android.hardware.display.DisplayTopology;
import android.hardware.input.InputManagerGlobal;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -49,6 +51,7 @@ import android.window.IGlobalDragListener;
import android.window.IUnhandledDragCallback;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.hidden_from_bootclasspath.com.android.window.flags.Flags;
import com.android.server.wm.WindowManagerInternal.IDragDropCallback;

import java.util.Objects;
@@ -56,6 +59,7 @@ import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

/**
 * Managing drag and drop operations initiated by View#startDragAndDrop.
@@ -83,6 +87,8 @@ class DragDropController {

    private WindowManagerService mService;
    private final Handler mHandler;
    private final Consumer<DisplayTopology> mDisplayTopologyListener =
            this::handleDisplayTopologyChange;

    // The global drag listener for handling cross-window drags
    private IGlobalDragListener mGlobalDragListener;
@@ -108,6 +114,17 @@ class DragDropController {
    DragDropController(WindowManagerService service, Looper looper) {
        mService = service;
        mHandler = new DragHandler(service, looper);
        if (Flags.enableConnectedDisplaysDnd()) {
            mService.mDisplayManager.registerTopologyListener(
                    new HandlerExecutor(mService.mH), mDisplayTopologyListener);
        }
    }

    @VisibleForTesting
    void cleanupListeners() {
        if (Flags.enableConnectedDisplaysDnd()) {
            mService.mDisplayManager.unregisterTopologyListener(mDisplayTopologyListener);
        }
    }

    @VisibleForTesting
@@ -481,6 +498,18 @@ class DragDropController {
        }
    }

    private void handleDisplayTopologyChange(DisplayTopology unused) {
        synchronized (mService.mGlobalLock) {
            if (mDragState == null) {
                return;
            }
            if (DEBUG_DRAG) {
                Slog.d(TAG_WM, "DisplayTopology changed, cancelling DragAndDrop");
            }
            cancelDragAndDrop(mDragState.mToken, true /* skipAnimation */);
        }
    }

    /**
     * Handles motion events.
     * @param keepHandling Whether if the drag operation is continuing or this is the last motion
+1 −0
Original line number Diff line number Diff line
@@ -101,6 +101,7 @@ android_test {
        "testng",
        "truth",
        "wmtests-support",
        "display_flags_lib",
    ],

    libs: [
+63 −18
Original line number Diff line number Diff line
@@ -32,10 +32,12 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_TOPOLOGY;
import static com.android.server.wm.DragDropController.MSG_UNHANDLED_DROP_LISTENER_TIMEOUT;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -56,6 +58,7 @@ import android.content.Intent;
import android.content.pm.ShortcutServiceInternal;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.display.VirtualDisplay;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -66,6 +69,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.view.DragEvent;
import android.view.InputChannel;
import android.view.SurfaceControl;
@@ -79,12 +83,14 @@ import androidx.test.filters.SmallTest;

import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.utils.VirtualDisplayTestRule;
import com.android.window.flags.Flags;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -105,6 +111,8 @@ import java.util.function.Consumer;
@Presubmit
@RunWith(WindowTestRunner.class)
public class DragDropControllerTests extends WindowTestsBase {
    @Rule
    public VirtualDisplayTestRule mVirtualDisplayTestRule = new VirtualDisplayTestRule();
    private static final int TIMEOUT_MS = 3000;
    private static final int TEST_UID = 12345;
    private static final int TEST_PROFILE_UID = 12345 * UserHandle.PER_USER_RANGE;
@@ -220,13 +228,17 @@ public class DragDropControllerTests extends WindowTestsBase {

    @After
    public void tearDown() throws Exception {
        final CountDownLatch latch;
        // Besides TestDragDropController, WMService also creates another DragDropController in
        // test, and since listeners are added on instantiation, it has to be cleared here as well.
        mTarget.cleanupListeners();
        mWm.mDragDropController.cleanupListeners();
        if (!mTarget.dragDropActiveLocked()) {
            return;
        }
        if (mToken != null) {
            mTarget.cancelDragAndDrop(mToken, false);
        }
        final CountDownLatch latch;
        latch = new CountDownLatch(1);
        mTarget.setOnClosedCallbackLocked(latch::countDown);
        if (mTarget.mIsAccessibilityDrag) {
@@ -337,12 +349,13 @@ public class DragDropControllerTests extends WindowTestsBase {

                    // Verify the drop event is only sent for the global intercept window
                    assertTrue(nonLocalWindowDragEvents.isEmpty());
                    assertTrue(last(localWindowDragEvents).getAction() != ACTION_DROP);
                    assertTrue(last(globalInterceptWindowDragEvents).getAction() == ACTION_DROP);
                    assertNotEquals(ACTION_DROP, localWindowDragEvents.getLast().getAction());
                    assertEquals(ACTION_DROP,
                            globalInterceptWindowDragEvents.getLast().getAction());

                    // Verify that item extras were not sent with the drop event
                    assertNull(last(localWindowDragEvents).getClipData());
                    assertFalse(last(globalInterceptWindowDragEvents).getClipData()
                    assertNull(localWindowDragEvents.getLast().getClipData());
                    assertFalse(globalInterceptWindowDragEvents.getLast().getClipData()
                            .willParcelWithActivityInfo());
                });
    }
@@ -384,7 +397,7 @@ public class DragDropControllerTests extends WindowTestsBase {
    }

    @Test
    public void testDragEventCoordinates() {
    public void testDragEventCoordinatesOverlappingWindows() {
        int dragStartX = mWindow.getBounds().centerX();
        int dragStartY = mWindow.getBounds().centerY();
        int startOffsetPx = 10;
@@ -429,7 +442,7 @@ public class DragDropControllerTests extends WindowTestsBase {
                        // Verify only window2 received the DROP event and coords are sent as-is.
                        assertEquals(1, dragEvents.size());
                        assertEquals(2, dragEvents2.size());
                        final DragEvent dropEvent = last(dragEvents2);
                        final DragEvent dropEvent = dragEvents2.getLast();
                        assertEquals(ACTION_DROP, dropEvent.getAction());
                        assertEquals(dropCoordsPx, dropEvent.getX(),  0.0 /* delta */);
                        assertEquals(dropCoordsPx, dropEvent.getY(),  0.0 /* delta */);
@@ -437,10 +450,10 @@ public class DragDropControllerTests extends WindowTestsBase {

                        mTarget.reportDropResult(iwindow2, true);
                        // Verify both windows received ACTION_DRAG_ENDED event.
                        assertEquals(ACTION_DRAG_ENDED, last(dragEvents).getAction());
                        assertEquals(window2.getDisplayId(), last(dragEvents).getDisplayId());
                        assertEquals(ACTION_DRAG_ENDED, last(dragEvents2).getAction());
                        assertEquals(window2.getDisplayId(), last(dragEvents2).getDisplayId());
                        assertEquals(ACTION_DRAG_ENDED, dragEvents.getLast().getAction());
                        assertEquals(window2.getDisplayId(), dragEvents.getLast().getDisplayId());
                        assertEquals(ACTION_DRAG_ENDED, dragEvents2.getLast().getAction());
                        assertEquals(window2.getDisplayId(), dragEvents2.getLast().getDisplayId());
                    } finally {
                        mTarget.continueDragStateClose();
                    }
@@ -493,7 +506,7 @@ public class DragDropControllerTests extends WindowTestsBase {
                        // Verify only window2 received the DROP event and coords are sent as-is
                        assertEquals(1, dragEvents.size());
                        assertEquals(2, dragEvents2.size());
                        final DragEvent dropEvent = last(dragEvents2);
                        final DragEvent dropEvent = dragEvents2.getLast();
                        assertEquals(ACTION_DROP, dropEvent.getAction());
                        assertEquals(dropCoordsPx, dropEvent.getX(),  0.0 /* delta */);
                        assertEquals(dropCoordsPx, dropEvent.getY(),  0.0 /* delta */);
@@ -501,10 +514,12 @@ public class DragDropControllerTests extends WindowTestsBase {

                        mTarget.reportDropResult(iwindow2, true);
                        // Verify both windows received ACTION_DRAG_ENDED event.
                        assertEquals(ACTION_DRAG_ENDED, last(dragEvents).getAction());
                        assertEquals(testDisplay.getDisplayId(), last(dragEvents).getDisplayId());
                        assertEquals(ACTION_DRAG_ENDED, last(dragEvents2).getAction());
                        assertEquals(testDisplay.getDisplayId(), last(dragEvents2).getDisplayId());
                        assertEquals(ACTION_DRAG_ENDED, dragEvents.getLast().getAction());
                        assertEquals(testDisplay.getDisplayId(),
                                dragEvents.getLast().getDisplayId());
                        assertEquals(ACTION_DRAG_ENDED, dragEvents2.getLast().getAction());
                        assertEquals(testDisplay.getDisplayId(),
                                dragEvents2.getLast().getDisplayId());
                    } finally {
                        mTarget.continueDragStateClose();
                    }
@@ -561,8 +576,30 @@ public class DragDropControllerTests extends WindowTestsBase {
                });
    }

    private DragEvent last(ArrayList<DragEvent> list) {
        return list.get(list.size() - 1);
    @Test
    @RequiresFlagsEnabled(FLAG_DISPLAY_TOPOLOGY)
    @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_DND)
    public void testDragCancelledOnTopologyChange() {
        VirtualDisplay virtualDisplay = createVirtualDisplay();
        // Necessary for now since DragState.sendDragStartedLocked() will recycle drag events
        // immediately after dispatching, which is a problem when using mockito arguments captor
        // because it returns and modifies the same drag event.
        TestIWindow iwindow = (TestIWindow) mWindow.mClient;
        final ArrayList<DragEvent> dragEvents = new ArrayList<>();
        iwindow.setDragEventJournal(dragEvents);

        startDrag(0, 0, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ,
                ClipData.newPlainText("label", "text"), (surface) -> {
                    final CountDownLatch latch = new CountDownLatch(1);
                    mTarget.setOnClosedCallbackLocked(latch::countDown);

                    // Release virtual display to trigger drag-and-drop cancellation.
                    virtualDisplay.release();
                    assertTrue(awaitInWmLock(() -> latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)));

                    assertEquals(2, dragEvents.size());
                    assertEquals(ACTION_DRAG_ENDED, dragEvents.getLast().getAction());
                });
    }

    @Test
@@ -942,4 +979,12 @@ public class DragDropControllerTests extends WindowTestsBase {
        assertNotNull(mToken);
        r.run();
    }

    private VirtualDisplay createVirtualDisplay() {
        final int width = 800;
        final int height = 600;
        final String name = getClass().getSimpleName() + "_VirtualDisplay";
        return mVirtualDisplayTestRule.createDisplayManagerAttachedVirtualDisplay(name, width,
                height);
    }
}
+6 −27
Original line number Diff line number Diff line
@@ -16,9 +16,6 @@

package com.android.server.wm;

import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
import static android.os.Build.HW_TIMEOUT_MULTIPLIER;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -41,10 +38,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.ImageReader;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -57,13 +51,14 @@ import android.widget.LinearLayout;
import androidx.test.filters.MediumTest;

import com.android.server.wm.utils.CommonUtils;
import com.android.server.wm.utils.VirtualDisplayTestRule;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -76,9 +71,9 @@ import java.util.function.Predicate;
@MediumTest
public class TaskStackChangedListenerTest {

    @Rule
    public VirtualDisplayTestRule mVirtualDisplayTestRule = new VirtualDisplayTestRule();
    private ITaskStackListener mTaskStackListener;
    private VirtualDisplay mVirtualDisplay;
    private ImageReader mImageReader;
    private final ArrayList<Activity> mStartedActivities = new ArrayList<>();

    private static final int WAIT_TIMEOUT_MS = 5000 * HW_TIMEOUT_MULTIPLIER;
@@ -94,10 +89,6 @@ public class TaskStackChangedListenerTest {
        if (mTaskStackListener != null) {
            ActivityTaskManager.getService().unregisterTaskStackListener(mTaskStackListener);
        }
        if (mVirtualDisplay != null) {
            mVirtualDisplay.release();
            mImageReader.close();
        }
        // Finish from bottom to top.
        final int size = mStartedActivities.size();
        for (int i = 0; i < size; i++) {
@@ -116,21 +107,9 @@ public class TaskStackChangedListenerTest {
    private VirtualDisplay createVirtualDisplay() {
        final int width = 800;
        final int height = 600;
        final int density = 160;
        final DisplayManager displayManager = getInstrumentation().getContext().getSystemService(
                DisplayManager.class);
        mImageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888,
                2 /* maxImages */);
        final int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
                | VIRTUAL_DISPLAY_FLAG_PUBLIC;
        final String name = getClass().getSimpleName() + "_VirtualDisplay";
        mVirtualDisplay = displayManager.createVirtualDisplay(name, width, height, density,
                mImageReader.getSurface(), flags);
        mVirtualDisplay.setSurface(mImageReader.getSurface());
        assertNotNull("display must be registered",
                Arrays.stream(displayManager.getDisplays()).filter(
                        d -> d.getName().equals(name)).findAny());
        return mVirtualDisplay;
        return mVirtualDisplayTestRule.createDisplayManagerAttachedVirtualDisplay(name, width,
                height);
    }

    @Test
+92 −0
Original line number Diff line number Diff line
/*
 * Copyright 2025 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.server.wm.utils;

import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import static org.junit.Assert.assertNotNull;

import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.ImageReader;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/** Provides wrapper test rule for creating and managing the cleanup for VirtualDisplay */
public class VirtualDisplayTestRule implements TestRule {
    private static final int DISPLAY_DENSITY = 160;

    private final List<VirtualDisplay> mVirtualDisplays = new ArrayList<>();
    private final List<ImageReader> mImageReaders = new ArrayList<>();
    private final DisplayManager mDisplayManager;

    public VirtualDisplayTestRule() {
        mDisplayManager = getInstrumentation().getTargetContext().getSystemService(
                DisplayManager.class);
    }

    @Override
    public Statement apply(Statement base, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                try {
                    base.evaluate();
                } finally {
                    tearDown();
                }
            }
        };
    }

    private void tearDown() {
        mVirtualDisplays.forEach(VirtualDisplay::release);
        mImageReaders.forEach(ImageReader::close);
    }

    /**
     * The virtual display in WindowTestsBase#createMockSimulatedDisplay is only attached to WM
     * DisplayWindowSettingsProvider. DisplayManager is not aware of mock simulated display and
     * therefore couldn't be used for actual Display-related testing (e.g. display listeners).
     * This method creates real VirtualDisplay through DisplayManager.
     */
    public VirtualDisplay createDisplayManagerAttachedVirtualDisplay(String name, int width,
            int height) {
        final ImageReader imageReader = ImageReader.newInstance(width, height,
                PixelFormat.RGBA_8888, 2 /* maxImages */);
        mImageReaders.add(imageReader);
        final int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
                | VIRTUAL_DISPLAY_FLAG_PUBLIC;
        final VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(name, width,
                height, DISPLAY_DENSITY, imageReader.getSurface(), flags);
        mVirtualDisplays.add(virtualDisplay);
        assertNotNull("display must be registered", Arrays.stream(
                mDisplayManager.getDisplays()).filter(d -> d.getName().equals(name)).findAny());
        return virtualDisplay;
    }
}