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

Commit 3e3b4221 authored by Nicolo' Mazzucato's avatar Nicolo' Mazzucato
Browse files

Enable non-activity windows to be re-parented between displays

This allow a window added using a WindowContext's WindowManager to be reparented to a different display without being removed and re-added, using "WindowContext#reparentToDisplayId" (hidden API).

A call to "reparentToDisplayId" updates the context and its resources to match the destination display ID and characteristics.

For this reason, it is necessary to inflate all the views that are moved with this API with a separate context from other views (e.g. creating one with applicationContext.createWindowContext(...)).

This also prevents "onAttached" and "onDetached" callbacks from being invoked after the view is reparented. Only a configurationChange + onMovedToDisplay is expected to be received from the window's root view.

The motivation of this change is that SysUI (especially flexiglass) is written assuming onAttached happens only once, and it would have taken a huge refactor to change this.

Also, using this API results in a much faster shade movement (as all the sysui code that was triggering "onAttached", is not triggered anymore).

Note that this API is used in SysUI in the child cl.

Bug: 362719719
Bug: 381258683
Bug: 381075014
Test: WindowContextListenerControllerTests, WindowManagerServiceTests, WindowTokenTests
Flag: com.android.window.flags.reparent_window_token_api
Change-Id: Icd17761951c26786d3e8120fce677265acf9390a
parent a424fedc
Loading
Loading
Loading
Loading
+21 −0
Original line number Original line Diff line number Diff line
@@ -932,6 +932,27 @@ interface IWindowManager
     */
     */
    void detachWindowContext(IBinder clientToken);
    void detachWindowContext(IBinder clientToken);


    /**
     * Reparents the {@link android.window.WindowContext} to the
     * {@link com.android.server.wm.DisplayArea} on another display.
     * This method also reparent the WindowContext associated WindowToken to another display if
     * necessary.
     * <p>
     * {@code type} and {@code options} must be the same as the previous call of
     * {@link #attachWindowContextToDisplayArea} on the same Context otherwise this will fail
     * silently.
     *
     * @param appThread the process that the window context is on.
     * @param clientToken the window context's token
     * @param type The window type of the WindowContext
     * @param displayId The new display id this context windows should be parented to
     * @param options Bundle the context was created with
     *
     * @return True if the operation was successful, False otherwise.
     */
    boolean reparentWindowContextToDisplayArea(in IApplicationThread appThread,
                IBinder clientToken, int displayId);

    /**
    /**
     * Registers a listener, which is to be called whenever cross-window blur is enabled/disabled.
     * Registers a listener, which is to be called whenever cross-window blur is enabled/disabled.
     *
     *
+12 −11
Original line number Original line Diff line number Diff line
@@ -32,6 +32,7 @@ import android.view.Display;
import android.view.WindowManager;
import android.view.WindowManager;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
import com.android.window.flags.Flags;


import java.lang.ref.Reference;
import java.lang.ref.Reference;


@@ -95,20 +96,20 @@ public class WindowContext extends ContextWrapper implements WindowProvider {
    }
    }


    /**
    /**
     * Updates this context to a new displayId.
     * Moves this context to another display.
     * <p>
     * <p>
     * Note that this doesn't re-parent previously attached windows (they should be removed and
     * Note that this re-parents all the previously attached windows. Resources associated with this
     * re-added manually after this is called). Resources associated with this context will have
     * context will have the correct value and configuration for the new display after this is
     * the correct value and configuration for the new display after this is called.
     * called.
     */
     */
    @Override
    public void reparentToDisplay(int displayId) {
    public void updateDisplay(int displayId) {
        if (Flags.reparentWindowTokenApi()) {
            if (displayId == getDisplayId()) {
            if (displayId == getDisplayId()) {
                return;
                return;
            }
            }
            super.updateDisplay(displayId);
            super.updateDisplay(displayId);
        mController.detachIfNeeded();
            mController.reparentToDisplayArea(mType, displayId, mOptions);
        mController.attachToDisplayArea(mType, displayId, mOptions);
        }
    }
    }


    @Override
    @Override
+15 −0
Original line number Original line Diff line number Diff line
@@ -158,6 +158,21 @@ public class WindowContextController {
        }
        }
    }
    }


    /**
     * Reparents the window context from the current attached display to another. {@code type} and
     * {@code options} must be the same as the previous attach call, otherwise this will fail
     * silently.
     */
    public void reparentToDisplayArea(
            @WindowType int type, int displayId, @Nullable Bundle options) {
        if (mAttachedToDisplayArea != AttachStatus.STATUS_ATTACHED) {
            attachToDisplayArea(type, displayId, options);
            return;
        }
        // No need to propagate type and options as this is already attached and they can't change.
        getWindowTokenClientController().reparentToDisplayArea(mToken, displayId);
    }

    /** Gets the {@link WindowTokenClientController}. */
    /** Gets the {@link WindowTokenClientController}. */
    @VisibleForTesting
    @VisibleForTesting
    @NonNull
    @NonNull
