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