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