1 /* 2 * Copyright (C) 2015 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.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL; 20 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN; 21 import static android.util.TypedValue.COMPLEX_UNIT_DIP; 22 import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK; 23 24 import static com.android.server.accessibility.AccessibilityManagerService.INVALID_SERVICE_ID; 25 26 import android.accessibilityservice.MagnificationConfig; 27 import android.animation.Animator; 28 import android.animation.ValueAnimator; 29 import android.annotation.NonNull; 30 import android.annotation.Nullable; 31 import android.content.BroadcastReceiver; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.IntentFilter; 35 import android.content.res.CompatibilityInfo; 36 import android.graphics.Rect; 37 import android.graphics.Region; 38 import android.hardware.display.DisplayManagerInternal; 39 import android.os.Handler; 40 import android.os.Message; 41 import android.text.TextUtils; 42 import android.util.DisplayMetrics; 43 import android.util.MathUtils; 44 import android.util.Slog; 45 import android.util.SparseArray; 46 import android.util.TypedValue; 47 import android.view.Display; 48 import android.view.DisplayInfo; 49 import android.view.MagnificationSpec; 50 import android.view.View; 51 import android.view.WindowManager; 52 import android.view.accessibility.MagnificationAnimationCallback; 53 import android.view.animation.DecelerateInterpolator; 54 55 import com.android.internal.R; 56 import com.android.internal.accessibility.common.MagnificationConstants; 57 import com.android.internal.annotations.GuardedBy; 58 import com.android.internal.annotations.VisibleForTesting; 59 import com.android.internal.util.function.pooled.PooledLambda; 60 import com.android.server.LocalServices; 61 import com.android.server.accessibility.AccessibilityManagerService; 62 import com.android.server.accessibility.AccessibilityTraceManager; 63 import com.android.server.wm.WindowManagerInternal; 64 65 import java.util.ArrayList; 66 import java.util.Locale; 67 import java.util.concurrent.Executor; 68 import java.util.function.Supplier; 69 70 /** 71 * This class is used to control and query the state of display magnification 72 * from the accessibility manager and related classes. It is responsible for 73 * holding the current state of magnification and animation, and it handles 74 * communication between the accessibility manager and window manager. 75 * 76 * Magnification is limited to the range controlled by 77 * {@link MagnificationScaleProvider#constrainScale(float)}, and can only occur inside the 78 * magnification region. If a value is out of bounds, it will be adjusted to guarantee these 79 * constraints. 80 */ 81 public class FullScreenMagnificationController implements 82 WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks { 83 private static final boolean DEBUG = false; 84 private static final String LOG_TAG = "FullScreenMagnificationController"; 85 86 private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false; 87 88 private final Object mLock; 89 90 private final ControllerContext mControllerCtx; 91 92 private final ScreenStateObserver mScreenStateObserver; 93 94 @GuardedBy("mLock") 95 private final ArrayList<MagnificationInfoChangedCallback> 96 mMagnificationInfoChangedCallbacks = new ArrayList<>(); 97 98 private final MagnificationScaleProvider mScaleProvider; 99 100 private final long mMainThreadId; 101 102 /** List of display Magnification, mapping from displayId -> DisplayMagnification. */ 103 @GuardedBy("mLock") 104 private final SparseArray<DisplayMagnification> mDisplays = new SparseArray<>(0); 105 106 private final Rect mTempRect = new Rect(); 107 // Whether the following typing focus feature for magnification is enabled. 108 private boolean mMagnificationFollowTypingEnabled = true; 109 // Whether the always on magnification feature is enabled. 110 private boolean mAlwaysOnMagnificationEnabled = false; 111 private final DisplayManagerInternal mDisplayManagerInternal; 112 113 private final MagnificationThumbnailFeatureFlag mMagnificationThumbnailFeatureFlag; 114 @NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier; 115 116 /** 117 * This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds 118 * magnification information per display. 119 */ 120 private final class DisplayMagnification implements 121 WindowManagerInternal.MagnificationCallbacks { 122 /** 123 * The current magnification spec. If an animation is running, this 124 * reflects the end state. 125 */ 126 private final MagnificationSpec mCurrentMagnificationSpec = new MagnificationSpec(); 127 128 private final Region mMagnificationRegion = Region.obtain(); 129 private final Rect mMagnificationBounds = new Rect(); 130 131 private final Rect mTempRect = new Rect(); 132 private final Rect mTempRect1 = new Rect(); 133 134 private final SpecAnimationBridge mSpecAnimationBridge; 135 136 // Flag indicating that we are registered with window manager. 137 private boolean mRegistered; 138 private boolean mUnregisterPending; 139 private boolean mDeleteAfterUnregister; 140 141 private final int mDisplayId; 142 143 private int mIdOfLastServiceToMagnify = INVALID_SERVICE_ID; 144 private boolean mMagnificationActivated = false; 145 146 @GuardedBy("mLock") @Nullable private MagnificationThumbnail mMagnificationThumbnail; 147 DisplayMagnification(int displayId)148 DisplayMagnification(int displayId) { 149 mDisplayId = displayId; 150 mSpecAnimationBridge = new SpecAnimationBridge(mControllerCtx, mLock, mDisplayId); 151 } 152 153 /** 154 * Registers magnification callback and get current magnification region from 155 * window manager. 156 * 157 * @return true if callback registers successful. 158 */ 159 @GuardedBy("mLock") register()160 boolean register() { 161 if (traceEnabled()) { 162 logTrace("setMagnificationCallbacks", 163 "displayID=" + mDisplayId + ";callback=" + this); 164 } 165 mRegistered = mControllerCtx.getWindowManager().setMagnificationCallbacks( 166 mDisplayId, this); 167 if (!mRegistered) { 168 Slog.w(LOG_TAG, "set magnification callbacks fail, displayId:" + mDisplayId); 169 return false; 170 } 171 mSpecAnimationBridge.setEnabled(true); 172 if (traceEnabled()) { 173 logTrace("getMagnificationRegion", 174 "displayID=" + mDisplayId + ";region=" + mMagnificationRegion); 175 } 176 // Obtain initial state. 177 mControllerCtx.getWindowManager().getMagnificationRegion( 178 mDisplayId, mMagnificationRegion); 179 mMagnificationRegion.getBounds(mMagnificationBounds); 180 181 createThumbnailIfSupported(); 182 183 return true; 184 } 185 186 /** 187 * Unregisters magnification callback from window manager. Callbacks to 188 * {@link FullScreenMagnificationController#unregisterCallbackLocked(int, boolean)} after 189 * unregistered. 190 * 191 * @param delete true if this instance should be removed from the SparseArray in 192 * FullScreenMagnificationController after unregistered, for example, 193 * display removed. 194 */ 195 @GuardedBy("mLock") unregister(boolean delete)196 void unregister(boolean delete) { 197 if (mRegistered) { 198 mSpecAnimationBridge.setEnabled(false); 199 if (traceEnabled()) { 200 logTrace("setMagnificationCallbacks", 201 "displayID=" + mDisplayId + ";callback=null"); 202 } 203 mControllerCtx.getWindowManager().setMagnificationCallbacks( 204 mDisplayId, null); 205 mMagnificationRegion.setEmpty(); 206 mRegistered = false; 207 unregisterCallbackLocked(mDisplayId, delete); 208 209 destroyThumbnail(); 210 } 211 mUnregisterPending = false; 212 } 213 214 /** 215 * Reset magnification status with animation enabled. {@link #unregister(boolean)} will be 216 * called after animation finished. 217 * 218 * @param delete true if this instance should be removed from the SparseArray in 219 * FullScreenMagnificationController after unregistered, for example, 220 * display removed. 221 */ 222 @GuardedBy("mLock") unregisterPending(boolean delete)223 void unregisterPending(boolean delete) { 224 mDeleteAfterUnregister = delete; 225 mUnregisterPending = true; 226 reset(true); 227 } 228 isRegistered()229 boolean isRegistered() { 230 return mRegistered; 231 } 232 isActivated()233 boolean isActivated() { 234 return mMagnificationActivated; 235 } 236 getScale()237 float getScale() { 238 return mCurrentMagnificationSpec.scale; 239 } 240 getOffsetX()241 float getOffsetX() { 242 return mCurrentMagnificationSpec.offsetX; 243 } 244 getOffsetY()245 float getOffsetY() { 246 return mCurrentMagnificationSpec.offsetY; 247 } 248 249 @GuardedBy("mLock") getCenterX()250 float getCenterX() { 251 return (mMagnificationBounds.width() / 2.0f 252 + mMagnificationBounds.left - getOffsetX()) / getScale(); 253 } 254 255 @GuardedBy("mLock") getCenterY()256 float getCenterY() { 257 return (mMagnificationBounds.height() / 2.0f 258 + mMagnificationBounds.top - getOffsetY()) / getScale(); 259 } 260 261 /** 262 * Returns the scale currently used by the window manager. If an 263 * animation is in progress, this reflects the current state of the 264 * animation. 265 * 266 * @return the scale currently used by the window manager 267 */ getSentScale()268 float getSentScale() { 269 return mSpecAnimationBridge.mSentMagnificationSpec.scale; 270 } 271 272 /** 273 * Returns the X offset currently used by the window manager. If an 274 * animation is in progress, this reflects the current state of the 275 * animation. 276 * 277 * @return the X offset currently used by the window manager 278 */ getSentOffsetX()279 float getSentOffsetX() { 280 return mSpecAnimationBridge.mSentMagnificationSpec.offsetX; 281 } 282 283 /** 284 * Returns the Y offset currently used by the window manager. If an 285 * animation is in progress, this reflects the current state of the 286 * animation. 287 * 288 * @return the Y offset currently used by the window manager 289 */ getSentOffsetY()290 float getSentOffsetY() { 291 return mSpecAnimationBridge.mSentMagnificationSpec.offsetY; 292 } 293 294 @Override onMagnificationRegionChanged(Region magnificationRegion)295 public void onMagnificationRegionChanged(Region magnificationRegion) { 296 final Message m = PooledLambda.obtainMessage( 297 DisplayMagnification::updateMagnificationRegion, this, 298 Region.obtain(magnificationRegion)); 299 mControllerCtx.getHandler().sendMessage(m); 300 } 301 302 @Override onRectangleOnScreenRequested(int left, int top, int right, int bottom)303 public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) { 304 final Message m = PooledLambda.obtainMessage( 305 DisplayMagnification::requestRectangleOnScreen, this, 306 left, top, right, bottom); 307 mControllerCtx.getHandler().sendMessage(m); 308 } 309 310 @Override onDisplaySizeChanged()311 public void onDisplaySizeChanged() { 312 // Treat as context change 313 onUserContextChanged(); 314 } 315 316 @Override onUserContextChanged()317 public void onUserContextChanged() { 318 final Message m = PooledLambda.obtainMessage( 319 FullScreenMagnificationController::onUserContextChanged, 320 FullScreenMagnificationController.this, mDisplayId); 321 mControllerCtx.getHandler().sendMessage(m); 322 323 synchronized (mLock) { 324 refreshThumbnail(); 325 } 326 } 327 328 @Override onImeWindowVisibilityChanged(boolean shown)329 public void onImeWindowVisibilityChanged(boolean shown) { 330 final Message m = PooledLambda.obtainMessage( 331 FullScreenMagnificationController::notifyImeWindowVisibilityChanged, 332 FullScreenMagnificationController.this, mDisplayId, shown); 333 mControllerCtx.getHandler().sendMessage(m); 334 } 335 336 /** 337 * Update our copy of the current magnification region 338 * 339 * @param magnified the magnified region 340 */ updateMagnificationRegion(Region magnified)341 void updateMagnificationRegion(Region magnified) { 342 synchronized (mLock) { 343 if (!mRegistered) { 344 // Don't update if we've unregistered 345 return; 346 } 347 if (!mMagnificationRegion.equals(magnified)) { 348 mMagnificationRegion.set(magnified); 349 mMagnificationRegion.getBounds(mMagnificationBounds); 350 351 refreshThumbnail(); 352 353 // It's possible that our magnification spec is invalid with the new bounds. 354 // Adjust the current spec's offsets if necessary. 355 if (updateCurrentSpecWithOffsetsLocked( 356 mCurrentMagnificationSpec.offsetX, mCurrentMagnificationSpec.offsetY)) { 357 sendSpecToAnimation(mCurrentMagnificationSpec, null); 358 } 359 onMagnificationChangedLocked(); 360 } 361 magnified.recycle(); 362 } 363 } 364 sendSpecToAnimation(MagnificationSpec spec, MagnificationAnimationCallback animationCallback)365 void sendSpecToAnimation(MagnificationSpec spec, 366 MagnificationAnimationCallback animationCallback) { 367 if (DEBUG) { 368 Slog.i(LOG_TAG, 369 "sendSpecToAnimation(spec = " + spec + ", animationCallback = " 370 + animationCallback + ")"); 371 } 372 if (Thread.currentThread().getId() == mMainThreadId) { 373 mSpecAnimationBridge.updateSentSpecMainThread(spec, animationCallback); 374 } else { 375 final Message m = PooledLambda.obtainMessage( 376 SpecAnimationBridge::updateSentSpecMainThread, 377 mSpecAnimationBridge, spec, animationCallback); 378 mControllerCtx.getHandler().sendMessage(m); 379 } 380 } 381 382 /** 383 * Get the ID of the last service that changed the magnification spec. 384 * 385 * @return The id 386 */ getIdOfLastServiceToMagnify()387 int getIdOfLastServiceToMagnify() { 388 return mIdOfLastServiceToMagnify; 389 } 390 391 @GuardedBy("mLock") onMagnificationChangedLocked()392 void onMagnificationChangedLocked() { 393 final float scale = getScale(); 394 final float centerX = getCenterX(); 395 final float centerY = getCenterY(); 396 final MagnificationConfig config = new MagnificationConfig.Builder() 397 .setMode(MAGNIFICATION_MODE_FULLSCREEN) 398 .setActivated(mMagnificationActivated) 399 .setScale(scale) 400 .setCenterX(centerX) 401 .setCenterY(centerY).build(); 402 mMagnificationInfoChangedCallbacks.forEach(callback -> { 403 callback.onFullScreenMagnificationChanged(mDisplayId, 404 mMagnificationRegion, config); 405 }); 406 if (mUnregisterPending && !isActivated()) { 407 unregister(mDeleteAfterUnregister); 408 } 409 410 if (isActivated()) { 411 updateThumbnail(scale, centerX, centerY); 412 } else { 413 hideThumbnail(); 414 } 415 } 416 417 @GuardedBy("mLock") magnificationRegionContains(float x, float y)418 boolean magnificationRegionContains(float x, float y) { 419 return mMagnificationRegion.contains((int) x, (int) y); 420 } 421 422 @GuardedBy("mLock") getMagnificationBounds(@onNull Rect outBounds)423 void getMagnificationBounds(@NonNull Rect outBounds) { 424 outBounds.set(mMagnificationBounds); 425 } 426 427 @GuardedBy("mLock") getMagnificationRegion(@onNull Region outRegion)428 void getMagnificationRegion(@NonNull Region outRegion) { 429 outRegion.set(mMagnificationRegion); 430 } 431 getDisplayMetricsForId()432 private DisplayMetrics getDisplayMetricsForId() { 433 final DisplayMetrics outMetrics = new DisplayMetrics(); 434 final DisplayInfo displayInfo = mDisplayManagerInternal.getDisplayInfo(mDisplayId); 435 if (displayInfo != null) { 436 displayInfo.getLogicalMetrics(outMetrics, 437 CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); 438 } else { 439 outMetrics.setToDefaults(); 440 } 441 return outMetrics; 442 } 443 requestRectangleOnScreen(int left, int top, int right, int bottom)444 void requestRectangleOnScreen(int left, int top, int right, int bottom) { 445 synchronized (mLock) { 446 final Rect magnifiedFrame = mTempRect; 447 getMagnificationBounds(magnifiedFrame); 448 if (!magnifiedFrame.intersects(left, top, right, bottom)) { 449 return; 450 } 451 452 final Rect magnifFrameInScreenCoords = mTempRect1; 453 getMagnifiedFrameInContentCoordsLocked(magnifFrameInScreenCoords); 454 455 final float scrollX; 456 final float scrollY; 457 // We offset an additional distance for a user to know the surrounding context. 458 DisplayMetrics metrics = getDisplayMetricsForId(); 459 final float offsetViewportX = (float) magnifFrameInScreenCoords.width() / 4; 460 final float offsetViewportY = 461 TypedValue.applyDimension(COMPLEX_UNIT_DIP, 10, metrics); 462 463 if (right - left > magnifFrameInScreenCoords.width()) { 464 final int direction = TextUtils 465 .getLayoutDirectionFromLocale(Locale.getDefault()); 466 if (direction == View.LAYOUT_DIRECTION_LTR) { 467 scrollX = left - magnifFrameInScreenCoords.left; 468 } else { 469 scrollX = right - magnifFrameInScreenCoords.right; 470 } 471 } else if (left < magnifFrameInScreenCoords.left) { 472 scrollX = left - magnifFrameInScreenCoords.left - offsetViewportX; 473 } else if (right > magnifFrameInScreenCoords.right) { 474 scrollX = right - magnifFrameInScreenCoords.right + offsetViewportX; 475 } else { 476 scrollX = 0; 477 } 478 479 if (bottom - top > magnifFrameInScreenCoords.height()) { 480 scrollY = top - magnifFrameInScreenCoords.top; 481 } else if (top < magnifFrameInScreenCoords.top) { 482 scrollY = top - magnifFrameInScreenCoords.top - offsetViewportY; 483 } else if (bottom > magnifFrameInScreenCoords.bottom) { 484 scrollY = bottom - magnifFrameInScreenCoords.bottom + offsetViewportY; 485 } else { 486 scrollY = 0; 487 } 488 489 final float scale = getScale(); 490 offsetMagnifiedRegion(scrollX * scale, scrollY * scale, INVALID_SERVICE_ID); 491 } 492 } 493 getMagnifiedFrameInContentCoordsLocked(Rect outFrame)494 void getMagnifiedFrameInContentCoordsLocked(Rect outFrame) { 495 final float scale = getSentScale(); 496 final float offsetX = getSentOffsetX(); 497 final float offsetY = getSentOffsetY(); 498 getMagnificationBounds(outFrame); 499 outFrame.offset((int) -offsetX, (int) -offsetY); 500 outFrame.scale(1.0f / scale); 501 } 502 503 @GuardedBy("mLock") setActivated(boolean activated)504 private boolean setActivated(boolean activated) { 505 if (DEBUG) { 506 Slog.i(LOG_TAG, "setActivated(activated = " + activated + ")"); 507 } 508 509 final boolean changed = (mMagnificationActivated != activated); 510 511 if (changed) { 512 mMagnificationActivated = activated; 513 mMagnificationInfoChangedCallbacks.forEach(callback -> { 514 callback.onFullScreenMagnificationActivationState( 515 mDisplayId, mMagnificationActivated); 516 }); 517 mControllerCtx.getWindowManager().setForceShowMagnifiableBounds( 518 mDisplayId, activated); 519 } 520 521 return changed; 522 } 523 524 @GuardedBy("mLock") reset(boolean animate)525 boolean reset(boolean animate) { 526 return reset(transformToStubCallback(animate)); 527 } 528 529 @GuardedBy("mLock") reset(MagnificationAnimationCallback animationCallback)530 boolean reset(MagnificationAnimationCallback animationCallback) { 531 if (!mRegistered) { 532 return false; 533 } 534 final MagnificationSpec spec = mCurrentMagnificationSpec; 535 final boolean changed = isActivated(); 536 setActivated(false); 537 if (changed) { 538 spec.clear(); 539 onMagnificationChangedLocked(); 540 } 541 mIdOfLastServiceToMagnify = INVALID_SERVICE_ID; 542 sendSpecToAnimation(spec, animationCallback); 543 544 hideThumbnail(); 545 546 return changed; 547 } 548 549 @GuardedBy("mLock") setScale(float scale, float pivotX, float pivotY, boolean animate, int id)550 boolean setScale(float scale, float pivotX, float pivotY, 551 boolean animate, int id) { 552 if (!mRegistered) { 553 return false; 554 } 555 // Constrain scale immediately for use in the pivot calculations. 556 scale = MagnificationScaleProvider.constrainScale(scale); 557 558 final Rect viewport = mTempRect; 559 mMagnificationRegion.getBounds(viewport); 560 final MagnificationSpec spec = mCurrentMagnificationSpec; 561 final float oldScale = spec.scale; 562 final float oldCenterX = 563 (viewport.width() / 2.0f - spec.offsetX + viewport.left) / oldScale; 564 final float oldCenterY = 565 (viewport.height() / 2.0f - spec.offsetY + viewport.top) / oldScale; 566 final float normPivotX = (pivotX - spec.offsetX) / oldScale; 567 final float normPivotY = (pivotY - spec.offsetY) / oldScale; 568 final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale); 569 final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale); 570 final float centerX = normPivotX + offsetX; 571 final float centerY = normPivotY + offsetY; 572 mIdOfLastServiceToMagnify = id; 573 return setScaleAndCenter(scale, centerX, centerY, transformToStubCallback(animate), id); 574 } 575 576 @GuardedBy("mLock") setScaleAndCenter(float scale, float centerX, float centerY, MagnificationAnimationCallback animationCallback, int id)577 boolean setScaleAndCenter(float scale, float centerX, float centerY, 578 MagnificationAnimationCallback animationCallback, int id) { 579 if (!mRegistered) { 580 return false; 581 } 582 if (DEBUG) { 583 Slog.i(LOG_TAG, 584 "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX 585 + ", centerY = " + centerY + ", endCallback = " 586 + animationCallback + ", id = " + id + ")"); 587 } 588 boolean changed = setActivated(true); 589 changed |= updateMagnificationSpecLocked(scale, centerX, centerY); 590 sendSpecToAnimation(mCurrentMagnificationSpec, animationCallback); 591 if (isActivated() && (id != INVALID_SERVICE_ID)) { 592 mIdOfLastServiceToMagnify = id; 593 mMagnificationInfoChangedCallbacks.forEach(callback -> { 594 callback.onRequestMagnificationSpec(mDisplayId, 595 mIdOfLastServiceToMagnify); 596 }); 597 } 598 return changed; 599 } 600 601 @GuardedBy("mLock") updateThumbnail(float scale, float centerX, float centerY)602 void updateThumbnail(float scale, float centerX, float centerY) { 603 if (mMagnificationThumbnail != null) { 604 mMagnificationThumbnail.updateThumbnail(scale, centerX, centerY); 605 } 606 } 607 608 @GuardedBy("mLock") refreshThumbnail()609 void refreshThumbnail() { 610 if (mMagnificationThumbnail != null) { 611 mMagnificationThumbnail.setThumbnailBounds( 612 mMagnificationBounds, 613 getScale(), 614 getCenterX(), 615 getCenterY() 616 ); 617 } 618 } 619 620 @GuardedBy("mLock") hideThumbnail()621 void hideThumbnail() { 622 if (mMagnificationThumbnail != null) { 623 mMagnificationThumbnail.hideThumbnail(); 624 } 625 } 626 627 @GuardedBy("mLock") createThumbnailIfSupported()628 void createThumbnailIfSupported() { 629 if (mMagnificationThumbnail == null) { 630 mMagnificationThumbnail = mThumbnailSupplier.get(); 631 // We call refreshThumbnail when the thumbnail is just created to set current 632 // magnification bounds to thumbnail. It to prevent the thumbnail size has not yet 633 // updated properly and thus shows with huge size. (b/276314641) 634 refreshThumbnail(); 635 } 636 } 637 638 @GuardedBy("mLock") destroyThumbnail()639 void destroyThumbnail() { 640 if (mMagnificationThumbnail != null) { 641 hideThumbnail(); 642 mMagnificationThumbnail = null; 643 } 644 } 645 onThumbnailFeatureFlagChanged()646 void onThumbnailFeatureFlagChanged() { 647 synchronized (mLock) { 648 destroyThumbnail(); 649 createThumbnailIfSupported(); 650 } 651 } 652 653 /** 654 * Updates the current magnification spec. 655 * 656 * @param scale the magnification scale 657 * @param centerX the unscaled, screen-relative X coordinate of the center 658 * of the viewport, or {@link Float#NaN} to leave unchanged 659 * @param centerY the unscaled, screen-relative Y coordinate of the center 660 * of the viewport, or {@link Float#NaN} to leave unchanged 661 * @return {@code true} if the magnification spec changed or {@code false} 662 * otherwise 663 */ updateMagnificationSpecLocked(float scale, float centerX, float centerY)664 boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY) { 665 // Handle defaults. 666 if (Float.isNaN(centerX)) { 667 centerX = getCenterX(); 668 } 669 if (Float.isNaN(centerY)) { 670 centerY = getCenterY(); 671 } 672 if (Float.isNaN(scale)) { 673 scale = getScale(); 674 } 675 676 // Compute changes. 677 boolean changed = false; 678 679 final float normScale = MagnificationScaleProvider.constrainScale(scale); 680 if (Float.compare(mCurrentMagnificationSpec.scale, normScale) != 0) { 681 mCurrentMagnificationSpec.scale = normScale; 682 changed = true; 683 } 684 685 final float nonNormOffsetX = mMagnificationBounds.width() / 2.0f 686 + mMagnificationBounds.left - centerX * normScale; 687 final float nonNormOffsetY = mMagnificationBounds.height() / 2.0f 688 + mMagnificationBounds.top - centerY * normScale; 689 changed |= updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY); 690 691 if (changed) { 692 onMagnificationChangedLocked(); 693 } 694 695 return changed; 696 } 697 698 @GuardedBy("mLock") offsetMagnifiedRegion(float offsetX, float offsetY, int id)699 void offsetMagnifiedRegion(float offsetX, float offsetY, int id) { 700 if (!mRegistered) { 701 return; 702 } 703 704 final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX; 705 final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY; 706 if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) { 707 onMagnificationChangedLocked(); 708 } 709 if (id != INVALID_SERVICE_ID) { 710 mIdOfLastServiceToMagnify = id; 711 } 712 sendSpecToAnimation(mCurrentMagnificationSpec, null); 713 } 714 updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY)715 boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) { 716 if (DEBUG) { 717 Slog.i(LOG_TAG, 718 "updateCurrentSpecWithOffsetsLocked(nonNormOffsetX = " + nonNormOffsetX 719 + ", nonNormOffsetY = " + nonNormOffsetY + ")"); 720 } 721 boolean changed = false; 722 final float offsetX = MathUtils.constrain( 723 nonNormOffsetX, getMinOffsetXLocked(), getMaxOffsetXLocked()); 724 if (Float.compare(mCurrentMagnificationSpec.offsetX, offsetX) != 0) { 725 mCurrentMagnificationSpec.offsetX = offsetX; 726 changed = true; 727 } 728 final float offsetY = MathUtils.constrain( 729 nonNormOffsetY, getMinOffsetYLocked(), getMaxOffsetYLocked()); 730 if (Float.compare(mCurrentMagnificationSpec.offsetY, offsetY) != 0) { 731 mCurrentMagnificationSpec.offsetY = offsetY; 732 changed = true; 733 } 734 return changed; 735 } 736 getMinOffsetXLocked()737 float getMinOffsetXLocked() { 738 final float viewportWidth = mMagnificationBounds.width(); 739 final float viewportLeft = mMagnificationBounds.left; 740 return (viewportLeft + viewportWidth) 741 - (viewportLeft + viewportWidth) * mCurrentMagnificationSpec.scale; 742 } 743 getMaxOffsetXLocked()744 float getMaxOffsetXLocked() { 745 return mMagnificationBounds.left 746 - mMagnificationBounds.left * mCurrentMagnificationSpec.scale; 747 } 748 getMinOffsetYLocked()749 float getMinOffsetYLocked() { 750 final float viewportHeight = mMagnificationBounds.height(); 751 final float viewportTop = mMagnificationBounds.top; 752 return (viewportTop + viewportHeight) 753 - (viewportTop + viewportHeight) * mCurrentMagnificationSpec.scale; 754 } 755 getMaxOffsetYLocked()756 float getMaxOffsetYLocked() { 757 return mMagnificationBounds.top 758 - mMagnificationBounds.top * mCurrentMagnificationSpec.scale; 759 } 760 761 @Override toString()762 public String toString() { 763 return "DisplayMagnification[" 764 + "mCurrentMagnificationSpec=" + mCurrentMagnificationSpec 765 + ", mMagnificationRegion=" + mMagnificationRegion 766 + ", mMagnificationBounds=" + mMagnificationBounds 767 + ", mDisplayId=" + mDisplayId 768 + ", mIdOfLastServiceToMagnify=" + mIdOfLastServiceToMagnify 769 + ", mRegistered=" + mRegistered 770 + ", mUnregisterPending=" + mUnregisterPending 771 + ']'; 772 } 773 } 774 775 /** 776 * FullScreenMagnificationController Constructor 777 */ FullScreenMagnificationController(@onNull Context context, @NonNull AccessibilityTraceManager traceManager, @NonNull Object lock, @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback, @NonNull MagnificationScaleProvider scaleProvider, @NonNull Executor backgroundExecutor)778 public FullScreenMagnificationController(@NonNull Context context, 779 @NonNull AccessibilityTraceManager traceManager, @NonNull Object lock, 780 @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback, 781 @NonNull MagnificationScaleProvider scaleProvider, 782 @NonNull Executor backgroundExecutor) { 783 this( 784 new ControllerContext( 785 context, 786 traceManager, 787 LocalServices.getService(WindowManagerInternal.class), 788 new Handler(context.getMainLooper()), 789 context.getResources().getInteger(R.integer.config_longAnimTime)), 790 lock, 791 magnificationInfoChangedCallback, 792 scaleProvider, 793 /* thumbnailSupplier= */ null, 794 backgroundExecutor); 795 } 796 797 /** Constructor for tests */ 798 @VisibleForTesting FullScreenMagnificationController( @onNull ControllerContext ctx, @NonNull Object lock, @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback, @NonNull MagnificationScaleProvider scaleProvider, Supplier<MagnificationThumbnail> thumbnailSupplier, @NonNull Executor backgroundExecutor)799 public FullScreenMagnificationController( 800 @NonNull ControllerContext ctx, 801 @NonNull Object lock, 802 @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback, 803 @NonNull MagnificationScaleProvider scaleProvider, 804 Supplier<MagnificationThumbnail> thumbnailSupplier, 805 @NonNull Executor backgroundExecutor) { 806 mControllerCtx = ctx; 807 mLock = lock; 808 mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId(); 809 mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this); 810 addInfoChangedCallback(magnificationInfoChangedCallback); 811 mScaleProvider = scaleProvider; 812 mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); 813 mMagnificationThumbnailFeatureFlag = new MagnificationThumbnailFeatureFlag(); 814 mMagnificationThumbnailFeatureFlag.addOnChangedListener( 815 backgroundExecutor, this::onMagnificationThumbnailFeatureFlagChanged); 816 if (thumbnailSupplier != null) { 817 mThumbnailSupplier = thumbnailSupplier; 818 } else { 819 mThumbnailSupplier = () -> { 820 if (mMagnificationThumbnailFeatureFlag.isFeatureFlagEnabled()) { 821 return new MagnificationThumbnail( 822 ctx.getContext(), 823 ctx.getContext().getSystemService(WindowManager.class), 824 new Handler(ctx.getContext().getMainLooper()) 825 ); 826 } 827 return null; 828 }; 829 } 830 } 831 onMagnificationThumbnailFeatureFlagChanged()832 private void onMagnificationThumbnailFeatureFlagChanged() { 833 synchronized (mLock) { 834 for (int i = 0; i < mDisplays.size(); i++) { 835 onMagnificationThumbnailFeatureFlagChanged(mDisplays.keyAt(i)); 836 } 837 } 838 } 839 onMagnificationThumbnailFeatureFlagChanged(int displayId)840 private void onMagnificationThumbnailFeatureFlagChanged(int displayId) { 841 synchronized (mLock) { 842 final DisplayMagnification display = mDisplays.get(displayId); 843 if (display == null) { 844 return; 845 } 846 display.onThumbnailFeatureFlagChanged(); 847 } 848 } 849 850 /** 851 * Start tracking the magnification region for services that control magnification and the 852 * magnification gesture handler. 853 * 854 * This tracking imposes a cost on the system, so we avoid tracking this data unless it's 855 * required. 856 * 857 * @param displayId The logical display id. 858 */ register(int displayId)859 public void register(int displayId) { 860 synchronized (mLock) { 861 DisplayMagnification display = mDisplays.get(displayId); 862 if (display == null) { 863 display = new DisplayMagnification(displayId); 864 } 865 if (display.isRegistered()) { 866 return; 867 } 868 if (display.register()) { 869 mDisplays.put(displayId, display); 870 mScreenStateObserver.registerIfNecessary(); 871 } 872 } 873 } 874 875 /** 876 * Stop requiring tracking the magnification region. We may remain registered while we 877 * reset magnification. 878 * 879 * @param displayId The logical display id. 880 */ unregister(int displayId)881 public void unregister(int displayId) { 882 synchronized (mLock) { 883 unregisterLocked(displayId, false); 884 } 885 } 886 887 /** 888 * Stop tracking all displays' magnification region. 889 */ unregisterAll()890 public void unregisterAll() { 891 synchronized (mLock) { 892 // display will be removed from array after unregister, we need to clone it to 893 // prevent error. 894 final SparseArray<DisplayMagnification> displays = mDisplays.clone(); 895 for (int i = 0; i < displays.size(); i++) { 896 unregisterLocked(displays.keyAt(i), false); 897 } 898 } 899 } 900 901 @Override onRectangleOnScreenRequested(int displayId, int left, int top, int right, int bottom)902 public void onRectangleOnScreenRequested(int displayId, int left, int top, int right, 903 int bottom) { 904 synchronized (mLock) { 905 if (!mMagnificationFollowTypingEnabled) { 906 return; 907 } 908 final DisplayMagnification display = mDisplays.get(displayId); 909 if (display == null) { 910 return; 911 } 912 if (!display.isActivated()) { 913 return; 914 } 915 final Rect magnifiedRegionBounds = mTempRect; 916 display.getMagnifiedFrameInContentCoordsLocked(magnifiedRegionBounds); 917 if (magnifiedRegionBounds.contains(left, top, right, bottom)) { 918 return; 919 } 920 display.onRectangleOnScreenRequested(left, top, right, bottom); 921 } 922 } 923 setMagnificationFollowTypingEnabled(boolean enabled)924 void setMagnificationFollowTypingEnabled(boolean enabled) { 925 mMagnificationFollowTypingEnabled = enabled; 926 } 927 isMagnificationFollowTypingEnabled()928 boolean isMagnificationFollowTypingEnabled() { 929 return mMagnificationFollowTypingEnabled; 930 } 931 setAlwaysOnMagnificationEnabled(boolean enabled)932 void setAlwaysOnMagnificationEnabled(boolean enabled) { 933 mAlwaysOnMagnificationEnabled = enabled; 934 } 935 isAlwaysOnMagnificationEnabled()936 boolean isAlwaysOnMagnificationEnabled() { 937 return mAlwaysOnMagnificationEnabled; 938 } 939 940 /** 941 * if the magnifier with given displayId is activated: 942 * 1. if {@link #isAlwaysOnMagnificationEnabled()}, zoom the magnifier to 100%, 943 * 2. otherwise, reset the magnification. 944 * 945 * @param displayId The logical display id. 946 */ onUserContextChanged(int displayId)947 void onUserContextChanged(int displayId) { 948 synchronized (mLock) { 949 if (!isActivated(displayId)) { 950 return; 951 } 952 953 if (isAlwaysOnMagnificationEnabled()) { 954 setScaleAndCenter(displayId, 1.0f, Float.NaN, Float.NaN, 955 true, 956 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); 957 } else { 958 reset(displayId, true); 959 } 960 } 961 } 962 963 /** 964 * Remove the display magnification with given id. 965 * 966 * @param displayId The logical display id. 967 */ onDisplayRemoved(int displayId)968 public void onDisplayRemoved(int displayId) { 969 synchronized (mLock) { 970 unregisterLocked(displayId, true); 971 } 972 } 973 974 /** 975 * Check if we are registered on specified display. Note that we may be planning to unregister 976 * at any moment. 977 * 978 * @return {@code true} if the controller is registered on specified display. 979 * {@code false} otherwise. 980 * 981 * @param displayId The logical display id. 982 */ isRegistered(int displayId)983 public boolean isRegistered(int displayId) { 984 synchronized (mLock) { 985 final DisplayMagnification display = mDisplays.get(displayId); 986 if (display == null) { 987 return false; 988 } 989 return display.isRegistered(); 990 } 991 } 992 993 /** 994 * @param displayId The logical display id. 995 * @return {@code true} if magnification is activated, 996 * {@code false} otherwise 997 */ isActivated(int displayId)998 public boolean isActivated(int displayId) { 999 synchronized (mLock) { 1000 final DisplayMagnification display = mDisplays.get(displayId); 1001 if (display == null) { 1002 return false; 1003 } 1004 return display.isActivated(); 1005 } 1006 } 1007 1008 /** 1009 * Returns whether the magnification region contains the specified 1010 * screen-relative coordinates. 1011 * 1012 * @param displayId The logical display id. 1013 * @param x the screen-relative X coordinate to check 1014 * @param y the screen-relative Y coordinate to check 1015 * @return {@code true} if the coordinate is contained within the 1016 * magnified region, or {@code false} otherwise 1017 */ magnificationRegionContains(int displayId, float x, float y)1018 public boolean magnificationRegionContains(int displayId, float x, float y) { 1019 synchronized (mLock) { 1020 final DisplayMagnification display = mDisplays.get(displayId); 1021 if (display == null) { 1022 return false; 1023 } 1024 return display.magnificationRegionContains(x, y); 1025 } 1026 } 1027 1028 /** 1029 * Populates the specified rect with the screen-relative bounds of the 1030 * magnification region. If magnification is not enabled, the returned 1031 * bounds will be empty. 1032 * 1033 * @param displayId The logical display id. 1034 * @param outBounds rect to populate with the bounds of the magnified 1035 * region 1036 */ getMagnificationBounds(int displayId, @NonNull Rect outBounds)1037 public void getMagnificationBounds(int displayId, @NonNull Rect outBounds) { 1038 synchronized (mLock) { 1039 final DisplayMagnification display = mDisplays.get(displayId); 1040 if (display == null) { 1041 return; 1042 } 1043 display.getMagnificationBounds(outBounds); 1044 } 1045 } 1046 1047 /** 1048 * Populates the specified region with the screen-relative magnification 1049 * region. If magnification is not enabled, then the returned region 1050 * will be empty. 1051 * 1052 * @param displayId The logical display id. 1053 * @param outRegion the region to populate 1054 */ getMagnificationRegion(int displayId, @NonNull Region outRegion)1055 public void getMagnificationRegion(int displayId, @NonNull Region outRegion) { 1056 synchronized (mLock) { 1057 final DisplayMagnification display = mDisplays.get(displayId); 1058 if (display == null) { 1059 return; 1060 } 1061 display.getMagnificationRegion(outRegion); 1062 } 1063 } 1064 1065 /** 1066 * Returns the magnification scale. If an animation is in progress, 1067 * this reflects the end state of the animation. 1068 * 1069 * @param displayId The logical display id. 1070 * @return the scale 1071 */ getScale(int displayId)1072 public float getScale(int displayId) { 1073 synchronized (mLock) { 1074 final DisplayMagnification display = mDisplays.get(displayId); 1075 if (display == null) { 1076 return 1.0f; 1077 } 1078 return display.getScale(); 1079 } 1080 } 1081 getLastActivatedScale(int displayId)1082 protected float getLastActivatedScale(int displayId) { 1083 return getScale(displayId); 1084 } 1085 1086 /** 1087 * Returns the X offset of the magnification viewport. If an animation 1088 * is in progress, this reflects the end state of the animation. 1089 * 1090 * @param displayId The logical display id. 1091 * @return the X offset 1092 */ getOffsetX(int displayId)1093 public float getOffsetX(int displayId) { 1094 synchronized (mLock) { 1095 final DisplayMagnification display = mDisplays.get(displayId); 1096 if (display == null) { 1097 return 0.0f; 1098 } 1099 return display.getOffsetX(); 1100 } 1101 } 1102 1103 /** 1104 * Returns the screen-relative X coordinate of the center of the 1105 * magnification viewport. 1106 * 1107 * @param displayId The logical display id. 1108 * @return the X coordinate 1109 */ getCenterX(int displayId)1110 public float getCenterX(int displayId) { 1111 synchronized (mLock) { 1112 final DisplayMagnification display = mDisplays.get(displayId); 1113 if (display == null) { 1114 return 0.0f; 1115 } 1116 return display.getCenterX(); 1117 } 1118 } 1119 1120 /** 1121 * Returns the Y offset of the magnification viewport. If an animation 1122 * is in progress, this reflects the end state of the animation. 1123 * 1124 * @param displayId The logical display id. 1125 * @return the Y offset 1126 */ getOffsetY(int displayId)1127 public float getOffsetY(int displayId) { 1128 synchronized (mLock) { 1129 final DisplayMagnification display = mDisplays.get(displayId); 1130 if (display == null) { 1131 return 0.0f; 1132 } 1133 return display.getOffsetY(); 1134 } 1135 } 1136 1137 /** 1138 * Returns the screen-relative Y coordinate of the center of the 1139 * magnification viewport. 1140 * 1141 * @param displayId The logical display id. 1142 * @return the Y coordinate 1143 */ getCenterY(int displayId)1144 public float getCenterY(int displayId) { 1145 synchronized (mLock) { 1146 final DisplayMagnification display = mDisplays.get(displayId); 1147 if (display == null) { 1148 return 0.0f; 1149 } 1150 return display.getCenterY(); 1151 } 1152 } 1153 1154 /** 1155 * Resets the magnification scale and center, optionally animating the 1156 * transition. 1157 * 1158 * @param displayId The logical display id. 1159 * @param animate {@code true} to animate the transition, {@code false} 1160 * to transition immediately 1161 * @return {@code true} if the magnification spec changed, {@code false} if 1162 * the spec did not change 1163 */ reset(int displayId, boolean animate)1164 public boolean reset(int displayId, boolean animate) { 1165 return reset(displayId, animate ? STUB_ANIMATION_CALLBACK : null); 1166 } 1167 1168 /** 1169 * Resets the magnification scale and center, optionally animating the 1170 * transition. 1171 * 1172 * @param displayId The logical display id. 1173 * @param animationCallback Called when the animation result is valid. 1174 * {@code null} to transition immediately 1175 * @return {@code true} if the magnification spec changed, {@code false} if 1176 * the spec did not change 1177 */ reset(int displayId, MagnificationAnimationCallback animationCallback)1178 public boolean reset(int displayId, 1179 MagnificationAnimationCallback animationCallback) { 1180 synchronized (mLock) { 1181 final DisplayMagnification display = mDisplays.get(displayId); 1182 if (display == null) { 1183 return false; 1184 } 1185 return display.reset(animationCallback); 1186 } 1187 } 1188 1189 /** 1190 * Scales the magnified region around the specified pivot point, 1191 * optionally animating the transition. If animation is disabled, the 1192 * transition is immediate. 1193 * 1194 * @param displayId The logical display id. 1195 * @param scale the target scale, must be >= 1 1196 * @param pivotX the screen-relative X coordinate around which to scale 1197 * @param pivotY the screen-relative Y coordinate around which to scale 1198 * @param animate {@code true} to animate the transition, {@code false} 1199 * to transition immediately 1200 * @param id the ID of the service requesting the change 1201 * @return {@code true} if the magnification spec changed, {@code false} if 1202 * the spec did not change 1203 */ setScale(int displayId, float scale, float pivotX, float pivotY, boolean animate, int id)1204 public boolean setScale(int displayId, float scale, float pivotX, float pivotY, 1205 boolean animate, int id) { 1206 synchronized (mLock) { 1207 final DisplayMagnification display = mDisplays.get(displayId); 1208 if (display == null) { 1209 return false; 1210 } 1211 return display.setScale(scale, pivotX, pivotY, animate, id); 1212 } 1213 } 1214 1215 /** 1216 * Sets the center of the magnified region, optionally animating the 1217 * transition. If animation is disabled, the transition is immediate. 1218 * 1219 * @param displayId The logical display id. 1220 * @param centerX the screen-relative X coordinate around which to 1221 * center 1222 * @param centerY the screen-relative Y coordinate around which to 1223 * center 1224 * @param animate {@code true} to animate the transition, {@code false} 1225 * to transition immediately 1226 * @param id the ID of the service requesting the change 1227 * @return {@code true} if the magnification spec changed, {@code false} if 1228 * the spec did not change 1229 */ setCenter(int displayId, float centerX, float centerY, boolean animate, int id)1230 public boolean setCenter(int displayId, float centerX, float centerY, boolean animate, int id) { 1231 synchronized (mLock) { 1232 final DisplayMagnification display = mDisplays.get(displayId); 1233 if (display == null) { 1234 return false; 1235 } 1236 return display.setScaleAndCenter(Float.NaN, centerX, centerY, 1237 animate ? STUB_ANIMATION_CALLBACK : null, id); 1238 } 1239 } 1240 1241 /** 1242 * Sets the scale and center of the magnified region, optionally 1243 * animating the transition. If animation is disabled, the transition 1244 * is immediate. 1245 * 1246 * @param displayId The logical display id. 1247 * @param scale the target scale, or {@link Float#NaN} to leave unchanged 1248 * @param centerX the screen-relative X coordinate around which to 1249 * center and scale, or {@link Float#NaN} to leave unchanged 1250 * @param centerY the screen-relative Y coordinate around which to 1251 * center and scale, or {@link Float#NaN} to leave unchanged 1252 * @param animate {@code true} to animate the transition, {@code false} 1253 * to transition immediately 1254 * @param id the ID of the service requesting the change 1255 * @return {@code true} if the magnification spec changed, {@code false} if 1256 * the spec did not change 1257 */ setScaleAndCenter(int displayId, float scale, float centerX, float centerY, boolean animate, int id)1258 public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY, 1259 boolean animate, int id) { 1260 return setScaleAndCenter(displayId, scale, centerX, centerY, 1261 transformToStubCallback(animate), id); 1262 } 1263 1264 /** 1265 * Sets the scale and center of the magnified region, optionally 1266 * animating the transition. If animation is disabled, the transition 1267 * is immediate. 1268 * 1269 * @param displayId The logical display id. 1270 * @param scale the target scale, or {@link Float#NaN} to leave unchanged 1271 * @param centerX the screen-relative X coordinate around which to 1272 * center and scale, or {@link Float#NaN} to leave unchanged 1273 * @param centerY the screen-relative Y coordinate around which to 1274 * center and scale, or {@link Float#NaN} to leave unchanged 1275 * @param animationCallback Called when the animation result is valid. 1276 * {@code null} to transition immediately 1277 * @param id the ID of the service requesting the change 1278 * @return {@code true} if the magnification spec changed, {@code false} if 1279 * the spec did not change 1280 */ setScaleAndCenter(int displayId, float scale, float centerX, float centerY, MagnificationAnimationCallback animationCallback, int id)1281 public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY, 1282 MagnificationAnimationCallback animationCallback, int id) { 1283 synchronized (mLock) { 1284 final DisplayMagnification display = mDisplays.get(displayId); 1285 if (display == null) { 1286 return false; 1287 } 1288 return display.setScaleAndCenter(scale, centerX, centerY, animationCallback, id); 1289 } 1290 } 1291 1292 /** 1293 * Offsets the magnified region. Note that the offsetX and offsetY values actually move in the 1294 * opposite direction as the offsets passed in here. 1295 * 1296 * @param displayId The logical display id. 1297 * @param offsetX the amount in pixels to offset the region in the X direction, in current 1298 * screen pixels. 1299 * @param offsetY the amount in pixels to offset the region in the Y direction, in current 1300 * screen pixels. 1301 * @param id the ID of the service requesting the change 1302 */ offsetMagnifiedRegion(int displayId, float offsetX, float offsetY, int id)1303 public void offsetMagnifiedRegion(int displayId, float offsetX, float offsetY, int id) { 1304 synchronized (mLock) { 1305 final DisplayMagnification display = mDisplays.get(displayId); 1306 if (display == null) { 1307 return; 1308 } 1309 display.offsetMagnifiedRegion(offsetX, offsetY, id); 1310 } 1311 } 1312 1313 /** 1314 * Get the ID of the last service that changed the magnification spec. 1315 * 1316 * @param displayId The logical display id. 1317 * @return The id 1318 */ getIdOfLastServiceToMagnify(int displayId)1319 public int getIdOfLastServiceToMagnify(int displayId) { 1320 synchronized (mLock) { 1321 final DisplayMagnification display = mDisplays.get(displayId); 1322 if (display == null) { 1323 return -1; 1324 } 1325 return display.getIdOfLastServiceToMagnify(); 1326 } 1327 } 1328 1329 /** 1330 * Persists the default display magnification scale to the current user's settings 1331 * <strong>if scale is >= {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}</strong>. 1332 * We assume if the scale is < {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}, there 1333 * will be no obvious magnification effect. 1334 */ persistScale(int displayId)1335 public void persistScale(int displayId) { 1336 final float scale = getScale(Display.DEFAULT_DISPLAY); 1337 if (scale < MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) { 1338 return; 1339 } 1340 mScaleProvider.putScale(scale, displayId); 1341 } 1342 1343 /** 1344 * Retrieves a previously persisted magnification scale from the current 1345 * user's settings. 1346 * 1347 * @return the previously persisted magnification scale, or the default 1348 * scale if none is available 1349 */ getPersistedScale(int displayId)1350 public float getPersistedScale(int displayId) { 1351 return MathUtils.constrain(mScaleProvider.getScale(displayId), 1352 MagnificationConstants.PERSISTED_SCALE_MIN_VALUE, 1353 MagnificationScaleProvider.MAX_SCALE); 1354 } 1355 1356 /** 1357 * Resets all displays' magnification if last magnifying service is disabled. 1358 * 1359 * @param connectionId 1360 */ resetAllIfNeeded(int connectionId)1361 public void resetAllIfNeeded(int connectionId) { 1362 synchronized (mLock) { 1363 for (int i = 0; i < mDisplays.size(); i++) { 1364 resetIfNeeded(mDisplays.keyAt(i), connectionId); 1365 } 1366 } 1367 } 1368 1369 /** 1370 * Resets magnification if magnification and auto-update are both enabled. 1371 * 1372 * @param displayId The logical display id. 1373 * @param animate whether the animate the transition 1374 * @return whether was {@link #isActivated(int)} activated} 1375 */ resetIfNeeded(int displayId, boolean animate)1376 boolean resetIfNeeded(int displayId, boolean animate) { 1377 synchronized (mLock) { 1378 final DisplayMagnification display = mDisplays.get(displayId); 1379 if (display == null || !display.isActivated()) { 1380 return false; 1381 } 1382 display.reset(animate); 1383 return true; 1384 } 1385 } 1386 1387 /** 1388 * Resets magnification if last magnifying service is disabled. 1389 * 1390 * @param displayId The logical display id. 1391 * @param connectionId the connection ID be disabled. 1392 * @return {@code true} on success, {@code false} on failure 1393 */ resetIfNeeded(int displayId, int connectionId)1394 boolean resetIfNeeded(int displayId, int connectionId) { 1395 synchronized (mLock) { 1396 final DisplayMagnification display = mDisplays.get(displayId); 1397 if (display == null || !display.isActivated() 1398 || connectionId != display.getIdOfLastServiceToMagnify()) { 1399 return false; 1400 } 1401 display.reset(true); 1402 return true; 1403 } 1404 } 1405 1406 /** 1407 * Notifies that the IME window visibility changed. 1408 * 1409 * @param displayId the logical display id 1410 * @param shown {@code true} means the IME window shows on the screen. Otherwise it's 1411 * hidden. 1412 */ notifyImeWindowVisibilityChanged(int displayId, boolean shown)1413 void notifyImeWindowVisibilityChanged(int displayId, boolean shown) { 1414 synchronized (mLock) { 1415 mMagnificationInfoChangedCallbacks.forEach(callback -> { 1416 callback.onImeWindowVisibilityChanged(displayId, shown); 1417 }); 1418 } 1419 } 1420 onScreenTurnedOff()1421 private void onScreenTurnedOff() { 1422 final Message m = PooledLambda.obtainMessage( 1423 FullScreenMagnificationController::resetAllIfNeeded, this, false); 1424 mControllerCtx.getHandler().sendMessage(m); 1425 } 1426 1427 /** 1428 * Resets magnification on all displays. 1429 * @param animate reset the magnification with animation 1430 */ resetAllIfNeeded(boolean animate)1431 void resetAllIfNeeded(boolean animate) { 1432 synchronized (mLock) { 1433 for (int i = 0; i < mDisplays.size(); i++) { 1434 resetIfNeeded(mDisplays.keyAt(i), animate); 1435 } 1436 } 1437 } 1438 unregisterLocked(int displayId, boolean delete)1439 private void unregisterLocked(int displayId, boolean delete) { 1440 final DisplayMagnification display = mDisplays.get(displayId); 1441 if (display == null) { 1442 return; 1443 } 1444 if (!display.isRegistered()) { 1445 if (delete) { 1446 mDisplays.remove(displayId); 1447 } 1448 return; 1449 } 1450 if (!display.isActivated()) { 1451 display.unregister(delete); 1452 } else { 1453 display.unregisterPending(delete); 1454 } 1455 } 1456 1457 /** 1458 * Callbacks from DisplayMagnification after display magnification unregistered. It will remove 1459 * DisplayMagnification instance if delete is true, and unregister screen state if 1460 * there is no registered display magnification. 1461 */ unregisterCallbackLocked(int displayId, boolean delete)1462 private void unregisterCallbackLocked(int displayId, boolean delete) { 1463 if (delete) { 1464 mDisplays.remove(displayId); 1465 } 1466 // unregister screen state if necessary 1467 boolean hasRegister = false; 1468 for (int i = 0; i < mDisplays.size(); i++) { 1469 final DisplayMagnification display = mDisplays.valueAt(i); 1470 hasRegister = display.isRegistered(); 1471 if (hasRegister) { 1472 break; 1473 } 1474 } 1475 if (!hasRegister) { 1476 mScreenStateObserver.unregister(); 1477 } 1478 } 1479 addInfoChangedCallback(@onNull MagnificationInfoChangedCallback callback)1480 void addInfoChangedCallback(@NonNull MagnificationInfoChangedCallback callback) { 1481 synchronized (mLock) { 1482 mMagnificationInfoChangedCallbacks.add(callback); 1483 } 1484 } 1485 removeInfoChangedCallback(@onNull MagnificationInfoChangedCallback callback)1486 void removeInfoChangedCallback(@NonNull MagnificationInfoChangedCallback callback) { 1487 synchronized (mLock) { 1488 mMagnificationInfoChangedCallbacks.remove(callback); 1489 } 1490 } 1491 traceEnabled()1492 private boolean traceEnabled() { 1493 return mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes( 1494 FLAGS_WINDOW_MANAGER_INTERNAL); 1495 } 1496 logTrace(String methodName, String params)1497 private void logTrace(String methodName, String params) { 1498 mControllerCtx.getTraceManager().logTrace( 1499 "WindowManagerInternal." + methodName, FLAGS_WINDOW_MANAGER_INTERNAL, params); 1500 } 1501 1502 @Override toString()1503 public String toString() { 1504 StringBuilder builder = new StringBuilder(); 1505 builder.append("MagnificationController["); 1506 builder.append(", mDisplays=").append(mDisplays); 1507 builder.append(", mScaleProvider=").append(mScaleProvider); 1508 builder.append("]"); 1509 return builder.toString(); 1510 } 1511 1512 /** 1513 * Class responsible for animating spec on the main thread and sending spec 1514 * updates to the window manager. 1515 */ 1516 private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener, 1517 Animator.AnimatorListener { 1518 private final ControllerContext mControllerCtx; 1519 1520 /** 1521 * The magnification spec that was sent to the window manager. This should 1522 * only be accessed with the lock held. 1523 */ 1524 private final MagnificationSpec mSentMagnificationSpec = new MagnificationSpec(); 1525 1526 private final MagnificationSpec mStartMagnificationSpec = new MagnificationSpec(); 1527 1528 private final MagnificationSpec mEndMagnificationSpec = new MagnificationSpec(); 1529 1530 /** 1531 * The animator should only be accessed and modified on the main (e.g. animation) thread. 1532 */ 1533 private final ValueAnimator mValueAnimator; 1534 1535 // Called when the callee wants animating and the sent spec matches the target spec. 1536 private MagnificationAnimationCallback mAnimationCallback; 1537 private final Object mLock; 1538 1539 private final int mDisplayId; 1540 1541 @GuardedBy("mLock") 1542 private boolean mEnabled = false; 1543 SpecAnimationBridge(ControllerContext ctx, Object lock, int displayId)1544 private SpecAnimationBridge(ControllerContext ctx, Object lock, int displayId) { 1545 mControllerCtx = ctx; 1546 mLock = lock; 1547 mDisplayId = displayId; 1548 final long animationDuration = mControllerCtx.getAnimationDuration(); 1549 mValueAnimator = mControllerCtx.newValueAnimator(); 1550 mValueAnimator.setDuration(animationDuration); 1551 mValueAnimator.setInterpolator(new DecelerateInterpolator(2.5f)); 1552 mValueAnimator.setFloatValues(0.0f, 1.0f); 1553 mValueAnimator.addUpdateListener(this); 1554 mValueAnimator.addListener(this); 1555 } 1556 1557 /** 1558 * Enabled means the bridge will accept input. When not enabled, the output of the animator 1559 * will be ignored 1560 */ setEnabled(boolean enabled)1561 public void setEnabled(boolean enabled) { 1562 synchronized (mLock) { 1563 if (enabled != mEnabled) { 1564 mEnabled = enabled; 1565 if (!mEnabled) { 1566 mSentMagnificationSpec.clear(); 1567 if (mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes( 1568 FLAGS_WINDOW_MANAGER_INTERNAL)) { 1569 mControllerCtx.getTraceManager().logTrace( 1570 "WindowManagerInternal.setMagnificationSpec", 1571 FLAGS_WINDOW_MANAGER_INTERNAL, 1572 "displayID=" + mDisplayId + ";spec=" + mSentMagnificationSpec); 1573 } 1574 mControllerCtx.getWindowManager().setMagnificationSpec( 1575 mDisplayId, mSentMagnificationSpec); 1576 } 1577 } 1578 } 1579 } 1580 updateSentSpecMainThread(MagnificationSpec spec, MagnificationAnimationCallback animationCallback)1581 void updateSentSpecMainThread(MagnificationSpec spec, 1582 MagnificationAnimationCallback animationCallback) { 1583 if (mValueAnimator.isRunning()) { 1584 mValueAnimator.cancel(); 1585 } 1586 1587 mAnimationCallback = animationCallback; 1588 // If the current and sent specs don't match, update the sent spec. 1589 synchronized (mLock) { 1590 final boolean changed = !mSentMagnificationSpec.equals(spec); 1591 if (changed) { 1592 if (mAnimationCallback != null) { 1593 animateMagnificationSpecLocked(spec); 1594 } else { 1595 setMagnificationSpecLocked(spec); 1596 } 1597 } else { 1598 sendEndCallbackMainThread(true); 1599 } 1600 } 1601 } 1602 sendEndCallbackMainThread(boolean success)1603 private void sendEndCallbackMainThread(boolean success) { 1604 if (mAnimationCallback != null) { 1605 if (DEBUG) { 1606 Slog.d(LOG_TAG, "sendEndCallbackMainThread: " + success); 1607 } 1608 mAnimationCallback.onResult(success); 1609 mAnimationCallback = null; 1610 } 1611 } 1612 1613 @GuardedBy("mLock") setMagnificationSpecLocked(MagnificationSpec spec)1614 private void setMagnificationSpecLocked(MagnificationSpec spec) { 1615 if (mEnabled) { 1616 if (DEBUG_SET_MAGNIFICATION_SPEC) { 1617 Slog.i(LOG_TAG, "Sending: " + spec); 1618 } 1619 1620 mSentMagnificationSpec.setTo(spec); 1621 if (mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes( 1622 FLAGS_WINDOW_MANAGER_INTERNAL)) { 1623 mControllerCtx.getTraceManager().logTrace( 1624 "WindowManagerInternal.setMagnificationSpec", 1625 FLAGS_WINDOW_MANAGER_INTERNAL, 1626 "displayID=" + mDisplayId + ";spec=" + mSentMagnificationSpec); 1627 } 1628 mControllerCtx.getWindowManager().setMagnificationSpec( 1629 mDisplayId, mSentMagnificationSpec); 1630 } 1631 } 1632 animateMagnificationSpecLocked(MagnificationSpec toSpec)1633 private void animateMagnificationSpecLocked(MagnificationSpec toSpec) { 1634 mEndMagnificationSpec.setTo(toSpec); 1635 mStartMagnificationSpec.setTo(mSentMagnificationSpec); 1636 mValueAnimator.start(); 1637 } 1638 1639 @Override onAnimationUpdate(ValueAnimator animation)1640 public void onAnimationUpdate(ValueAnimator animation) { 1641 synchronized (mLock) { 1642 if (mEnabled) { 1643 float fract = animation.getAnimatedFraction(); 1644 MagnificationSpec magnificationSpec = new MagnificationSpec(); 1645 magnificationSpec.scale = mStartMagnificationSpec.scale 1646 + (mEndMagnificationSpec.scale - mStartMagnificationSpec.scale) * fract; 1647 magnificationSpec.offsetX = mStartMagnificationSpec.offsetX 1648 + (mEndMagnificationSpec.offsetX - mStartMagnificationSpec.offsetX) 1649 * fract; 1650 magnificationSpec.offsetY = mStartMagnificationSpec.offsetY 1651 + (mEndMagnificationSpec.offsetY - mStartMagnificationSpec.offsetY) 1652 * fract; 1653 setMagnificationSpecLocked(magnificationSpec); 1654 } 1655 } 1656 } 1657 1658 @Override onAnimationStart(Animator animation)1659 public void onAnimationStart(Animator animation) { 1660 } 1661 1662 @Override onAnimationEnd(Animator animation)1663 public void onAnimationEnd(Animator animation) { 1664 sendEndCallbackMainThread(true); 1665 } 1666 1667 @Override onAnimationCancel(Animator animation)1668 public void onAnimationCancel(Animator animation) { 1669 sendEndCallbackMainThread(false); 1670 } 1671 1672 @Override onAnimationRepeat(Animator animation)1673 public void onAnimationRepeat(Animator animation) { 1674 1675 } 1676 } 1677 1678 private static class ScreenStateObserver extends BroadcastReceiver { 1679 private final Context mContext; 1680 private final FullScreenMagnificationController mController; 1681 private boolean mRegistered = false; 1682 ScreenStateObserver(Context context, FullScreenMagnificationController controller)1683 ScreenStateObserver(Context context, FullScreenMagnificationController controller) { 1684 mContext = context; 1685 mController = controller; 1686 } 1687 registerIfNecessary()1688 public void registerIfNecessary() { 1689 if (!mRegistered) { 1690 mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF)); 1691 mRegistered = true; 1692 } 1693 } 1694 unregister()1695 public void unregister() { 1696 if (mRegistered) { 1697 mContext.unregisterReceiver(this); 1698 mRegistered = false; 1699 } 1700 } 1701 1702 @Override onReceive(Context context, Intent intent)1703 public void onReceive(Context context, Intent intent) { 1704 mController.onScreenTurnedOff(); 1705 } 1706 } 1707 1708 /** 1709 * This class holds resources used between the classes in MagnificationController, and 1710 * functions for tests to mock it. 1711 */ 1712 @VisibleForTesting 1713 public static class ControllerContext { 1714 private final Context mContext; 1715 private final AccessibilityTraceManager mTrace; 1716 private final WindowManagerInternal mWindowManager; 1717 private final Handler mHandler; 1718 private final Long mAnimationDuration; 1719 1720 /** 1721 * Constructor for ControllerContext. 1722 */ ControllerContext(@onNull Context context, @NonNull AccessibilityTraceManager traceManager, @NonNull WindowManagerInternal windowManager, @NonNull Handler handler, long animationDuration)1723 public ControllerContext(@NonNull Context context, 1724 @NonNull AccessibilityTraceManager traceManager, 1725 @NonNull WindowManagerInternal windowManager, 1726 @NonNull Handler handler, 1727 long animationDuration) { 1728 mContext = context; 1729 mTrace = traceManager; 1730 mWindowManager = windowManager; 1731 mHandler = handler; 1732 mAnimationDuration = animationDuration; 1733 } 1734 1735 /** 1736 * @return A context. 1737 */ 1738 @NonNull getContext()1739 public Context getContext() { 1740 return mContext; 1741 } 1742 1743 /** 1744 * @return AccessibilityTraceManager 1745 */ 1746 @NonNull getTraceManager()1747 public AccessibilityTraceManager getTraceManager() { 1748 return mTrace; 1749 } 1750 1751 /** 1752 * @return WindowManagerInternal 1753 */ 1754 @NonNull getWindowManager()1755 public WindowManagerInternal getWindowManager() { 1756 return mWindowManager; 1757 } 1758 1759 /** 1760 * @return Handler for main looper 1761 */ 1762 @NonNull getHandler()1763 public Handler getHandler() { 1764 return mHandler; 1765 } 1766 1767 /** 1768 * Create a new ValueAnimator. 1769 * 1770 * @return ValueAnimator 1771 */ 1772 @NonNull newValueAnimator()1773 public ValueAnimator newValueAnimator() { 1774 return new ValueAnimator(); 1775 } 1776 1777 /** 1778 * @return Configuration of animation duration. 1779 */ getAnimationDuration()1780 public long getAnimationDuration() { 1781 return mAnimationDuration; 1782 } 1783 } 1784 1785 @Nullable transformToStubCallback(boolean animate)1786 private static MagnificationAnimationCallback transformToStubCallback(boolean animate) { 1787 return animate ? STUB_ANIMATION_CALLBACK : null; 1788 } 1789 1790 interface MagnificationInfoChangedCallback { 1791 1792 /** 1793 * Called when the {@link MagnificationSpec} is changed with non-default 1794 * scale by the service. 1795 * 1796 * @param displayId the logical display id 1797 * @param serviceId the ID of the service requesting the change 1798 */ onRequestMagnificationSpec(int displayId, int serviceId)1799 void onRequestMagnificationSpec(int displayId, int serviceId); 1800 1801 /** 1802 * Called when the state of the magnification activation is changed. 1803 * 1804 * @param displayId the logical display id 1805 * @param activated {@code true} if the magnification is activated, otherwise {@code false}. 1806 */ onFullScreenMagnificationActivationState(int displayId, boolean activated)1807 void onFullScreenMagnificationActivationState(int displayId, boolean activated); 1808 1809 /** 1810 * Called when the IME window visibility changed. 1811 * 1812 * @param displayId the logical display id 1813 * @param shown {@code true} means the IME window shows on the screen. Otherwise it's 1814 * hidden. 1815 */ onImeWindowVisibilityChanged(int displayId, boolean shown)1816 void onImeWindowVisibilityChanged(int displayId, boolean shown); 1817 1818 /** 1819 * Called when the magnification spec changed. 1820 * 1821 * @param displayId The logical display id 1822 * @param region The region of the screen currently active for magnification. 1823 * The returned region will be empty if the magnification is not active. 1824 * @param config The magnification config. That has magnification mode, the new scale and 1825 * the new screen-relative center position 1826 */ onFullScreenMagnificationChanged(int displayId, @NonNull Region region, @NonNull MagnificationConfig config)1827 void onFullScreenMagnificationChanged(int displayId, @NonNull Region region, 1828 @NonNull MagnificationConfig config); 1829 } 1830 } 1831