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; 18 19 import com.android.internal.R; 20 import com.android.internal.annotations.GuardedBy; 21 import com.android.internal.os.SomeArgs; 22 import com.android.server.LocalServices; 23 24 import android.animation.ObjectAnimator; 25 import android.animation.TypeEvaluator; 26 import android.animation.ValueAnimator; 27 import android.annotation.NonNull; 28 import android.content.BroadcastReceiver; 29 import android.content.ContentResolver; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.graphics.Rect; 34 import android.graphics.Region; 35 import android.os.AsyncTask; 36 import android.os.Handler; 37 import android.os.Looper; 38 import android.os.Message; 39 import android.provider.Settings; 40 import android.text.TextUtils; 41 import android.util.MathUtils; 42 import android.util.Property; 43 import android.util.Slog; 44 import android.view.MagnificationSpec; 45 import android.view.View; 46 import android.view.WindowManagerInternal; 47 import android.view.animation.DecelerateInterpolator; 48 49 import java.util.Locale; 50 51 /** 52 * This class is used to control and query the state of display magnification 53 * from the accessibility manager and related classes. It is responsible for 54 * holding the current state of magnification and animation, and it handles 55 * communication between the accessibility manager and window manager. 56 */ 57 class MagnificationController { 58 private static final String LOG_TAG = "MagnificationController"; 59 60 private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false; 61 62 private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1; 63 64 private static final int INVALID_ID = -1; 65 66 private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f; 67 68 private static final float MIN_SCALE = 1.0f; 69 private static final float MAX_SCALE = 5.0f; 70 71 /** 72 * The minimum scaling factor that can be persisted to secure settings. 73 * This must be > 1.0 to ensure that magnification is actually set to an 74 * enabled state when the scaling factor is restored from settings. 75 */ 76 private static final float MIN_PERSISTED_SCALE = 2.0f; 77 78 private final Object mLock; 79 80 /** 81 * The current magnification spec. If an animation is running, this 82 * reflects the end state. 83 */ 84 private final MagnificationSpec mCurrentMagnificationSpec = MagnificationSpec.obtain(); 85 86 private final Region mMagnificationRegion = Region.obtain(); 87 private final Rect mMagnificationBounds = new Rect(); 88 89 private final Rect mTempRect = new Rect(); 90 private final Rect mTempRect1 = new Rect(); 91 92 private final AccessibilityManagerService mAms; 93 private final ContentResolver mContentResolver; 94 95 private final ScreenStateObserver mScreenStateObserver; 96 private final WindowStateObserver mWindowStateObserver; 97 98 private final SpecAnimationBridge mSpecAnimationBridge; 99 100 private int mUserId; 101 102 private int mIdOfLastServiceToMagnify = INVALID_ID; 103 104 // Flag indicating that we are registered with window manager. 105 private boolean mRegistered; 106 107 private boolean mUnregisterPending; 108 109 public MagnificationController(Context context, AccessibilityManagerService ams, Object lock) { 110 mAms = ams; 111 mContentResolver = context.getContentResolver(); 112 mScreenStateObserver = new ScreenStateObserver(context, this); 113 mWindowStateObserver = new WindowStateObserver(context, this); 114 mLock = lock; 115 mSpecAnimationBridge = new SpecAnimationBridge(context, mLock); 116 } 117 118 /** 119 * Start tracking the magnification region for services that control magnification and the 120 * magnification gesture handler. 121 * 122 * This tracking imposes a cost on the system, so we avoid tracking this data 123 * unless it's required. 124 */ 125 public void register() { 126 synchronized (mLock) { 127 if (!mRegistered) { 128 mScreenStateObserver.register(); 129 mWindowStateObserver.register(); 130 mSpecAnimationBridge.setEnabled(true); 131 // Obtain initial state. 132 mWindowStateObserver.getMagnificationRegion(mMagnificationRegion); 133 mMagnificationRegion.getBounds(mMagnificationBounds); 134 mRegistered = true; 135 } 136 } 137 } 138 139 /** 140 * Stop requiring tracking the magnification region. We may remain registered while we 141 * reset magnification. 142 */ 143 public void unregister() { 144 synchronized (mLock) { 145 if (!isMagnifying()) { 146 unregisterInternalLocked(); 147 } else { 148 mUnregisterPending = true; 149 resetLocked(true); 150 } 151 } 152 } 153 154 /** 155 * Check if we are registered. Note that we may be planning to unregister at any moment. 156 * 157 * @return {@code true} if the controller is registered. {@code false} otherwise. 158 */ 159 public boolean isRegisteredLocked() { 160 return mRegistered; 161 } 162 163 private void unregisterInternalLocked() { 164 if (mRegistered) { 165 mSpecAnimationBridge.setEnabled(false); 166 mScreenStateObserver.unregister(); 167 mWindowStateObserver.unregister(); 168 mMagnificationRegion.setEmpty(); 169 mRegistered = false; 170 } 171 mUnregisterPending = false; 172 } 173 174 /** 175 * @return {@code true} if magnification is active, e.g. the scale 176 * is > 1, {@code false} otherwise 177 */ 178 public boolean isMagnifying() { 179 return mCurrentMagnificationSpec.scale > 1.0f; 180 } 181 182 /** 183 * Update our copy of the current magnification region 184 * 185 * @param magnified the magnified region 186 * @param updateSpec {@code true} to update the scale and center based on 187 * the region bounds, {@code false} to leave them as-is 188 */ 189 private void onMagnificationRegionChanged(Region magnified, boolean updateSpec) { 190 synchronized (mLock) { 191 if (!mRegistered) { 192 // Don't update if we've unregistered 193 return; 194 } 195 boolean magnificationChanged = false; 196 boolean boundsChanged = false; 197 198 if (!mMagnificationRegion.equals(magnified)) { 199 mMagnificationRegion.set(magnified); 200 mMagnificationRegion.getBounds(mMagnificationBounds); 201 boundsChanged = true; 202 } 203 if (updateSpec) { 204 final MagnificationSpec sentSpec = mSpecAnimationBridge.mSentMagnificationSpec; 205 final float scale = sentSpec.scale; 206 final float offsetX = sentSpec.offsetX; 207 final float offsetY = sentSpec.offsetY; 208 209 // Compute the new center and update spec as needed. 210 final float centerX = (mMagnificationBounds.width() / 2.0f 211 + mMagnificationBounds.left - offsetX) / scale; 212 final float centerY = (mMagnificationBounds.height() / 2.0f 213 + mMagnificationBounds.top - offsetY) / scale; 214 magnificationChanged = setScaleAndCenterLocked( 215 scale, centerX, centerY, false, INVALID_ID); 216 } 217 218 // If magnification changed we already notified for the change. 219 if (boundsChanged && updateSpec && !magnificationChanged) { 220 onMagnificationChangedLocked(); 221 } 222 } 223 } 224 225 /** 226 * Returns whether the magnification region contains the specified 227 * screen-relative coordinates. 228 * 229 * @param x the screen-relative X coordinate to check 230 * @param y the screen-relative Y coordinate to check 231 * @return {@code true} if the coordinate is contained within the 232 * magnified region, or {@code false} otherwise 233 */ 234 public boolean magnificationRegionContains(float x, float y) { 235 synchronized (mLock) { 236 return mMagnificationRegion.contains((int) x, (int) y); 237 } 238 } 239 240 /** 241 * Populates the specified rect with the screen-relative bounds of the 242 * magnification region. If magnification is not enabled, the returned 243 * bounds will be empty. 244 * 245 * @param outBounds rect to populate with the bounds of the magnified 246 * region 247 */ 248 public void getMagnificationBounds(@NonNull Rect outBounds) { 249 synchronized (mLock) { 250 outBounds.set(mMagnificationBounds); 251 } 252 } 253 254 /** 255 * Populates the specified region with the screen-relative magnification 256 * region. If magnification is not enabled, then the returned region 257 * will be empty. 258 * 259 * @param outRegion the region to populate 260 */ 261 public void getMagnificationRegion(@NonNull Region outRegion) { 262 synchronized (mLock) { 263 outRegion.set(mMagnificationRegion); 264 } 265 } 266 267 /** 268 * Returns the magnification scale. If an animation is in progress, 269 * this reflects the end state of the animation. 270 * 271 * @return the scale 272 */ 273 public float getScale() { 274 return mCurrentMagnificationSpec.scale; 275 } 276 277 /** 278 * Returns the X offset of the magnification viewport. If an animation 279 * is in progress, this reflects the end state of the animation. 280 * 281 * @return the X offset 282 */ 283 public float getOffsetX() { 284 return mCurrentMagnificationSpec.offsetX; 285 } 286 287 288 /** 289 * Returns the screen-relative X coordinate of the center of the 290 * magnification viewport. 291 * 292 * @return the X coordinate 293 */ 294 public float getCenterX() { 295 synchronized (mLock) { 296 return (mMagnificationBounds.width() / 2.0f 297 + mMagnificationBounds.left - getOffsetX()) / getScale(); 298 } 299 } 300 301 /** 302 * Returns the Y offset of the magnification viewport. If an animation 303 * is in progress, this reflects the end state of the animation. 304 * 305 * @return the Y offset 306 */ 307 public float getOffsetY() { 308 return mCurrentMagnificationSpec.offsetY; 309 } 310 311 /** 312 * Returns the screen-relative Y coordinate of the center of the 313 * magnification viewport. 314 * 315 * @return the Y coordinate 316 */ 317 public float getCenterY() { 318 synchronized (mLock) { 319 return (mMagnificationBounds.height() / 2.0f 320 + mMagnificationBounds.top - getOffsetY()) / getScale(); 321 } 322 } 323 324 /** 325 * Returns the scale currently used by the window manager. If an 326 * animation is in progress, this reflects the current state of the 327 * animation. 328 * 329 * @return the scale currently used by the window manager 330 */ 331 public float getSentScale() { 332 return mSpecAnimationBridge.mSentMagnificationSpec.scale; 333 } 334 335 /** 336 * Returns the X offset currently used by the window manager. If an 337 * animation is in progress, this reflects the current state of the 338 * animation. 339 * 340 * @return the X offset currently used by the window manager 341 */ 342 public float getSentOffsetX() { 343 return mSpecAnimationBridge.mSentMagnificationSpec.offsetX; 344 } 345 346 /** 347 * Returns the Y offset currently used by the window manager. If an 348 * animation is in progress, this reflects the current state of the 349 * animation. 350 * 351 * @return the Y offset currently used by the window manager 352 */ 353 public float getSentOffsetY() { 354 return mSpecAnimationBridge.mSentMagnificationSpec.offsetY; 355 } 356 357 /** 358 * Resets the magnification scale and center, optionally animating the 359 * transition. 360 * 361 * @param animate {@code true} to animate the transition, {@code false} 362 * to transition immediately 363 * @return {@code true} if the magnification spec changed, {@code false} if 364 * the spec did not change 365 */ 366 public boolean reset(boolean animate) { 367 synchronized (mLock) { 368 return resetLocked(animate); 369 } 370 } 371 372 private boolean resetLocked(boolean animate) { 373 if (!mRegistered) { 374 return false; 375 } 376 final MagnificationSpec spec = mCurrentMagnificationSpec; 377 final boolean changed = !spec.isNop(); 378 if (changed) { 379 spec.clear(); 380 onMagnificationChangedLocked(); 381 } 382 mIdOfLastServiceToMagnify = INVALID_ID; 383 mSpecAnimationBridge.updateSentSpec(spec, animate); 384 return changed; 385 } 386 387 /** 388 * Scales the magnified region around the specified pivot point, 389 * optionally animating the transition. If animation is disabled, the 390 * transition is immediate. 391 * 392 * @param scale the target scale, must be >= 1 393 * @param pivotX the screen-relative X coordinate around which to scale 394 * @param pivotY the screen-relative Y coordinate around which to scale 395 * @param animate {@code true} to animate the transition, {@code false} 396 * to transition immediately 397 * @param id the ID of the service requesting the change 398 * @return {@code true} if the magnification spec changed, {@code false} if 399 * the spec did not change 400 */ 401 public boolean setScale(float scale, float pivotX, float pivotY, boolean animate, int id) { 402 synchronized (mLock) { 403 if (!mRegistered) { 404 return false; 405 } 406 // Constrain scale immediately for use in the pivot calculations. 407 scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE); 408 409 final Rect viewport = mTempRect; 410 mMagnificationRegion.getBounds(viewport); 411 final MagnificationSpec spec = mCurrentMagnificationSpec; 412 final float oldScale = spec.scale; 413 final float oldCenterX = (viewport.width() / 2.0f - spec.offsetX) / oldScale; 414 final float oldCenterY = (viewport.height() / 2.0f - spec.offsetY) / oldScale; 415 final float normPivotX = (pivotX - spec.offsetX) / oldScale; 416 final float normPivotY = (pivotY - spec.offsetY) / oldScale; 417 final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale); 418 final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale); 419 final float centerX = normPivotX + offsetX; 420 final float centerY = normPivotY + offsetY; 421 mIdOfLastServiceToMagnify = id; 422 return setScaleAndCenterLocked(scale, centerX, centerY, animate, id); 423 } 424 } 425 426 /** 427 * Sets the center of the magnified region, optionally animating the 428 * transition. If animation is disabled, the transition is immediate. 429 * 430 * @param centerX the screen-relative X coordinate around which to 431 * center 432 * @param centerY the screen-relative Y coordinate around which to 433 * center 434 * @param animate {@code true} to animate the transition, {@code false} 435 * to transition immediately 436 * @param id the ID of the service requesting the change 437 * @return {@code true} if the magnification spec changed, {@code false} if 438 * the spec did not change 439 */ 440 public boolean setCenter(float centerX, float centerY, boolean animate, int id) { 441 synchronized (mLock) { 442 if (!mRegistered) { 443 return false; 444 } 445 return setScaleAndCenterLocked(Float.NaN, centerX, centerY, animate, id); 446 } 447 } 448 449 /** 450 * Sets the scale and center of the magnified region, optionally 451 * animating the transition. If animation is disabled, the transition 452 * is immediate. 453 * 454 * @param scale the target scale, or {@link Float#NaN} to leave unchanged 455 * @param centerX the screen-relative X coordinate around which to 456 * center and scale, or {@link Float#NaN} to leave unchanged 457 * @param centerY the screen-relative Y coordinate around which to 458 * center and scale, or {@link Float#NaN} to leave unchanged 459 * @param animate {@code true} to animate the transition, {@code false} 460 * to transition immediately 461 * @param id the ID of the service requesting the change 462 * @return {@code true} if the magnification spec changed, {@code false} if 463 * the spec did not change 464 */ 465 public boolean setScaleAndCenter( 466 float scale, float centerX, float centerY, boolean animate, int id) { 467 synchronized (mLock) { 468 if (!mRegistered) { 469 return false; 470 } 471 return setScaleAndCenterLocked(scale, centerX, centerY, animate, id); 472 } 473 } 474 475 private boolean setScaleAndCenterLocked(float scale, float centerX, float centerY, 476 boolean animate, int id) { 477 final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY); 478 mSpecAnimationBridge.updateSentSpec(mCurrentMagnificationSpec, animate); 479 if (isMagnifying() && (id != INVALID_ID)) { 480 mIdOfLastServiceToMagnify = id; 481 } 482 return changed; 483 } 484 485 /** 486 * Offsets the center of the magnified region. 487 * 488 * @param offsetX the amount in pixels to offset the X center 489 * @param offsetY the amount in pixels to offset the Y center 490 * @param id the ID of the service requesting the change 491 */ 492 public void offsetMagnifiedRegionCenter(float offsetX, float offsetY, int id) { 493 synchronized (mLock) { 494 if (!mRegistered) { 495 return; 496 } 497 498 final MagnificationSpec currSpec = mCurrentMagnificationSpec; 499 final float nonNormOffsetX = currSpec.offsetX - offsetX; 500 currSpec.offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0); 501 final float nonNormOffsetY = currSpec.offsetY - offsetY; 502 currSpec.offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0); 503 if (id != INVALID_ID) { 504 mIdOfLastServiceToMagnify = id; 505 } 506 mSpecAnimationBridge.updateSentSpec(currSpec, false); 507 } 508 } 509 510 /** 511 * Get the ID of the last service that changed the magnification spec. 512 * 513 * @return The id 514 */ 515 public int getIdOfLastServiceToMagnify() { 516 return mIdOfLastServiceToMagnify; 517 } 518 519 private void onMagnificationChangedLocked() { 520 mAms.onMagnificationStateChanged(); 521 mAms.notifyMagnificationChanged(mMagnificationRegion, 522 getScale(), getCenterX(), getCenterY()); 523 if (mUnregisterPending && !isMagnifying()) { 524 unregisterInternalLocked(); 525 } 526 } 527 528 /** 529 * Persists the current magnification scale to the current user's settings. 530 */ 531 public void persistScale() { 532 final float scale = mCurrentMagnificationSpec.scale; 533 final int userId = mUserId; 534 535 new AsyncTask<Void, Void, Void>() { 536 @Override 537 protected Void doInBackground(Void... params) { 538 Settings.Secure.putFloatForUser(mContentResolver, 539 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, userId); 540 return null; 541 } 542 }.execute(); 543 } 544 545 /** 546 * Retrieves a previously persisted magnification scale from the current 547 * user's settings. 548 * 549 * @return the previously persisted magnification scale, or the default 550 * scale if none is available 551 */ 552 public float getPersistedScale() { 553 return Settings.Secure.getFloatForUser(mContentResolver, 554 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 555 DEFAULT_MAGNIFICATION_SCALE, mUserId); 556 } 557 558 /** 559 * Updates the current magnification spec. 560 * 561 * @param scale the magnification scale 562 * @param centerX the unscaled, screen-relative X coordinate of the center 563 * of the viewport, or {@link Float#NaN} to leave unchanged 564 * @param centerY the unscaled, screen-relative Y coordinate of the center 565 * of the viewport, or {@link Float#NaN} to leave unchanged 566 * @return {@code true} if the magnification spec changed or {@code false} 567 * otherwise 568 */ 569 private boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY) { 570 // Handle defaults. 571 if (Float.isNaN(centerX)) { 572 centerX = getCenterX(); 573 } 574 if (Float.isNaN(centerY)) { 575 centerY = getCenterY(); 576 } 577 if (Float.isNaN(scale)) { 578 scale = getScale(); 579 } 580 581 // Ensure requested center is within the magnification region. 582 if (!magnificationRegionContains(centerX, centerY)) { 583 return false; 584 } 585 586 // Compute changes. 587 final MagnificationSpec currSpec = mCurrentMagnificationSpec; 588 boolean changed = false; 589 590 final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE); 591 if (Float.compare(currSpec.scale, normScale) != 0) { 592 currSpec.scale = normScale; 593 changed = true; 594 } 595 596 final float nonNormOffsetX = mMagnificationBounds.width() / 2.0f 597 + mMagnificationBounds.left - centerX * scale; 598 final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0); 599 if (Float.compare(currSpec.offsetX, offsetX) != 0) { 600 currSpec.offsetX = offsetX; 601 changed = true; 602 } 603 604 final float nonNormOffsetY = mMagnificationBounds.height() / 2.0f 605 + mMagnificationBounds.top - centerY * scale; 606 final float offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0); 607 if (Float.compare(currSpec.offsetY, offsetY) != 0) { 608 currSpec.offsetY = offsetY; 609 changed = true; 610 } 611 612 if (changed) { 613 onMagnificationChangedLocked(); 614 } 615 616 return changed; 617 } 618 619 private float getMinOffsetXLocked() { 620 final float viewportWidth = mMagnificationBounds.width(); 621 return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale; 622 } 623 624 private float getMinOffsetYLocked() { 625 final float viewportHeight = mMagnificationBounds.height(); 626 return viewportHeight - viewportHeight * mCurrentMagnificationSpec.scale; 627 } 628 629 /** 630 * Sets the currently active user ID. 631 * 632 * @param userId the currently active user ID 633 */ 634 public void setUserId(int userId) { 635 if (mUserId != userId) { 636 mUserId = userId; 637 638 synchronized (mLock) { 639 if (isMagnifying()) { 640 reset(false); 641 } 642 } 643 } 644 } 645 646 private boolean isScreenMagnificationAutoUpdateEnabled() { 647 return (Settings.Secure.getInt(mContentResolver, 648 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE, 649 DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1); 650 } 651 652 /** 653 * Resets magnification if magnification and auto-update are both enabled. 654 * 655 * @param animate whether the animate the transition 656 * @return {@code true} if magnification was reset to the disabled state, 657 * {@code false} if magnification is still active 658 */ 659 boolean resetIfNeeded(boolean animate) { 660 synchronized (mLock) { 661 if (isMagnifying() && isScreenMagnificationAutoUpdateEnabled()) { 662 reset(animate); 663 return true; 664 } 665 return false; 666 } 667 } 668 669 private void getMagnifiedFrameInContentCoordsLocked(Rect outFrame) { 670 final float scale = getSentScale(); 671 final float offsetX = getSentOffsetX(); 672 final float offsetY = getSentOffsetY(); 673 getMagnificationBounds(outFrame); 674 outFrame.offset((int) -offsetX, (int) -offsetY); 675 outFrame.scale(1.0f / scale); 676 } 677 678 private void requestRectangleOnScreen(int left, int top, int right, int bottom) { 679 synchronized (mLock) { 680 final Rect magnifiedFrame = mTempRect; 681 getMagnificationBounds(magnifiedFrame); 682 if (!magnifiedFrame.intersects(left, top, right, bottom)) { 683 return; 684 } 685 686 final Rect magnifFrameInScreenCoords = mTempRect1; 687 getMagnifiedFrameInContentCoordsLocked(magnifFrameInScreenCoords); 688 689 final float scrollX; 690 final float scrollY; 691 if (right - left > magnifFrameInScreenCoords.width()) { 692 final int direction = TextUtils 693 .getLayoutDirectionFromLocale(Locale.getDefault()); 694 if (direction == View.LAYOUT_DIRECTION_LTR) { 695 scrollX = left - magnifFrameInScreenCoords.left; 696 } else { 697 scrollX = right - magnifFrameInScreenCoords.right; 698 } 699 } else if (left < magnifFrameInScreenCoords.left) { 700 scrollX = left - magnifFrameInScreenCoords.left; 701 } else if (right > magnifFrameInScreenCoords.right) { 702 scrollX = right - magnifFrameInScreenCoords.right; 703 } else { 704 scrollX = 0; 705 } 706 707 if (bottom - top > magnifFrameInScreenCoords.height()) { 708 scrollY = top - magnifFrameInScreenCoords.top; 709 } else if (top < magnifFrameInScreenCoords.top) { 710 scrollY = top - magnifFrameInScreenCoords.top; 711 } else if (bottom > magnifFrameInScreenCoords.bottom) { 712 scrollY = bottom - magnifFrameInScreenCoords.bottom; 713 } else { 714 scrollY = 0; 715 } 716 717 final float scale = getScale(); 718 offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale, INVALID_ID); 719 } 720 } 721 722 /** 723 * Class responsible for animating spec on the main thread and sending spec 724 * updates to the window manager. 725 */ 726 private static class SpecAnimationBridge { 727 private static final int ACTION_UPDATE_SPEC = 1; 728 729 private final Handler mHandler; 730 private final WindowManagerInternal mWindowManager; 731 732 /** 733 * The magnification spec that was sent to the window manager. This should 734 * only be accessed with the lock held. 735 */ 736 private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain(); 737 738 /** 739 * The animator that updates the sent spec. This should only be accessed 740 * and modified on the main (e.g. animation) thread. 741 */ 742 private final ValueAnimator mTransformationAnimator; 743 744 private final long mMainThreadId; 745 private final Object mLock; 746 747 @GuardedBy("mLock") 748 private boolean mEnabled = false; 749 750 private SpecAnimationBridge(Context context, Object lock) { 751 mLock = lock; 752 final Looper mainLooper = context.getMainLooper(); 753 mMainThreadId = mainLooper.getThread().getId(); 754 755 mHandler = new UpdateHandler(context); 756 mWindowManager = LocalServices.getService(WindowManagerInternal.class); 757 758 final MagnificationSpecProperty property = new MagnificationSpecProperty(); 759 final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator(); 760 final long animationDuration = context.getResources().getInteger( 761 R.integer.config_longAnimTime); 762 mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator, 763 mSentMagnificationSpec); 764 mTransformationAnimator.setDuration(animationDuration); 765 mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f)); 766 } 767 768 /** 769 * Enabled means the bridge will accept input. When not enabled, the output of the animator 770 * will be ignored 771 */ 772 public void setEnabled(boolean enabled) { 773 synchronized (mLock) { 774 if (enabled != mEnabled) { 775 mEnabled = enabled; 776 if (!mEnabled) { 777 mSentMagnificationSpec.clear(); 778 mWindowManager.setMagnificationSpec(mSentMagnificationSpec); 779 } 780 } 781 } 782 } 783 784 public void updateSentSpec(MagnificationSpec spec, boolean animate) { 785 if (Thread.currentThread().getId() == mMainThreadId) { 786 // Already on the main thread, don't bother proxying. 787 updateSentSpecInternal(spec, animate); 788 } else { 789 mHandler.obtainMessage(ACTION_UPDATE_SPEC, 790 animate ? 1 : 0, 0, spec).sendToTarget(); 791 } 792 } 793 794 /** 795 * Updates the sent spec. 796 */ 797 private void updateSentSpecInternal(MagnificationSpec spec, boolean animate) { 798 if (mTransformationAnimator.isRunning()) { 799 mTransformationAnimator.cancel(); 800 } 801 802 // If the current and sent specs don't match, update the sent spec. 803 synchronized (mLock) { 804 final boolean changed = !mSentMagnificationSpec.equals(spec); 805 if (changed) { 806 if (animate) { 807 animateMagnificationSpecLocked(spec); 808 } else { 809 setMagnificationSpecLocked(spec); 810 } 811 } 812 } 813 } 814 815 private void animateMagnificationSpecLocked(MagnificationSpec toSpec) { 816 mTransformationAnimator.setObjectValues(mSentMagnificationSpec, toSpec); 817 mTransformationAnimator.start(); 818 } 819 820 private void setMagnificationSpecLocked(MagnificationSpec spec) { 821 if (mEnabled) { 822 if (DEBUG_SET_MAGNIFICATION_SPEC) { 823 Slog.i(LOG_TAG, "Sending: " + spec); 824 } 825 826 mSentMagnificationSpec.setTo(spec); 827 mWindowManager.setMagnificationSpec(spec); 828 } 829 } 830 831 private class UpdateHandler extends Handler { 832 public UpdateHandler(Context context) { 833 super(context.getMainLooper()); 834 } 835 836 @Override 837 public void handleMessage(Message msg) { 838 switch (msg.what) { 839 case ACTION_UPDATE_SPEC: 840 final boolean animate = msg.arg1 == 1; 841 final MagnificationSpec spec = (MagnificationSpec) msg.obj; 842 updateSentSpecInternal(spec, animate); 843 break; 844 } 845 } 846 } 847 848 private static class MagnificationSpecProperty 849 extends Property<SpecAnimationBridge, MagnificationSpec> { 850 public MagnificationSpecProperty() { 851 super(MagnificationSpec.class, "spec"); 852 } 853 854 @Override 855 public MagnificationSpec get(SpecAnimationBridge object) { 856 synchronized (object.mLock) { 857 return object.mSentMagnificationSpec; 858 } 859 } 860 861 @Override 862 public void set(SpecAnimationBridge object, MagnificationSpec value) { 863 synchronized (object.mLock) { 864 object.setMagnificationSpecLocked(value); 865 } 866 } 867 } 868 869 private static class MagnificationSpecEvaluator 870 implements TypeEvaluator<MagnificationSpec> { 871 private final MagnificationSpec mTempSpec = MagnificationSpec.obtain(); 872 873 @Override 874 public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec, 875 MagnificationSpec toSpec) { 876 final MagnificationSpec result = mTempSpec; 877 result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction; 878 result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction; 879 result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction; 880 return result; 881 } 882 } 883 } 884 885 private static class ScreenStateObserver extends BroadcastReceiver { 886 private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1; 887 888 private final Context mContext; 889 private final MagnificationController mController; 890 private final Handler mHandler; 891 892 public ScreenStateObserver(Context context, MagnificationController controller) { 893 mContext = context; 894 mController = controller; 895 mHandler = new StateChangeHandler(context); 896 } 897 898 public void register() { 899 mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF)); 900 } 901 902 public void unregister() { 903 mContext.unregisterReceiver(this); 904 } 905 906 @Override 907 public void onReceive(Context context, Intent intent) { 908 mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE, 909 intent.getAction()).sendToTarget(); 910 } 911 912 private void handleOnScreenStateChange() { 913 mController.resetIfNeeded(false); 914 } 915 916 private class StateChangeHandler extends Handler { 917 public StateChangeHandler(Context context) { 918 super(context.getMainLooper()); 919 } 920 921 @Override 922 public void handleMessage(Message message) { 923 switch (message.what) { 924 case MESSAGE_ON_SCREEN_STATE_CHANGE: 925 handleOnScreenStateChange(); 926 break; 927 } 928 } 929 } 930 } 931 932 /** 933 * This class handles the screen magnification when accessibility is enabled. 934 */ 935 private static class WindowStateObserver 936 implements WindowManagerInternal.MagnificationCallbacks { 937 private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1; 938 private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2; 939 private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3; 940 private static final int MESSAGE_ON_ROTATION_CHANGED = 4; 941 942 private final MagnificationController mController; 943 private final WindowManagerInternal mWindowManager; 944 private final Handler mHandler; 945 946 private boolean mSpecIsDirty; 947 948 public WindowStateObserver(Context context, MagnificationController controller) { 949 mController = controller; 950 mWindowManager = LocalServices.getService(WindowManagerInternal.class); 951 mHandler = new CallbackHandler(context); 952 } 953 954 public void register() { 955 mWindowManager.setMagnificationCallbacks(this); 956 } 957 958 public void unregister() { 959 mWindowManager.setMagnificationCallbacks(null); 960 } 961 962 @Override 963 public void onMagnificationRegionChanged(Region magnificationRegion) { 964 final SomeArgs args = SomeArgs.obtain(); 965 args.arg1 = Region.obtain(magnificationRegion); 966 mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget(); 967 } 968 969 private void handleOnMagnifiedBoundsChanged(Region magnificationRegion) { 970 mController.onMagnificationRegionChanged(magnificationRegion, mSpecIsDirty); 971 mSpecIsDirty = false; 972 } 973 974 @Override 975 public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) { 976 final SomeArgs args = SomeArgs.obtain(); 977 args.argi1 = left; 978 args.argi2 = top; 979 args.argi3 = right; 980 args.argi4 = bottom; 981 mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget(); 982 } 983 984 private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) { 985 mController.requestRectangleOnScreen(left, top, right, bottom); 986 } 987 988 @Override 989 public void onRotationChanged(int rotation) { 990 mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget(); 991 } 992 993 private void handleOnRotationChanged() { 994 // If there was a rotation and magnification is still enabled, 995 // we'll need to rewrite the spec to reflect the new screen 996 // configuration. Conveniently, we'll receive a callback from 997 // the window manager with updated bounds for the magnified 998 // region. 999 mSpecIsDirty = !mController.resetIfNeeded(true); 1000 } 1001 1002 @Override 1003 public void onUserContextChanged() { 1004 mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED); 1005 } 1006 1007 private void handleOnUserContextChanged() { 1008 mController.resetIfNeeded(true); 1009 } 1010 1011 /** 1012 * This method is used to get the magnification region in the tiny time slice between 1013 * registering the callbacks and handling the message. 1014 * TODO: Elimiante this extra path, perhaps by processing the message immediately 1015 * 1016 * @param outMagnificationRegion 1017 */ 1018 public void getMagnificationRegion(@NonNull Region outMagnificationRegion) { 1019 mWindowManager.getMagnificationRegion(outMagnificationRegion); 1020 } 1021 1022 private class CallbackHandler extends Handler { 1023 public CallbackHandler(Context context) { 1024 super(context.getMainLooper()); 1025 } 1026 1027 @Override 1028 public void handleMessage(Message message) { 1029 switch (message.what) { 1030 case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: { 1031 final SomeArgs args = (SomeArgs) message.obj; 1032 final Region magnifiedBounds = (Region) args.arg1; 1033 handleOnMagnifiedBoundsChanged(magnifiedBounds); 1034 magnifiedBounds.recycle(); 1035 } break; 1036 case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: { 1037 final SomeArgs args = (SomeArgs) message.obj; 1038 final int left = args.argi1; 1039 final int top = args.argi2; 1040 final int right = args.argi3; 1041 final int bottom = args.argi4; 1042 handleOnRectangleOnScreenRequested(left, top, right, bottom); 1043 args.recycle(); 1044 } break; 1045 case MESSAGE_ON_USER_CONTEXT_CHANGED: { 1046 handleOnUserContextChanged(); 1047 } break; 1048 case MESSAGE_ON_ROTATION_CHANGED: { 1049 handleOnRotationChanged(); 1050 } break; 1051 } 1052 } 1053 } 1054 } 1055 } 1056