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