• 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 package android.window;
17 
18 import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
19 import static android.window.ConfigurationHelper.isDifferentDisplay;
20 import static android.window.ConfigurationHelper.shouldUpdateResources;
21 
22 import android.annotation.AnyThread;
23 import android.annotation.BinderThread;
24 import android.annotation.MainThread;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.app.ActivityThread;
28 import android.app.IWindowToken;
29 import android.app.ResourcesManager;
30 import android.content.Context;
31 import android.content.res.Configuration;
32 import android.inputmethodservice.AbstractInputMethodService;
33 import android.os.Build;
34 import android.os.Bundle;
35 import android.os.Debug;
36 import android.os.Handler;
37 import android.os.IBinder;
38 import android.os.RemoteException;
39 import android.util.Log;
40 import android.view.IWindowManager;
41 import android.view.WindowManager.LayoutParams.WindowType;
42 import android.view.WindowManagerGlobal;
43 
44 import com.android.internal.annotations.GuardedBy;
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.internal.util.function.pooled.PooledLambda;
47 
48 import java.lang.ref.WeakReference;
49 
50 /**
51  * This class is used to receive {@link Configuration} changes from the associated window manager
52  * node on the server side, and apply the change to the {@link Context#getResources() associated
53  * Resources} of the attached {@link Context}. It is also used as
54  * {@link Context#getWindowContextToken() the token of non-Activity UI Contexts}.
55  *
56  * @see WindowContext
57  * @see android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int, Bundle)
58  *
59  * @hide
60  */
61 public class WindowTokenClient extends IWindowToken.Stub {
62     private static final String TAG = WindowTokenClient.class.getSimpleName();
63 
64     /**
65      * Attached {@link Context} for this window token to update configuration and resources.
66      * Initialized by {@link #attachContext(Context)}.
67      */
68     private WeakReference<Context> mContextRef = null;
69 
70     private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
71 
72     private IWindowManager mWms;
73 
74     @GuardedBy("itself")
75     private final Configuration mConfiguration = new Configuration();
76 
77     private boolean mShouldDumpConfigForIme;
78 
79     private boolean mAttachToWindowContainer;
80 
81     private final Handler mHandler = ActivityThread.currentActivityThread().getHandler();
82 
83     /**
84      * Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
85      * can only attach one {@link Context}.
86      * <p>This method must be called before invoking
87      * {@link android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int,
88      * Bundle)}.<p/>
89      *
90      * @param context context to be attached
91      * @throws IllegalStateException if attached context has already existed.
92      */
attachContext(@onNull Context context)93     public void attachContext(@NonNull Context context) {
94         if (mContextRef != null) {
95             throw new IllegalStateException("Context is already attached.");
96         }
97         mContextRef = new WeakReference<>(context);
98         mShouldDumpConfigForIme = Build.IS_DEBUGGABLE
99                 && context instanceof AbstractInputMethodService;
100     }
101 
102     /**
103      * Attaches this {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}.
104      *
105      * @param type The window type of the {@link WindowContext}
106      * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
107      * @param options The window context launched option
108      * @return {@code true} if attaching successfully.
109      */
attachToDisplayArea(@indowType int type, int displayId, @Nullable Bundle options)110     public boolean attachToDisplayArea(@WindowType int type, int displayId,
111             @Nullable Bundle options) {
112         try {
113             final Configuration configuration = getWindowManagerService()
114                     .attachWindowContextToDisplayArea(this, type, displayId, options);
115             if (configuration == null) {
116                 return false;
117             }
118             onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */);
119             mAttachToWindowContainer = true;
120             return true;
121         } catch (RemoteException e) {
122             throw e.rethrowFromSystemServer();
123         }
124     }
125 
126     /**
127      * Attaches this {@link WindowTokenClient} to a {@code DisplayContent}.
128      *
129      * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
130      * @return {@code true} if attaching successfully.
131      */
attachToDisplayContent(int displayId)132     public boolean attachToDisplayContent(int displayId) {
133         final IWindowManager wms = getWindowManagerService();
134         // #createSystemUiContext may call this method before WindowManagerService is initialized.
135         if (wms == null) {
136             return false;
137         }
138         try {
139             final Configuration configuration = wms.attachToDisplayContent(this, displayId);
140             if (configuration == null) {
141                 return false;
142             }
143             onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */);
144             mAttachToWindowContainer = true;
145             return true;
146         } catch (RemoteException e) {
147             throw e.rethrowFromSystemServer();
148         }
149     }
150 
151     /**
152      * Attaches this {@link WindowTokenClient} to a {@code windowToken}.
153      *
154      * @param windowToken the window token to associated with
155      */
attachToWindowToken(IBinder windowToken)156     public void attachToWindowToken(IBinder windowToken) {
157         try {
158             getWindowManagerService().attachWindowContextToWindowToken(this, windowToken);
159             mAttachToWindowContainer = true;
160         } catch (RemoteException e) {
161             throw e.rethrowFromSystemServer();
162         }
163     }
164 
165     /** Detaches this {@link WindowTokenClient} from associated WindowContainer if there's one. */
detachFromWindowContainerIfNeeded()166     public void detachFromWindowContainerIfNeeded() {
167         if (!mAttachToWindowContainer) {
168             return;
169         }
170         try {
171             getWindowManagerService().detachWindowContextFromWindowContainer(this);
172         } catch (RemoteException e) {
173             throw e.rethrowFromSystemServer();
174         }
175     }
176 
getWindowManagerService()177     private IWindowManager getWindowManagerService() {
178         if (mWms == null) {
179             mWms = WindowManagerGlobal.getWindowManagerService();
180         }
181         return mWms;
182     }
183 
184     /**
185      * Called when {@link Configuration} updates from the server side receive.
186      *
187      * @param newConfig the updated {@link Configuration}
188      * @param newDisplayId the updated {@link android.view.Display} ID
189      */
190     @BinderThread
191     @Override
onConfigurationChanged(Configuration newConfig, int newDisplayId)192     public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
193         mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig,
194                 newDisplayId, true /* shouldReportConfigChange */).recycleOnUse());
195     }
196 
197     // TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService
198     //  are inherited from WindowProvider.
199     /**
200      * Called when {@link Configuration} updates from the server side receive.
201      *
202      * Similar to {@link #onConfigurationChanged(Configuration, int)}, but adds a flag to control
203      * whether to dispatch configuration update or not.
204      * <p>
205      * Note that this method must be executed on the main thread if
206      * {@code shouldReportConfigChange} is {@code true}, which is usually from
207      * {@link IWindowToken#onConfigurationChanged(Configuration, int)}
208      * directly, while this method could be run on any thread if it is used to initialize
209      * Context's {@code Configuration} via {@link #attachToDisplayArea(int, int, Bundle)}
210      * or {@link #attachToDisplayContent(int)}.
211      *
212      * @param shouldReportConfigChange {@code true} to indicate that the {@code Configuration}
213      *                                 should be dispatched to listeners.
214      *
215      */
216     @AnyThread
217     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
onConfigurationChanged(Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange)218     public void onConfigurationChanged(Configuration newConfig, int newDisplayId,
219             boolean shouldReportConfigChange) {
220         final Context context = mContextRef.get();
221         if (context == null) {
222             return;
223         }
224         final boolean displayChanged;
225         final boolean shouldUpdateResources;
226         final int diff;
227         final Configuration currentConfig;
228 
229         synchronized (mConfiguration) {
230             displayChanged = isDifferentDisplay(context.getDisplayId(), newDisplayId);
231             shouldUpdateResources = shouldUpdateResources(this, mConfiguration,
232                     newConfig, newConfig /* overrideConfig */, displayChanged,
233                     null /* configChanged */);
234             diff = mConfiguration.diffPublicOnly(newConfig);
235             currentConfig = mShouldDumpConfigForIme ? new Configuration(mConfiguration) : null;
236             if (shouldUpdateResources) {
237                 mConfiguration.setTo(newConfig);
238             }
239         }
240 
241         if (!shouldUpdateResources && mShouldDumpConfigForIme) {
242             Log.d(TAG, "Configuration not dispatch to IME because configuration is up"
243                     + " to date. Current config=" + context.getResources().getConfiguration()
244                     + ", reported config=" + currentConfig
245                     + ", updated config=" + newConfig);
246         }
247         if (shouldUpdateResources) {
248             // TODO(ag/9789103): update resource manager logic to track non-activity tokens
249             mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
250 
251             if (shouldReportConfigChange && context instanceof WindowContext) {
252                 final WindowContext windowContext = (WindowContext) context;
253                 windowContext.dispatchConfigurationChanged(newConfig);
254             }
255 
256 
257             if (shouldReportConfigChange && diff != 0
258                     && context instanceof WindowProviderService) {
259                 final WindowProviderService windowProviderService = (WindowProviderService) context;
260                 windowProviderService.onConfigurationChanged(newConfig);
261             }
262             freeTextLayoutCachesIfNeeded(diff);
263             if (mShouldDumpConfigForIme) {
264                 if (!shouldReportConfigChange) {
265                     Log.d(TAG, "Only apply configuration update to Resources because "
266                             + "shouldReportConfigChange is false.\n" + Debug.getCallers(5));
267                 } else if (diff == 0) {
268                     Log.d(TAG, "Configuration not dispatch to IME because configuration has no "
269                             + " public difference with updated config. "
270                             + " Current config=" + context.getResources().getConfiguration()
271                             + ", reported config=" + currentConfig
272                             + ", updated config=" + newConfig);
273                 }
274             }
275         }
276         if (displayChanged) {
277             context.updateDisplay(newDisplayId);
278         }
279     }
280 
281     @BinderThread
282     @Override
onWindowTokenRemoved()283     public void onWindowTokenRemoved() {
284         mHandler.post(PooledLambda.obtainRunnable(
285                 WindowTokenClient::onWindowTokenRemovedInner, this).recycleOnUse());
286     }
287 
288     @MainThread
onWindowTokenRemovedInner()289     private void onWindowTokenRemovedInner() {
290         final Context context = mContextRef.get();
291         if (context != null) {
292             context.destroy();
293             mContextRef.clear();
294         }
295     }
296 }
297