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