1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.accessibility;
18
19import com.android.internal.R;
20import com.android.internal.annotations.GuardedBy;
21import com.android.internal.os.SomeArgs;
22import com.android.server.LocalServices;
23
24import android.animation.ObjectAnimator;
25import android.animation.TypeEvaluator;
26import android.animation.ValueAnimator;
27import android.annotation.NonNull;
28import android.content.BroadcastReceiver;
29import android.content.ContentResolver;
30import android.content.Context;
31import android.content.Intent;
32import android.content.IntentFilter;
33import android.graphics.Rect;
34import android.graphics.Region;
35import android.os.AsyncTask;
36import android.os.Handler;
37import android.os.Looper;
38import android.os.Message;
39import android.provider.Settings;
40import android.text.TextUtils;
41import android.util.MathUtils;
42import android.util.Property;
43import android.util.Slog;
44import android.view.MagnificationSpec;
45import android.view.View;
46import android.view.WindowManagerInternal;
47import android.view.animation.DecelerateInterpolator;
48
49import 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 */
57class 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