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