diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 8df26cbbb4f4c..480ea8a25f91b 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2409,17 +2409,35 @@ class ContextImpl extends Context { + "other visual contexts, such as Activity or one created with " + "Context#createDisplayContext(Display)"); } - return new WindowContext(this, null /* token */, type, options); + return new WindowContext(this, type, options); } ContextImpl createBaseWindowContext(IBinder token) { ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mFeatureId, mSplitName, token, mUser, mFlags, mClassLoader, null); context.mIsUiContext = true; + context.mIsAssociatedWithDisplay = true; return context; } + Resources createWindowContextResources() { + final String resDir = mPackageInfo.getResDir(); + final String[] splitResDirs = mPackageInfo.getSplitResDirs(); + final String[] overlayDirs = mPackageInfo.getOverlayDirs(); + final String[] libDirs = mPackageInfo.getApplicationInfo().sharedLibraryFiles; + final int displayId = getDisplayId(); + final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY) + ? mPackageInfo.getCompatibilityInfo() + : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; + final List loaders = mResources.getLoaders(); + + // TODO(b/128338354): Rename to createTokenResources + return mResourcesManager.createBaseActivityResources(mToken, resDir, splitResDirs, + overlayDirs, libDirs, displayId, null /* overrideConfig */, + compatInfo, mClassLoader, loaders); + } + @Override public @NonNull Context createFeatureContext(@Nullable String featureId) { return new ContextImpl(this, mMainThread, mPackageInfo, featureId, mSplitName, diff --git a/core/java/android/app/IWindowToken.aidl b/core/java/android/app/IWindowToken.aidl new file mode 100644 index 0000000000000..8ea881fba09cc --- /dev/null +++ b/core/java/android/app/IWindowToken.aidl @@ -0,0 +1,33 @@ +/* + ** Copyright 2020, 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; + +import android.content.res.Configuration; +import android.view.IWindow; + +/** + * Callback to receive configuration changes from {@link com.android.server.WindowToken}. + * WindowToken can be regarded to as a group of {@link android.view.IWindow} added from the same + * visual context, such as {@link Activity} or one created with + * {@link android.content.Context#createWindowContext(int)}. When WindowToken receives configuration + * changes and/or when it is moved between displays, it will propagate the changes to client side + * via this interface. + * @see android.content.Context#createWindowContext(int) + * {@hide} + */ +oneway interface IWindowToken { + void onConfigurationChanged(in Configuration newConfig, int newDisplayId); +} diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java index d279983a5793c..5684562708098 100644 --- a/core/java/android/app/WindowContext.java +++ b/core/java/android/app/WindowContext.java @@ -15,10 +15,12 @@ */ package android.app; +import static android.view.WindowManagerGlobal.ADD_OKAY; + import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.ContextWrapper; -import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -26,73 +28,62 @@ import android.view.IWindowManager; import android.view.WindowManagerGlobal; import android.view.WindowManagerImpl; +import java.lang.ref.Reference; + /** * {@link WindowContext} is a context for non-activity windows such as * {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} windows or system * windows. Its resources and configuration are adjusted to the area of the display that will be - * used when a new window is added via {@link android.view.WindowManager.addView}. + * used when a new window is added via {@link android.view.WindowManager#addView}. * * @see Context#createWindowContext(int, Bundle) * @hide */ -// TODO(b/128338354): Handle config/display changes from server side. public class WindowContext extends ContextWrapper { private final WindowManagerImpl mWindowManager; private final IWindowManager mWms; - private final IBinder mToken; - private final int mDisplayId; + private final WindowTokenClient mToken; private boolean mOwnsToken; /** - * Default constructor. Can either accept an existing token or generate one and registers it - * with the server if necessary. + * Default constructor. Will generate a {@link WindowTokenClient} and attach this context to + * the token. * * @param base Base {@link Context} for this new instance. - * @param token A valid {@link com.android.server.wm.WindowToken}. Pass {@code null} to generate - * one. * @param type Window type to be used with this context. * @hide */ - public WindowContext(Context base, IBinder token, int type, Bundle options) { + public WindowContext(@NonNull Context base, int type, @Nullable Bundle options) { + // Correct base context will be built once the token is resolved, so passing 'null' here. super(null /* base */); mWms = WindowManagerGlobal.getWindowManagerService(); - if (token != null && !isWindowToken(token)) { - throw new IllegalArgumentException("Token must be registered to server."); - } - mToken = token != null ? token : new Binder(); + mToken = new WindowTokenClient(); + final ContextImpl contextImpl = createBaseWindowContext(base, mToken); attachBaseContext(contextImpl); contextImpl.setOuterContext(this); - mDisplayId = getDisplayId(); + mToken.attachContext(this); + mWindowManager = new WindowManagerImpl(this); mWindowManager.setDefaultToken(mToken); - // TODO(b/128338354): Obtain the correct config from WM and adjust resources. - if (token != null) { - mOwnsToken = false; - return; - } + int result; try { - mWms.addWindowTokenWithOptions(mToken, type, mDisplayId, options, getPackageName()); + // Register the token with WindowManager. This will also call back with the current + // config back to the client. + result = mWms.addWindowTokenWithOptions( + mToken, type, getDisplayId(), options, getPackageName()); + // TODO(window-context): remove token with a DeathObserver } catch (RemoteException e) { mOwnsToken = false; throw e.rethrowFromSystemServer(); } - mOwnsToken = true; - } - - /** Check if the passed window token is registered with the server. */ - private boolean isWindowToken(@NonNull IBinder token) { - try { - return mWms.isWindowToken(token); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - return false; + mOwnsToken = result == ADD_OKAY; + Reference.reachabilityFence(this); } private static ContextImpl createBaseWindowContext(Context outer, IBinder token) { @@ -112,7 +103,7 @@ public class WindowContext extends ContextWrapper { protected void finalize() throws Throwable { if (mOwnsToken) { try { - mWms.removeWindowToken(mToken, mDisplayId); + mWms.removeWindowToken(mToken, getDisplayId()); mOwnsToken = false; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/app/WindowTokenClient.java b/core/java/android/app/WindowTokenClient.java new file mode 100644 index 0000000000000..ed0179bb9839b --- /dev/null +++ b/core/java/android/app/WindowTokenClient.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 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; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.IBinder; + +/** + * Client implementation of {@link IWindowToken}. It can receive configuration change callbacks from + * server when window token config is updated or when it is moved between displays, and update the + * resources associated with this token on the client side. This will make sure that + * {@link WindowContext} instances will have updated resources and configuration. + * @hide + */ +public class WindowTokenClient extends IWindowToken.Stub { + /** + * Attached {@link Context} for this window token to update configuration and resources. + * Initialized by {@link #attachContext(Context)}. + */ + private Context mContext = null; + + private final ResourcesManager mResourcesManager = ResourcesManager.getInstance(); + + /** + * Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient} + * can only attach one {@link Context}. + *

