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