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