This method must be called before invoking + * {@link android.view.IWindowManager#addWindowTokenWithOptions(IBinder, int, int, Bundle, + * String)}.

+ * + * @param context context to be attached + * @throws IllegalStateException if attached context has already existed. + */ + void attachContext(@NonNull Context context) { + if (mContext != null) { + throw new IllegalStateException("Context is already attached."); + } + mContext = context; + ContextImpl impl = ContextImpl.getImpl(mContext); + impl.setResources(impl.createWindowContextResources()); + } + + @Override + public void onConfigurationChanged(Configuration newConfig, int newDisplayId) { + final int currentDisplayId = mContext.getDisplayId(); + final boolean displayChanged = newDisplayId != currentDisplayId; + final Configuration config = new Configuration(mContext.getResources() + .getConfiguration()); + final boolean configChanged = config.isOtherSeqNewer(newConfig) + && config.updateFrom(newConfig) != 0; + if (displayChanged || configChanged) { + // TODO(ag/9789103): update resource manager logic to track non-activity tokens + mResourcesManager.updateResourcesForActivity(asBinder(), config, newDisplayId, + displayChanged); + } + if (displayChanged) { + mContext.updateDisplay(newDisplayId); + } + } +} diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index e731323845d38..b0d0a14c4d416 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -458,7 +458,7 @@ public interface WindowManager extends ViewManager { } /** - * Returns the largets {@link WindowMetrics} an app may expect in the current system state. + * Returns the largest {@link WindowMetrics} an app may expect in the current system state. *

* The metrics describe the size of the largest potential area the window might occupy with * {@link LayoutParams#MATCH_PARENT MATCH_PARENT} width and height, and the {@link WindowInsets} diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 5b23dc0481cb1..145fd8b824cf7 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -709,6 +709,18 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "-650040763": { + "message": "rotationForOrientation(orient=%d, last=%d); user=%d %s", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayRotation.java" + }, + "-639305784": { + "message": "Could not report config changes to the window token client.", + "level": "WARN", + "group": "WM_ERROR", + "at": "com\/android\/server\/wm\/WindowToken.java" + }, "-635082269": { "message": "******** booted=%b msg=%b haveBoot=%b haveApp=%b haveWall=%b wallEnabled=%b haveKeyguard=%b", "level": "INFO", diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4835215f8b52c..337764ae32f5a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2583,11 +2583,18 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void addWindowToken(IBinder binder, int type, int displayId) { addWindowTokenWithOptions(binder, type, displayId, null /* options */, - null /* packageName */); + null /* packageName */, false /* fromClientToken */); } + @Override public int addWindowTokenWithOptions(IBinder binder, int type, int displayId, Bundle options, String packageName) { + return addWindowTokenWithOptions(binder, type, displayId, options, packageName, + true /* fromClientToken */); + } + + private int addWindowTokenWithOptions(IBinder binder, int type, int displayId, Bundle options, + String packageName, boolean fromClientToken) { final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()"); if (!callerCanManageAppTokens) { diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 48c7812afec07..76a03150c8df5 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; @@ -23,6 +24,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT; +import static com.android.server.wm.ProtoLogGroup.WM_ERROR; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; @@ -36,10 +38,12 @@ import static com.android.server.wm.WindowTokenProto.WINDOWS; import static com.android.server.wm.WindowTokenProto.WINDOW_CONTAINER; import android.annotation.CallSuper; +import android.app.IWindowToken; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Debug; import android.os.IBinder; +import android.os.RemoteException; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; import android.view.InsetsState; @@ -91,6 +95,11 @@ class WindowToken extends WindowContainer { private FixedRotationTransformState mFixedRotationTransformState; + private Configuration mLastReportedConfig; + private int mLastReportedDisplay = INVALID_DISPLAY; + + private final boolean mFromClientToken; + /** * Used to fix the transform of the token to be rotated to a rotation different than it's * display. The window frames and surfaces corresponding to this token will be layouted and @@ -167,13 +176,21 @@ class WindowToken extends WindowContainer { } WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty, - DisplayContent dc, boolean ownerCanManageAppTokens, boolean roundedCornerOverlay) { + DisplayContent dc, boolean ownerCanManageAppTokens, boolean fromClientToken) { + this(service, _token, type, persistOnEmpty, dc, ownerCanManageAppTokens, + false /* roundedCornersOverlay */, fromClientToken); + } + + WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty, + DisplayContent dc, boolean ownerCanManageAppTokens, boolean roundedCornerOverlay, + boolean fromClientToken) { super(service); token = _token; windowType = type; mPersistOnEmpty = persistOnEmpty; mOwnerCanManageAppTokens = ownerCanManageAppTokens; mRoundedCornerOverlay = roundedCornerOverlay; + mFromClientToken = fromClientToken; if (dc != null) { dc.addWindowToken(token, this); } @@ -305,8 +322,47 @@ class WindowToken extends WindowContainer { // up with goodToGo, so we don't move a window // to another display before the window behind // it is ready. - super.onDisplayChanged(dc); + reportConfigToWindowTokenClient(); + } + + @Override + public void onConfigurationChanged(Configuration newParentConfig) { + super.onConfigurationChanged(newParentConfig); + reportConfigToWindowTokenClient(); + } + + void reportConfigToWindowTokenClient() { + if (asActivityRecord() != null) { + // Activities are updated through ATM callbacks. + return; + } + + // Unfortunately, this WindowToken is not from WindowContext so it cannot handle + // its own configuration changes. + if (!mFromClientToken) { + return; + } + + final Configuration config = getConfiguration(); + final int displayId = getDisplayContent().getDisplayId(); + if (config.equals(mLastReportedConfig) && displayId == mLastReportedDisplay) { + // No changes since last reported time. + return; + } + + mLastReportedConfig = config; + mLastReportedDisplay = displayId; + + IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(token); + if (windowTokenClient != null) { + try { + windowTokenClient.onConfigurationChanged(config, displayId); + } catch (RemoteException e) { + ProtoLog.w(WM_ERROR, + "Could not report config changes to the window token client."); + } + } } @Override