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