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