• 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 static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
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     @MainThread
110     @VisibleForTesting(visibility = PACKAGE)
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(visibility = PACKAGE)
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     /**
125      * Called when {@link Configuration} updates from the server side receive.
126      *
127      * Similar to {@link #onConfigurationChanged(Configuration, int)}, but adds a flag to control
128      * whether to dispatch configuration update or not.
129      * <p>
130      * Note that this method must be executed on the main thread if
131      * {@code shouldReportConfigChange} is {@code true}, which is usually from
132      * {@link #onConfigurationChanged(Configuration, int)}
133      * directly, while this method could be run on any thread if it is used to initialize
134      * Context's {@code Configuration} via {@link WindowTokenClientController#attachToDisplayArea}
135      * or {@link WindowTokenClientController#attachToDisplayContent}.
136      *
137      * @param shouldReportConfigChange {@code true} to indicate that the {@code Configuration}
138      *                                 should be dispatched to listeners.
139      */
140     @AnyThread
onConfigurationChanged(@onNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange)141     public void onConfigurationChanged(@NonNull Configuration newConfig, int newDisplayId,
142             boolean shouldReportConfigChange) {
143         final Context context = mContextRef.get();
144         if (context == null) {
145             return;
146         }
147         if (shouldReportConfigChange) {
148             // Only report to ClientTransactionListenerController when shouldReportConfigChange.
149             final ClientTransactionListenerController controller =
150                     getClientTransactionListenerController();
151             controller.onContextConfigurationPreChanged(context);
152             try {
153                 onConfigurationChangedInner(context, newConfig, newDisplayId,
154                         shouldReportConfigChange);
155             } finally {
156                 controller.onContextConfigurationPostChanged(context);
157             }
158         } else {
159             onConfigurationChangedInner(context, newConfig, newDisplayId, shouldReportConfigChange);
160         }
161     }
162 
163     /** Handles onConfiguration changed. */
164     @VisibleForTesting
onConfigurationChangedInner(@onNull Context context, @NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange)165     public void onConfigurationChangedInner(@NonNull Context context,
166             @NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange) {
167         CompatibilityInfo.applyOverrideIfNeeded(newConfig);
168         final boolean displayChanged;
169         final boolean shouldUpdateResources;
170         final int publicDiff;
171         final Configuration currentConfig;
172 
173         synchronized (mConfiguration) {
174             displayChanged = isDifferentDisplay(context.getDisplayId(), newDisplayId);
175             shouldUpdateResources = shouldUpdateResources(this, mConfiguration,
176                     newConfig, newConfig /* overrideConfig */, displayChanged,
177                     null /* configChanged */);
178             publicDiff = mConfiguration.diffPublicOnly(newConfig);
179             currentConfig = mShouldDumpConfigForIme ? new Configuration(mConfiguration) : null;
180             if (shouldUpdateResources) {
181                 mConfiguration.setTo(newConfig);
182             }
183         }
184 
185         if (!shouldUpdateResources && mShouldDumpConfigForIme) {
186             Log.d(TAG, "Configuration not dispatch to IME because configuration is up"
187                     + " to date. Current config=" + context.getResources().getConfiguration()
188                     + ", reported config=" + currentConfig
189                     + ", updated config=" + newConfig
190                     + ", updated display ID=" + newDisplayId);
191         }
192         // Update display first. In case callers want to obtain display information(
193         // ex: DisplayMetrics) in #onConfigurationChanged callback.
194         if (displayChanged) {
195             context.updateDisplay(newDisplayId);
196         }
197         if (shouldUpdateResources) {
198             // TODO(ag/9789103): update resource manager logic to track non-activity tokens
199             mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId);
200 
201             if (shouldReportConfigChange && context instanceof ConfigurationDispatcher dispatcher) {
202                 // Updating resources implies some fields of configuration are updated despite they
203                 // are public or not.
204                 if (dispatcher.shouldReportPrivateChanges() || publicDiff != 0) {
205                     dispatcher.dispatchConfigurationChanged(newConfig);
206                 }
207             }
208 
209             freeTextLayoutCachesIfNeeded(publicDiff);
210             if (mShouldDumpConfigForIme) {
211                 if (!shouldReportConfigChange) {
212                     Log.d(TAG, "Only apply configuration update to Resources because "
213                             + "shouldReportConfigChange is false. "
214                             + "context=" + context
215                             + ", config=" + context.getResources().getConfiguration()
216                             + ", display ID=" + context.getDisplayId() + "\n"
217                             + Debug.getCallers(5));
218                 } else if (publicDiff == 0) {
219                     Log.d(TAG, "Configuration not dispatch to IME because configuration has no "
220                             + " public difference with updated config. "
221                             + " Current config=" + context.getResources().getConfiguration()
222                             + ", reported config=" + currentConfig
223                             + ", updated config=" + newConfig
224                             + ", display ID=" + context.getDisplayId());
225                 }
226             }
227         }
228     }
229 
230     /**
231      * Called when the attached window is removed from the display.
232      */
233     @VisibleForTesting(visibility = PACKAGE)
234     @MainThread
onWindowTokenRemoved()235     public void onWindowTokenRemoved() {
236         final Context context = mContextRef.get();
237         if (context != null) {
238             context.destroy();
239             mContextRef.clear();
240         }
241     }
242 
243     /** Gets {@link ClientTransactionListenerController}. */
244     @VisibleForTesting
245     @NonNull
getClientTransactionListenerController()246     public ClientTransactionListenerController getClientTransactionListenerController() {
247         return ClientTransactionListenerController.getInstance();
248     }
249 }
250