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