1 /* 2 * Copyright (C) 2020 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_FULLSCREEN; 20 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW; 21 import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION; 22 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL; 23 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; 24 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE; 25 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; 26 import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK; 27 28 import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID; 29 30 import android.accessibilityservice.MagnificationConfig; 31 import android.annotation.NonNull; 32 import android.annotation.Nullable; 33 import android.annotation.UserIdInt; 34 import android.content.Context; 35 import android.graphics.PointF; 36 import android.graphics.Rect; 37 import android.graphics.Region; 38 import android.os.SystemClock; 39 import android.os.UserHandle; 40 import android.provider.Settings; 41 import android.util.Slog; 42 import android.util.SparseArray; 43 import android.util.SparseBooleanArray; 44 import android.util.SparseIntArray; 45 import android.util.SparseLongArray; 46 import android.view.accessibility.MagnificationAnimationCallback; 47 48 import com.android.internal.accessibility.util.AccessibilityStatsLogUtils; 49 import com.android.internal.annotations.GuardedBy; 50 import com.android.internal.annotations.VisibleForTesting; 51 import com.android.server.LocalServices; 52 import com.android.server.accessibility.AccessibilityManagerService; 53 import com.android.server.wm.WindowManagerInternal; 54 55 /** 56 * Handles all magnification controllers initialization, generic interactions, 57 * magnification mode transition and magnification switch UI show/hide logic 58 * in the following callbacks: 59 * 60 * <ol> 61 * <li> 1. {@link #onTouchInteractionStart} shows magnification switch UI when 62 * the user touch interaction starts if magnification capabilities is all. </li> 63 * <li> 2. {@link #onTouchInteractionEnd} shows magnification switch UI when 64 * the user touch interaction ends if magnification capabilities is all. </li> 65 * <li> 3. {@link #onWindowMagnificationActivationState} updates magnification switch UI 66 * depending on magnification capabilities and magnification active state when window 67 * magnification activation state change.</li> 68 * <li> 4. {@link #onFullScreenMagnificationActivationState} updates magnification switch UI 69 * depending on magnification capabilities and magnification active state when fullscreen 70 * magnification activation state change.</li> 71 * <li> 4. {@link #onRequestMagnificationSpec} updates magnification switch UI depending on 72 * magnification capabilities and magnification active state when new magnification spec is 73 * changed by external request from calling public APIs. </li> 74 * </ol> 75 * 76 * <b>Note</b> Updates magnification switch UI when magnification mode transition 77 * is done and before invoking {@link TransitionCallBack#onResult}. 78 */ 79 public class MagnificationController implements WindowMagnificationManager.Callback, 80 MagnificationGestureHandler.Callback, 81 FullScreenMagnificationController.MagnificationInfoChangedCallback, 82 WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks { 83 84 private static final boolean DEBUG = false; 85 private static final String TAG = "MagnificationController"; 86 87 private final AccessibilityManagerService mAms; 88 private final PointF mTempPoint = new PointF(); 89 private final Object mLock; 90 private final Context mContext; 91 @GuardedBy("mLock") 92 private final SparseArray<DisableMagnificationCallback> 93 mMagnificationEndRunnableSparseArray = new SparseArray(); 94 95 private final MagnificationScaleProvider mScaleProvider; 96 private FullScreenMagnificationController mFullScreenMagnificationController; 97 private WindowMagnificationManager mWindowMagnificationMgr; 98 private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; 99 /** Whether the platform supports window magnification feature. */ 100 private final boolean mSupportWindowMagnification; 101 102 @GuardedBy("mLock") 103 private final SparseIntArray mCurrentMagnificationModeArray = new SparseIntArray(); 104 @GuardedBy("mLock") 105 private final SparseIntArray mLastMagnificationActivatedModeArray = new SparseIntArray(); 106 // Track the active user to reset the magnification and get the associated user settings. 107 private @UserIdInt int mUserId = UserHandle.USER_SYSTEM; 108 @GuardedBy("mLock") 109 private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray(); 110 @GuardedBy("mLock") 111 private final SparseLongArray mWindowModeEnabledTimeArray = new SparseLongArray(); 112 @GuardedBy("mLock") 113 private final SparseLongArray mFullScreenModeEnabledTimeArray = new SparseLongArray(); 114 115 /** 116 * The transitioning magnification modes on the displays. The controller notifies 117 * magnification change depending on the target config mode. 118 * If the target mode is null, it means the config mode of the display is not 119 * transitioning. 120 */ 121 @GuardedBy("mLock") 122 private final SparseArray<Integer> mTransitionModes = new SparseArray(); 123 124 @GuardedBy("mLock") 125 private final SparseArray<WindowManagerInternal.AccessibilityControllerInternal 126 .UiChangesForAccessibilityCallbacks> mAccessibilityCallbacksDelegateArray = 127 new SparseArray<>(); 128 129 /** 130 * A callback to inform the magnification transition result on the given display. 131 */ 132 public interface TransitionCallBack { 133 /** 134 * Invoked when the transition ends. 135 * 136 * @param displayId The display id. 137 * @param success {@code true} if the transition success. 138 */ onResult(int displayId, boolean success)139 void onResult(int displayId, boolean success); 140 } 141 MagnificationController(AccessibilityManagerService ams, Object lock, Context context, MagnificationScaleProvider scaleProvider)142 public MagnificationController(AccessibilityManagerService ams, Object lock, 143 Context context, MagnificationScaleProvider scaleProvider) { 144 mAms = ams; 145 mLock = lock; 146 mContext = context; 147 mScaleProvider = scaleProvider; 148 LocalServices.getService(WindowManagerInternal.class) 149 .getAccessibilityController().setUiChangesForAccessibilityCallbacks(this); 150 mSupportWindowMagnification = context.getPackageManager().hasSystemFeature( 151 FEATURE_WINDOW_MAGNIFICATION); 152 } 153 154 @VisibleForTesting MagnificationController(AccessibilityManagerService ams, Object lock, Context context, FullScreenMagnificationController fullScreenMagnificationController, WindowMagnificationManager windowMagnificationManager, MagnificationScaleProvider scaleProvider)155 public MagnificationController(AccessibilityManagerService ams, Object lock, 156 Context context, FullScreenMagnificationController fullScreenMagnificationController, 157 WindowMagnificationManager windowMagnificationManager, 158 MagnificationScaleProvider scaleProvider) { 159 this(ams, lock, context, scaleProvider); 160 mFullScreenMagnificationController = fullScreenMagnificationController; 161 mWindowMagnificationMgr = windowMagnificationManager; 162 } 163 164 @Override onPerformScaleAction(int displayId, float scale)165 public void onPerformScaleAction(int displayId, float scale) { 166 getWindowMagnificationMgr().setScale(displayId, scale); 167 getWindowMagnificationMgr().persistScale(displayId); 168 } 169 170 @Override onAccessibilityActionPerformed(int displayId)171 public void onAccessibilityActionPerformed(int displayId) { 172 updateMagnificationButton(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); 173 } 174 175 @Override onTouchInteractionStart(int displayId, int mode)176 public void onTouchInteractionStart(int displayId, int mode) { 177 handleUserInteractionChanged(displayId, mode); 178 } 179 180 @Override onTouchInteractionEnd(int displayId, int mode)181 public void onTouchInteractionEnd(int displayId, int mode) { 182 handleUserInteractionChanged(displayId, mode); 183 } 184 handleUserInteractionChanged(int displayId, int mode)185 private void handleUserInteractionChanged(int displayId, int mode) { 186 if (mMagnificationCapabilities != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) { 187 return; 188 } 189 if (isActivated(displayId, mode)) { 190 getWindowMagnificationMgr().showMagnificationButton(displayId, mode); 191 } 192 } 193 updateMagnificationButton(int displayId, int mode)194 private void updateMagnificationButton(int displayId, int mode) { 195 final boolean isActivated = isActivated(displayId, mode); 196 final boolean showButton; 197 synchronized (mLock) { 198 showButton = isActivated && mMagnificationCapabilities 199 == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL; 200 } 201 if (showButton) { 202 getWindowMagnificationMgr().showMagnificationButton(displayId, mode); 203 } else { 204 getWindowMagnificationMgr().removeMagnificationButton(displayId); 205 } 206 } 207 208 /** Returns {@code true} if the platform supports window magnification feature. */ supportWindowMagnification()209 public boolean supportWindowMagnification() { 210 return mSupportWindowMagnification; 211 } 212 213 /** 214 * Transitions to the target Magnification mode with current center of the magnification mode 215 * if it is available. 216 * 217 * @param displayId The logical display 218 * @param targetMode The target magnification mode 219 * @param transitionCallBack The callback invoked when the transition is finished. 220 */ transitionMagnificationModeLocked(int displayId, int targetMode, @NonNull TransitionCallBack transitionCallBack)221 public void transitionMagnificationModeLocked(int displayId, int targetMode, 222 @NonNull TransitionCallBack transitionCallBack) { 223 final PointF currentCenter = getCurrentMagnificationCenterLocked(displayId, targetMode); 224 final DisableMagnificationCallback animationCallback = 225 getDisableMagnificationEndRunnableLocked(displayId); 226 227 if (currentCenter == null && animationCallback == null) { 228 transitionCallBack.onResult(displayId, true); 229 return; 230 } 231 232 if (animationCallback != null) { 233 if (animationCallback.mCurrentMode == targetMode) { 234 animationCallback.restoreToCurrentMagnificationMode(); 235 return; 236 } else { 237 Slog.w(TAG, "discard duplicate request"); 238 return; 239 } 240 } 241 242 if (currentCenter == null) { 243 Slog.w(TAG, "Invalid center, ignore it"); 244 transitionCallBack.onResult(displayId, true); 245 return; 246 } 247 248 setTransitionState(displayId, targetMode); 249 250 final FullScreenMagnificationController screenMagnificationController = 251 getFullScreenMagnificationController(); 252 final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr(); 253 final float scale = getTargetModeScaleFromCurrentMagnification(displayId, targetMode); 254 final DisableMagnificationCallback animationEndCallback = 255 new DisableMagnificationCallback(transitionCallBack, displayId, targetMode, 256 scale, currentCenter, true); 257 if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { 258 screenMagnificationController.reset(displayId, animationEndCallback); 259 } else { 260 windowMagnificationMgr.disableWindowMagnification(displayId, false, 261 animationEndCallback); 262 } 263 setDisableMagnificationCallbackLocked(displayId, animationEndCallback); 264 } 265 266 /** 267 * Transitions to the targeting magnification config mode with current center of the 268 * magnification mode if it is available. It disables the current magnifier immediately then 269 * transitions to the targeting magnifier. 270 * 271 * @param displayId The logical display id 272 * @param config The targeting magnification config 273 * @param animate {@code true} to animate the transition, {@code false} 274 * to transition immediately 275 * @param id The ID of the service requesting the change 276 */ transitionMagnificationConfigMode(int displayId, MagnificationConfig config, boolean animate, int id)277 public void transitionMagnificationConfigMode(int displayId, MagnificationConfig config, 278 boolean animate, int id) { 279 if (DEBUG) { 280 Slog.d(TAG, "transitionMagnificationConfigMode displayId = " + displayId 281 + ", config = " + config); 282 } 283 synchronized (mLock) { 284 final int targetMode = config.getMode(); 285 final PointF currentCenter = getCurrentMagnificationCenterLocked(displayId, targetMode); 286 final PointF magnificationCenter = new PointF(config.getCenterX(), config.getCenterY()); 287 if (currentCenter != null) { 288 final float centerX = Float.isNaN(config.getCenterX()) 289 ? currentCenter.x 290 : config.getCenterX(); 291 final float centerY = Float.isNaN(config.getCenterY()) 292 ? currentCenter.y 293 : config.getCenterY(); 294 magnificationCenter.set(centerX, centerY); 295 } 296 297 final DisableMagnificationCallback animationCallback = 298 getDisableMagnificationEndRunnableLocked(displayId); 299 if (animationCallback != null) { 300 Slog.w(TAG, "Discard previous animation request"); 301 animationCallback.setExpiredAndRemoveFromListLocked(); 302 } 303 final FullScreenMagnificationController screenMagnificationController = 304 getFullScreenMagnificationController(); 305 final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr(); 306 final float targetScale = Float.isNaN(config.getScale()) 307 ? getTargetModeScaleFromCurrentMagnification(displayId, targetMode) 308 : config.getScale(); 309 try { 310 setTransitionState(displayId, targetMode); 311 312 if (targetMode == MAGNIFICATION_MODE_WINDOW) { 313 screenMagnificationController.reset(displayId, false); 314 windowMagnificationMgr.enableWindowMagnification(displayId, 315 targetScale, magnificationCenter.x, magnificationCenter.y, 316 animate ? STUB_ANIMATION_CALLBACK : null, id); 317 } else if (targetMode == MAGNIFICATION_MODE_FULLSCREEN) { 318 windowMagnificationMgr.disableWindowMagnification(displayId, false, null); 319 if (!screenMagnificationController.isRegistered(displayId)) { 320 screenMagnificationController.register(displayId); 321 } 322 screenMagnificationController.setScaleAndCenter(displayId, targetScale, 323 magnificationCenter.x, magnificationCenter.y, animate, 324 id); 325 } 326 } finally { 327 // Reset transition state after enabling target mode. 328 setTransitionState(displayId, null); 329 } 330 } 331 } 332 333 /** 334 * Sets magnification config mode transition state. Called when the mode transition starts and 335 * ends. If the targetMode and the display id are null, it resets all 336 * the transition state. 337 * 338 * @param displayId The logical display id 339 * @param targetMode The transition target mode. It is not transitioning, if the target mode 340 * is set null 341 */ setTransitionState(Integer displayId, Integer targetMode)342 private void setTransitionState(Integer displayId, Integer targetMode) { 343 synchronized (mLock) { 344 if (targetMode == null && displayId == null) { 345 mTransitionModes.clear(); 346 } else { 347 mTransitionModes.put(displayId, targetMode); 348 } 349 } 350 } 351 352 // We assume the target mode is different from the current mode, and there is only 353 // two modes, so we get the target scale from another mode. getTargetModeScaleFromCurrentMagnification(int displayId, int targetMode)354 private float getTargetModeScaleFromCurrentMagnification(int displayId, int targetMode) { 355 if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { 356 return getFullScreenMagnificationController().getScale(displayId); 357 } else { 358 return getWindowMagnificationMgr().getScale(displayId); 359 } 360 } 361 362 /** 363 * Return {@code true} if disable magnification animation callback of the display is running. 364 * 365 * @param displayId The logical display id 366 */ hasDisableMagnificationCallback(int displayId)367 public boolean hasDisableMagnificationCallback(int displayId) { 368 synchronized (mLock) { 369 final DisableMagnificationCallback animationCallback = 370 getDisableMagnificationEndRunnableLocked(displayId); 371 if (animationCallback != null) { 372 return true; 373 } 374 } 375 return false; 376 } 377 378 @GuardedBy("mLock") setCurrentMagnificationModeAndSwitchDelegate(int displayId, int mode)379 private void setCurrentMagnificationModeAndSwitchDelegate(int displayId, int mode) { 380 mCurrentMagnificationModeArray.put(displayId, mode); 381 assignMagnificationWindowManagerDelegateByMode(displayId, mode); 382 } 383 384 @GuardedBy("mLock") assignMagnificationWindowManagerDelegateByMode(int displayId, int mode)385 private void assignMagnificationWindowManagerDelegateByMode(int displayId, int mode) { 386 if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { 387 mAccessibilityCallbacksDelegateArray.put(displayId, 388 getFullScreenMagnificationController()); 389 } else if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { 390 mAccessibilityCallbacksDelegateArray.put(displayId, getWindowMagnificationMgr()); 391 } else { 392 mAccessibilityCallbacksDelegateArray.delete(displayId); 393 } 394 } 395 396 @Override onRectangleOnScreenRequested(int displayId, int left, int top, int right, int bottom)397 public void onRectangleOnScreenRequested(int displayId, int left, int top, int right, 398 int bottom) { 399 WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks 400 delegate; 401 synchronized (mLock) { 402 delegate = mAccessibilityCallbacksDelegateArray.get(displayId); 403 } 404 if (delegate != null) { 405 delegate.onRectangleOnScreenRequested(displayId, left, top, right, bottom); 406 } 407 } 408 409 @Override onRequestMagnificationSpec(int displayId, int serviceId)410 public void onRequestMagnificationSpec(int displayId, int serviceId) { 411 final WindowMagnificationManager windowMagnificationManager; 412 synchronized (mLock) { 413 if (serviceId == MAGNIFICATION_GESTURE_HANDLER_ID) { 414 return; 415 } 416 updateMagnificationButton(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); 417 windowMagnificationManager = mWindowMagnificationMgr; 418 } 419 if (windowMagnificationManager != null) { 420 mWindowMagnificationMgr.disableWindowMagnification(displayId, false); 421 } 422 } 423 424 @Override onWindowMagnificationActivationState(int displayId, boolean activated)425 public void onWindowMagnificationActivationState(int displayId, boolean activated) { 426 if (activated) { 427 synchronized (mLock) { 428 mWindowModeEnabledTimeArray.put(displayId, SystemClock.uptimeMillis()); 429 setCurrentMagnificationModeAndSwitchDelegate(displayId, 430 ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); 431 mLastMagnificationActivatedModeArray.put(displayId, 432 ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); 433 } 434 logMagnificationModeWithImeOnIfNeeded(displayId); 435 disableFullScreenMagnificationIfNeeded(displayId); 436 } else { 437 long duration; 438 synchronized (mLock) { 439 setCurrentMagnificationModeAndSwitchDelegate(displayId, 440 ACCESSIBILITY_MAGNIFICATION_MODE_NONE); 441 duration = SystemClock.uptimeMillis() - mWindowModeEnabledTimeArray.get(displayId); 442 } 443 logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, duration); 444 } 445 updateMagnificationButton(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); 446 } 447 448 @Override onChangeMagnificationMode(int displayId, int magnificationMode)449 public void onChangeMagnificationMode(int displayId, int magnificationMode) { 450 mAms.changeMagnificationMode(displayId, magnificationMode); 451 } 452 453 @Override onSourceBoundsChanged(int displayId, Rect bounds)454 public void onSourceBoundsChanged(int displayId, Rect bounds) { 455 if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_WINDOW)) { 456 final MagnificationConfig config = new MagnificationConfig.Builder() 457 .setMode(MAGNIFICATION_MODE_WINDOW) 458 .setScale(getWindowMagnificationMgr().getScale(displayId)) 459 .setCenterX(bounds.exactCenterX()) 460 .setCenterY(bounds.exactCenterY()).build(); 461 mAms.notifyMagnificationChanged(displayId, new Region(bounds), config); 462 } 463 } 464 465 @Override onFullScreenMagnificationChanged(int displayId, @NonNull Region region, @NonNull MagnificationConfig config)466 public void onFullScreenMagnificationChanged(int displayId, @NonNull Region region, 467 @NonNull MagnificationConfig config) { 468 if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_FULLSCREEN)) { 469 mAms.notifyMagnificationChanged(displayId, region, config); 470 } 471 } 472 473 /** 474 * Should notify magnification change for the given display under the conditions below 475 * 476 * <ol> 477 * <li> 1. No mode transitioning and the change mode is active. </li> 478 * <li> 2. No mode transitioning and all the modes are inactive. </li> 479 * <li> 3. It is mode transitioning and the change mode is the transition mode. </li> 480 * </ol> 481 * 482 * @param displayId The logical display id 483 * @param changeMode The mode that has magnification spec change 484 */ shouldNotifyMagnificationChange(int displayId, int changeMode)485 private boolean shouldNotifyMagnificationChange(int displayId, int changeMode) { 486 synchronized (mLock) { 487 final boolean fullScreenMagnifying = mFullScreenMagnificationController != null 488 && mFullScreenMagnificationController.isMagnifying(displayId); 489 final boolean windowEnabled = mWindowMagnificationMgr != null 490 && mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId); 491 final Integer transitionMode = mTransitionModes.get(displayId); 492 if (((changeMode == MAGNIFICATION_MODE_FULLSCREEN && fullScreenMagnifying) 493 || (changeMode == MAGNIFICATION_MODE_WINDOW && windowEnabled)) 494 && (transitionMode == null)) { 495 return true; 496 } 497 if ((!fullScreenMagnifying && !windowEnabled) 498 && (transitionMode == null)) { 499 return true; 500 } 501 if (transitionMode != null && changeMode == transitionMode) { 502 return true; 503 } 504 } 505 return false; 506 } 507 disableFullScreenMagnificationIfNeeded(int displayId)508 private void disableFullScreenMagnificationIfNeeded(int displayId) { 509 final FullScreenMagnificationController fullScreenMagnificationController = 510 getFullScreenMagnificationController(); 511 // Internal request may be for transition, so we just need to check external request. 512 final boolean isMagnifyByExternalRequest = 513 fullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId) > 0; 514 if (isMagnifyByExternalRequest || isActivated(displayId, 515 ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)) { 516 fullScreenMagnificationController.reset(displayId, false); 517 } 518 } 519 520 @Override onFullScreenMagnificationActivationState(int displayId, boolean activated)521 public void onFullScreenMagnificationActivationState(int displayId, boolean activated) { 522 if (activated) { 523 synchronized (mLock) { 524 mFullScreenModeEnabledTimeArray.put(displayId, SystemClock.uptimeMillis()); 525 setCurrentMagnificationModeAndSwitchDelegate(displayId, 526 ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); 527 mLastMagnificationActivatedModeArray.put(displayId, 528 ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); 529 } 530 logMagnificationModeWithImeOnIfNeeded(displayId); 531 disableWindowMagnificationIfNeeded(displayId); 532 } else { 533 long duration; 534 synchronized (mLock) { 535 setCurrentMagnificationModeAndSwitchDelegate(displayId, 536 ACCESSIBILITY_MAGNIFICATION_MODE_NONE); 537 duration = SystemClock.uptimeMillis() 538 - mFullScreenModeEnabledTimeArray.get(displayId); 539 } 540 logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, duration); 541 } 542 updateMagnificationButton(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); 543 } 544 disableWindowMagnificationIfNeeded(int displayId)545 private void disableWindowMagnificationIfNeeded(int displayId) { 546 final WindowMagnificationManager windowMagnificationManager = 547 getWindowMagnificationMgr(); 548 if (isActivated(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)) { 549 windowMagnificationManager.disableWindowMagnification(displayId, false); 550 } 551 } 552 553 @Override onImeWindowVisibilityChanged(int displayId, boolean shown)554 public void onImeWindowVisibilityChanged(int displayId, boolean shown) { 555 synchronized (mLock) { 556 mIsImeVisibleArray.put(displayId, shown); 557 } 558 getWindowMagnificationMgr().onImeWindowVisibilityChanged(displayId, shown); 559 logMagnificationModeWithImeOnIfNeeded(displayId); 560 } 561 562 /** 563 * Returns the last activated magnification mode. If there is no activated magnifier before, it 564 * returns fullscreen mode by default. 565 */ getLastMagnificationActivatedMode(int displayId)566 public int getLastMagnificationActivatedMode(int displayId) { 567 synchronized (mLock) { 568 return mLastMagnificationActivatedModeArray.get(displayId, 569 ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); 570 } 571 } 572 573 /** 574 * Wrapper method of logging the magnification activated mode and its duration of the usage 575 * when the magnification is disabled. 576 * 577 * @param mode The activated magnification mode. 578 * @param duration The duration in milliseconds during the magnification is activated. 579 */ 580 @VisibleForTesting logMagnificationUsageState(int mode, long duration)581 public void logMagnificationUsageState(int mode, long duration) { 582 AccessibilityStatsLogUtils.logMagnificationUsageState(mode, duration); 583 } 584 585 /** 586 * Wrapper method of logging the activated mode of the magnification when the IME window 587 * is shown on the screen. 588 * 589 * @param mode The activated magnification mode. 590 */ 591 @VisibleForTesting logMagnificationModeWithIme(int mode)592 public void logMagnificationModeWithIme(int mode) { 593 AccessibilityStatsLogUtils.logMagnificationModeWithImeOn(mode); 594 } 595 596 /** 597 * Updates the active user ID of {@link FullScreenMagnificationController} and {@link 598 * WindowMagnificationManager}. 599 * 600 * @param userId the currently active user ID 601 */ updateUserIdIfNeeded(int userId)602 public void updateUserIdIfNeeded(int userId) { 603 if (mUserId == userId) { 604 return; 605 } 606 mUserId = userId; 607 final FullScreenMagnificationController fullMagnificationController; 608 final WindowMagnificationManager windowMagnificationManager; 609 synchronized (mLock) { 610 fullMagnificationController = mFullScreenMagnificationController; 611 windowMagnificationManager = mWindowMagnificationMgr; 612 mAccessibilityCallbacksDelegateArray.clear(); 613 mCurrentMagnificationModeArray.clear(); 614 mLastMagnificationActivatedModeArray.clear(); 615 mIsImeVisibleArray.clear(); 616 } 617 618 mScaleProvider.onUserChanged(userId); 619 if (fullMagnificationController != null) { 620 fullMagnificationController.resetAllIfNeeded(false); 621 } 622 if (windowMagnificationManager != null) { 623 windowMagnificationManager.disableAllWindowMagnifiers(); 624 } 625 } 626 627 /** 628 * Removes the magnification instance with given id. 629 * 630 * @param displayId The logical display id. 631 */ onDisplayRemoved(int displayId)632 public void onDisplayRemoved(int displayId) { 633 synchronized (mLock) { 634 if (mFullScreenMagnificationController != null) { 635 mFullScreenMagnificationController.onDisplayRemoved(displayId); 636 } 637 if (mWindowMagnificationMgr != null) { 638 mWindowMagnificationMgr.onDisplayRemoved(displayId); 639 } 640 mAccessibilityCallbacksDelegateArray.delete(displayId); 641 mCurrentMagnificationModeArray.delete(displayId); 642 mLastMagnificationActivatedModeArray.delete(displayId); 643 mIsImeVisibleArray.delete(displayId); 644 } 645 mScaleProvider.onDisplayRemoved(displayId); 646 } 647 648 /** 649 * Called when the given user is removed. 650 */ onUserRemoved(int userId)651 public void onUserRemoved(int userId) { 652 mScaleProvider.onUserRemoved(userId); 653 } 654 setMagnificationCapabilities(int capabilities)655 public void setMagnificationCapabilities(int capabilities) { 656 mMagnificationCapabilities = capabilities; 657 } 658 659 /** 660 * Called when the following typing focus feature is switched. 661 * 662 * @param enabled Enable the following typing focus feature 663 */ setMagnificationFollowTypingEnabled(boolean enabled)664 public void setMagnificationFollowTypingEnabled(boolean enabled) { 665 getWindowMagnificationMgr().setMagnificationFollowTypingEnabled(enabled); 666 getFullScreenMagnificationController().setMagnificationFollowTypingEnabled(enabled); 667 } 668 getDisableMagnificationEndRunnableLocked( int displayId)669 private DisableMagnificationCallback getDisableMagnificationEndRunnableLocked( 670 int displayId) { 671 return mMagnificationEndRunnableSparseArray.get(displayId); 672 } 673 setDisableMagnificationCallbackLocked(int displayId, @Nullable DisableMagnificationCallback callback)674 private void setDisableMagnificationCallbackLocked(int displayId, 675 @Nullable DisableMagnificationCallback callback) { 676 mMagnificationEndRunnableSparseArray.put(displayId, callback); 677 if (DEBUG) { 678 Slog.d(TAG, "setDisableMagnificationCallbackLocked displayId = " + displayId 679 + ", callback = " + callback); 680 } 681 } 682 logMagnificationModeWithImeOnIfNeeded(int displayId)683 private void logMagnificationModeWithImeOnIfNeeded(int displayId) { 684 final int currentActivateMode; 685 686 synchronized (mLock) { 687 currentActivateMode = mCurrentMagnificationModeArray.get(displayId, 688 ACCESSIBILITY_MAGNIFICATION_MODE_NONE); 689 if (!mIsImeVisibleArray.get(displayId, false) 690 || currentActivateMode == ACCESSIBILITY_MAGNIFICATION_MODE_NONE) { 691 return; 692 } 693 } 694 logMagnificationModeWithIme(currentActivateMode); 695 } 696 697 /** 698 * Getter of {@link FullScreenMagnificationController}. 699 * 700 * @return {@link FullScreenMagnificationController}. 701 */ getFullScreenMagnificationController()702 public FullScreenMagnificationController getFullScreenMagnificationController() { 703 synchronized (mLock) { 704 if (mFullScreenMagnificationController == null) { 705 mFullScreenMagnificationController = new FullScreenMagnificationController(mContext, 706 mAms.getTraceManager(), mLock, this, mScaleProvider); 707 } 708 } 709 return mFullScreenMagnificationController; 710 } 711 712 /** 713 * Is {@link #mFullScreenMagnificationController} is initialized. 714 * @return {code true} if {@link #mFullScreenMagnificationController} is initialized. 715 */ isFullScreenMagnificationControllerInitialized()716 public boolean isFullScreenMagnificationControllerInitialized() { 717 synchronized (mLock) { 718 return mFullScreenMagnificationController != null; 719 } 720 } 721 722 /** 723 * Getter of {@link WindowMagnificationManager}. 724 * 725 * @return {@link WindowMagnificationManager}. 726 */ getWindowMagnificationMgr()727 public WindowMagnificationManager getWindowMagnificationMgr() { 728 synchronized (mLock) { 729 if (mWindowMagnificationMgr == null) { 730 mWindowMagnificationMgr = new WindowMagnificationManager(mContext, 731 mLock, this, mAms.getTraceManager(), 732 mScaleProvider); 733 } 734 return mWindowMagnificationMgr; 735 } 736 } 737 getCurrentMagnificationCenterLocked(int displayId, int targetMode)738 private @Nullable PointF getCurrentMagnificationCenterLocked(int displayId, int targetMode) { 739 if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { 740 if (mWindowMagnificationMgr == null 741 || !mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId)) { 742 return null; 743 } 744 mTempPoint.set(mWindowMagnificationMgr.getCenterX(displayId), 745 mWindowMagnificationMgr.getCenterY(displayId)); 746 } else { 747 if (mFullScreenMagnificationController == null 748 || !mFullScreenMagnificationController.isMagnifying(displayId)) { 749 return null; 750 } 751 mTempPoint.set(mFullScreenMagnificationController.getCenterX(displayId), 752 mFullScreenMagnificationController.getCenterY(displayId)); 753 } 754 return mTempPoint; 755 } 756 757 /** 758 * Return {@code true} if the specified magnification mode on the given display is activated 759 * or not. 760 * 761 * @param displayId The logical displayId. 762 * @param mode It's either ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN or 763 * ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW. 764 */ isActivated(int displayId, int mode)765 public boolean isActivated(int displayId, int mode) { 766 boolean isActivated = false; 767 if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { 768 synchronized (mLock) { 769 if (mFullScreenMagnificationController == null) { 770 return false; 771 } 772 isActivated = mFullScreenMagnificationController.isMagnifying(displayId) 773 || mFullScreenMagnificationController.isForceShowMagnifiableBounds( 774 displayId); 775 } 776 } else if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { 777 synchronized (mLock) { 778 if (mWindowMagnificationMgr == null) { 779 return false; 780 } 781 isActivated = mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId); 782 } 783 } 784 return isActivated; 785 } 786 787 private final class DisableMagnificationCallback implements 788 MagnificationAnimationCallback { 789 private final TransitionCallBack mTransitionCallBack; 790 private boolean mExpired = false; 791 private final int mDisplayId; 792 // The mode the in-progress animation is going to. 793 private final int mTargetMode; 794 // The mode the in-progress animation is going from. 795 private final int mCurrentMode; 796 private final float mCurrentScale; 797 private final PointF mCurrentCenter = new PointF(); 798 private final boolean mAnimate; 799 DisableMagnificationCallback(@ullable TransitionCallBack transitionCallBack, int displayId, int targetMode, float scale, PointF currentCenter, boolean animate)800 DisableMagnificationCallback(@Nullable TransitionCallBack transitionCallBack, 801 int displayId, int targetMode, float scale, PointF currentCenter, boolean animate) { 802 mTransitionCallBack = transitionCallBack; 803 mDisplayId = displayId; 804 mTargetMode = targetMode; 805 mCurrentMode = mTargetMode ^ ACCESSIBILITY_MAGNIFICATION_MODE_ALL; 806 mCurrentScale = scale; 807 mCurrentCenter.set(currentCenter); 808 mAnimate = animate; 809 } 810 811 @Override onResult(boolean success)812 public void onResult(boolean success) { 813 synchronized (mLock) { 814 if (DEBUG) { 815 Slog.d(TAG, "onResult success = " + success); 816 } 817 if (mExpired) { 818 return; 819 } 820 setExpiredAndRemoveFromListLocked(); 821 setTransitionState(mDisplayId, null); 822 823 if (success) { 824 adjustCurrentCenterIfNeededLocked(); 825 applyMagnificationModeLocked(mTargetMode); 826 } else { 827 // Notify magnification change if magnification is inactive when the 828 // transition is failed. This is for the failed transition from 829 // full-screen to window mode. Disable magnification callback helps to send 830 // magnification inactive change since FullScreenMagnificationController 831 // would not notify magnification change if the spec is not changed. 832 final FullScreenMagnificationController screenMagnificationController = 833 getFullScreenMagnificationController(); 834 if (mCurrentMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN 835 && !screenMagnificationController.isMagnifying(mDisplayId)) { 836 MagnificationConfig.Builder configBuilder = 837 new MagnificationConfig.Builder(); 838 Region region = new Region(); 839 configBuilder.setMode(MAGNIFICATION_MODE_FULLSCREEN) 840 .setScale(screenMagnificationController.getScale(mDisplayId)) 841 .setCenterX(screenMagnificationController.getCenterX(mDisplayId)) 842 .setCenterY(screenMagnificationController.getCenterY(mDisplayId)); 843 screenMagnificationController.getMagnificationRegion(mDisplayId, 844 region); 845 mAms.notifyMagnificationChanged(mDisplayId, region, configBuilder.build()); 846 } 847 } 848 updateMagnificationButton(mDisplayId, mTargetMode); 849 if (mTransitionCallBack != null) { 850 mTransitionCallBack.onResult(mDisplayId, success); 851 } 852 } 853 } 854 adjustCurrentCenterIfNeededLocked()855 private void adjustCurrentCenterIfNeededLocked() { 856 if (mTargetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { 857 return; 858 } 859 final Region outRegion = new Region(); 860 getFullScreenMagnificationController().getMagnificationRegion(mDisplayId, outRegion); 861 if (outRegion.contains((int) mCurrentCenter.x, (int) mCurrentCenter.y)) { 862 return; 863 } 864 final Rect bounds = outRegion.getBounds(); 865 mCurrentCenter.set(bounds.exactCenterX(), bounds.exactCenterY()); 866 } 867 restoreToCurrentMagnificationMode()868 void restoreToCurrentMagnificationMode() { 869 synchronized (mLock) { 870 if (mExpired) { 871 return; 872 } 873 setExpiredAndRemoveFromListLocked(); 874 setTransitionState(mDisplayId, null); 875 applyMagnificationModeLocked(mCurrentMode); 876 updateMagnificationButton(mDisplayId, mCurrentMode); 877 if (mTransitionCallBack != null) { 878 mTransitionCallBack.onResult(mDisplayId, true); 879 } 880 } 881 } 882 setExpiredAndRemoveFromListLocked()883 void setExpiredAndRemoveFromListLocked() { 884 mExpired = true; 885 setDisableMagnificationCallbackLocked(mDisplayId, null); 886 } 887 applyMagnificationModeLocked(int mode)888 private void applyMagnificationModeLocked(int mode) { 889 if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { 890 final FullScreenMagnificationController fullScreenMagnificationController = 891 getFullScreenMagnificationController(); 892 if (!fullScreenMagnificationController.isRegistered(mDisplayId)) { 893 fullScreenMagnificationController.register(mDisplayId); 894 } 895 fullScreenMagnificationController.setScaleAndCenter(mDisplayId, mCurrentScale, 896 mCurrentCenter.x, mCurrentCenter.y, mAnimate, 897 MAGNIFICATION_GESTURE_HANDLER_ID); 898 } else { 899 getWindowMagnificationMgr().enableWindowMagnification(mDisplayId, 900 mCurrentScale, mCurrentCenter.x, 901 mCurrentCenter.y, mAnimate ? STUB_ANIMATION_CALLBACK : null, 902 MAGNIFICATION_GESTURE_HANDLER_ID); 903 } 904 } 905 } 906 } 907