• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 android.window;
18 
19 import static android.view.WindowManager.LayoutParams.WindowType;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.app.ActivityThread;
24 import android.app.IApplicationThread;
25 import android.app.servertransaction.WindowContextInfoChangeItem;
26 import android.app.servertransaction.WindowContextWindowRemovalItem;
27 import android.content.Context;
28 import android.content.res.Configuration;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.IBinder;
32 import android.os.RemoteException;
33 import android.util.ArraySet;
34 import android.util.Log;
35 import android.view.IWindowManager;
36 import android.view.WindowManagerGlobal;
37 
38 import com.android.internal.annotations.GuardedBy;
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.window.flags.Flags;
41 
42 /**
43  * Singleton controller to manage the attached {@link WindowTokenClient}s, and to dispatch
44  * corresponding window configuration change from server side.
45  * @hide
46  */
47 public class WindowTokenClientController {
48 
49     private static final String TAG = WindowTokenClientController.class.getSimpleName();
50     private static WindowTokenClientController sController;
51 
52     private final Object mLock = new Object();
53     private final IApplicationThread mAppThread = ActivityThread.currentActivityThread()
54             .getApplicationThread();
55     private final Handler mHandler = ActivityThread.currentActivityThread().getHandler();
56 
57     /** Attached {@link WindowTokenClient}. */
58     @GuardedBy("mLock")
59     private final ArraySet<WindowTokenClient> mWindowTokenClients = new ArraySet<>();
60 
61     /** Gets the singleton controller. */
62     @NonNull
getInstance()63     public static WindowTokenClientController getInstance() {
64         synchronized (WindowTokenClientController.class) {
65             if (sController == null) {
66                 sController = new WindowTokenClientController();
67             }
68             return sController;
69         }
70     }
71 
72     /** Overrides the {@link #getInstance()} for test only. */
73     @VisibleForTesting
overrideForTesting(@onNull WindowTokenClientController controller)74     public static void overrideForTesting(@NonNull WindowTokenClientController controller) {
75         synchronized (WindowTokenClientController.class) {
76             sController = controller;
77         }
78     }
79 
80     /** Creates a new instance for test only. */
81     @VisibleForTesting
82     @NonNull
createInstanceForTesting()83     public static WindowTokenClientController createInstanceForTesting() {
84         return new WindowTokenClientController();
85     }
86 
WindowTokenClientController()87     private WindowTokenClientController() {}
88 
89     /** Gets the {@link WindowContext} instance for the token. */
90     @Nullable
getWindowContext(@onNull IBinder clientToken)91     public Context getWindowContext(@NonNull IBinder clientToken) {
92         if (!(clientToken instanceof WindowTokenClient windowTokenClient)) {
93             return null;
94         }
95         synchronized (mLock) {
96             if (!mWindowTokenClients.contains(windowTokenClient)) {
97                 return null;
98             }
99         }
100         return windowTokenClient.getContext();
101     }
102 
103     /**
104      * Attaches a {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}.
105      *
106      * @param client The {@link WindowTokenClient} to attach.
107      * @param type The window type of the {@link WindowContext}
108      * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
109      * @param options The window context launched option
110      * @return {@code true} if attaching successfully.
111      */
attachToDisplayArea(@onNull WindowTokenClient client, @WindowType int type, int displayId, @Nullable Bundle options)112     public boolean attachToDisplayArea(@NonNull WindowTokenClient client,
113             @WindowType int type, int displayId, @Nullable Bundle options) {
114         final WindowContextInfo info;
115         try {
116             info = getWindowManagerService().attachWindowContextToDisplayArea(
117                     mAppThread, client, type, displayId, options);
118         } catch (RemoteException e) {
119             throw e.rethrowFromSystemServer();
120         }
121         if (info == null) {
122             return false;
123         }
124         onWindowContextTokenAttached(client, info, false /* shouldReportConfigChange */);
125         return true;
126     }
127 
128     /**
129      * Attaches a {@link WindowTokenClient} to a {@code DisplayContent}.
130      *
131      * @param client The {@link WindowTokenClient} to attach.
132      * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
133      * @return {@code true} if attaching successfully.
134      */
attachToDisplayContent(@onNull WindowTokenClient client, int displayId)135     public boolean attachToDisplayContent(@NonNull WindowTokenClient client, int displayId) {
136         final IWindowManager wms = getWindowManagerService();
137         if (wms == null) {
138             // #createSystemUiContext may call this method before WindowManagerService is
139             // initialized.
140             // Regardless of whether or not it is ready, keep track of the token so that when WMS
141             // is initialized later, the SystemUiContext will start reporting from
142             // DisplayContent#registerSystemUiContext, and WindowTokenClientController can report
143             // the Configuration to the correct client.
144             if (Flags.trackSystemUiContextBeforeWms()) {
145                 recordWindowContextToken(client);
146             }
147             return false;
148         }
149         final WindowContextInfo info;
150         try {
151             info = wms.attachWindowContextToDisplayContent(mAppThread, client, displayId);
152         } catch (RemoteException e) {
153             throw e.rethrowFromSystemServer();
154         } catch (Exception e) {
155             Log.e(TAG, "Failed attachToDisplayContent", e);
156             return false;
157         }
158         if (info == null) {
159             return false;
160         }
161         onWindowContextTokenAttached(client, info, false /* shouldReportConfigChange */);
162         return true;
163     }
164 
165     /**
166      * Attaches this {@link WindowTokenClient} to a {@code windowToken}.
167      *
168      * @param client The {@link WindowTokenClient} to attach.
169      * @param windowToken the window token to associated with
170      * @return {@code true} if attaching successfully.
171      */
attachToWindowToken(@onNull WindowTokenClient client, @NonNull IBinder windowToken)172     public boolean attachToWindowToken(@NonNull WindowTokenClient client,
173             @NonNull IBinder windowToken) {
174         final WindowContextInfo info;
175         try {
176             info = getWindowManagerService().attachWindowContextToWindowToken(
177                     mAppThread, client, windowToken);
178         } catch (RemoteException e) {
179             throw e.rethrowFromSystemServer();
180         }
181         if (info == null) {
182             return false;
183         }
184         // We currently report configuration for WindowToken after attached.
185         onWindowContextTokenAttached(client, info, true /* shouldReportConfigChange */);
186         return true;
187     }
188 
189     /** Detaches a {@link WindowTokenClient} from associated WindowContainer if there's one. */
detachIfNeeded(@onNull WindowTokenClient client)190     public void detachIfNeeded(@NonNull WindowTokenClient client) {
191         synchronized (mLock) {
192             if (!mWindowTokenClients.remove(client)) {
193                 return;
194             }
195         }
196         final IWindowManager wms = getWindowManagerService();
197         if (wms == null) {
198             // #createSystemUiContext may call this method before WindowManagerService is
199             // initialized. If it is GC'ed before WMS is initialized, skip calling into WMS.
200             return;
201         }
202         try {
203             wms.detachWindowContext(client);
204         } catch (RemoteException e) {
205             throw e.rethrowFromSystemServer();
206         }
207     }
208 
209     /**
210      * Reparents a {@link WindowTokenClient} and its associated WindowContainer if there's one.
211      */
reparentToDisplayArea(@onNull WindowTokenClient client, int displayId)212     public void reparentToDisplayArea(@NonNull WindowTokenClient client, int displayId) {
213         try {
214             if (!getWindowManagerService().reparentWindowContextToDisplayArea(mAppThread, client,
215                     displayId)) {
216                 Log.e(TAG,
217                         "Didn't succeed reparenting of " + client + " to displayId=" + displayId);
218             }
219         } catch (RemoteException e) {
220             throw e.rethrowFromSystemServer();
221         }
222     }
223 
onWindowContextTokenAttached(@onNull WindowTokenClient client, @NonNull WindowContextInfo info, boolean shouldReportConfigChange)224     private void onWindowContextTokenAttached(@NonNull WindowTokenClient client,
225             @NonNull WindowContextInfo info, boolean shouldReportConfigChange) {
226         recordWindowContextToken(client);
227         if (shouldReportConfigChange) {
228             // Should trigger an #onConfigurationChanged callback to the WindowContext. Post the
229             // dispatch in the next loop to prevent the callback from being dispatched before
230             // #onCreate or WindowContext creation..
231             client.postOnConfigurationChanged(info.getConfiguration(), info.getDisplayId());
232         } else {
233             // Apply the config change directly in case users get stale values after WindowContext
234             // creation.
235             client.onConfigurationChanged(info.getConfiguration(), info.getDisplayId(),
236                     false /* shouldReportConfigChange */);
237         }
238     }
239 
recordWindowContextToken(@onNull WindowTokenClient client)240     private void recordWindowContextToken(@NonNull WindowTokenClient client) {
241         synchronized (mLock) {
242             mWindowTokenClients.add(client);
243         }
244     }
245 
246     /** Called when receives {@link WindowContextInfoChangeItem}. */
onWindowContextInfoChanged(@onNull IBinder clientToken, @NonNull WindowContextInfo info)247     public void onWindowContextInfoChanged(@NonNull IBinder clientToken,
248             @NonNull WindowContextInfo info) {
249         final WindowTokenClient windowTokenClient = getWindowTokenClientIfAttached(clientToken);
250         if (windowTokenClient != null) {
251             windowTokenClient.onConfigurationChanged(info.getConfiguration(), info.getDisplayId());
252         }
253     }
254 
255     /** Called when receives {@link WindowContextWindowRemovalItem}. */
onWindowContextWindowRemoved(@onNull IBinder clientToken)256     public void onWindowContextWindowRemoved(@NonNull IBinder clientToken) {
257         final WindowTokenClient windowTokenClient = getWindowTokenClientIfAttached(clientToken);
258         if (windowTokenClient != null) {
259             windowTokenClient.onWindowTokenRemoved();
260         }
261     }
262 
263     /** Propagates the configuration change to the client token. */
onWindowConfigurationChanged(@onNull IBinder clientToken, @NonNull Configuration config, int displayId)264     public void onWindowConfigurationChanged(@NonNull IBinder clientToken,
265             @NonNull Configuration config, int displayId) {
266         final WindowTokenClient windowTokenClient = getWindowTokenClientIfAttached(clientToken);
267         if (windowTokenClient != null) {
268             // Let's make sure it's called on the main thread!
269             if (mHandler.getLooper().isCurrentThread()) {
270                 windowTokenClient.onConfigurationChanged(config, displayId);
271             } else {
272                 windowTokenClient.postOnConfigurationChanged(config, displayId);
273             }
274         }
275     }
276 
277     @Nullable
getWindowTokenClientIfAttached(@onNull IBinder clientToken)278     private WindowTokenClient getWindowTokenClientIfAttached(@NonNull IBinder clientToken) {
279         if (!(clientToken instanceof WindowTokenClient windowTokenClient)) {
280             Log.e(TAG, "getWindowTokenClient failed for non-window token " + clientToken);
281             return null;
282         }
283         synchronized (mLock) {
284             if (!mWindowTokenClients.contains(windowTokenClient)) {
285                 Log.w(TAG, "Can't find attached WindowTokenClient for " + clientToken);
286                 return null;
287             }
288         }
289         return windowTokenClient;
290     }
291 
292     /** Gets the {@link IWindowManager}. */
293     @VisibleForTesting
294     @Nullable
getWindowManagerService()295     public IWindowManager getWindowManagerService() {
296         return WindowManagerGlobal.getWindowManagerService();
297     }
298 }
299