• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 
17 package android.app.servertransaction;
18 
19 import static android.app.WindowConfiguration.areConfigurationsEqualForDisplay;
20 import static android.view.Display.INVALID_DISPLAY;
21 
22 import static java.util.Objects.requireNonNull;
23 
24 import android.annotation.NonNull;
25 import android.app.Activity;
26 import android.app.ActivityThread;
27 import android.content.Context;
28 import android.content.res.Configuration;
29 import android.hardware.display.DisplayManagerGlobal;
30 import android.os.IBinder;
31 import android.util.ArrayMap;
32 import android.util.ArraySet;
33 import android.util.Log;
34 import android.window.ActivityWindowInfo;
35 
36 import com.android.internal.annotations.GuardedBy;
37 import com.android.internal.annotations.VisibleForTesting;
38 
39 import java.util.concurrent.RejectedExecutionException;
40 import java.util.function.BiConsumer;
41 
42 /**
43  * Singleton controller to manage listeners to individual {@link ClientTransaction}.
44  *
45  * @hide
46  */
47 public class ClientTransactionListenerController {
48 
49     private static final String TAG = "ClientTransactionListenerController";
50 
51     private static ClientTransactionListenerController sController;
52 
53     private final Object mLock = new Object();
54     private final DisplayManagerGlobal mDisplayManager;
55 
56     /** Listeners registered via {@link #registerActivityWindowInfoChangedListener(BiConsumer)}. */
57     @GuardedBy("mLock")
58     private final ArraySet<BiConsumer<IBinder, ActivityWindowInfo>>
59             mActivityWindowInfoChangedListeners = new ArraySet<>();
60 
61     /**
62      * Keeps track of the Context whose Configuration will get updated, mapping to the config before
63      * the change.
64      */
65     @GuardedBy("mLock")
66     private final ArrayMap<Context, Configuration> mContextToPreChangedConfigMap = new ArrayMap<>();
67 
68     /** Whether there is an {@link ClientTransaction} being executed. */
69     @GuardedBy("mLock")
70     private boolean mIsClientTransactionExecuting;
71 
72     /** Gets the singleton controller. */
73     @NonNull
getInstance()74     public static ClientTransactionListenerController getInstance() {
75         synchronized (ClientTransactionListenerController.class) {
76             if (sController == null) {
77                 sController = new ClientTransactionListenerController(
78                         DisplayManagerGlobal.getInstance());
79             }
80             return sController;
81         }
82     }
83 
84     /** Creates a new instance for test only. */
85     @VisibleForTesting
86     @NonNull
createInstanceForTesting( @onNull DisplayManagerGlobal displayManager)87     public static ClientTransactionListenerController createInstanceForTesting(
88             @NonNull DisplayManagerGlobal displayManager) {
89         return new ClientTransactionListenerController(displayManager);
90     }
91 
ClientTransactionListenerController(@onNull DisplayManagerGlobal displayManager)92     private ClientTransactionListenerController(@NonNull DisplayManagerGlobal displayManager) {
93         mDisplayManager = requireNonNull(displayManager);
94     }
95 
96     /**
97      * Registers to listen on activity {@link ActivityWindowInfo} change.
98      * The listener will be invoked with two parameters: {@link Activity#getActivityToken()} and
99      * {@link ActivityWindowInfo}.
100      */
registerActivityWindowInfoChangedListener( @onNull BiConsumer<IBinder, ActivityWindowInfo> listener)101     public void registerActivityWindowInfoChangedListener(
102             @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
103         synchronized (mLock) {
104             mActivityWindowInfoChangedListeners.add(listener);
105         }
106     }
107 
108     /**
109      * Unregisters the listener that was previously registered via
110      * {@link #registerActivityWindowInfoChangedListener(BiConsumer)}
111      */
unregisterActivityWindowInfoChangedListener( @onNull BiConsumer<IBinder, ActivityWindowInfo> listener)112     public void unregisterActivityWindowInfoChangedListener(
113             @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
114         synchronized (mLock) {
115             mActivityWindowInfoChangedListeners.remove(listener);
116         }
117     }
118 
119     /**
120      * Called when receives a {@link ClientTransaction} that is updating an activity's
121      * {@link ActivityWindowInfo}.
122      */
onActivityWindowInfoChanged(@onNull IBinder activityToken, @NonNull ActivityWindowInfo activityWindowInfo)123     public void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
124             @NonNull ActivityWindowInfo activityWindowInfo) {
125         final Object[] activityWindowInfoChangedListeners;
126         synchronized (mLock) {
127             if (mActivityWindowInfoChangedListeners.isEmpty()) {
128                 return;
129             }
130             activityWindowInfoChangedListeners = mActivityWindowInfoChangedListeners.toArray();
131         }
132         for (Object activityWindowInfoChangedListener : activityWindowInfoChangedListeners) {
133             ((BiConsumer<IBinder, ActivityWindowInfo>) activityWindowInfoChangedListener)
134                     .accept(activityToken, new ActivityWindowInfo(activityWindowInfo));
135         }
136     }
137 
138     /** Called when starts executing a remote {@link ClientTransaction}. */
onClientTransactionStarted()139     public void onClientTransactionStarted() {
140         synchronized (mLock) {
141             mIsClientTransactionExecuting = true;
142         }
143     }
144 
145     /** Called when finishes executing a remote {@link ClientTransaction}. */
onClientTransactionFinished()146     public void onClientTransactionFinished() {
147         final ArraySet<Integer> configUpdatedDisplayIds;
148         synchronized (mLock) {
149             mIsClientTransactionExecuting = false;
150 
151             // When {@link Configuration} is changed, we want to trigger display change callback as
152             // well, because Display reads some fields from {@link Configuration}.
153             if (mContextToPreChangedConfigMap.isEmpty()) {
154                 return;
155             }
156 
157             // Calculate display ids that have config changed.
158             configUpdatedDisplayIds = new ArraySet<>();
159             final int contextCount = mContextToPreChangedConfigMap.size();
160             try {
161                 for (int i = 0; i < contextCount; i++) {
162                     final Context context = mContextToPreChangedConfigMap.keyAt(i);
163                     final Configuration preChangedConfig = mContextToPreChangedConfigMap.valueAt(i);
164                     if (shouldReportDisplayChange(context, preChangedConfig)) {
165                         configUpdatedDisplayIds.add(context.getDisplayId());
166                     }
167                 }
168             } finally {
169                 mContextToPreChangedConfigMap.clear();
170             }
171         }
172 
173         // Dispatch the display changed callbacks.
174         try {
175             final int displayCount = configUpdatedDisplayIds.size();
176             for (int i = 0; i < displayCount; i++) {
177                 final int displayId = configUpdatedDisplayIds.valueAt(i);
178                 onDisplayChanged(displayId);
179             }
180         } catch (RejectedExecutionException e) {
181             Log.w(TAG, "Failed to notify DisplayListener because the Handler is shutting down");
182         }
183     }
184 
185     /** Called before updating the Configuration of the given {@code context}. */
onContextConfigurationPreChanged(@onNull Context context)186     public void onContextConfigurationPreChanged(@NonNull Context context) {
187         if (ActivityThread.isSystem()) {
188             // Not enable for system server.
189             return;
190         }
191         synchronized (mLock) {
192             if (mContextToPreChangedConfigMap.containsKey(context)) {
193                 // There is an earlier change that hasn't been reported yet.
194                 return;
195             }
196             mContextToPreChangedConfigMap.put(context,
197                     new Configuration(context.getResources().getConfiguration()));
198         }
199     }
200 
201     /** Called after updating the Configuration of the given {@code context}. */
onContextConfigurationPostChanged(@onNull Context context)202     public void onContextConfigurationPostChanged(@NonNull Context context) {
203         if (ActivityThread.isSystem()) {
204             // Not enable for system server.
205             return;
206         }
207         int changedDisplayId = INVALID_DISPLAY;
208         synchronized (mLock) {
209             if (mIsClientTransactionExecuting) {
210                 // Wait until #onClientTransactionFinished to prevent it from triggering the same
211                 // #onDisplayChanged multiple times within the same ClientTransaction.
212                 return;
213             }
214             final Configuration preChangedConfig = mContextToPreChangedConfigMap.remove(context);
215             if (preChangedConfig != null && shouldReportDisplayChange(context, preChangedConfig)) {
216                 changedDisplayId = context.getDisplayId();
217             }
218         }
219 
220         if (changedDisplayId != INVALID_DISPLAY) {
221             try {
222                 onDisplayChanged(changedDisplayId);
223             } catch (RejectedExecutionException e) {
224                 Log.w(TAG, "Failed to notify DisplayListener because the Handler is shutting down");
225             }
226         }
227     }
228 
shouldReportDisplayChange(@onNull Context context, @NonNull Configuration preChangedConfig)229     private boolean shouldReportDisplayChange(@NonNull Context context,
230             @NonNull Configuration preChangedConfig) {
231         final Configuration postChangedConfig = context.getResources().getConfiguration();
232         return !areConfigurationsEqualForDisplay(postChangedConfig, preChangedConfig);
233     }
234 
235     /**
236      * Called when receives a {@link Configuration} changed event that is updating display-related
237      * window configuration.
238      *
239      * @throws RejectedExecutionException if the display listener handler is closing.
240      */
241     @VisibleForTesting
onDisplayChanged(int displayId)242     public void onDisplayChanged(int displayId) throws RejectedExecutionException {
243         mDisplayManager.handleDisplayChangeFromWindowManager(displayId);
244     }
245 }
246