Loading core/java/android/window/WindowContext.java +17 −1 Original line number Diff line number Diff line Loading @@ -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); } Loading Loading @@ -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 Loading core/java/android/window/WindowProvider.java +16 −1 Original line number Diff line number Diff line Loading @@ -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 Loading core/java/android/window/flags/windowing_sdk.aconfig +11 −0 Original line number Diff line number Diff line Loading @@ -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 } } core/tests/coretests/src/android/window/WindowContextTest.java +76 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); } Loading packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt +27 −4 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading
core/java/android/window/WindowContext.java +17 −1 Original line number Diff line number Diff line Loading @@ -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); } Loading Loading @@ -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 Loading
core/java/android/window/WindowProvider.java +16 −1 Original line number Diff line number Diff line Loading @@ -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 Loading
core/java/android/window/flags/windowing_sdk.aconfig +11 −0 Original line number Diff line number Diff line Loading @@ -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 } }
core/tests/coretests/src/android/window/WindowContextTest.java +76 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); } Loading
packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt +27 −4 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading @@ -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 Loading @@ -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