1 /* 2 * Copyright (C) 2021 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.server.accessibility.magnification; 18 19 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_DEFAULT; 20 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN; 21 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW; 22 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; 23 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; 24 import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK; 25 26 import android.accessibilityservice.MagnificationConfig; 27 import android.annotation.NonNull; 28 import android.graphics.Region; 29 import android.util.Slog; 30 import android.view.Display; 31 32 import java.io.PrintWriter; 33 import java.util.ArrayList; 34 35 /** 36 * Processor class for AccessibilityService connection to control magnification on the specified 37 * display. This wraps the function of magnification controller. 38 * 39 * <p> 40 * If the magnification config uses {@link DEFAULT_MODE}. This processor will control the current 41 * activated magnifier on the display. If there is no magnifier activated, it controls 42 * full-screen magnifier by default. 43 * </p> 44 * 45 * <p> 46 * If the magnification config uses {@link FULLSCREEN_MODE}. This processor will control 47 * full-screen magnifier on the display. 48 * </p> 49 * 50 * <p> 51 * If the magnification config uses {@link WINDOW_MODE}. This processor will control 52 * the activated window magnifier on the display. 53 * </p> 54 * 55 * @see MagnificationController 56 * @see FullScreenMagnificationController 57 */ 58 public class MagnificationProcessor { 59 60 private static final String TAG = "MagnificationProcessor"; 61 private static final boolean DEBUG = false; 62 63 private final MagnificationController mController; 64 MagnificationProcessor(MagnificationController controller)65 public MagnificationProcessor(MagnificationController controller) { 66 mController = controller; 67 } 68 69 /** 70 * Gets the magnification config of the display. 71 * 72 * @param displayId The logical display id 73 * @return the magnification config 74 */ getMagnificationConfig(int displayId)75 public @NonNull MagnificationConfig getMagnificationConfig(int displayId) { 76 final int mode = getControllingMode(displayId); 77 MagnificationConfig.Builder builder = new MagnificationConfig.Builder(); 78 if (mode == MAGNIFICATION_MODE_FULLSCREEN) { 79 final FullScreenMagnificationController fullScreenMagnificationController = 80 mController.getFullScreenMagnificationController(); 81 builder.setMode(mode) 82 .setActivated(mController.isActivated(displayId, MAGNIFICATION_MODE_FULLSCREEN)) 83 .setScale(fullScreenMagnificationController.getScale(displayId)) 84 .setCenterX(fullScreenMagnificationController.getCenterX(displayId)) 85 .setCenterY(fullScreenMagnificationController.getCenterY(displayId)); 86 } else if (mode == MAGNIFICATION_MODE_WINDOW) { 87 final MagnificationConnectionManager magnificationConnectionManager = 88 mController.getMagnificationConnectionManager(); 89 builder.setMode(mode) 90 .setActivated(mController.isActivated(displayId, MAGNIFICATION_MODE_WINDOW)) 91 .setScale(magnificationConnectionManager.getScale(displayId)) 92 .setCenterX(magnificationConnectionManager.getCenterX(displayId)) 93 .setCenterY(magnificationConnectionManager.getCenterY(displayId)); 94 } else { 95 // For undefined mode, set enabled to false 96 builder.setActivated(false); 97 } 98 return builder.build(); 99 } 100 101 /** 102 * Sets the magnification config of the display. If animation is disabled, the transition 103 * is immediate. 104 * 105 * @param displayId The logical display id 106 * @param config The magnification config 107 * @param animate {@code true} to animate from the current config or 108 * {@code false} to set the config immediately 109 * @param id The ID of the service requesting the change 110 * @return {@code true} if the magnification spec changed, {@code false} if the spec did not 111 * change 112 */ setMagnificationConfig(int displayId, @NonNull MagnificationConfig config, boolean animate, int id)113 public boolean setMagnificationConfig(int displayId, @NonNull MagnificationConfig config, 114 boolean animate, int id) { 115 if (DEBUG) { 116 Slog.d(TAG, "setMagnificationConfig config=" + config); 117 } 118 if (transitionModeIfNeeded(displayId, config, animate, id)) { 119 return true; 120 } 121 122 int configMode = config.getMode(); 123 if (configMode == MAGNIFICATION_MODE_DEFAULT) { 124 configMode = getControllingMode(displayId); 125 } 126 // Check should activate or deactivate the target mode in config 127 boolean configActivated = config.isActivated(); 128 if (configMode == MAGNIFICATION_MODE_FULLSCREEN) { 129 if (configActivated) { 130 return setScaleAndCenterForFullScreenMagnification(displayId, config.getScale(), 131 config.getCenterX(), config.getCenterY(), 132 animate, id); 133 } else { 134 return resetFullscreenMagnification(displayId, animate); 135 } 136 } else if (configMode == MAGNIFICATION_MODE_WINDOW) { 137 if (configActivated) { 138 return mController.getMagnificationConnectionManager().enableWindowMagnification( 139 displayId, config.getScale(), config.getCenterX(), config.getCenterY(), 140 animate ? STUB_ANIMATION_CALLBACK : null, 141 id); 142 } else { 143 return mController.getMagnificationConnectionManager() 144 .disableWindowMagnification(displayId, false); 145 } 146 } 147 return false; 148 } 149 setScaleAndCenterForFullScreenMagnification(int displayId, float scale, float centerX, float centerY, boolean animate, int id)150 private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale, 151 float centerX, float centerY, boolean animate, int id) { 152 153 if (!isRegistered(displayId)) { 154 register(displayId); 155 } 156 return mController.getFullScreenMagnificationController().setScaleAndCenter( 157 displayId, scale, centerX, centerY, animate, id); 158 } 159 160 /** 161 * Returns {@code true} if transition magnification mode needed. And it is no need to transition 162 * mode when the controlling mode is unchanged or the controlling magnifier is not activated. 163 */ transitionModeIfNeeded(int displayId, MagnificationConfig config, boolean animate, int id)164 private boolean transitionModeIfNeeded(int displayId, MagnificationConfig config, 165 boolean animate, int id) { 166 int currentMode = getControllingMode(displayId); 167 if (config.getMode() == MagnificationConfig.MAGNIFICATION_MODE_DEFAULT) { 168 return false; 169 } 170 // Target mode is as same as current mode and is not transitioning. 171 if (currentMode == config.getMode() && !mController.hasDisableMagnificationCallback( 172 displayId)) { 173 return false; 174 } 175 mController.transitionMagnificationConfigMode(displayId, config, animate, id); 176 return true; 177 } 178 179 /** 180 * Returns the magnification scale of full-screen magnification on the display. 181 * If an animation is in progress, this reflects the end state of the animation. 182 * 183 * @param displayId The logical display id. 184 * @return the scale 185 */ getScale(int displayId)186 public float getScale(int displayId) { 187 return mController.getFullScreenMagnificationController().getScale(displayId); 188 } 189 190 /** 191 * Returns the magnification center in X coordinate of full-screen magnification. 192 * If the service can control magnification but fullscreen magnifier is not registered, it will 193 * register the magnifier for this call then unregister the magnifier finally to make the 194 * magnification center correct. 195 * 196 * @param displayId The logical display id 197 * @param canControlMagnification Whether the service can control magnification 198 * @return the X coordinate 199 */ getCenterX(int displayId, boolean canControlMagnification)200 public float getCenterX(int displayId, boolean canControlMagnification) { 201 boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId, 202 canControlMagnification); 203 try { 204 return mController.getFullScreenMagnificationController().getCenterX(displayId); 205 } finally { 206 if (registeredJustForThisCall) { 207 unregister(displayId); 208 } 209 } 210 } 211 212 /** 213 * Returns the magnification center in Y coordinate of full-screen magnification. 214 * If the service can control magnification but fullscreen magnifier is not registered, it will 215 * register the magnifier for this call then unregister the magnifier finally to make the 216 * magnification center correct. 217 * 218 * @param displayId The logical display id 219 * @param canControlMagnification Whether the service can control magnification 220 * @return the Y coordinate 221 */ getCenterY(int displayId, boolean canControlMagnification)222 public float getCenterY(int displayId, boolean canControlMagnification) { 223 boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId, 224 canControlMagnification); 225 try { 226 return mController.getFullScreenMagnificationController().getCenterY(displayId); 227 } finally { 228 if (registeredJustForThisCall) { 229 unregister(displayId); 230 } 231 } 232 } 233 234 /** 235 * Returns the region of the screen currently active for magnification if the 236 * controlling magnification is {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN}. 237 * Returns the region of screen projected on the magnification window if the controlling 238 * magnification is {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW}. 239 * <p> 240 * If the controlling mode is {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN}, 241 * the returned region will be empty if the magnification is 242 * not active. And the magnification is active if magnification gestures are enabled 243 * or if a service is running that can control magnification. 244 * </p><p> 245 * If the controlling mode is {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW}, 246 * the returned region will be empty if the magnification is not activated. 247 * </p> 248 * 249 * @param displayId The logical display id 250 * @param outRegion the region to populate 251 * @param canControlMagnification Whether the service can control magnification 252 */ getCurrentMagnificationRegion(int displayId, @NonNull Region outRegion, boolean canControlMagnification)253 public void getCurrentMagnificationRegion(int displayId, @NonNull Region outRegion, 254 boolean canControlMagnification) { 255 int currentMode = getControllingMode(displayId); 256 if (currentMode == MAGNIFICATION_MODE_FULLSCREEN) { 257 getFullscreenMagnificationRegion(displayId, outRegion, canControlMagnification); 258 } else if (currentMode == MAGNIFICATION_MODE_WINDOW) { 259 mController.getMagnificationConnectionManager().getMagnificationSourceBounds(displayId, 260 outRegion); 261 } 262 } 263 264 /** 265 * Returns the magnification bounds of full-screen magnification on the given display. 266 * 267 * @param displayId The logical display id 268 * @param outRegion the region to populate 269 * @param canControlMagnification Whether the service can control magnification 270 */ getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion, boolean canControlMagnification)271 public void getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion, 272 boolean canControlMagnification) { 273 boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId, 274 canControlMagnification); 275 try { 276 mController.getFullScreenMagnificationController().getMagnificationRegion(displayId, 277 outRegion); 278 } finally { 279 if (registeredJustForThisCall) { 280 unregister(displayId); 281 } 282 } 283 } 284 285 /** 286 * Resets the controlling magnifier on the given display. 287 * For resetting window magnifier, it disables the magnifier by setting the scale to 1. 288 * 289 * @param displayId The logical display id. 290 * @param animate {@code true} to animate the transition, {@code false} 291 * to transition immediately 292 * @return {@code true} if the magnification spec changed, {@code false} if 293 * the spec did not change 294 */ resetCurrentMagnification(int displayId, boolean animate)295 public boolean resetCurrentMagnification(int displayId, boolean animate) { 296 int mode = getControllingMode(displayId); 297 if (mode == MAGNIFICATION_MODE_FULLSCREEN) { 298 return mController.getFullScreenMagnificationController().reset(displayId, animate); 299 } else if (mode == MAGNIFICATION_MODE_WINDOW) { 300 return mController.getMagnificationConnectionManager().disableWindowMagnification( 301 displayId, false, animate ? STUB_ANIMATION_CALLBACK : null); 302 } 303 return false; 304 } 305 306 /** 307 * Resets the full-screen magnification on the given display. 308 * 309 * @param displayId The logical display id. 310 * @param animate {@code true} to animate the transition, {@code false} 311 * to transition immediately 312 * @return {@code true} if the magnification spec changed, {@code false} if 313 * the spec did not change 314 */ resetFullscreenMagnification(int displayId, boolean animate)315 public boolean resetFullscreenMagnification(int displayId, boolean animate) { 316 return mController.getFullScreenMagnificationController().reset(displayId, animate); 317 } 318 319 /** 320 * Resets all the magnifiers on all the displays. 321 * Called when the a11y service connection that has changed the current magnification spec is 322 * unbound or the binder died. 323 * 324 * @param connectionId The connection id 325 */ resetAllIfNeeded(int connectionId)326 public void resetAllIfNeeded(int connectionId) { 327 mController.getFullScreenMagnificationController().resetAllIfNeeded(connectionId); 328 mController.getMagnificationConnectionManager().resetAllIfNeeded(connectionId); 329 } 330 331 /** 332 * {@link FullScreenMagnificationController#isActivated(int)} 333 * {@link MagnificationConnectionManager#isWindowMagnifierEnabled(int)} 334 */ isMagnifying(int displayId)335 public boolean isMagnifying(int displayId) { 336 int mode = getControllingMode(displayId); 337 if (mode == MAGNIFICATION_MODE_FULLSCREEN) { 338 return mController.getFullScreenMagnificationController().isActivated(displayId); 339 } else if (mode == MAGNIFICATION_MODE_WINDOW) { 340 return mController.getMagnificationConnectionManager().isWindowMagnifierEnabled( 341 displayId); 342 } 343 return false; 344 } 345 346 /** 347 * Returns the current controlling magnification mode on the given display. 348 * If there is no magnifier activated, it fallbacks to the last activated mode. 349 * And the last activated mode is {@link FULLSCREEN_MODE} by default. 350 * 351 * @param displayId The logical display id 352 */ getControllingMode(int displayId)353 public int getControllingMode(int displayId) { 354 if (mController.isActivated(displayId, 355 ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)) { 356 return MAGNIFICATION_MODE_WINDOW; 357 } else if (mController.isActivated(displayId, 358 ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)) { 359 return MAGNIFICATION_MODE_FULLSCREEN; 360 } else { 361 return (mController.getLastMagnificationActivatedMode(displayId) 362 == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) 363 ? MAGNIFICATION_MODE_WINDOW 364 : MAGNIFICATION_MODE_FULLSCREEN; 365 } 366 } 367 registerDisplayMagnificationIfNeeded(int displayId, boolean canControlMagnification)368 private boolean registerDisplayMagnificationIfNeeded(int displayId, 369 boolean canControlMagnification) { 370 if (!isRegistered(displayId) && canControlMagnification) { 371 register(displayId); 372 return true; 373 } 374 return false; 375 } 376 isRegistered(int displayId)377 private boolean isRegistered(int displayId) { 378 return mController.getFullScreenMagnificationController().isRegistered(displayId); 379 } 380 381 /** 382 * {@link FullScreenMagnificationController#register(int)} 383 */ register(int displayId)384 private void register(int displayId) { 385 mController.getFullScreenMagnificationController().register(displayId); 386 } 387 388 /** 389 * {@link FullScreenMagnificationController#unregister(int)} (int)} 390 */ unregister(int displayId)391 private void unregister(int displayId) { 392 mController.getFullScreenMagnificationController().unregister(displayId); 393 } 394 395 /** 396 * Dumps magnification configuration {@link MagnificationConfig} and state for each 397 * {@link Display} 398 */ dump(final PrintWriter pw, ArrayList<Display> displaysList)399 public void dump(final PrintWriter pw, ArrayList<Display> displaysList) { 400 for (int i = 0; i < displaysList.size(); i++) { 401 final int displayId = displaysList.get(i).getDisplayId(); 402 403 final MagnificationConfig config = getMagnificationConfig(displayId); 404 pw.println("Magnifier on display#" + displayId); 405 pw.append(" " + config).println(); 406 407 final Region region = new Region(); 408 getCurrentMagnificationRegion(displayId, region, true); 409 if (!region.isEmpty()) { 410 pw.append(" Magnification region=").append(region.toString()).println(); 411 } 412 pw.append(" IdOfLastServiceToMagnify=" 413 + getIdOfLastServiceToMagnify(config.getMode(), displayId)).println(); 414 415 dumpTrackingTypingFocusEnabledState(pw, displayId, config.getMode()); 416 } 417 pw.append(" SupportWindowMagnification=" 418 + mController.supportWindowMagnification()).println(); 419 pw.append(" WindowMagnificationConnectionState=" 420 + mController.getMagnificationConnectionManager().getConnectionState()).println(); 421 } 422 getIdOfLastServiceToMagnify(int mode, int displayId)423 private int getIdOfLastServiceToMagnify(int mode, int displayId) { 424 return (mode == MAGNIFICATION_MODE_FULLSCREEN) 425 ? mController.getFullScreenMagnificationController() 426 .getIdOfLastServiceToMagnify(displayId) 427 : mController.getMagnificationConnectionManager().getIdOfLastServiceToMagnify( 428 displayId); 429 } 430 dumpTrackingTypingFocusEnabledState(final PrintWriter pw, int displayId, int mode)431 private void dumpTrackingTypingFocusEnabledState(final PrintWriter pw, int displayId, 432 int mode) { 433 if (mode == MAGNIFICATION_MODE_WINDOW) { 434 pw.append(" TrackingTypingFocusEnabled=" 435 + mController.getMagnificationConnectionManager() 436 .isTrackingTypingFocusEnabled(displayId)) 437 .println(); 438 } 439 } 440 } 441