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