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