+15 −0
Original line number Original line Diff line number Diff line
@@ -197,6 +197,21 @@ public class WindowTokenClientController {
        }
        }
    }
    }


    /**
     * Reparents a {@link WindowTokenClient} and its associated WindowContainer if there's one.
     */
    public void reparentToDisplayArea(@NonNull WindowTokenClient client, int displayId) {
        try {
            if (!getWindowManagerService().reparentWindowContextToDisplayArea(mAppThread, client,
                    displayId)) {
                Log.e(TAG,
                        "Didn't succeed reparenting of " + client + " to displayId=" + displayId);
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    private void onWindowContextTokenAttached(@NonNull WindowTokenClient client,
    private void onWindowContextTokenAttached(@NonNull WindowTokenClient client,
            @NonNull WindowContextInfo info, boolean shouldReportConfigChange) {
            @NonNull WindowContextInfo info, boolean shouldReportConfigChange) {
        recordWindowContextToken(client);
        recordWindowContextToken(client);
+31 −6
Original line number Original line Diff line number Diff line
@@ -33,7 +33,9 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;


import android.app.Activity;
import android.app.Activity;
import android.app.EmptyActivity;
import android.app.EmptyActivity;
@@ -48,7 +50,9 @@ import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Binder;
import android.os.IBinder;
import android.os.IBinder;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.Display;
import android.view.Display;
import android.view.IWindowManager;
import android.view.IWindowManager;
import android.view.View;
import android.view.View;
@@ -64,6 +68,7 @@ import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.rule.ActivityTestRule;


import com.android.frameworks.coretests.R;
import com.android.frameworks.coretests.R;
import com.android.window.flags.Flags;


import org.junit.After;
import org.junit.After;
import org.junit.Before;
import org.junit.Before;
@@ -91,6 +96,8 @@ public class WindowContextTest {
    public ActivityTestRule<EmptyActivity> mActivityRule =
    public ActivityTestRule<EmptyActivity> mActivityRule =
            new ActivityTestRule<>(EmptyActivity.class, false /* initialTouchMode */,
            new ActivityTestRule<>(EmptyActivity.class, false /* initialTouchMode */,
                    false /* launchActivity */);
                    false /* launchActivity */);
    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();


    private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
    private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
    private final WindowContext mWindowContext = createWindowContext();
    private final WindowContext mWindowContext = createWindowContext();
@@ -340,17 +347,35 @@ public class WindowContextTest {
    }
    }


    @Test
    @Test
    public void updateDisplay_wasAttached_detachThenAttachedPropagatedToTokenController() {
    @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API)
    public void reparentToDisplayId_wasAttached_reparentToDisplayAreaPropagatedToTokenController() {
        final WindowTokenClientController mockWindowTokenClientController =
                mock(WindowTokenClientController.class);
        when(mockWindowTokenClientController.attachToDisplayArea(any(), anyInt(), anyInt(),
                any())).thenReturn(true);
        WindowTokenClientController.overrideForTesting(mockWindowTokenClientController);

        mWindowContext.reparentToDisplay(DEFAULT_DISPLAY + 1);

        verify(mockWindowTokenClientController).reparentToDisplayArea(any(),
                /* displayId= */ eq(DEFAULT_DISPLAY + 1)
        );
    }

    @Test
    @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API)
    public void reparentToDisplayId_sameDisplayId_noReparenting() {
        final WindowTokenClientController mockWindowTokenClientController =
        final WindowTokenClientController mockWindowTokenClientController =
                mock(WindowTokenClientController.class);
                mock(WindowTokenClientController.class);
        when(mockWindowTokenClientController.attachToDisplayArea(any(), anyInt(), anyInt(),
                any())).thenReturn(true);
        WindowTokenClientController.overrideForTesting(mockWindowTokenClientController);
        WindowTokenClientController.overrideForTesting(mockWindowTokenClientController);


        mWindowContext.updateDisplay(DEFAULT_DISPLAY + 1);
        mWindowContext.reparentToDisplay(DEFAULT_DISPLAY);


        verify(mockWindowTokenClientController).detachIfNeeded(any());
        verify(mockWindowTokenClientController, never()).reparentToDisplayArea(any(),
        verify(mockWindowTokenClientController).attachToDisplayArea(any(),
                /* displayId= */ eq(DEFAULT_DISPLAY)
                anyInt(), /* displayId= */ eq(DEFAULT_DISPLAY + 1),
        );
                any());
    }
    }


    private WindowContext createWindowContext() {
    private WindowContext createWindowContext() {
Loading