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 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