Migrate WindowContext#onConfigurationChanged to ClientTransaction (1/n)

Create a client side WindowTokenClientController to keep track of the
registered WindowTokenClient. Refactor all the WMS attach/detach to the
controller.

Bug: 290876897
Test: Refactor, pass existing test
Change-Id: I5c3d5b6a60fbc23836b8d7ca95df24feec69554c
This commit is contained in:
Chris Li
2023-07-13 16:32:25 +08:00
parent e3595e6daf
commit 2a928e54f4
5 changed files with 184 additions and 102 deletions

View File

@@ -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;

View File

@@ -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 */);
}
}

View File

@@ -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.");

View File

@@ -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();
/**
@@ -100,88 +91,6 @@ public class WindowTokenClient extends IWindowToken.Stub {
&& context instanceof AbstractInputMethodService;
}
/**
* 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.
*
@@ -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();

View File

@@ -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