Handle config/display changes for WindowContext

Introduce IWindowToken to report config/display changes from
server side. When config change callback is received,
it will update the resources associated with the window token.

Test: WindowContextTests
Bug: 128338354
Bug: 146820733

Change-Id: I871bd78a21dbde1286786e65c340b6259b873660
This commit is contained in:
Andrii Kulian
2020-01-27 22:17:31 -08:00
committed by Charles Chen
parent 80c2e07e00
commit 02c0e4d2ac
8 changed files with 231 additions and 38 deletions

View File

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

View File

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

View File

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

View File

@@ -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}.
* <p>This method must be called before invoking
* {@link android.view.IWindowManager#addWindowTokenWithOptions(IBinder, int, int, Bundle,
* String)}.<p/>
*
* @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);
}
}
}

View File

@@ -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.
* <p>
* 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}

View File

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

View File

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

View File

@@ -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<WindowState> {
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<WindowState> {
}
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<WindowState> {
// 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