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

Commit c6ad64eb authored by Charles Chen's avatar Charles Chen
Browse files

Reparent to the default display with display removal

Add displayRemoveBehavior to enable the WindowContext owned
WindowToken to reparent to the default display when
the associated display is going to removed.

It is to fix the race when relying on DisplayListener to
reparent the token.

Test: atest WindowContextTest
Test: atest WindowManagerServiceTest WindowContextistenerControllerTest
Test: atest InputMethodDialogWindowContextTest
Bug: 404532651
Flag: com.android.window.flags.reparent_to_default_with_display_removal
Change-Id: Ia97726407b8274c02c32dc68bba9ba85cda84608
parent ac53603b
Loading
Loading
Loading
Loading
+17 −1
Original line number Diff line number Diff line
@@ -98,8 +98,15 @@ public class WindowContext extends ContextWrapper implements WindowProvider,
        mOptions = options;
        mWindowManager = createWindowContextWindowManager(this);
        WindowTokenClient token = (WindowTokenClient) requireNonNull(getWindowContextToken());
        mController = new WindowContextController(token);
        registerCleaner(this);
        mController = new WindowContextController(requireNonNull(token));

        if (!Flags.reparentToDefaultWithDisplayRemoval()
                && shouldFallbackToDefaultDisplay(mOptions)) {
            throw new UnsupportedOperationException(
                    Flags.FLAG_REPARENT_TO_DEFAULT_WITH_DISPLAY_REMOVAL + " is not enabled");
        }

        Reference.reachabilityFence(this);
    }

@@ -247,6 +254,15 @@ public class WindowContext extends ContextWrapper implements WindowProvider,
        );
    }

    /**
     * Checks if the WindowContext should be reparented to the default display when the currently
     * attached display is removed.
     */
    public static boolean shouldFallbackToDefaultDisplay(@Nullable Bundle options) {
        return options != null
                && options.getBoolean(KEY_REPARENT_TO_DEFAULT_DISPLAY_WITH_DISPLAY_REMOVAL, false);
    }

/* === WindowProvider APIs === */

    @Override
+16 −1
Original line number Diff line number Diff line
@@ -32,7 +32,22 @@ import android.view.WindowManager.LayoutParams.WindowType;
 */
public interface WindowProvider {
    /** @hide */
    String KEY_IS_WINDOW_PROVIDER_SERVICE = "android.windowContext.isWindowProviderService";
    String KEY_IS_WINDOW_PROVIDER_SERVICE = "android.window.WindowProvider.isWindowProviderService";

    /**
     * The key to indicate whether the WindowContext should be reparented to the default display
     * when the currently attached display is removed.
     * <p>
     * By default, the value is {@code false}, which means the WindowContext is removed with
     * the display removal. If the value is {@code true}, the WindowContext will be reparented to
     * the default display instead.
     * <p>
     * Type: Boolean
     *
     * @hide
     */
    String KEY_REPARENT_TO_DEFAULT_DISPLAY_WITH_DISPLAY_REMOVAL =
            "android.window.WindowProvider.reparentToDefaultDisplayWithDisplayRemoval";

    /** Gets the window type of this provider */
    @WindowType
+11 −0
Original line number Diff line number Diff line
@@ -215,3 +215,14 @@ flag {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    namespace: "windowing_sdk"
    name: "reparent_to_default_with_display_removal"
    description: "Enable WindowContext to reparent to the default display with display detached"
    bug: "404532651"
    is_fixed_read_only: true
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
+76 −0
Original line number Diff line number Diff line
@@ -16,12 +16,15 @@

package android.window;

import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.window.WindowProvider.KEY_REPARENT_TO_DEFAULT_DISPLAY_WITH_DISPLAY_REMOVAL;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -52,9 +55,13 @@ import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.ImageReader;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.EnableFlags;
@@ -552,6 +559,75 @@ public class WindowContextTest {
        }
    }

    @EnableFlags({
            Flags.FLAG_REPARENT_WINDOW_TOKEN_API,
            Flags.FLAG_REPARENT_TO_DEFAULT_WITH_DISPLAY_REMOVAL,
    })
    @Test
    public void testDisplayRemovePolicyReparentToDefault_notAddWindow_reparent() {
        testDisplayRemovePolicyReparentToDefault(false /* shouldVerifyAddingView */);
    }

    @EnableFlags({
            Flags.FLAG_REPARENT_WINDOW_TOKEN_API,
            Flags.FLAG_REPARENT_TO_DEFAULT_WITH_DISPLAY_REMOVAL,
    })
    @Test
    public void testDisplayRemovePolicyReparentToDefault_addWindow_reparent() {
        testDisplayRemovePolicyReparentToDefault(true /* shouldVerifyAddingView */);
    }

