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