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.window.flags.Flags.windowTokenConfigThreadSafe; 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 @VisibleForTesting 110 @MainThread 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 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 // TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService 125 // are inherited from WindowProvider. 126 /** 127 * Called when {@link Configuration} updates from the server side receive. 128 * 129 * Similar to {@link #onConfigurationChanged(Configuration, int)}, but adds a flag to control 130 * whether to dispatch configuration update or not. 131 * <p> 132 * Note that this method must be executed on the main thread if 133 * {@code shouldReportConfigChange} is {@code true}, which is usually from 134 * {@link #onConfigurationChanged(Configuration, int)} 135 * directly, while this method could be run on any thread if it is used to initialize 136 * Context's {@code Configuration} via {@link WindowTokenClientController#attachToDisplayArea} 137 * or {@link WindowTokenClientController#attachToDisplayContent}. 138 * 139 * @param shouldReportConfigChange {@code true} to indicate that the {@code Configuration} 140 * should be dispatched to listeners. 141 */ 142 @AnyThread onConfigurationChanged(@onNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange)143 public void onConfigurationChanged(@NonNull Configuration newConfig, int newDisplayId, 144 boolean shouldReportConfigChange) { 145 final Context context = mContextRef.get(); 146 if (context == null) { 147 return; 148 } 149 if (shouldReportConfigChange && windowTokenConfigThreadSafe()) { 150 // Only report to ClientTransactionListenerController when shouldReportConfigChange. 151 final ClientTransactionListenerController controller = 152 getClientTransactionListenerController(); 153 controller.onContextConfigurationPreChanged(context); 154 try { 155 onConfigurationChangedInner(context, newConfig, newDisplayId, 156 shouldReportConfigChange); 157 } finally { 158 controller.onContextConfigurationPostChanged(context); 159 } 160 } else { 161 onConfigurationChangedInner(context, newConfig, newDisplayId, shouldReportConfigChange); 162 } 163 } 164 165 /** Handles onConfiguration changed. */ 166 @VisibleForTesting onConfigurationChangedInner(@onNull Context context, @NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange)167 public void onConfigurationChangedInner(@NonNull Context context, 168 @NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange) { 169 CompatibilityInfo.applyOverrideScaleIfNeeded(newConfig); 170 final boolean displayChanged; 171 final boolean shouldUpdateResources; 172 final int diff; 173 final Configuration currentConfig; 174 175 synchronized (mConfiguration) { 176 displayChanged = isDifferentDisplay(context.getDisplayId(), newDisplayId); 177 shouldUpdateResources = shouldUpdateResources(this, mConfiguration, 178 newConfig, newConfig /* overrideConfig */, displayChanged, 179 null /* configChanged */); 180 diff = mConfiguration.diffPublicOnly(newConfig); 181 currentConfig = mShouldDumpConfigForIme ? new Configuration(mConfiguration) : null; 182 if (shouldUpdateResources) { 183 mConfiguration.setTo(newConfig); 184 } 185 } 186 187 if (!shouldUpdateResources && mShouldDumpConfigForIme) { 188 Log.d(TAG, "Configuration not dispatch to IME because configuration is up" 189 + " to date. Current config=" + context.getResources().getConfiguration() 190 + ", reported config=" + currentConfig 191 + ", updated config=" + newConfig 192 + ", updated display ID=" + newDisplayId); 193 } 194 // Update display first. In case callers want to obtain display information( 195 // ex: DisplayMetrics) in #onConfigurationChanged callback. 196 if (displayChanged) { 197 context.updateDisplay(newDisplayId); 198 } 199 if (shouldUpdateResources) { 200 // TODO(ag/9789103): update resource manager logic to track non-activity tokens 201 mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId); 202 203 if (shouldReportConfigChange && context instanceof WindowContext) { 204 final WindowContext windowContext = (WindowContext) context; 205 windowContext.dispatchConfigurationChanged(newConfig); 206 } 207 208 if (shouldReportConfigChange && diff != 0 209 && context instanceof WindowProviderService) { 210 final WindowProviderService windowProviderService = (WindowProviderService) context; 211 windowProviderService.onConfigurationChanged(newConfig); 212 } 213 freeTextLayoutCachesIfNeeded(diff); 214 if (mShouldDumpConfigForIme) { 215 if (!shouldReportConfigChange) { 216 Log.d(TAG, "Only apply configuration update to Resources because " 217 + "shouldReportConfigChange is false. " 218 + "context=" + context 219 + ", config=" + context.getResources().getConfiguration() 220 + ", display ID=" + context.getDisplayId() + "\n" 221 + Debug.getCallers(5)); 222 } else if (diff == 0) { 223 Log.d(TAG, "Configuration not dispatch to IME because configuration has no " 224 + " public difference with updated config. " 225 + " Current config=" + context.getResources().getConfiguration() 226 + ", reported config=" + currentConfig 227 + ", updated config=" + newConfig 228 + ", display ID=" + context.getDisplayId()); 229 } 230 } 231 } 232 } 233 234 /** 235 * Called when the attached window is removed from the display. 236 */ 237 @VisibleForTesting 238 @MainThread onWindowTokenRemoved()239 public void onWindowTokenRemoved() { 240 final Context context = mContextRef.get(); 241 if (context != null) { 242 context.destroy(); 243 mContextRef.clear(); 244 } 245 } 246 247 /** Gets {@link ClientTransactionListenerController}. */ 248 @VisibleForTesting 249 @NonNull getClientTransactionListenerController()250 public ClientTransactionListenerController getClientTransactionListenerController() { 251 return ClientTransactionListenerController.getInstance(); 252 } 253 } 254