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