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