blob: 547535d90e5aaa70f35d242914d1a608257aad23 [file] [log] [blame]
/*
* 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.window;
import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
import static android.window.ConfigurationHelper.isDifferentDisplay;
import static android.window.ConfigurationHelper.shouldUpdateResources;
import android.annotation.BinderThread;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.IWindowToken;
import android.app.ResourcesManager;
import android.content.Context;
import android.content.res.Configuration;
import android.inputmethodservice.AbstractInputMethodService;
import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
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.VisibleForTesting;
import java.lang.ref.WeakReference;
/**
* This class is used to receive {@link Configuration} changes from the associated window manager
* node on the server side, and apply the change to the {@link Context#getResources() associated
* Resources} of the attached {@link Context}. It is also used as
* {@link Context#getWindowContextToken() the token of non-Activity UI Contexts}.
*
* @see WindowContext
* @see android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int, Bundle)
*
* @hide
*/
public class WindowTokenClient extends IWindowToken.Stub {
private static final String TAG = WindowTokenClient.class.getSimpleName();
/**
* Attached {@link Context} for this window token to update configuration and resources.
* Initialized by {@link #attachContext(Context)}.
*/
private WeakReference<Context> mContextRef = null;
private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
private IWindowManager mWms;
private final Configuration mConfiguration = new Configuration();
private boolean mShouldDumpConfigForIme;
private boolean mAttachToWindowContainer;
private final Handler mHandler = new Handler(Looper.getMainLooper());
/**
* 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#attachWindowContextToDisplayArea(IBinder, int, int,
* Bundle)}.<p/>
*
* @param context context to be attached
* @throws IllegalStateException if attached context has already existed.
*/
public void attachContext(@NonNull Context context) {
if (mContextRef != null) {
throw new IllegalStateException("Context is already attached.");
}
mContextRef = new WeakReference<>(context);
mConfiguration.setTo(context.getResources().getConfiguration());
mShouldDumpConfigForIme = Build.IS_DEBUGGABLE
&& 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;
}
mHandler.post(() -> 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}
* @param newDisplayId the updated {@link android.view.Display} ID
*/
@BinderThread
@Override
public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
mHandler.post(() -> onConfigurationChanged(newConfig, newDisplayId,
true /* shouldReportConfigChange */));
}
// TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService
// are inherited from WindowProvider.
/**
* 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.
*/
@MainThread
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void onConfigurationChanged(Configuration newConfig, int newDisplayId,
boolean shouldReportConfigChange) {
final Context context = mContextRef.get();
if (context == null) {
return;
}
final boolean displayChanged = isDifferentDisplay(context.getDisplayId(), newDisplayId);
final boolean shouldUpdateResources = shouldUpdateResources(this, mConfiguration,
newConfig, newConfig /* overrideConfig */, displayChanged,
null /* configChanged */);
if (!shouldUpdateResources && mShouldDumpConfigForIme) {
Log.d(TAG, "Configuration not dispatch to IME because configuration is up"
+ " to date. Current config=" + context.getResources().getConfiguration()
+ ", reported config=" + mConfiguration
+ ", updated config=" + newConfig);
}
if (shouldUpdateResources) {
// TODO(ag/9789103): update resource manager logic to track non-activity tokens
mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
if (shouldReportConfigChange && context instanceof WindowContext) {
final WindowContext windowContext = (WindowContext) context;
windowContext.dispatchConfigurationChanged(newConfig);
}
final int diff = mConfiguration.diffPublicOnly(newConfig);
if (shouldReportConfigChange && diff != 0
&& context instanceof WindowProviderService) {
final WindowProviderService windowProviderService = (WindowProviderService) context;
windowProviderService.onConfigurationChanged(newConfig);
}
freeTextLayoutCachesIfNeeded(diff);
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);
}
if (displayChanged) {
context.updateDisplay(newDisplayId);
}
}
@BinderThread
@Override
public void onWindowTokenRemoved() {
mHandler.post(() -> {
final Context context = mContextRef.get();
if (context != null) {
context.destroy();
mContextRef.clear();
}
});
}
}