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 android.annotation.AnyThread; 23 import android.annotation.BinderThread; 24 import android.annotation.MainThread; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.app.ActivityThread; 28 import android.app.IWindowToken; 29 import android.app.ResourcesManager; 30 import android.content.Context; 31 import android.content.res.CompatibilityInfo; 32 import android.content.res.Configuration; 33 import android.inputmethodservice.AbstractInputMethodService; 34 import android.os.Build; 35 import android.os.Bundle; 36 import android.os.Debug; 37 import android.os.Handler; 38 import android.os.IBinder; 39 import android.os.RemoteException; 40 import android.util.Log; 41 import android.view.IWindowManager; 42 import android.view.WindowManager.LayoutParams.WindowType; 43 import android.view.WindowManagerGlobal; 44 45 import com.android.internal.annotations.GuardedBy; 46 import com.android.internal.annotations.VisibleForTesting; 47 import com.android.internal.util.function.pooled.PooledLambda; 48 49 import java.lang.ref.WeakReference; 50 51 /** 52 * This class is used to receive {@link Configuration} changes from the associated window manager 53 * node on the server side, and apply the change to the {@link Context#getResources() associated 54 * Resources} of the attached {@link Context}. It is also used as 55 * {@link Context#getWindowContextToken() the token of non-Activity UI Contexts}. 56 * 57 * @see WindowContext 58 * @see android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int, Bundle) 59 * 60 * @hide 61 */ 62 public class WindowTokenClient extends IWindowToken.Stub { 63 private static final String TAG = WindowTokenClient.class.getSimpleName(); 64 65 /** 66 * Attached {@link Context} for this window token to update configuration and resources. 67 * Initialized by {@link #attachContext(Context)}. 68 */ 69 private WeakReference<Context> mContextRef = null; 70 71 private final ResourcesManager mResourcesManager = ResourcesManager.getInstance(); 72 73 private IWindowManager mWms; 74 75 @GuardedBy("itself") 76 private final Configuration mConfiguration = new Configuration(); 77 78 private boolean mShouldDumpConfigForIme; 79 80 private boolean mAttachToWindowContainer; 81 82 private final Handler mHandler = ActivityThread.currentActivityThread().getHandler(); 83 84 /** 85 * Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient} 86 * can only attach one {@link Context}. 87 * <p>This method must be called before invoking 88 * {@link android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int, 89 * Bundle)}.<p/> 90 * 91 * @param context context to be attached 92 * @throws IllegalStateException if attached context has already existed. 93 */ attachContext(@onNull Context context)94 public void attachContext(@NonNull Context context) { 95 if (mContextRef != null) { 96 throw new IllegalStateException("Context is already attached."); 97 } 98 mContextRef = new WeakReference<>(context); 99 mShouldDumpConfigForIme = Build.IS_DEBUGGABLE 100 && context instanceof AbstractInputMethodService; 101 } 102 103 /** 104 * Attaches this {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}. 105 * 106 * @param type The window type of the {@link WindowContext} 107 * @param displayId The {@link Context#getDisplayId() ID of display} to associate with 108 * @param options The window context launched option 109 * @return {@code true} if attaching successfully. 110 */ attachToDisplayArea(@indowType int type, int displayId, @Nullable Bundle options)111 public boolean attachToDisplayArea(@WindowType int type, int displayId, 112 @Nullable Bundle options) { 113 try { 114 final Configuration configuration = getWindowManagerService() 115 .attachWindowContextToDisplayArea(this, type, displayId, options); 116 if (configuration == null) { 117 return false; 118 } 119 onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */); 120 mAttachToWindowContainer = true; 121 return true; 122 } catch (RemoteException e) { 123 throw e.rethrowFromSystemServer(); 124 } 125 } 126 127 /** 128 * Attaches this {@link WindowTokenClient} to a {@code DisplayContent}. 129 * 130 * @param displayId The {@link Context#getDisplayId() ID of display} to associate with 131 * @return {@code true} if attaching successfully. 132 */ attachToDisplayContent(int displayId)133 public boolean attachToDisplayContent(int displayId) { 134 final IWindowManager wms = getWindowManagerService(); 135 // #createSystemUiContext may call this method before WindowManagerService is initialized. 136 if (wms == null) { 137 return false; 138 } 139 try { 140 final Configuration configuration = wms.attachToDisplayContent(this, displayId); 141 if (configuration == null) { 142 return false; 143 } 144 onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */); 145 mAttachToWindowContainer = true; 146 return true; 147 } catch (RemoteException e) { 148 throw e.rethrowFromSystemServer(); 149 } 150 } 151 152 /** 153 * Attaches this {@link WindowTokenClient} to a {@code windowToken}. 154 * 155 * @param windowToken the window token to associated with 156 */ attachToWindowToken(IBinder windowToken)157 public void attachToWindowToken(IBinder windowToken) { 158 try { 159 getWindowManagerService().attachWindowContextToWindowToken(this, windowToken); 160 mAttachToWindowContainer = true; 161 } catch (RemoteException e) { 162 throw e.rethrowFromSystemServer(); 163 } 164 } 165 166 /** Detaches this {@link WindowTokenClient} from associated WindowContainer if there's one. */ detachFromWindowContainerIfNeeded()167 public void detachFromWindowContainerIfNeeded() { 168 if (!mAttachToWindowContainer) { 169 return; 170 } 171 try { 172 getWindowManagerService().detachWindowContextFromWindowContainer(this); 173 } catch (RemoteException e) { 174 throw e.rethrowFromSystemServer(); 175 } 176 } 177 getWindowManagerService()178 private IWindowManager getWindowManagerService() { 179 if (mWms == null) { 180 mWms = WindowManagerGlobal.getWindowManagerService(); 181 } 182 return mWms; 183 } 184 185 /** 186 * Called when {@link Configuration} updates from the server side receive. 187 * 188 * @param newConfig the updated {@link Configuration} 189 * @param newDisplayId the updated {@link android.view.Display} ID 190 */ 191 @BinderThread 192 @Override onConfigurationChanged(Configuration newConfig, int newDisplayId)193 public void onConfigurationChanged(Configuration newConfig, int newDisplayId) { 194 mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig, 195 newDisplayId, true /* shouldReportConfigChange */).recycleOnUse()); 196 } 197 198 // TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService 199 // are inherited from WindowProvider. 200 /** 201 * Called when {@link Configuration} updates from the server side receive. 202 * 203 * Similar to {@link #onConfigurationChanged(Configuration, int)}, but adds a flag to control 204 * whether to dispatch configuration update or not. 205 * <p> 206 * Note that this method must be executed on the main thread if 207 * {@code shouldReportConfigChange} is {@code true}, which is usually from 208 * {@link IWindowToken#onConfigurationChanged(Configuration, int)} 209 * directly, while this method could be run on any thread if it is used to initialize 210 * Context's {@code Configuration} via {@link #attachToDisplayArea(int, int, Bundle)} 211 * or {@link #attachToDisplayContent(int)}. 212 * 213 * @param shouldReportConfigChange {@code true} to indicate that the {@code Configuration} 214 * should be dispatched to listeners. 215 * 216 */ 217 @AnyThread 218 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) onConfigurationChanged(Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange)219 public void onConfigurationChanged(Configuration newConfig, int newDisplayId, 220 boolean shouldReportConfigChange) { 221 final Context context = mContextRef.get(); 222 if (context == null) { 223 return; 224 } 225 CompatibilityInfo.applyOverrideScaleIfNeeded(newConfig); 226 final boolean displayChanged; 227 final boolean shouldUpdateResources; 228 final int diff; 229 final Configuration currentConfig; 230 231 synchronized (mConfiguration) { 232 displayChanged = isDifferentDisplay(context.getDisplayId(), newDisplayId); 233 shouldUpdateResources = shouldUpdateResources(this, mConfiguration, 234 newConfig, newConfig /* overrideConfig */, displayChanged, 235 null /* configChanged */); 236 diff = mConfiguration.diffPublicOnly(newConfig); 237 currentConfig = mShouldDumpConfigForIme ? new Configuration(mConfiguration) : null; 238 if (shouldUpdateResources) { 239 mConfiguration.setTo(newConfig); 240 } 241 } 242 243 if (!shouldUpdateResources && mShouldDumpConfigForIme) { 244 Log.d(TAG, "Configuration not dispatch to IME because configuration is up" 245 + " to date. Current config=" + context.getResources().getConfiguration() 246 + ", reported config=" + currentConfig 247 + ", updated config=" + newConfig); 248 } 249 if (shouldUpdateResources) { 250 // TODO(ag/9789103): update resource manager logic to track non-activity tokens 251 mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId); 252 253 if (shouldReportConfigChange && context instanceof WindowContext) { 254 final WindowContext windowContext = (WindowContext) context; 255 windowContext.dispatchConfigurationChanged(newConfig); 256 } 257 258 259 if (shouldReportConfigChange && diff != 0 260 && context instanceof WindowProviderService) { 261 final WindowProviderService windowProviderService = (WindowProviderService) context; 262 windowProviderService.onConfigurationChanged(newConfig); 263 } 264 freeTextLayoutCachesIfNeeded(diff); 265 if (mShouldDumpConfigForIme) { 266 if (!shouldReportConfigChange) { 267 Log.d(TAG, "Only apply configuration update to Resources because " 268 + "shouldReportConfigChange is false.\n" + Debug.getCallers(5)); 269 } else if (diff == 0) { 270 Log.d(TAG, "Configuration not dispatch to IME because configuration has no " 271 + " public difference with updated config. " 272 + " Current config=" + context.getResources().getConfiguration() 273 + ", reported config=" + currentConfig 274 + ", updated config=" + newConfig); 275 } 276 } 277 } 278 if (displayChanged) { 279 context.updateDisplay(newDisplayId); 280 } 281 } 282 283 @BinderThread 284 @Override onWindowTokenRemoved()285 public void onWindowTokenRemoved() { 286 mHandler.post(PooledLambda.obtainRunnable( 287 WindowTokenClient::onWindowTokenRemovedInner, this).recycleOnUse()); 288 } 289 290 @MainThread onWindowTokenRemovedInner()291 private void onWindowTokenRemovedInner() { 292 final Context context = mContextRef.get(); 293 if (context != null) { 294 context.destroy(); 295 mContextRef.clear(); 296 } 297 } 298 } 299