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