• 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.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