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