• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 com.android.wm.shell.sysui;
18 
19 import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
20 import static android.content.pm.ActivityInfo.CONFIG_FONT_SCALE;
21 import static android.content.pm.ActivityInfo.CONFIG_LAYOUT_DIRECTION;
22 import static android.content.pm.ActivityInfo.CONFIG_LOCALE;
23 import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
24 import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
25 
26 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
27 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SYSUI_EVENTS;
28 
29 import android.content.Context;
30 import android.content.pm.ActivityInfo;
31 import android.content.pm.UserInfo;
32 import android.content.res.Configuration;
33 import android.os.Bundle;
34 import android.util.ArrayMap;
35 
36 import androidx.annotation.NonNull;
37 import androidx.annotation.VisibleForTesting;
38 
39 import com.android.internal.protolog.common.ProtoLog;
40 import com.android.wm.shell.common.ExternalInterfaceBinder;
41 import com.android.wm.shell.common.ShellExecutor;
42 import com.android.wm.shell.common.annotations.ExternalThread;
43 
44 import java.io.PrintWriter;
45 import java.util.List;
46 import java.util.concurrent.CopyOnWriteArrayList;
47 import java.util.function.Supplier;
48 
49 /**
50  * Handles event callbacks from SysUI that can be used within the Shell.
51  */
52 public class ShellController {
53     private static final String TAG = ShellController.class.getSimpleName();
54 
55     private final ShellInit mShellInit;
56     private final ShellCommandHandler mShellCommandHandler;
57     private final ShellExecutor mMainExecutor;
58     private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
59 
60     private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
61             new CopyOnWriteArrayList<>();
62     private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners =
63             new CopyOnWriteArrayList<>();
64     private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners =
65             new CopyOnWriteArrayList<>();
66 
67     private ArrayMap<String, Supplier<ExternalInterfaceBinder>> mExternalInterfaceSuppliers =
68             new ArrayMap<>();
69     // References to the existing interfaces, to be invalidated when they are recreated
70     private ArrayMap<String, ExternalInterfaceBinder> mExternalInterfaces = new ArrayMap<>();
71 
72     private Configuration mLastConfiguration;
73 
74 
ShellController(ShellInit shellInit, ShellCommandHandler shellCommandHandler, ShellExecutor mainExecutor)75     public ShellController(ShellInit shellInit, ShellCommandHandler shellCommandHandler,
76             ShellExecutor mainExecutor) {
77         mShellInit = shellInit;
78         mShellCommandHandler = shellCommandHandler;
79         mMainExecutor = mainExecutor;
80         shellInit.addInitCallback(this::onInit, this);
81     }
82 
onInit()83     private void onInit() {
84         mShellCommandHandler.addDumpCallback(this::dump, this);
85     }
86 
87     /**
88      * Returns the external interface to this controller.
89      */
asShell()90     public ShellInterface asShell() {
91         return mImpl;
92     }
93 
94     /**
95      * Adds a new configuration listener. The configuration change callbacks are not made in any
96      * particular order.
97      */
addConfigurationChangeListener(ConfigurationChangeListener listener)98     public void addConfigurationChangeListener(ConfigurationChangeListener listener) {
99         mConfigChangeListeners.remove(listener);
100         mConfigChangeListeners.add(listener);
101     }
102 
103     /**
104      * Removes an existing configuration listener.
105      */
removeConfigurationChangeListener(ConfigurationChangeListener listener)106     public void removeConfigurationChangeListener(ConfigurationChangeListener listener) {
107         mConfigChangeListeners.remove(listener);
108     }
109 
110     /**
111      * Adds a new Keyguard listener. The Keyguard change callbacks are not made in any
112      * particular order.
113      */
addKeyguardChangeListener(KeyguardChangeListener listener)114     public void addKeyguardChangeListener(KeyguardChangeListener listener) {
115         mKeyguardChangeListeners.remove(listener);
116         mKeyguardChangeListeners.add(listener);
117     }
118 
119     /**
120      * Removes an existing Keyguard listener.
121      */
removeKeyguardChangeListener(KeyguardChangeListener listener)122     public void removeKeyguardChangeListener(KeyguardChangeListener listener) {
123         mKeyguardChangeListeners.remove(listener);
124     }
125 
126     /**
127      * Adds a new user-change listener. The user change callbacks are not made in any
128      * particular order.
129      */
addUserChangeListener(UserChangeListener listener)130     public void addUserChangeListener(UserChangeListener listener) {
131         mUserChangeListeners.remove(listener);
132         mUserChangeListeners.add(listener);
133     }
134 
135     /**
136      * Removes an existing user-change listener.
137      */
removeUserChangeListener(UserChangeListener listener)138     public void removeUserChangeListener(UserChangeListener listener) {
139         mUserChangeListeners.remove(listener);
140     }
141 
142     /**
143      * Adds an interface that can be called from a remote process. This method takes a supplier
144      * because each binder reference is valid for a single process, and in multi-user mode, SysUI
145      * will request new binder instances for each instance of Launcher that it provides binders
146      * to.
147      *
148      * @param extra the key for the interface, {@see ShellSharedConstants}
149      * @param binderSupplier the supplier of the binder to pass to the external process
150      * @param callerInstance the instance of the caller, purely for logging
151      */
addExternalInterface(String extra, Supplier<ExternalInterfaceBinder> binderSupplier, Object callerInstance)152     public void addExternalInterface(String extra, Supplier<ExternalInterfaceBinder> binderSupplier,
153             Object callerInstance) {
154         ProtoLog.v(WM_SHELL_INIT, "Adding external interface from %s with key %s",
155                 callerInstance.getClass().getSimpleName(), extra);
156         if (mExternalInterfaceSuppliers.containsKey(extra)) {
157             throw new IllegalArgumentException("Supplier with same key already exists: "
158                     + extra);
159         }
160         mExternalInterfaceSuppliers.put(extra, binderSupplier);
161     }
162 
163     /**
164      * Updates the given bundle with the set of external interfaces, invalidating the old set of
165      * binders.
166      */
167     @VisibleForTesting
createExternalInterfaces(Bundle output)168     public void createExternalInterfaces(Bundle output) {
169         // Invalidate the old binders
170         for (int i = 0; i < mExternalInterfaces.size(); i++) {
171             mExternalInterfaces.valueAt(i).invalidate();
172         }
173         mExternalInterfaces.clear();
174 
175         // Create new binders for each key
176         for (int i = 0; i < mExternalInterfaceSuppliers.size(); i++) {
177             final String key = mExternalInterfaceSuppliers.keyAt(i);
178             final ExternalInterfaceBinder b = mExternalInterfaceSuppliers.valueAt(i).get();
179             mExternalInterfaces.put(key, b);
180             output.putBinder(key, b.asBinder());
181         }
182     }
183 
184     @VisibleForTesting
onConfigurationChanged(Configuration newConfig)185     void onConfigurationChanged(Configuration newConfig) {
186         // The initial config is send on startup and doesn't trigger listener callbacks
187         if (mLastConfiguration == null) {
188             mLastConfiguration = new Configuration(newConfig);
189             ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Initial Configuration: %s", newConfig);
190             return;
191         }
192 
193         final int diff = newConfig.diff(mLastConfiguration);
194         ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "New configuration change: %s", newConfig);
195         ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "\tchanges=%s",
196                 Configuration.configurationDiffToString(diff));
197         final boolean densityFontScaleChanged = (diff & CONFIG_FONT_SCALE) != 0
198                 || (diff & ActivityInfo.CONFIG_DENSITY) != 0;
199         final boolean smallestScreenWidthChanged = (diff & CONFIG_SMALLEST_SCREEN_SIZE) != 0;
200         final boolean themeChanged = (diff & CONFIG_ASSETS_PATHS) != 0
201                 || (diff & CONFIG_UI_MODE) != 0;
202         final boolean localOrLayoutDirectionChanged = (diff & CONFIG_LOCALE) != 0
203                 || (diff & CONFIG_LAYOUT_DIRECTION) != 0;
204 
205         // Update the last configuration and call listeners
206         mLastConfiguration.updateFrom(newConfig);
207         for (ConfigurationChangeListener listener : mConfigChangeListeners) {
208             listener.onConfigurationChanged(newConfig);
209             if (densityFontScaleChanged) {
210                 listener.onDensityOrFontScaleChanged();
211             }
212             if (smallestScreenWidthChanged) {
213                 listener.onSmallestScreenWidthChanged();
214             }
215             if (themeChanged) {
216                 listener.onThemeChanged();
217             }
218             if (localOrLayoutDirectionChanged) {
219                 listener.onLocaleOrLayoutDirectionChanged();
220             }
221         }
222     }
223 
224     @VisibleForTesting
onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss)225     void onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss) {
226         ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Keyguard visibility changed: visible=%b "
227                 + "occluded=%b animatingDismiss=%b", visible, occluded, animatingDismiss);
228         for (KeyguardChangeListener listener : mKeyguardChangeListeners) {
229             listener.onKeyguardVisibilityChanged(visible, occluded, animatingDismiss);
230         }
231     }
232 
233     @VisibleForTesting
onKeyguardDismissAnimationFinished()234     void onKeyguardDismissAnimationFinished() {
235         ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Keyguard dismiss animation finished");
236         for (KeyguardChangeListener listener : mKeyguardChangeListeners) {
237             listener.onKeyguardDismissAnimationFinished();
238         }
239     }
240 
241     @VisibleForTesting
onUserChanged(int newUserId, @NonNull Context userContext)242     void onUserChanged(int newUserId, @NonNull Context userContext) {
243         ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "User changed: id=%d", newUserId);
244         for (UserChangeListener listener : mUserChangeListeners) {
245             listener.onUserChanged(newUserId, userContext);
246         }
247     }
248 
249     @VisibleForTesting
onUserProfilesChanged(@onNull List<UserInfo> profiles)250     void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {
251         ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "User profiles changed");
252         for (UserChangeListener listener : mUserChangeListeners) {
253             listener.onUserProfilesChanged(profiles);
254         }
255     }
256 
dump(@onNull PrintWriter pw, String prefix)257     public void dump(@NonNull PrintWriter pw, String prefix) {
258         final String innerPrefix = prefix + "  ";
259         pw.println(prefix + TAG);
260         pw.println(innerPrefix + "mConfigChangeListeners=" + mConfigChangeListeners.size());
261         pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration);
262         pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size());
263         pw.println(innerPrefix + "mUserChangeListeners=" + mUserChangeListeners.size());
264 
265         if (!mExternalInterfaces.isEmpty()) {
266             pw.println(innerPrefix + "mExternalInterfaces={");
267             for (String key : mExternalInterfaces.keySet()) {
268                 pw.println(innerPrefix + "\t" + key + ": " + mExternalInterfaces.get(key));
269             }
270             pw.println(innerPrefix + "}");
271         }
272     }
273 
274     /**
275      * The interface for calls from outside the Shell, within the host process.
276      */
277     @ExternalThread
278     private class ShellInterfaceImpl implements ShellInterface {
279         @Override
onInit()280         public void onInit() {
281             try {
282                 mMainExecutor.executeBlocking(() -> mShellInit.init());
283             } catch (InterruptedException e) {
284                 throw new RuntimeException("Failed to initialize the Shell in 2s", e);
285             }
286         }
287 
288         @Override
onConfigurationChanged(Configuration newConfiguration)289         public void onConfigurationChanged(Configuration newConfiguration) {
290             mMainExecutor.execute(() ->
291                     ShellController.this.onConfigurationChanged(newConfiguration));
292         }
293 
294         @Override
onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss)295         public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
296                 boolean animatingDismiss) {
297             mMainExecutor.execute(() ->
298                     ShellController.this.onKeyguardVisibilityChanged(visible, occluded,
299                             animatingDismiss));
300         }
301 
302         @Override
onKeyguardDismissAnimationFinished()303         public void onKeyguardDismissAnimationFinished() {
304             mMainExecutor.execute(() ->
305                     ShellController.this.onKeyguardDismissAnimationFinished());
306         }
307 
308         @Override
onUserChanged(int newUserId, @NonNull Context userContext)309         public void onUserChanged(int newUserId, @NonNull Context userContext) {
310             mMainExecutor.execute(() ->
311                     ShellController.this.onUserChanged(newUserId, userContext));
312         }
313 
314         @Override
onUserProfilesChanged(@onNull List<UserInfo> profiles)315         public void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {
316             mMainExecutor.execute(() ->
317                     ShellController.this.onUserProfilesChanged(profiles));
318         }
319 
320         @Override
handleCommand(String[] args, PrintWriter pw)321         public boolean handleCommand(String[] args, PrintWriter pw) {
322             try {
323                 boolean[] result = new boolean[1];
324                 mMainExecutor.executeBlocking(() -> {
325                     result[0] = mShellCommandHandler.handleCommand(args, pw);
326                 });
327                 return result[0];
328             } catch (InterruptedException e) {
329                 throw new RuntimeException("Failed to handle Shell command in 2s", e);
330             }
331         }
332 
333         @Override
createExternalInterfaces(Bundle bundle)334         public void createExternalInterfaces(Bundle bundle) {
335             try {
336                 mMainExecutor.executeBlocking(() -> {
337                     ShellController.this.createExternalInterfaces(bundle);
338                 });
339             } catch (InterruptedException e) {
340                 throw new RuntimeException("Failed to get Shell command in 2s", e);
341             }
342         }
343 
344         @Override
dump(PrintWriter pw)345         public void dump(PrintWriter pw) {
346             try {
347                 mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
348             } catch (InterruptedException e) {
349                 throw new RuntimeException("Failed to dump the Shell in 2s", e);
350             }
351         }
352     }
353 }
354