diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 24fb76affdbea..249a60633ff04 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2721,10 +2721,13 @@ class ContextImpl extends Context { // need to override their display in ResourcesManager. baseContext.mForceDisplayOverrideInResources = false; baseContext.mContextType = CONTEXT_TYPE_WINDOW_CONTEXT; - baseContext.mDisplay = display; final Resources windowContextResources = createWindowContextResources(baseContext); baseContext.setResources(windowContextResources); + // Associate the display with window context resources so that configuration update from + // the server side will also apply to the display's metrics. + baseContext.mDisplay = ResourcesManager.getInstance() + .getAdjustedDisplay(display.getDisplayId(), windowContextResources); return baseContext; } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 305cb97dbc775..f4539c2f88598 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -814,9 +814,10 @@ interface IWindowManager * @param displayId The display associated with the window context * @param options A bundle used to pass window-related options and choose the right DisplayArea * - * @return {@code true} if the WindowContext is attached to the DisplayArea successfully. + * @return the DisplayArea's {@link android.app.res.Configuration} if the WindowContext is + * attached to the DisplayArea successfully. {@code null}, otherwise. */ - boolean attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId, + Configuration attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId, in Bundle options); /** diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java index 69d7b4c7d696b..5d400853540f2 100644 --- a/core/java/android/window/WindowContext.java +++ b/core/java/android/window/WindowContext.java @@ -26,7 +26,6 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.res.Configuration; import android.os.Bundle; -import android.os.IBinder; import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; @@ -73,7 +72,7 @@ public class WindowContext extends ContextWrapper { mType = type; mOptions = options; mWindowManager = createWindowContextWindowManager(this); - IBinder token = getWindowContextToken(); + WindowTokenClient token = (WindowTokenClient) getWindowContextToken(); mController = new WindowContextController(token); Reference.reachabilityFence(this); diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java index d84f571931fdd..5aa623388574e 100644 --- a/core/java/android/window/WindowContextController.java +++ b/core/java/android/window/WindowContextController.java @@ -19,6 +19,7 @@ package android.window; 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; @@ -46,7 +47,7 @@ public class WindowContextController { @VisibleForTesting public boolean mAttachedToDisplayArea; @NonNull - private final IBinder mToken; + private final WindowTokenClient mToken; /** * Window Context Controller constructor @@ -54,14 +55,13 @@ public class WindowContextController { * @param token The token used to attach to a window manager node. It is usually from * {@link Context#getWindowContextToken()}. */ - public WindowContextController(@NonNull IBinder token) { - mToken = token; - mWms = WindowManagerGlobal.getWindowManagerService(); + public WindowContextController(@NonNull WindowTokenClient token) { + this(token, WindowManagerGlobal.getWindowManagerService()); } /** Used for test only. DO NOT USE it in production code. */ @VisibleForTesting - public WindowContextController(@NonNull IBinder token, IWindowManager mockWms) { + public WindowContextController(@NonNull WindowTokenClient token, IWindowManager mockWms) { mToken = token; mWms = mockWms; } @@ -81,8 +81,15 @@ public class WindowContextController { + "a DisplayArea once."); } try { - mAttachedToDisplayArea = mWms.attachWindowContextToDisplayArea(mToken, type, displayId, - options); + final Configuration configuration = mWms.attachWindowContextToDisplayArea(mToken, type, + displayId, options); + if (configuration != null) { + mAttachedToDisplayArea = true; + // Send the DisplayArea's configuration to WindowContext directly instead of + // waiting for dispatching from WMS. + mToken.onConfigurationChanged(configuration, displayId, + false /* shouldReportConfigChange */); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index c32a268cef5c0..284b4b98b4c9d 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -29,9 +29,12 @@ import android.content.res.Configuration; import android.inputmethodservice.AbstractInputMethodService; import android.os.Build; import android.os.Bundle; +import android.os.Debug; import android.os.IBinder; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + import java.lang.ref.WeakReference; /** @@ -65,7 +68,7 @@ public class WindowTokenClient extends IWindowToken.Stub { * can only attach one {@link Context}. *
This method must be called before invoking * {@link android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int, - * Bundle, boolean)}.
+ * Bundle)}. * * @param context context to be attached * @throws IllegalStateException if attached context has already existed. @@ -80,8 +83,26 @@ public class WindowTokenClient extends IWindowToken.Stub { && context instanceof AbstractInputMethodService; } + /** + * Called when {@link Configuration} updates from the server side receive. + * + * @param newConfig the updated {@link Configuration} + * @param newDisplayId the updated {@link android.view.Display} ID + */ @Override public void onConfigurationChanged(Configuration newConfig, int newDisplayId) { + onConfigurationChanged(newConfig, newDisplayId, true /* shouldReportConfigChange */); + } + + /** + * Called when {@link Configuration} updates from the server side receive. + * + * Similar to {@link #onConfigurationChanged(Configuration, int)}, but adds a flag to control + * whether to dispatch configuration update or not. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void onConfigurationChanged(Configuration newConfig, int newDisplayId, + boolean shouldReportConfigChange) { final Context context = mContextRef.get(); if (context == null) { return; @@ -102,9 +123,10 @@ public class WindowTokenClient extends IWindowToken.Stub { // TODO(ag/9789103): update resource manager logic to track non-activity tokens mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId); - if (context instanceof WindowContext) { + if (shouldReportConfigChange && context instanceof WindowContext) { + final WindowContext windowContext = (WindowContext) context; ActivityThread.currentActivityThread().getHandler().post( - () -> ((WindowContext) context).dispatchConfigurationChanged(newConfig)); + () -> windowContext.dispatchConfigurationChanged(newConfig)); } // Dispatch onConfigurationChanged only if there's a significant public change to @@ -115,17 +137,24 @@ public class WindowTokenClient extends IWindowToken.Stub { ? new SizeConfigurationBuckets(sizeConfigurations) : null; final int diff = diffPublicWithSizeBuckets(mConfiguration, newConfig, buckets); - if (context instanceof WindowProviderService && diff != 0) { - ActivityThread.currentActivityThread().getHandler().post(() -> - ((WindowProviderService) context).onConfigurationChanged(newConfig)); + if (shouldReportConfigChange && diff != 0 + && context instanceof WindowProviderService) { + final WindowProviderService windowProviderService = (WindowProviderService) context; + ActivityThread.currentActivityThread().getHandler().post( + () -> windowProviderService.onConfigurationChanged(newConfig)); } freeTextLayoutCachesIfNeeded(diff); - if (diff == 0 && mShouldDumpConfigForIme) { - Log.d(TAG, "Configuration not dispatch to IME because configuration has no " - + " public difference with updated config. " - + " Current config=" + context.getResources().getConfiguration() - + ", reported config=" + mConfiguration - + ", updated config=" + newConfig); + if (mShouldDumpConfigForIme) { + if (!shouldReportConfigChange) { + Log.d(TAG, "Only apply configuration update to Resources because " + + "shouldReportConfigChange is false.\n" + Debug.getCallers(5)); + } else if (diff == 0) { + Log.d(TAG, "Configuration not dispatch to IME because configuration has no " + + " public difference with updated config. " + + " Current config=" + context.getResources().getConfiguration() + + ", reported config=" + mConfiguration + + ", updated config=" + newConfig); + } } mConfiguration.setTo(newConfig); } diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java index 020f4a06b892d..a6e351d9cee72 100644 --- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java +++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java @@ -22,12 +22,15 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 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.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import android.content.res.Configuration; import android.os.Binder; import android.platform.test.annotations.Presubmit; import android.view.IWindowManager; @@ -38,6 +41,8 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; /** * Tests for {@link WindowContextController} @@ -53,15 +58,18 @@ import org.junit.runner.RunWith; @Presubmit public class WindowContextControllerTest { private WindowContextController mController; + @Mock private IWindowManager mMockWms; + @Mock + private WindowTokenClient mMockToken; @Before public void setUp() throws Exception { - mMockWms = mock(IWindowManager.class); - mController = new WindowContextController(new Binder(), mMockWms); - - doReturn(true).when(mMockWms).attachWindowContextToDisplayArea(any(), anyInt(), - anyInt(), any()); + MockitoAnnotations.initMocks(this); + mController = new WindowContextController(mMockToken, mMockWms); + doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean()); + doReturn(new Configuration()).when(mMockWms).attachWindowContextToDisplayArea(any(), + anyInt(), anyInt(), any()); } @Test(expected = IllegalStateException.class) @@ -85,6 +93,8 @@ public class WindowContextControllerTest { null /* options */); assertThat(mController.mAttachedToDisplayArea).isTrue(); + verify(mMockToken).onConfigurationChanged(any(), eq(DEFAULT_DISPLAY), + eq(false) /* shouldReportConfigChange */); mController.detachIfNeeded(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index ff99f4aeb611c..53e02e5395a50 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2720,8 +2720,8 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public boolean attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId, - Bundle options) { + public Configuration attachWindowContextToDisplayArea(IBinder clientToken, int + type, int displayId, Bundle options) { final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS, "attachWindowContextToDisplayArea", false /* printLog */); final int callingUid = Binder.getCallingUid(); @@ -2732,15 +2732,17 @@ public class WindowManagerService extends IWindowManager.Stub if (dc == null) { ProtoLog.w(WM_ERROR, "attachWindowContextToDisplayArea: trying to attach" + " to a non-existing display:%d", displayId); - return false; + return null; } // TODO(b/155340867): Investigate if we still need roundedCornerOverlay after // the feature b/155340867 is completed. final DisplayArea da = dc.findAreaForWindowType(type, options, callerCanManageAppTokens, false /* roundedCornerOverlay */); + // TODO(b/190019118): Avoid to send onConfigurationChanged because it has been done + // in return value of attachWindowContextToDisplayArea. mWindowContextListenerController.registerWindowContainerListener(clientToken, da, callingUid, type, options); - return true; + return da.getConfiguration(); } } finally { Binder.restoreCallingIdentity(origId); diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java index f2418c68358d0..a8ede13e5de64 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java @@ -82,7 +82,7 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { mWm.mWindowContextListenerController.registerWindowContainerListener(clientToken, dc.getImeContainer(), 1000 /* ownerUid */, TYPE_INPUT_METHOD_DIALOG, null /* options */); - return true; + return dc.getImeContainer().getConfiguration(); }).when(wms).attachWindowContextToDisplayArea(any(), eq(TYPE_INPUT_METHOD_DIALOG), anyInt(), any());