254 lines
11 KiB
Java
254 lines
11 KiB
Java
/*
|
|
* 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 static com.android.window.flags.Flags.windowTokenConfigThreadSafe;
|
|
|
|
import android.annotation.AnyThread;
|
|
import android.annotation.MainThread;
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.app.ActivityThread;
|
|
import android.app.ResourcesManager;
|
|
import android.app.servertransaction.ClientTransactionListenerController;
|
|
import android.content.Context;
|
|
import android.content.res.CompatibilityInfo;
|
|
import android.content.res.Configuration;
|
|
import android.inputmethodservice.AbstractInputMethodService;
|
|
import android.os.Binder;
|
|
import android.os.Build;
|
|
import android.os.Debug;
|
|
import android.os.Handler;
|
|
import android.util.Log;
|
|
|
|
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;
|
|
|
|
/**
|
|
* 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
|
|
*
|
|
* @hide
|
|
*/
|
|
public class WindowTokenClient extends Binder {
|
|
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();
|
|
|
|
@GuardedBy("itself")
|
|
private final Configuration mConfiguration = new Configuration();
|
|
|
|
private boolean mShouldDumpConfigForIme;
|
|
|
|
private final Handler mHandler = ActivityThread.currentActivityThread().getHandler();
|
|
|
|
/**
|
|
* 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}.<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);
|
|
mShouldDumpConfigForIme = Build.IS_DEBUGGABLE
|
|
&& context instanceof AbstractInputMethodService;
|
|
}
|
|
|
|
/**
|
|
* Gets the {@link Context} that this {@link WindowTokenClient} is attached through
|
|
* {@link #attachContext(Context)}.
|
|
*/
|
|
@Nullable
|
|
public Context getContext() {
|
|
return mContextRef != null ? mContextRef.get() : null;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
@VisibleForTesting
|
|
@MainThread
|
|
public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
|
|
onConfigurationChanged(newConfig, newDisplayId, true /* shouldReportConfigChange */);
|
|
}
|
|
|
|
/**
|
|
* Posts an {@link #onConfigurationChanged} to the main thread.
|
|
*/
|
|
@VisibleForTesting
|
|
public void postOnConfigurationChanged(@NonNull Configuration newConfig, int newDisplayId) {
|
|
mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig,
|
|
newDisplayId, true /* shouldReportConfigChange */).recycleOnUse());
|
|
}
|
|
|
|
// 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.
|
|
* <p>
|
|
* Note that this method must be executed on the main thread if
|
|
* {@code shouldReportConfigChange} is {@code true}, which is usually from
|
|
* {@link #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 WindowTokenClientController#attachToDisplayArea}
|
|
* or {@link WindowTokenClientController#attachToDisplayContent}.
|
|
*
|
|
* @param shouldReportConfigChange {@code true} to indicate that the {@code Configuration}
|
|
* should be dispatched to listeners.
|
|
*/
|
|
@AnyThread
|
|
public void onConfigurationChanged(@NonNull Configuration newConfig, int newDisplayId,
|
|
boolean shouldReportConfigChange) {
|
|
final Context context = mContextRef.get();
|
|
if (context == null) {
|
|
return;
|
|
}
|
|
if (shouldReportConfigChange && windowTokenConfigThreadSafe()) {
|
|
// Only report to ClientTransactionListenerController when shouldReportConfigChange.
|
|
final ClientTransactionListenerController controller =
|
|
getClientTransactionListenerController();
|
|
controller.onContextConfigurationPreChanged(context);
|
|
try {
|
|
onConfigurationChangedInner(context, newConfig, newDisplayId,
|
|
shouldReportConfigChange);
|
|
} finally {
|
|
controller.onContextConfigurationPostChanged(context);
|
|
}
|
|
} else {
|
|
onConfigurationChangedInner(context, newConfig, newDisplayId, shouldReportConfigChange);
|
|
}
|
|
}
|
|
|
|
/** Handles onConfiguration changed. */
|
|
@VisibleForTesting
|
|
public void onConfigurationChangedInner(@NonNull Context context,
|
|
@NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange) {
|
|
CompatibilityInfo.applyOverrideScaleIfNeeded(newConfig);
|
|
final boolean displayChanged;
|
|
final boolean shouldUpdateResources;
|
|
final int diff;
|
|
final Configuration currentConfig;
|
|
|
|
synchronized (mConfiguration) {
|
|
displayChanged = isDifferentDisplay(context.getDisplayId(), newDisplayId);
|
|
shouldUpdateResources = shouldUpdateResources(this, mConfiguration,
|
|
newConfig, newConfig /* overrideConfig */, displayChanged,
|
|
null /* configChanged */);
|
|
diff = mConfiguration.diffPublicOnly(newConfig);
|
|
currentConfig = mShouldDumpConfigForIme ? new Configuration(mConfiguration) : null;
|
|
if (shouldUpdateResources) {
|
|
mConfiguration.setTo(newConfig);
|
|
}
|
|
}
|
|
|
|
if (!shouldUpdateResources && mShouldDumpConfigForIme) {
|
|
Log.d(TAG, "Configuration not dispatch to IME because configuration is up"
|
|
+ " to date. Current config=" + context.getResources().getConfiguration()
|
|
+ ", reported config=" + currentConfig
|
|
+ ", updated config=" + newConfig
|
|
+ ", updated display ID=" + newDisplayId);
|
|
}
|
|
// Update display first. In case callers want to obtain display information(
|
|
// ex: DisplayMetrics) in #onConfigurationChanged callback.
|
|
if (displayChanged) {
|
|
context.updateDisplay(newDisplayId);
|
|
}
|
|
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);
|
|
}
|
|
|
|
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. "
|
|
+ "context=" + context
|
|
+ ", config=" + context.getResources().getConfiguration()
|
|
+ ", display ID=" + context.getDisplayId() + "\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=" + currentConfig
|
|
+ ", updated config=" + newConfig
|
|
+ ", display ID=" + context.getDisplayId());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when the attached window is removed from the display.
|
|
*/
|
|
@VisibleForTesting
|
|
@MainThread
|
|
public void onWindowTokenRemoved() {
|
|
final Context context = mContextRef.get();
|
|
if (context != null) {
|
|
context.destroy();
|
|
mContextRef.clear();
|
|
}
|
|
}
|
|
|
|
/** Gets {@link ClientTransactionListenerController}. */
|
|
@VisibleForTesting
|
|
@NonNull
|
|
public ClientTransactionListenerController getClientTransactionListenerController() {
|
|
return ClientTransactionListenerController.getInstance();
|
|
}
|
|
}
|