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