• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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