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 static com.android.window.flags.Flags.windowTokenConfigThreadSafe;
23 
24 import android.annotation.AnyThread;
25 import android.annotation.MainThread;
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.app.ActivityThread;
29 import android.app.ResourcesManager;
30 import android.app.servertransaction.ClientTransactionListenerController;
31 import android.content.Context;
32 import android.content.res.CompatibilityInfo;
33 import android.content.res.Configuration;
34 import android.inputmethodservice.AbstractInputMethodService;
35 import android.os.Binder;
36 import android.os.Build;
37 import android.os.Debug;
38 import android.os.Handler;
39 import android.util.Log;
40 
41 import com.android.internal.annotations.GuardedBy;
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.util.function.pooled.PooledLambda;
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
55  *
56  * @hide
57  */
58 public class WindowTokenClient extends Binder {
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     @GuardedBy("itself")
70     private final Configuration mConfiguration = new Configuration();
71 
72     private boolean mShouldDumpConfigForIme;
73 
74     private final Handler mHandler = ActivityThread.currentActivityThread().getHandler();
75 
76     /**
77      * Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
78      * can only attach one {@link Context}.
79      * <p>This method must be called before invoking
80      * {@link android.view.IWindowManager#attachWindowContextToDisplayArea}.<p/>
81      *
82      * @param context context to be attached
83      * @throws IllegalStateException if attached context has already existed.
84      */
attachContext(@onNull Context context)85     public void attachContext(@NonNull Context context) {
86         if (mContextRef != null) {
87             throw new IllegalStateException("Context is already attached.");
88         }
89         mContextRef = new WeakReference<>(context);
90         mShouldDumpConfigForIme = Build.IS_DEBUGGABLE
91                 && context instanceof AbstractInputMethodService;
92     }
93 
94     /**
95      * Gets the {@link Context} that this {@link WindowTokenClient} is attached through
96      * {@link #attachContext(Context)}.
97      */
98     @Nullable
getContext()99     public Context getContext() {
100         return mContextRef != null ? mContextRef.get() : null;
101     }
102 
103     /**
104      * Called when {@link Configuration} updates from the server side receive.
105      *
106      * @param newConfig the updated {@link Configuration}
107      * @param newDisplayId the updated {@link android.view.Display} ID
108      */
109     @VisibleForTesting
110     @MainThread
onConfigurationChanged(Configuration newConfig, int newDisplayId)111     public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
112         onConfigurationChanged(newConfig, newDisplayId, true /* shouldReportConfigChange */);
113     }
114 
115     /**
116      * Posts an {@link #onConfigurationChanged} to the main thread.
117      */
118     @VisibleForTesting
postOnConfigurationChanged(@onNull Configuration newConfig, int newDisplayId)119     public void postOnConfigurationChanged(@NonNull Configuration newConfig, int newDisplayId) {
120         mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig,
121                 newDisplayId, true /* shouldReportConfigChange */).recycleOnUse());
122     }
123 
124     // TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService
125     //  are inherited from WindowProvider.
126     /**
127      * Called when {@link Configuration} updates from the server side receive.
128      *
129      * Similar to {@link #onConfigurationChanged(Configuration, int)}, but adds a flag to control
130      * whether to dispatch configuration update or not.
131      * <p>
132      * Note that this method must be executed on the main thread if
133      * {@code shouldReportConfigChange} is {@code true}, which is usually from
134      * {@link #onConfigurationChanged(Configuration, int)}
135      * directly, while this method could be run on any thread if it is used to initialize
136      * Context's {@code Configuration} via {@link WindowTokenClientController#attachToDisplayArea}
137      * or {@link WindowTokenClientController#attachToDisplayContent}.
138      *
139      * @param shouldReportConfigChange {@code true} to indicate that the {@code Configuration}
140      *                                 should be dispatched to listeners.
141      */
142     @AnyThread
onConfigurationChanged(@onNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange)143     public void onConfigurationChanged(@NonNull Configuration newConfig, int newDisplayId,
144             boolean shouldReportConfigChange) {
145         final Context context = mContextRef.get();
146         if (context == null) {
147             return;
148         }
149         if (shouldReportConfigChange && windowTokenConfigThreadSafe()) {
150             // Only report to ClientTransactionListenerController when shouldReportConfigChange.
151             final ClientTransactionListenerController controller =
152                     getClientTransactionListenerController();
153             controller.onContextConfigurationPreChanged(context);
154             try {
155                 onConfigurationChangedInner(context, newConfig, newDisplayId,
156                         shouldReportConfigChange);
157             } finally {
158                 controller.onContextConfigurationPostChanged(context);
159             }
160         } else {
161             onConfigurationChangedInner(context, newConfig, newDisplayId, shouldReportConfigChange);
162         }
163     }
164 
165     /** Handles onConfiguration changed. */
166     @VisibleForTesting
onConfigurationChangedInner(@onNull Context context, @NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange)167     public void onConfigurationChangedInner(@NonNull Context context,
168             @NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange) {
169         CompatibilityInfo.applyOverrideScaleIfNeeded(newConfig);
170         final boolean displayChanged;
171         final boolean shouldUpdateResources;
172         final int diff;
173         final Configuration currentConfig;
174 
175         synchronized (mConfiguration) {
176             displayChanged = isDifferentDisplay(context.getDisplayId(), newDisplayId);
177             shouldUpdateResources = shouldUpdateResources(this, mConfiguration,
178                     newConfig, newConfig /* overrideConfig */, displayChanged,
179                     null /* configChanged */);
180             diff = mConfiguration.diffPublicOnly(newConfig);
181             currentConfig = mShouldDumpConfigForIme ? new Configuration(mConfiguration) : null;
182             if (shouldUpdateResources) {
183                 mConfiguration.setTo(newConfig);
184             }
185         }
186 
187         if (!shouldUpdateResources && mShouldDumpConfigForIme) {
188             Log.d(TAG, "Configuration not dispatch to IME because configuration is up"
189                     + " to date. Current config=" + context.getResources().getConfiguration()
190                     + ", reported config=" + currentConfig
191                     + ", updated config=" + newConfig
192                     + ", updated display ID=" + newDisplayId);
193         }
194         // Update display first. In case callers want to obtain display information(
195         // ex: DisplayMetrics) in #onConfigurationChanged callback.
196         if (displayChanged) {
197             context.updateDisplay(newDisplayId);
198         }
199         if (shouldUpdateResources) {
200             // TODO(ag/9789103): update resource manager logic to track non-activity tokens
201             mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
202 
203             if (shouldReportConfigChange && context instanceof WindowContext) {
204                 final WindowContext windowContext = (WindowContext) context;
205                 windowContext.dispatchConfigurationChanged(newConfig);
206             }
207 
208             if (shouldReportConfigChange && diff != 0
209                     && context instanceof WindowProviderService) {
210                 final WindowProviderService windowProviderService = (WindowProviderService) context;
211                 windowProviderService.onConfigurationChanged(newConfig);
212             }
213             freeTextLayoutCachesIfNeeded(diff);
214             if (mShouldDumpConfigForIme) {
215                 if (!shouldReportConfigChange) {
216                     Log.d(TAG, "Only apply configuration update to Resources because "
217                             + "shouldReportConfigChange is false. "
218                             + "context=" + context
219                             + ", config=" + context.getResources().getConfiguration()
220                             + ", display ID=" + context.getDisplayId() + "\n"
221                             + Debug.getCallers(5));
222                 } else if (diff == 0) {
223                     Log.d(TAG, "Configuration not dispatch to IME because configuration has no "
224                             + " public difference with updated config. "
225                             + " Current config=" + context.getResources().getConfiguration()
226                             + ", reported config=" + currentConfig
227                             + ", updated config=" + newConfig
228                             + ", display ID=" + context.getDisplayId());
229                 }
230             }
231         }
232     }
233 
234     /**
235      * Called when the attached window is removed from the display.
236      */
237     @VisibleForTesting
238     @MainThread
onWindowTokenRemoved()239     public void onWindowTokenRemoved() {
240         final Context context = mContextRef.get();
241         if (context != null) {
242             context.destroy();
243             mContextRef.clear();
244         }
245     }
246 
247     /** Gets {@link ClientTransactionListenerController}. */
248     @VisibleForTesting
249     @NonNull
getClientTransactionListenerController()250     public ClientTransactionListenerController getClientTransactionListenerController() {
251         return ClientTransactionListenerController.getInstance();
252     }
253 }
254