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