script-astra/Android/Sdk/sources/android-35/android/window/WindowTokenClient.java
localadmin 4380f00a78 init
2025-01-20 18:15:20 +03:00

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