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