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