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 17 package com.android.server.wm; 18 19 import static android.view.Display.INVALID_DISPLAY; 20 import static android.view.Display.isSuspendedState; 21 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; 22 import static android.window.WindowProviderService.isWindowProviderService; 23 24 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; 25 import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR; 26 27 import android.annotation.NonNull; 28 import android.annotation.Nullable; 29 import android.app.IWindowToken; 30 import android.content.Context; 31 import android.content.res.Configuration; 32 import android.os.Bundle; 33 import android.os.IBinder; 34 import android.os.RemoteException; 35 import android.util.ArrayMap; 36 import android.view.View; 37 import android.view.WindowManager.LayoutParams.WindowType; 38 import android.window.WindowContext; 39 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.internal.protolog.common.ProtoLog; 42 43 import java.util.Objects; 44 45 /** 46 * A controller to register/unregister {@link WindowContainerListener} for {@link WindowContext}. 47 * 48 * <ul> 49 * <li>When a {@link WindowContext} is created, it registers the listener via 50 * {@link WindowManagerService#attachWindowContextToDisplayArea(IBinder, int, int, Bundle)} 51 * automatically.</li> 52 * <li>When the {@link WindowContext} adds the first window to the screen via 53 * {@link android.view.WindowManager#addView(View, android.view.ViewGroup.LayoutParams)}, 54 * {@link WindowManagerService} then updates the {@link WindowContextListenerImpl} to listen 55 * to corresponding {@link WindowToken} via this controller.</li> 56 * <li>When the {@link WindowContext} is GCed, it unregisters the previously 57 * registered listener via 58 * {@link WindowManagerService#detachWindowContextFromWindowContainer(IBinder)}. 59 * {@link WindowManagerService} is also responsible for removing the 60 * {@link WindowContext} created {@link WindowToken}.</li> 61 * </ul> 62 * <p>Note that the listener may be removed earlier than the 63 * {@link #unregisterWindowContainerListener(IBinder)} if the listened {@link WindowContainer} was 64 * removed. An example is that the {@link DisplayArea} is removed when users unfold the 65 * foldable devices. Another example is that the associated external display is detached.</p> 66 */ 67 class WindowContextListenerController { 68 @VisibleForTesting 69 final ArrayMap<IBinder, WindowContextListenerImpl> mListeners = new ArrayMap<>(); 70 71 /** 72 * @see #registerWindowContainerListener(IBinder, WindowContainer, int, int, Bundle, boolean) 73 */ registerWindowContainerListener(@onNull IBinder clientToken, @NonNull WindowContainer<?> container, int ownerUid, @WindowType int type, @Nullable Bundle options)74 void registerWindowContainerListener(@NonNull IBinder clientToken, 75 @NonNull WindowContainer<?> container, int ownerUid, @WindowType int type, 76 @Nullable Bundle options) { 77 registerWindowContainerListener(clientToken, container, ownerUid, type, options, 78 true /* shouDispatchConfigWhenRegistering */); 79 } 80 81 /** 82 * Registers the listener to a {@code container} which is associated with 83 * a {@code clientToken}, which is a {@link android.window.WindowContext} representation. If the 84 * listener associated with {@code clientToken} hasn't been initialized yet, create one 85 * {@link WindowContextListenerImpl}. Otherwise, the listener associated with 86 * {@code clientToken} switches to listen to the {@code container}. 87 * 88 * @param clientToken the token to associate with the listener 89 * @param container the {@link WindowContainer} which the listener is going to listen to. 90 * @param ownerUid the caller UID 91 * @param type the window type 92 * @param options a bundle used to pass window-related options. 93 * @param shouDispatchConfigWhenRegistering {@code true} to indicate the current 94 * {@code container}'s config will dispatch to the client side when 95 * registering the {@link WindowContextListenerImpl} 96 */ registerWindowContainerListener(@onNull IBinder clientToken, @NonNull WindowContainer<?> container, int ownerUid, @WindowType int type, @Nullable Bundle options, boolean shouDispatchConfigWhenRegistering)97 void registerWindowContainerListener(@NonNull IBinder clientToken, 98 @NonNull WindowContainer<?> container, int ownerUid, @WindowType int type, 99 @Nullable Bundle options, boolean shouDispatchConfigWhenRegistering) { 100 WindowContextListenerImpl listener = mListeners.get(clientToken); 101 if (listener == null) { 102 listener = new WindowContextListenerImpl(clientToken, container, ownerUid, type, 103 options); 104 listener.register(shouDispatchConfigWhenRegistering); 105 } else { 106 listener.updateContainer(container); 107 } 108 } 109 unregisterWindowContainerListener(IBinder clientToken)110 void unregisterWindowContainerListener(IBinder clientToken) { 111 final WindowContextListenerImpl listener = mListeners.get(clientToken); 112 // Listeners may be removed earlier. An example is the display where the listener is 113 // located is detached. In this case, all window containers on the display, as well as 114 // their listeners will be removed before their listeners are unregistered. 115 if (listener == null) { 116 return; 117 } 118 listener.unregister(); 119 if (listener.mDeathRecipient != null) { 120 listener.mDeathRecipient.unlinkToDeath(); 121 } 122 } 123 dispatchPendingConfigurationIfNeeded(int displayId)124 void dispatchPendingConfigurationIfNeeded(int displayId) { 125 for (int i = mListeners.size() - 1; i >= 0; --i) { 126 final WindowContextListenerImpl listener = mListeners.valueAt(i); 127 if (listener.getWindowContainer().getDisplayContent().getDisplayId() == displayId 128 && listener.mHasPendingConfiguration) { 129 listener.reportConfigToWindowTokenClient(); 130 } 131 } 132 } 133 134 /** 135 * Verifies if the caller is allowed to do the operation to the listener specified by 136 * {@code clientToken}. 137 */ assertCallerCanModifyListener(IBinder clientToken, boolean callerCanManageAppTokens, int callingUid)138 boolean assertCallerCanModifyListener(IBinder clientToken, boolean callerCanManageAppTokens, 139 int callingUid) { 140 final WindowContextListenerImpl listener = mListeners.get(clientToken); 141 if (listener == null) { 142 ProtoLog.i(WM_DEBUG_ADD_REMOVE, "The listener does not exist."); 143 return false; 144 } 145 if (callerCanManageAppTokens) { 146 return true; 147 } 148 if (callingUid != listener.mOwnerUid) { 149 throw new UnsupportedOperationException("Uid mismatch. Caller uid is " + callingUid 150 + ", while the listener's owner is from " + listener.mOwnerUid); 151 } 152 return true; 153 } 154 hasListener(IBinder clientToken)155 boolean hasListener(IBinder clientToken) { 156 return mListeners.containsKey(clientToken); 157 } 158 getWindowType(IBinder clientToken)159 @WindowType int getWindowType(IBinder clientToken) { 160 final WindowContextListenerImpl listener = mListeners.get(clientToken); 161 return listener != null ? listener.mType : INVALID_WINDOW_TYPE; 162 } 163 getOptions(IBinder clientToken)164 @Nullable Bundle getOptions(IBinder clientToken) { 165 final WindowContextListenerImpl listener = mListeners.get(clientToken); 166 return listener != null ? listener.mOptions : null; 167 } 168 getContainer(IBinder clientToken)169 @Nullable WindowContainer<?> getContainer(IBinder clientToken) { 170 final WindowContextListenerImpl listener = mListeners.get(clientToken); 171 return listener != null ? listener.mContainer : null; 172 } 173 174 @Override toString()175 public String toString() { 176 final StringBuilder builder = new StringBuilder("WindowContextListenerController{"); 177 builder.append("mListeners=["); 178 179 final int size = mListeners.values().size(); 180 for (int i = 0; i < size; i++) { 181 builder.append(mListeners.valueAt(i)); 182 if (i != size - 1) { 183 builder.append(", "); 184 } 185 } 186 builder.append("]}"); 187 return builder.toString(); 188 } 189 190 @VisibleForTesting 191 class WindowContextListenerImpl implements WindowContainerListener { 192 @NonNull private final IWindowToken mClientToken; 193 private final int mOwnerUid; 194 @NonNull private WindowContainer<?> mContainer; 195 /** 196 * The options from {@link Context#createWindowContext(int, Bundle)}. 197 * <p>It can be used for choosing the {@link DisplayArea} where the window context 198 * is located. </p> 199 */ 200 @Nullable private final Bundle mOptions; 201 @WindowType private final int mType; 202 203 private DeathRecipient mDeathRecipient; 204 205 private int mLastReportedDisplay = INVALID_DISPLAY; 206 private Configuration mLastReportedConfig; 207 208 private boolean mHasPendingConfiguration; 209 WindowContextListenerImpl(IBinder clientToken, WindowContainer<?> container, int ownerUid, @WindowType int type, @Nullable Bundle options)210 private WindowContextListenerImpl(IBinder clientToken, WindowContainer<?> container, 211 int ownerUid, @WindowType int type, @Nullable Bundle options) { 212 mClientToken = IWindowToken.Stub.asInterface(clientToken); 213 mContainer = Objects.requireNonNull(container); 214 mOwnerUid = ownerUid; 215 mType = type; 216 mOptions = options; 217 218 final DeathRecipient deathRecipient = new DeathRecipient(); 219 try { 220 deathRecipient.linkToDeath(); 221 mDeathRecipient = deathRecipient; 222 } catch (RemoteException e) { 223 ProtoLog.e(WM_ERROR, "Could not register window container listener token=%s, " 224 + "container=%s", clientToken, mContainer); 225 } 226 } 227 228 /** TEST ONLY: returns the {@link WindowContainer} of the listener */ 229 @VisibleForTesting getWindowContainer()230 WindowContainer<?> getWindowContainer() { 231 return mContainer; 232 } 233 updateContainer(@onNull WindowContainer<?> newContainer)234 private void updateContainer(@NonNull WindowContainer<?> newContainer) { 235 Objects.requireNonNull(newContainer); 236 237 if (mContainer.equals(newContainer)) { 238 return; 239 } 240 mContainer.unregisterWindowContainerListener(this); 241 mContainer = newContainer; 242 clear(); 243 register(); 244 } 245 register()246 private void register() { 247 register(true /* shouldDispatchConfig */); 248 } 249 register(boolean shouldDispatchConfig)250 private void register(boolean shouldDispatchConfig) { 251 final IBinder token = mClientToken.asBinder(); 252 if (mDeathRecipient == null) { 253 throw new IllegalStateException("Invalid client token: " + token); 254 } 255 mListeners.putIfAbsent(token, this); 256 mContainer.registerWindowContainerListener(this, shouldDispatchConfig); 257 } 258 unregister()259 private void unregister() { 260 mContainer.unregisterWindowContainerListener(this); 261 mListeners.remove(mClientToken.asBinder()); 262 } 263 clear()264 private void clear() { 265 mLastReportedConfig = null; 266 mLastReportedDisplay = INVALID_DISPLAY; 267 } 268 269 @Override onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig)270 public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) { 271 reportConfigToWindowTokenClient(); 272 } 273 274 @Override onDisplayChanged(DisplayContent dc)275 public void onDisplayChanged(DisplayContent dc) { 276 reportConfigToWindowTokenClient(); 277 } 278 reportConfigToWindowTokenClient()279 private void reportConfigToWindowTokenClient() { 280 if (mDeathRecipient == null) { 281 throw new IllegalStateException("Invalid client token: " + mClientToken.asBinder()); 282 } 283 final DisplayContent dc = mContainer.getDisplayContent(); 284 if (!dc.isReady()) { 285 // Do not report configuration when booting. The latest configuration will be sent 286 // when WindowManagerService#displayReady(). 287 return; 288 } 289 // If the display of window context associated window container is suspended, don't 290 // report the configuration update. Note that we still dispatch the configuration update 291 // to WindowProviderService to make it compatible with Service#onConfigurationChanged. 292 // Service always receives #onConfigurationChanged callback regardless of display state. 293 if (!isWindowProviderService(mOptions) && isSuspendedState(dc.getDisplayInfo().state)) { 294 mHasPendingConfiguration = true; 295 return; 296 } 297 final Configuration config = mContainer.getConfiguration(); 298 final int displayId = dc.getDisplayId(); 299 if (mLastReportedConfig == null) { 300 mLastReportedConfig = new Configuration(); 301 } 302 if (config.equals(mLastReportedConfig) && displayId == mLastReportedDisplay) { 303 // No changes since last reported time. 304 return; 305 } 306 307 mLastReportedConfig.setTo(config); 308 mLastReportedDisplay = displayId; 309 310 try { 311 mClientToken.onConfigurationChanged(config, displayId); 312 } catch (RemoteException e) { 313 ProtoLog.w(WM_ERROR, "Could not report config changes to the window token client."); 314 } 315 mHasPendingConfiguration = false; 316 } 317 318 @Override onRemoved()319 public void onRemoved() { 320 if (mDeathRecipient == null) { 321 throw new IllegalStateException("Invalid client token: " + mClientToken.asBinder()); 322 } 323 final WindowToken windowToken = mContainer.asWindowToken(); 324 if (windowToken != null && windowToken.isFromClient()) { 325 // If the WindowContext created WindowToken is removed by 326 // WMS#postWindowRemoveCleanupLocked, the WindowContext should switch back to 327 // listen to previous associated DisplayArea. 328 final DisplayContent dc = windowToken.mWmService.mRoot 329 .getDisplayContent(mLastReportedDisplay); 330 // If we cannot obtain the DisplayContent, the DisplayContent may also be removed. 331 // We should proceed the removal process. 332 if (dc != null) { 333 final DisplayArea<?> da = dc.findAreaForToken(windowToken); 334 updateContainer(da); 335 return; 336 } 337 } 338 mDeathRecipient.unlinkToDeath(); 339 try { 340 mClientToken.onWindowTokenRemoved(); 341 } catch (RemoteException e) { 342 ProtoLog.w(WM_ERROR, "Could not report token removal to the window token client."); 343 } 344 unregister(); 345 } 346 347 @Override toString()348 public String toString() { 349 return "WindowContextListenerImpl{clientToken=" + mClientToken.asBinder() + ", " 350 + "container=" + mContainer + "}"; 351 } 352 353 private class DeathRecipient implements IBinder.DeathRecipient { 354 @Override binderDied()355 public void binderDied() { 356 synchronized (mContainer.mWmService.mGlobalLock) { 357 mDeathRecipient = null; 358 unregister(); 359 } 360 } 361 linkToDeath()362 void linkToDeath() throws RemoteException { 363 mClientToken.asBinder().linkToDeath(this, 0); 364 } 365 unlinkToDeath()366 void unlinkToDeath() { 367 mClientToken.asBinder().unlinkToDeath(this, 0); 368 } 369 } 370 } 371 } 372