Home | History | Annotate | Download | only in accessibility
      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