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