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