diff options
5 files changed, 184 insertions, 102 deletions
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index ab567ac20bd2..4eaebe07777f 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -27,6 +27,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.UiContext; +import android.app.servertransaction.WindowTokenClientController; import android.companion.virtual.VirtualDeviceManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.AttributionSource; @@ -3276,7 +3277,8 @@ class ContextImpl extends Context { // if this Context is not a WindowContext. WindowContext finalization is handled in // WindowContext class. if (mToken instanceof WindowTokenClient && mOwnsToken) { - ((WindowTokenClient) mToken).detachFromWindowContainerIfNeeded(); + WindowTokenClientController.getInstance().detachIfNeeded( + (WindowTokenClient) mToken); } super.finalize(); } @@ -3304,7 +3306,7 @@ class ContextImpl extends Context { final WindowTokenClient token = new WindowTokenClient(); final ContextImpl context = systemContext.createWindowContextBase(token, displayId); token.attachContext(context); - token.attachToDisplayContent(displayId); + WindowTokenClientController.getInstance().attachToDisplayContent(token, displayId); context.mContextType = CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI; context.mOwnsToken = true; diff --git a/core/java/android/app/servertransaction/WindowTokenClientController.java b/core/java/android/app/servertransaction/WindowTokenClientController.java new file mode 100644 index 000000000000..28e2040de9d5 --- /dev/null +++ b/core/java/android/app/servertransaction/WindowTokenClientController.java @@ -0,0 +1,164 @@ +/* + * 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 android.app.servertransaction; + +import static android.view.WindowManager.LayoutParams.WindowType; +import static android.view.WindowManagerGlobal.getWindowManagerService; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.view.IWindowManager; +import android.window.WindowContext; +import android.window.WindowTokenClient; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +/** + * Singleton controller to manage the attached {@link WindowTokenClient}s, and to dispatch + * corresponding window configuration change from server side. + * @hide + */ +public class WindowTokenClientController { + + private static WindowTokenClientController sController; + + private final Object mLock = new Object(); + + /** Mapping from a client defined token to the {@link WindowTokenClient} it represents. */ + @GuardedBy("mLock") + private final ArrayMap<IBinder, WindowTokenClient> mWindowTokenClientMap = new ArrayMap<>(); + + /** Gets the singleton controller. */ + public static WindowTokenClientController getInstance() { + synchronized (WindowTokenClientController.class) { + if (sController == null) { + sController = new WindowTokenClientController(); + } + return sController; + } + } + + /** Overrides the {@link #getInstance()} for test only. */ + @VisibleForTesting + public static void overrideInstance(@NonNull WindowTokenClientController controller) { + synchronized (WindowTokenClientController.class) { + sController = controller; + } + } + + private WindowTokenClientController() {} + + /** + * Attaches a {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}. + * + * @param client The {@link WindowTokenClient} to attach. + * @param type The window type of the {@link WindowContext} + * @param displayId The {@link Context#getDisplayId() ID of display} to associate with + * @param options The window context launched option + * @return {@code true} if attaching successfully. + */ + public boolean attachToDisplayArea(@NonNull WindowTokenClient client, + @WindowType int type, int displayId, @Nullable Bundle options) { + final Configuration configuration; + try { + configuration = getWindowManagerService() + .attachWindowContextToDisplayArea(client, type, displayId, options); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (configuration == null) { + return false; + } + onWindowContainerTokenAttached(client, displayId, configuration); + return true; + } + + /** + * Attaches a {@link WindowTokenClient} to a {@code DisplayContent}. + * + * @param client The {@link WindowTokenClient} to attach. + * @param displayId The {@link Context#getDisplayId() ID of display} to associate with + * @return {@code true} if attaching successfully. + */ + public boolean attachToDisplayContent(@NonNull WindowTokenClient client, int displayId) { + final IWindowManager wms = getWindowManagerService(); + // #createSystemUiContext may call this method before WindowManagerService is initialized. + if (wms == null) { + return false; + } + final Configuration configuration; + try { + configuration = wms.attachToDisplayContent(client, displayId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (configuration == null) { + return false; + } + onWindowContainerTokenAttached(client, displayId, configuration); + return true; + } + + /** + * Attaches this {@link WindowTokenClient} to a {@code windowToken}. + * + * @param client The {@link WindowTokenClient} to attach. + * @param windowToken the window token to associated with + */ + public void attachToWindowToken(@NonNull WindowTokenClient client, + @NonNull IBinder windowToken) { + try { + getWindowManagerService().attachWindowContextToWindowToken(client, windowToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + // We don't report configuration change for now. + synchronized (mLock) { + mWindowTokenClientMap.put(client.asBinder(), client); + } + } + + /** Detaches a {@link WindowTokenClient} from associated WindowContainer if there's one. */ + public void detachIfNeeded(@NonNull WindowTokenClient client) { + synchronized (mLock) { + if (mWindowTokenClientMap.remove(client.asBinder()) == null) { + return; + } + } + try { + getWindowManagerService().detachWindowContextFromWindowContainer(client); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private void onWindowContainerTokenAttached(@NonNull WindowTokenClient client, int displayId, + @NonNull Configuration configuration) { + synchronized (mLock) { + mWindowTokenClientMap.put(client.asBinder(), client); + } + client.onConfigurationChanged(configuration, displayId, + false /* shouldReportConfigChange */); + } +} diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java index 4b9a957f541d..eb270e2fb2f0 100644 --- a/core/java/android/window/WindowContextController.java +++ b/core/java/android/window/WindowContextController.java @@ -21,6 +21,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.servertransaction.WindowTokenClientController; import android.content.Context; import android.os.Bundle; import android.os.IBinder; @@ -104,7 +105,8 @@ public class WindowContextController { throw new IllegalStateException("A Window Context can be only attached to " + "a DisplayArea once."); } - mAttachedToDisplayArea = mToken.attachToDisplayArea(type, displayId, options) + mAttachedToDisplayArea = WindowTokenClientController.getInstance().attachToDisplayArea( + mToken, type, displayId, options) ? AttachStatus.STATUS_ATTACHED : AttachStatus.STATUS_FAILED; if (mAttachedToDisplayArea == AttachStatus.STATUS_FAILED) { Log.w(TAG, "attachToDisplayArea fail, type:" + type + ", displayId:" @@ -140,13 +142,13 @@ public class WindowContextController { throw new IllegalStateException("The Window Context should have been attached" + " to a DisplayArea. AttachToDisplayArea:" + mAttachedToDisplayArea); } - mToken.attachToWindowToken(windowToken); + WindowTokenClientController.getInstance().attachToWindowToken(mToken, windowToken); } /** Detaches the window context from the node it's currently associated with. */ public void detachIfNeeded() { if (mAttachedToDisplayArea == AttachStatus.STATUS_ATTACHED) { - mToken.detachFromWindowContainerIfNeeded(); + WindowTokenClientController.getInstance().detachIfNeeded(mToken); mAttachedToDisplayArea = AttachStatus.STATUS_DETACHED; if (DEBUG_ATTACH) { Log.d(TAG, "Detach Window Context."); diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index a208634abe78..55b823be3cb8 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -23,10 +23,10 @@ import android.annotation.AnyThread; import android.annotation.BinderThread; import android.annotation.MainThread; import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.ActivityThread; import android.app.IWindowToken; import android.app.ResourcesManager; +import android.app.servertransaction.WindowTokenClientController; import android.content.Context; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -36,14 +36,9 @@ import android.os.Bundle; import android.os.Debug; import android.os.Handler; import android.os.IBinder; -import android.os.RemoteException; import android.util.Log; -import android.view.IWindowManager; -import android.view.WindowManager.LayoutParams.WindowType; -import android.view.WindowManagerGlobal; import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.function.pooled.PooledLambda; import java.lang.ref.WeakReference; @@ -70,15 +65,11 @@ public class WindowTokenClient extends IWindowToken.Stub { private final ResourcesManager mResourcesManager = ResourcesManager.getInstance(); - private IWindowManager mWms; - @GuardedBy("itself") private final Configuration mConfiguration = new Configuration(); private boolean mShouldDumpConfigForIme; - private boolean mAttachToWindowContainer; - private final Handler mHandler = ActivityThread.currentActivityThread().getHandler(); /** @@ -101,88 +92,6 @@ public class WindowTokenClient extends IWindowToken.Stub { } /** - * Attaches this {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}. - * - * @param type The window type of the {@link WindowContext} - * @param displayId The {@link Context#getDisplayId() ID of display} to associate with - * @param options The window context launched option - * @return {@code true} if attaching successfully. - */ - public boolean attachToDisplayArea(@WindowType int type, int displayId, - @Nullable Bundle options) { - try { - final Configuration configuration = getWindowManagerService() - .attachWindowContextToDisplayArea(this, type, displayId, options); - if (configuration == null) { - return false; - } - onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */); - mAttachToWindowContainer = true; - return true; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Attaches this {@link WindowTokenClient} to a {@code DisplayContent}. - * - * @param displayId The {@link Context#getDisplayId() ID of display} to associate with - * @return {@code true} if attaching successfully. - */ - public boolean attachToDisplayContent(int displayId) { - final IWindowManager wms = getWindowManagerService(); - // #createSystemUiContext may call this method before WindowManagerService is initialized. - if (wms == null) { - return false; - } - try { - final Configuration configuration = wms.attachToDisplayContent(this, displayId); - if (configuration == null) { - return false; - } - onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */); - mAttachToWindowContainer = true; - return true; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Attaches this {@link WindowTokenClient} to a {@code windowToken}. - * - * @param windowToken the window token to associated with - */ - public void attachToWindowToken(IBinder windowToken) { - try { - getWindowManagerService().attachWindowContextToWindowToken(this, windowToken); - mAttachToWindowContainer = true; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** Detaches this {@link WindowTokenClient} from associated WindowContainer if there's one. */ - public void detachFromWindowContainerIfNeeded() { - if (!mAttachToWindowContainer) { - return; - } - try { - getWindowManagerService().detachWindowContextFromWindowContainer(this); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - private IWindowManager getWindowManagerService() { - if (mWms == null) { - mWms = WindowManagerGlobal.getWindowManagerService(); - } - return mWms; - } - - /** * Called when {@link Configuration} updates from the server side receive. * * @param newConfig the updated {@link Configuration} @@ -207,15 +116,14 @@ public class WindowTokenClient extends IWindowToken.Stub { * {@code shouldReportConfigChange} is {@code true}, which is usually from * {@link IWindowToken#onConfigurationChanged(Configuration, int)} * directly, while this method could be run on any thread if it is used to initialize - * Context's {@code Configuration} via {@link #attachToDisplayArea(int, int, Bundle)} - * or {@link #attachToDisplayContent(int)}. + * Context's {@code Configuration} via {@link WindowTokenClientController#attachToDisplayArea} + * or {@link WindowTokenClientController#attachToDisplayContent}. * * @param shouldReportConfigChange {@code true} to indicate that the {@code Configuration} * should be dispatched to listeners. * */ @AnyThread - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void onConfigurationChanged(Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange) { final Context context = mContextRef.get(); diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java index a52d2e88145f..467d555f161f 100644 --- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java +++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java @@ -24,11 +24,13 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import android.app.servertransaction.WindowTokenClientController; import android.os.Binder; import android.platform.test.annotations.Presubmit; @@ -56,6 +58,8 @@ import org.mockito.MockitoAnnotations; public class WindowContextControllerTest { private WindowContextController mController; @Mock + private WindowTokenClientController mWindowTokenClientController; + @Mock private WindowTokenClient mMockToken; @Before @@ -63,7 +67,9 @@ public class WindowContextControllerTest { MockitoAnnotations.initMocks(this); mController = new WindowContextController(mMockToken); doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean()); - doReturn(true).when(mMockToken).attachToDisplayArea(anyInt(), anyInt(), any()); + WindowTokenClientController.overrideInstance(mWindowTokenClientController); + doReturn(true).when(mWindowTokenClientController).attachToDisplayArea( + eq(mMockToken), anyInt(), anyInt(), any()); } @Test(expected = IllegalStateException.class) @@ -78,7 +84,7 @@ public class WindowContextControllerTest { public void testDetachIfNeeded_NotAttachedYet_DoNothing() { mController.detachIfNeeded(); - verify(mMockToken, never()).detachFromWindowContainerIfNeeded(); + verify(mWindowTokenClientController, never()).detachIfNeeded(any()); } @Test |