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.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.common.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 hasListener(IBinder clientToken)170 boolean hasListener(IBinder clientToken) { 171 return mListeners.containsKey(clientToken); 172 } 173 getWindowType(IBinder clientToken)174 @WindowType int getWindowType(IBinder clientToken) { 175 final WindowContextListenerImpl listener = mListeners.get(clientToken); 176 return listener != null ? listener.mType : INVALID_WINDOW_TYPE; 177 } 178 getOptions(IBinder clientToken)179 @Nullable Bundle getOptions(IBinder clientToken) { 180 final WindowContextListenerImpl listener = mListeners.get(clientToken); 181 return listener != null ? listener.mOptions : null; 182 } 183 getContainer(IBinder clientToken)184 @Nullable WindowContainer<?> getContainer(IBinder clientToken) { 185 final WindowContextListenerImpl listener = mListeners.get(clientToken); 186 return listener != null ? listener.mContainer : null; 187 } 188 189 @Override toString()190 public String toString() { 191 final StringBuilder builder = new StringBuilder("WindowContextListenerController{"); 192 builder.append("mListeners=["); 193 194 final int size = mListeners.values().size(); 195 for (int i = 0; i < size; i++) { 196 builder.append(mListeners.valueAt(i)); 197 if (i != size - 1) { 198 builder.append(", "); 199 } 200 } 201 builder.append("]}"); 202 return builder.toString(); 203 } 204 205 @VisibleForTesting 206 class WindowContextListenerImpl implements WindowContainerListener { 207 @NonNull 208 private final WindowProcessController mWpc; 209 @NonNull 210 private final IBinder mClientToken; 211 @NonNull 212 private WindowContainer<?> mContainer; 213 /** 214 * The options from {@link Context#createWindowContext(int, Bundle)}. 215 * <p>It can be used for choosing the {@link DisplayArea} where the window context 216 * is located. </p> 217 */ 218 @Nullable private final Bundle mOptions; 219 @WindowType private final int mType; 220 221 private DeathRecipient mDeathRecipient; 222 223 private int mLastReportedDisplay = INVALID_DISPLAY; 224 private Configuration mLastReportedConfig; 225 226 private boolean mHasPendingConfiguration; 227 WindowContextListenerImpl(@onNull WindowProcessController wpc, @NonNull IBinder clientToken, @NonNull WindowContainer<?> container, @WindowType int type, @Nullable Bundle options)228 private WindowContextListenerImpl(@NonNull WindowProcessController wpc, 229 @NonNull IBinder clientToken, @NonNull WindowContainer<?> container, 230 @WindowType int type, @Nullable Bundle options) { 231 mWpc = Objects.requireNonNull(wpc); 232 mClientToken = clientToken; 233 mContainer = Objects.requireNonNull(container); 234 mType = type; 235 mOptions = options; 236 237 final DeathRecipient deathRecipient = new DeathRecipient(); 238 try { 239 deathRecipient.linkToDeath(); 240 mDeathRecipient = deathRecipient; 241 } catch (RemoteException e) { 242 ProtoLog.e(WM_ERROR, "Could not register window container listener token=%s, " 243 + "container=%s", clientToken, mContainer); 244 } 245 } 246 247 /** TEST ONLY: returns the {@link WindowContainer} of the listener */ 248 @VisibleForTesting getWindowContainer()249 WindowContainer<?> getWindowContainer() { 250 return mContainer; 251 } 252 getUid()253 int getUid() { 254 return mWpc.mUid; 255 } 256 updateContainer(@onNull WindowContainer<?> newContainer)257 private void updateContainer(@NonNull WindowContainer<?> newContainer) { 258 Objects.requireNonNull(newContainer); 259 260 if (mContainer.equals(newContainer)) { 261 return; 262 } 263 mContainer.unregisterWindowContainerListener(this); 264 mContainer = newContainer; 265 clear(); 266 register(); 267 } 268 register()269 private void register() { 270 register(true /* shouldDispatchConfig */); 271 } 272 register(boolean shouldDispatchConfig)273 private void register(boolean shouldDispatchConfig) { 274 final IBinder token = mClientToken; 275 if (mDeathRecipient == null) { 276 throw new IllegalStateException("Invalid client token: " + token); 277 } 278 mListeners.putIfAbsent(token, this); 279 mContainer.registerWindowContainerListener(this, shouldDispatchConfig); 280 } 281 unregister()282 private void unregister() { 283 mContainer.unregisterWindowContainerListener(this); 284 mListeners.remove(mClientToken); 285 } 286 clear()287 private void clear() { 288 mLastReportedConfig = null; 289 mLastReportedDisplay = INVALID_DISPLAY; 290 } 291 292 @Override onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig)293 public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) { 294 dispatchWindowContextInfoChange(); 295 } 296 297 @Override onDisplayChanged(DisplayContent dc)298 public void onDisplayChanged(DisplayContent dc) { 299 dispatchWindowContextInfoChange(); 300 } 301 dispatchWindowContextInfoChange()302 private void dispatchWindowContextInfoChange() { 303 if (mDeathRecipient == null) { 304 throw new IllegalStateException("Invalid client token: " + mClientToken); 305 } 306 final DisplayContent dc = mContainer.getDisplayContent(); 307 if (!dc.isReady()) { 308 // Do not report configuration when booting. The latest configuration will be sent 309 // when WindowManagerService#displayReady(). 310 return; 311 } 312 // If the display of window context associated window container is suspended, don't 313 // report the configuration update. Note that we still dispatch the configuration update 314 // to WindowProviderService to make it compatible with Service#onConfigurationChanged. 315 // Service always receives #onConfigurationChanged callback regardless of display state. 316 if (!isWindowProviderService(mOptions) && isSuspendedState(dc.getDisplayInfo().state)) { 317 mHasPendingConfiguration = true; 318 return; 319 } 320 final Configuration config = mContainer.getConfiguration(); 321 final int displayId = dc.getDisplayId(); 322 if (mLastReportedConfig == null) { 323 mLastReportedConfig = new Configuration(); 324 } 325 if (config.equals(mLastReportedConfig) && displayId == mLastReportedDisplay) { 326 // No changes since last reported time. 327 return; 328 } 329 330 mLastReportedConfig.setTo(config); 331 mLastReportedDisplay = displayId; 332 333 mWpc.scheduleClientTransactionItem(WindowContextInfoChangeItem.obtain( 334 mClientToken, config, displayId)); 335 mHasPendingConfiguration = false; 336 } 337 338 @Override onRemoved()339 public void onRemoved() { 340 if (mDeathRecipient == null) { 341 throw new IllegalStateException("Invalid client token: " + mClientToken); 342 } 343 final WindowToken windowToken = mContainer.asWindowToken(); 344 if (windowToken != null && windowToken.isFromClient()) { 345 // If the WindowContext created WindowToken is removed by 346 // WMS#postWindowRemoveCleanupLocked, the WindowContext should switch back to 347 // listen to previous associated DisplayArea. 348 final DisplayContent dc = windowToken.mWmService.mRoot 349 .getDisplayContent(mLastReportedDisplay); 350 // If we cannot obtain the DisplayContent, the DisplayContent may also be removed. 351 // We should proceed the removal process. 352 if (dc != null) { 353 final DisplayArea<?> da = dc.findAreaForToken(windowToken); 354 updateContainer(da); 355 return; 356 } 357 } 358 mDeathRecipient.unlinkToDeath(); 359 mWpc.scheduleClientTransactionItem(WindowContextWindowRemovalItem.obtain(mClientToken)); 360 unregister(); 361 } 362 363 @Override toString()364 public String toString() { 365 return "WindowContextListenerImpl{clientToken=" + mClientToken + ", " 366 + "container=" + mContainer + "}"; 367 } 368 369 private class DeathRecipient implements IBinder.DeathRecipient { 370 @Override binderDied()371 public void binderDied() { 372 synchronized (mContainer.mWmService.mGlobalLock) { 373 mDeathRecipient = null; 374 unregister(); 375 } 376 } 377 linkToDeath()378 void linkToDeath() throws RemoteException { 379 mClientToken.linkToDeath(this, 0); 380 } 381 unlinkToDeath()382 void unlinkToDeath() { 383 mClientToken.unlinkToDeath(this, 0); 384 } 385 } 386 } 387 } 388