1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.window; 17 18 import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded; 19 import static android.window.ConfigurationHelper.isDifferentDisplay; 20 import static android.window.ConfigurationHelper.shouldUpdateResources; 21 22 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; 23 24 import android.annotation.AnyThread; 25 import android.annotation.MainThread; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.app.ActivityThread; 29 import android.app.ResourcesManager; 30 import android.app.servertransaction.ClientTransactionListenerController; 31 import android.content.Context; 32 import android.content.res.CompatibilityInfo; 33 import android.content.res.Configuration; 34 import android.inputmethodservice.AbstractInputMethodService; 35 import android.os.Binder; 36 import android.os.Build; 37 import android.os.Debug; 38 import android.os.Handler; 39 import android.util.Log; 40 41 import com.android.internal.annotations.GuardedBy; 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.internal.util.function.pooled.PooledLambda; 44 45 import java.lang.ref.WeakReference; 46 47 /** 48 * This class is used to receive {@link Configuration} changes from the associated window manager 49 * node on the server side, and apply the change to the {@link Context#getResources() associated 50 * Resources} of the attached {@link Context}. It is also used as 51 * {@link Context#getWindowContextToken() the token of non-Activity UI Contexts}. 52 * 53 * @see WindowContext 54 * @see android.view.IWindowManager#attachWindowContextToDisplayArea 55 * 56 * @hide 57 */ 58 public class WindowTokenClient extends Binder { 59 private static final String TAG = WindowTokenClient.class.getSimpleName(); 60 61 /** 62 * Attached {@link Context} for this window token to update configuration and resources. 63 * Initialized by {@link #attachContext(Context)}. 64 */ 65 private WeakReference<Context> mContextRef = null; 66 67 private final ResourcesManager mResourcesManager = ResourcesManager.getInstance(); 68 69 @GuardedBy("itself") 70 private final Configuration mConfiguration = new Configuration(); 71 72 private boolean mShouldDumpConfigForIme; 73 74 private final Handler mHandler = ActivityThread.currentActivityThread().getHandler(); 75 76 /** 77 * Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient} 78 * can only attach one {@link Context}. 79 * <p>This method must be called before invoking 80 * {@link android.view.IWindowManager#attachWindowContextToDisplayArea}.<p/> 81 * 82 * @param context context to be attached 83 * @throws IllegalStateException if attached context has already existed. 84 */ attachContext(@onNull Context context)85 public void attachContext(@NonNull Context context) { 86 if (mContextRef != null) { 87 throw new IllegalStateException("Context is already attached."); 88 } 89 mContextRef = new WeakReference<>(context); 90 mShouldDumpConfigForIme = Build.IS_DEBUGGABLE 91 && context instanceof AbstractInputMethodService; 92 } 93 94 /** 95 * Gets the {@link Context} that this {@link WindowTokenClient} is attached through 96 * {@link #attachContext(Context)}. 97 */ 98 @Nullable getContext()99 public Context getContext() { 100 return mContextRef != null ? mContextRef.get() : null; 101 } 102 103 /** 104 * Called when {@link Configuration} updates from the server side receive. 105 * 106 * @param newConfig the updated {@link Configuration} 107 * @param newDisplayId the updated {@link android.view.Display} ID 108 */ 109 @MainThread 110 @VisibleForTesting(visibility = PACKAGE) onConfigurationChanged(Configuration newConfig, int newDisplayId)111 public void onConfigurationChanged(Configuration newConfig, int newDisplayId) { 112 onConfigurationChanged(newConfig, newDisplayId, true /* shouldReportConfigChange */); 113 } 114 115 /** 116 * Posts an {@link #onConfigurationChanged} to the main thread. 117 */ 118 @VisibleForTesting(visibility = PACKAGE) postOnConfigurationChanged(@onNull Configuration newConfig, int newDisplayId)119 public void postOnConfigurationChanged(@NonNull Configuration newConfig, int newDisplayId) { 120 mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig, 121 newDisplayId, true /* shouldReportConfigChange */).recycleOnUse()); 122 } 123 124 /** 125 * Called when {@link Configuration} updates from the server side receive. 126 * 127 * Similar to {@link #onConfigurationChanged(Configuration, int)}, but adds a flag to control 128 * whether to dispatch configuration update or not. 129 * <p> 130 * Note that this method must be executed on the main thread if 131 * {@code shouldReportConfigChange} is {@code true}, which is usually from 132 * {@link #onConfigurationChanged(Configuration, int)} 133 * directly, while this method could be run on any thread if it is used to initialize 134 * Context's {@code Configuration} via {@link WindowTokenClientController#attachToDisplayArea} 135 * or {@link WindowTokenClientController#attachToDisplayContent}. 136 * 137 * @param shouldReportConfigChange {@code true} to indicate that the {@code Configuration} 138 * should be dispatched to listeners. 139 */ 140 @AnyThread onConfigurationChanged(@onNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange)141 public void onConfigurationChanged(@NonNull Configuration newConfig, int newDisplayId, 142 boolean shouldReportConfigChange) { 143 final Context context = mContextRef.get(); 144 if (context == null) { 145 return; 146 } 147 if (shouldReportConfigChange) { 148 // Only report to ClientTransactionListenerController when shouldReportConfigChange. 149 final ClientTransactionListenerController controller = 150 getClientTransactionListenerController(); 151 controller.onContextConfigurationPreChanged(context); 152 try { 153 onConfigurationChangedInner(context, newConfig, newDisplayId, 154 shouldReportConfigChange); 155 } finally { 156 controller.onContextConfigurationPostChanged(context); 157 } 158 } else { 159 onConfigurationChangedInner(context, newConfig, newDisplayId, shouldReportConfigChange); 160 } 161 } 162 163 /** Handles onConfiguration changed. */ 164 @VisibleForTesting onConfigurationChangedInner(@onNull Context context, @NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange)165 public void onConfigurationChangedInner(@NonNull Context context, 166 @NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange) { 167 CompatibilityInfo.applyOverrideIfNeeded(newConfig); 168 final boolean displayChanged; 169 final boolean shouldUpdateResources; 170 final int publicDiff; 171 final Configuration currentConfig; 172 173 synchronized (mConfiguration) { 174 displayChanged = isDifferentDisplay(context.getDisplayId(), newDisplayId); 175 shouldUpdateResources = shouldUpdateResources(this, mConfiguration, 176 newConfig, newConfig /* overrideConfig */, displayChanged, 177 null /* configChanged */); 178 publicDiff = mConfiguration.diffPublicOnly(newConfig); 179 currentConfig = mShouldDumpConfigForIme ? new Configuration(mConfiguration) : null; 180 if (shouldUpdateResources) { 181 mConfiguration.setTo(newConfig); 182 } 183 } 184 185 if (!shouldUpdateResources && mShouldDumpConfigForIme) { 186 Log.d(TAG, "Configuration not dispatch to IME because configuration is up" 187 + " to date. Current config=" + context.getResources().getConfiguration() 188 + ", reported config=" + currentConfig 189 + ", updated config=" + newConfig 190 + ", updated display ID=" + newDisplayId); 191 } 192 // Update display first. In case callers want to obtain display information( 193 // ex: DisplayMetrics) in #onConfigurationChanged callback. 194 if (displayChanged) { 195 context.updateDisplay(newDisplayId); 196 } 197 if (shouldUpdateResources) { 198 // TODO(ag/9789103): update resource manager logic to track non-activity tokens 199 mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId); 200 201 if (shouldReportConfigChange && context instanceof ConfigurationDispatcher dispatcher) { 202 // Updating resources implies some fields of configuration are updated despite they 203 // are public or not. 204 if (dispatcher.shouldReportPrivateChanges() || publicDiff != 0) { 205 dispatcher.dispatchConfigurationChanged(newConfig); 206 } 207 } 208 209 freeTextLayoutCachesIfNeeded(publicDiff); 210 if (mShouldDumpConfigForIme) { 211 if (!shouldReportConfigChange) { 212 Log.d(TAG, "Only apply configuration update to Resources because " 213 + "shouldReportConfigChange is false. " 214 + "context=" + context 215 + ", config=" + context.getResources().getConfiguration() 216 + ", display ID=" + context.getDisplayId() + "\n" 217 + Debug.getCallers(5)); 218 } else if (publicDiff == 0) { 219 Log.d(TAG, "Configuration not dispatch to IME because configuration has no " 220 + " public difference with updated config. " 221 + " Current config=" + context.getResources().getConfiguration() 222 + ", reported config=" + currentConfig 223 + ", updated config=" + newConfig 224 + ", display ID=" + context.getDisplayId()); 225 } 226 } 227 } 228 } 229 230 /** 231 * Called when the attached window is removed from the display. 232 */ 233 @VisibleForTesting(visibility = PACKAGE) 234 @MainThread onWindowTokenRemoved()235 public void onWindowTokenRemoved() { 236 final Context context = mContextRef.get(); 237 if (context != null) { 238 context.destroy(); 239 mContextRef.clear(); 240 } 241 } 242 243 /** Gets {@link ClientTransactionListenerController}. */ 244 @VisibleForTesting 245 @NonNull getClientTransactionListenerController()246 public ClientTransactionListenerController getClientTransactionListenerController() { 247 return ClientTransactionListenerController.getInstance(); 248 } 249 } 250