    private void testDisplayRemovePolicyReparentToDefault(boolean shouldVerifyAddingView) {
        final VirtualDisplay virtualDisplay = createVirtualDisplay();

        // Attach the WindowContext to the virtual display
        final Display display = virtualDisplay.getDisplay();
        final Bundle options = new Bundle();
        options.putBoolean(KEY_REPARENT_TO_DEFAULT_DISPLAY_WITH_DISPLAY_REMOVAL, true);
        final Context windowContext = mInstrumentation.getTargetContext().createWindowContext(
                display, TYPE_APPLICATION_OVERLAY, options);

        final int virtualDisplayId = display.getDisplayId();
        assertWithMessage("WindowContext must be attached to display#" + virtualDisplayId)
                .that(windowContext.getDisplay().getDisplayId()).isEqualTo(virtualDisplayId);

        if (shouldVerifyAddingView) {
            final View view = new View(windowContext);
            final AttachStateListener listener = new AttachStateListener();
            view.addOnAttachStateChangeListener(listener);

            mInstrumentation.runOnMainSync(() ->
                    windowContext.getSystemService(WindowManager.class)
                            .addView(view,
                                    new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY)));

            // Checks that the view is attached.
            try {
                assertThat(listener.mLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)).isTrue();
            } catch (InterruptedException e) {
                fail("Failure due to " + e);
            }

        }
        virtualDisplay.release();

        PollingCheck.waitFor(() -> windowContext.getDisplayId() == DEFAULT_DISPLAY);
    }

    @NonNull
    private VirtualDisplay createVirtualDisplay() {
        final int width = 800;
        final int height = 480;
        final int density = 160;
        ImageReader reader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888,
                2 /* maxImages */);
        final Context context = mInstrumentation.getTargetContext();
        final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
        return displayManager.createVirtualDisplay(
                WindowContextTest.class.getName(), width, height, density, reader.getSurface(),
                VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
    }

    private WindowContext createWindowContext() {
        return createWindowContext(TYPE_APPLICATION_OVERLAY);
    }
+27 −4
Original line number Diff line number Diff line
@@ -18,11 +18,13 @@ package com.android.systemui.shade

import android.content.Context
import android.content.res.Resources
import android.os.Bundle
import android.view.LayoutInflater
import android.view.WindowManager
import android.view.WindowManager.LayoutParams
import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE
import android.window.WindowContext
import android.window.WindowProvider.KEY_REPARENT_TO_DEFAULT_DISPLAY_WITH_DISPLAY_REMOVAL
import com.android.app.tracing.TrackGroupUtils.trackGroup
import com.android.systemui.CoreStartable
import com.android.systemui.common.ui.ConfigurationState
@@ -53,7 +55,7 @@ import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.phone.ConfigurationForwarder
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.utils.windowmanager.WindowManagerProvider
import com.android.systemui.utils.windowmanager.WindowManagerUtils
import com.android.window.flags.Flags
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
@@ -80,16 +82,37 @@ object ShadeDisplayAwareModule {
    @Provides
    @ShadeDisplayAware
    @SysUISingleton
    fun provideShadeDisplayAwareContext(context: Context): Context {
    fun provideShadeDisplayAwareContext(
        context: Context,
        @ShadeDisplayAware shadeContextBuildOptions: Bundle?,
    ): Context {
        return if (ShadeWindowGoesAround.isEnabled) {
            context
                .createWindowContext(context.display, TYPE_NOTIFICATION_SHADE, /* options= */ null)
                .createWindowContext(
                    context.display,
                    TYPE_NOTIFICATION_SHADE,
                    shadeContextBuildOptions,
                )
                .apply { setTheme(R.style.Theme_SystemUI) }
        } else {
            context
        }
    }

    @Provides
    @ShadeDisplayAware
    @SysUISingleton
    fun provideShadeContextBuildOptions(): Bundle? =
        if (Flags.reparentToDefaultWithDisplayRemoval()) {
            // Enables to reparent this WindowContext to the default display if the currently
            // attached display is removed.
            Bundle().apply {
                putBoolean(KEY_REPARENT_TO_DEFAULT_DISPLAY_WITH_DISPLAY_REMOVAL, true)
            }
        } else {
            null
        }

    @Provides
    @ShadeDisplayAware
    @SysUISingleton
@@ -116,7 +139,7 @@ object ShadeDisplayAwareModule {
    fun provideShadeWindowManager(
        defaultWindowManager: WindowManager,
        @ShadeDisplayAware context: Context,
        windowManagerProvider: WindowManagerProvider
        windowManagerProvider: WindowManagerProvider,
    ): WindowManager {
        return if (ShadeWindowGoesAround.isEnabled) {
            windowManagerProvider.getWindowManager(context)
Loading