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