• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.magnification;
18 
19 import android.animation.Animator;
20 import android.animation.ValueAnimator;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.graphics.Rect;
28 import android.graphics.Region;
29 import android.os.AsyncTask;
30 import android.os.Handler;
31 import android.os.Message;
32 import android.provider.Settings;
33 import android.text.TextUtils;
34 import android.util.MathUtils;
35 import android.util.Slog;
36 import android.util.SparseArray;
37 import android.view.Display;
38 import android.view.MagnificationSpec;
39 import android.view.View;
40 import android.view.accessibility.MagnificationAnimationCallback;
41 import android.view.animation.DecelerateInterpolator;
42 
43 import com.android.internal.R;
44 import com.android.internal.annotations.GuardedBy;
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.internal.util.function.pooled.PooledLambda;
47 import com.android.server.LocalServices;
48 import com.android.server.accessibility.AccessibilityManagerService;
49 import com.android.server.wm.WindowManagerInternal;
50 
51 import java.util.Locale;
52 
53 /**
54  * This class is used to control and query the state of display magnification
55  * from the accessibility manager and related classes. It is responsible for
56  * holding the current state of magnification and animation, and it handles
57  * communication between the accessibility manager and window manager.
58  *
59  * Magnification is limited to the range [MIN_SCALE, MAX_SCALE], and can only occur inside the
60  * magnification region. If a value is out of bounds, it will be adjusted to guarantee these
61  * constraints.
62  */
63 public class FullScreenMagnificationController {
64     private static final boolean DEBUG = false;
65     private static final String LOG_TAG = "FullScreenMagnificationController";
66 
67     private static final MagnificationAnimationCallback STUB_ANIMATION_CALLBACK = success -> {
68     };
69     public static final float MIN_SCALE = 1.0f;
70     public static final float MAX_SCALE = 8.0f;
71 
72     private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
73 
74     private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
75 
76     private final Object mLock;
77 
78     private final ControllerContext mControllerCtx;
79 
80     private final ScreenStateObserver mScreenStateObserver;
81 
82     private final MagnificationInfoChangedCallback mMagnificationInfoChangedCallback;
83 
84     private int mUserId;
85 
86     private final long mMainThreadId;
87 
88     /** List of display Magnification, mapping from displayId -> DisplayMagnification. */
89     @GuardedBy("mLock")
90     private final SparseArray<DisplayMagnification> mDisplays = new SparseArray<>(0);
91 
92     /**
93      * This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds
94      * magnification information per display.
95      */
96     private final class DisplayMagnification implements
97             WindowManagerInternal.MagnificationCallbacks {
98         /**
99          * The current magnification spec. If an animation is running, this
100          * reflects the end state.
101          */
102         private final MagnificationSpec mCurrentMagnificationSpec = new MagnificationSpec();
103 
104         private final Region mMagnificationRegion = Region.obtain();
105         private final Rect mMagnificationBounds = new Rect();
106 
107         private final Rect mTempRect = new Rect();
108         private final Rect mTempRect1 = new Rect();
109 
110         private final SpecAnimationBridge mSpecAnimationBridge;
111 
112         // Flag indicating that we are registered with window manager.
113         private boolean mRegistered;
114         private boolean mUnregisterPending;
115         private boolean mDeleteAfterUnregister;
116 
117         private boolean mForceShowMagnifiableBounds;
118 
119         private final int mDisplayId;
120 
121         private static final int INVALID_ID = -1;
122         private int mIdOfLastServiceToMagnify = INVALID_ID;
123         private boolean mMagnificationActivated = false;
124 
DisplayMagnification(int displayId)125         DisplayMagnification(int displayId) {
126             mDisplayId = displayId;
127             mSpecAnimationBridge = new SpecAnimationBridge(mControllerCtx, mLock, mDisplayId);
128         }
129 
130         /**
131          * Registers magnification callback and get current magnification region from
132          * window manager.
133          *
134          * @return true if callback registers successful.
135          */
136         @GuardedBy("mLock")
register()137         boolean register() {
138             mRegistered = mControllerCtx.getWindowManager().setMagnificationCallbacks(
139                     mDisplayId, this);
140             if (!mRegistered) {
141                 Slog.w(LOG_TAG, "set magnification callbacks fail, displayId:" + mDisplayId);
142                 return false;
143             }
144             mSpecAnimationBridge.setEnabled(true);
145             // Obtain initial state.
146             mControllerCtx.getWindowManager().getMagnificationRegion(
147                     mDisplayId, mMagnificationRegion);
148             mMagnificationRegion.getBounds(mMagnificationBounds);
149             return true;
150         }
151 
152         /**
153          * Unregisters magnification callback from window manager. Callbacks to
154          * {@link FullScreenMagnificationController#unregisterCallbackLocked(int, boolean)} after
155          * unregistered.
156          *
157          * @param delete true if this instance should be removed from the SparseArray in
158          *               FullScreenMagnificationController after unregistered, for example,
159          *               display removed.
160          */
161         @GuardedBy("mLock")
unregister(boolean delete)162         void unregister(boolean delete) {
163             if (mRegistered) {
164                 mSpecAnimationBridge.setEnabled(false);
165                 mControllerCtx.getWindowManager().setMagnificationCallbacks(
166                         mDisplayId, null);
167                 mMagnificationRegion.setEmpty();
168                 mRegistered = false;
169                 unregisterCallbackLocked(mDisplayId, delete);
170             }
171             mUnregisterPending = false;
172         }
173 
174         /**
175          * Reset magnification status with animation enabled. {@link #unregister(boolean)} will be
176          * called after animation finished.
177          *
178          * @param delete true if this instance should be removed from the SparseArray in
179          *               FullScreenMagnificationController after unregistered, for example,
180          *               display removed.
181          */
182         @GuardedBy("mLock")
unregisterPending(boolean delete)183         void unregisterPending(boolean delete) {
184             mDeleteAfterUnregister = delete;
185             mUnregisterPending = true;
186             reset(true);
187         }
188 
isRegistered()189         boolean isRegistered() {
190             return mRegistered;
191         }
192 
isMagnifying()193         boolean isMagnifying() {
194             return mCurrentMagnificationSpec.scale > 1.0f;
195         }
196 
getScale()197         float getScale() {
198             return mCurrentMagnificationSpec.scale;
199         }
200 
getOffsetX()201         float getOffsetX() {
202             return mCurrentMagnificationSpec.offsetX;
203         }
204 
getOffsetY()205         float getOffsetY() {
206             return mCurrentMagnificationSpec.offsetY;
207         }
208 
209         @GuardedBy("mLock")
getCenterX()210         float getCenterX() {
211             return (mMagnificationBounds.width() / 2.0f
212                     + mMagnificationBounds.left - getOffsetX()) / getScale();
213         }
214 
215         @GuardedBy("mLock")
getCenterY()216         float getCenterY() {
217             return (mMagnificationBounds.height() / 2.0f
218                     + mMagnificationBounds.top - getOffsetY()) / getScale();
219         }
220 
221         /**
222          * Returns the scale currently used by the window manager. If an
223          * animation is in progress, this reflects the current state of the
224          * animation.
225          *
226          * @return the scale currently used by the window manager
227          */
getSentScale()228         float getSentScale() {
229             return mSpecAnimationBridge.mSentMagnificationSpec.scale;
230         }
231 
232         /**
233          * Returns the X offset currently used by the window manager. If an
234          * animation is in progress, this reflects the current state of the
235          * animation.
236          *
237          * @return the X offset currently used by the window manager
238          */
getSentOffsetX()239         float getSentOffsetX() {
240             return mSpecAnimationBridge.mSentMagnificationSpec.offsetX;
241         }
242 
243         /**
244          * Returns the Y offset currently used by the window manager. If an
245          * animation is in progress, this reflects the current state of the
246          * animation.
247          *
248          * @return the Y offset currently used by the window manager
249          */
getSentOffsetY()250         float getSentOffsetY() {
251             return mSpecAnimationBridge.mSentMagnificationSpec.offsetY;
252         }
253 
254         @Override
onMagnificationRegionChanged(Region magnificationRegion)255         public void onMagnificationRegionChanged(Region magnificationRegion) {
256             final Message m = PooledLambda.obtainMessage(
257                     DisplayMagnification::updateMagnificationRegion, this,
258                     Region.obtain(magnificationRegion));
259             mControllerCtx.getHandler().sendMessage(m);
260         }
261 
262         @Override
onRectangleOnScreenRequested(int left, int top, int right, int bottom)263         public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
264             final Message m = PooledLambda.obtainMessage(
265                     DisplayMagnification::requestRectangleOnScreen, this,
266                     left, top, right, bottom);
267             mControllerCtx.getHandler().sendMessage(m);
268         }
269 
270         @Override
onRotationChanged(int rotation)271         public void onRotationChanged(int rotation) {
272             // Treat as context change and reset
273             final Message m = PooledLambda.obtainMessage(
274                     FullScreenMagnificationController::resetIfNeeded,
275                     FullScreenMagnificationController.this, mDisplayId, true);
276             mControllerCtx.getHandler().sendMessage(m);
277         }
278 
279         @Override
onUserContextChanged()280         public void onUserContextChanged() {
281             final Message m = PooledLambda.obtainMessage(
282                     FullScreenMagnificationController::resetIfNeeded,
283                     FullScreenMagnificationController.this, mDisplayId, true);
284             mControllerCtx.getHandler().sendMessage(m);
285         }
286 
287         @Override
onImeWindowVisibilityChanged(boolean shown)288         public void onImeWindowVisibilityChanged(boolean shown) {
289             final Message m = PooledLambda.obtainMessage(
290                     FullScreenMagnificationController::notifyImeWindowVisibilityChanged,
291                     FullScreenMagnificationController.this, shown);
292             mControllerCtx.getHandler().sendMessage(m);
293         }
294 
295         /**
296          * Update our copy of the current magnification region
297          *
298          * @param magnified the magnified region
299          */
updateMagnificationRegion(Region magnified)300         void updateMagnificationRegion(Region magnified) {
301             synchronized (mLock) {
302                 if (!mRegistered) {
303                     // Don't update if we've unregistered
304                     return;
305                 }
306                 if (!mMagnificationRegion.equals(magnified)) {
307                     mMagnificationRegion.set(magnified);
308                     mMagnificationRegion.getBounds(mMagnificationBounds);
309                     // It's possible that our magnification spec is invalid with the new bounds.
310                     // Adjust the current spec's offsets if necessary.
311                     if (updateCurrentSpecWithOffsetsLocked(
312                             mCurrentMagnificationSpec.offsetX, mCurrentMagnificationSpec.offsetY)) {
313                         sendSpecToAnimation(mCurrentMagnificationSpec, null);
314                     }
315                     onMagnificationChangedLocked();
316                 }
317                 magnified.recycle();
318             }
319         }
320 
sendSpecToAnimation(MagnificationSpec spec, MagnificationAnimationCallback animationCallback)321         void sendSpecToAnimation(MagnificationSpec spec,
322                 MagnificationAnimationCallback animationCallback) {
323             if (DEBUG) {
324                 Slog.i(LOG_TAG,
325                         "sendSpecToAnimation(spec = " + spec + ", animationCallback = "
326                                 + animationCallback + ")");
327             }
328             if (Thread.currentThread().getId() == mMainThreadId) {
329                 mSpecAnimationBridge.updateSentSpecMainThread(spec, animationCallback);
330             } else {
331                 final Message m = PooledLambda.obtainMessage(
332                         SpecAnimationBridge::updateSentSpecMainThread,
333                         mSpecAnimationBridge, spec, animationCallback);
334                 mControllerCtx.getHandler().sendMessage(m);
335             }
336 
337             final boolean lastMagnificationActivated = mMagnificationActivated;
338             mMagnificationActivated = spec.scale > 1.0f;
339             if (mMagnificationActivated != lastMagnificationActivated) {
340                 mMagnificationInfoChangedCallback.onFullScreenMagnificationActivationState(
341                         mMagnificationActivated);
342             }
343         }
344 
345         /**
346          * Get the ID of the last service that changed the magnification spec.
347          *
348          * @return The id
349          */
getIdOfLastServiceToMagnify()350         int getIdOfLastServiceToMagnify() {
351             return mIdOfLastServiceToMagnify;
352         }
353 
onMagnificationChangedLocked()354         void onMagnificationChangedLocked() {
355             mControllerCtx.getAms().notifyMagnificationChanged(mDisplayId, mMagnificationRegion,
356                     getScale(), getCenterX(), getCenterY());
357             if (mUnregisterPending && !isMagnifying()) {
358                 unregister(mDeleteAfterUnregister);
359             }
360         }
361 
362         @GuardedBy("mLock")
magnificationRegionContains(float x, float y)363         boolean magnificationRegionContains(float x, float y) {
364             return mMagnificationRegion.contains((int) x, (int) y);
365         }
366 
367         @GuardedBy("mLock")
getMagnificationBounds(@onNull Rect outBounds)368         void getMagnificationBounds(@NonNull Rect outBounds) {
369             outBounds.set(mMagnificationBounds);
370         }
371 
372         @GuardedBy("mLock")
getMagnificationRegion(@onNull Region outRegion)373         void getMagnificationRegion(@NonNull Region outRegion) {
374             outRegion.set(mMagnificationRegion);
375         }
376 
requestRectangleOnScreen(int left, int top, int right, int bottom)377         void requestRectangleOnScreen(int left, int top, int right, int bottom) {
378             synchronized (mLock) {
379                 final Rect magnifiedFrame = mTempRect;
380                 getMagnificationBounds(magnifiedFrame);
381                 if (!magnifiedFrame.intersects(left, top, right, bottom)) {
382                     return;
383                 }
384 
385                 final Rect magnifFrameInScreenCoords = mTempRect1;
386                 getMagnifiedFrameInContentCoordsLocked(magnifFrameInScreenCoords);
387 
388                 final float scrollX;
389                 final float scrollY;
390                 if (right - left > magnifFrameInScreenCoords.width()) {
391                     final int direction = TextUtils
392                             .getLayoutDirectionFromLocale(Locale.getDefault());
393                     if (direction == View.LAYOUT_DIRECTION_LTR) {
394                         scrollX = left - magnifFrameInScreenCoords.left;
395                     } else {
396                         scrollX = right - magnifFrameInScreenCoords.right;
397                     }
398                 } else if (left < magnifFrameInScreenCoords.left) {
399                     scrollX = left - magnifFrameInScreenCoords.left;
400                 } else if (right > magnifFrameInScreenCoords.right) {
401                     scrollX = right - magnifFrameInScreenCoords.right;
402                 } else {
403                     scrollX = 0;
404                 }
405 
406                 if (bottom - top > magnifFrameInScreenCoords.height()) {
407                     scrollY = top - magnifFrameInScreenCoords.top;
408                 } else if (top < magnifFrameInScreenCoords.top) {
409                     scrollY = top - magnifFrameInScreenCoords.top;
410                 } else if (bottom > magnifFrameInScreenCoords.bottom) {
411                     scrollY = bottom - magnifFrameInScreenCoords.bottom;
412                 } else {
413                     scrollY = 0;
414                 }
415 
416                 final float scale = getScale();
417                 offsetMagnifiedRegion(scrollX * scale, scrollY * scale, INVALID_ID);
418             }
419         }
420 
getMagnifiedFrameInContentCoordsLocked(Rect outFrame)421         void getMagnifiedFrameInContentCoordsLocked(Rect outFrame) {
422             final float scale = getSentScale();
423             final float offsetX = getSentOffsetX();
424             final float offsetY = getSentOffsetY();
425             getMagnificationBounds(outFrame);
426             outFrame.offset((int) -offsetX, (int) -offsetY);
427             outFrame.scale(1.0f / scale);
428         }
429 
430         @GuardedBy("mLock")
setForceShowMagnifiableBounds(boolean show)431         void setForceShowMagnifiableBounds(boolean show) {
432             if (mRegistered) {
433                 mForceShowMagnifiableBounds = show;
434                 mControllerCtx.getWindowManager().setForceShowMagnifiableBounds(
435                         mDisplayId, show);
436             }
437         }
438 
439         @GuardedBy("mLock")
isForceShowMagnifiableBounds()440         boolean isForceShowMagnifiableBounds() {
441             return mRegistered && mForceShowMagnifiableBounds;
442         }
443 
444         @GuardedBy("mLock")
reset(boolean animate)445         boolean reset(boolean animate) {
446             return reset(transformToStubCallback(animate));
447         }
448 
449         @GuardedBy("mLock")
reset(MagnificationAnimationCallback animationCallback)450         boolean reset(MagnificationAnimationCallback animationCallback) {
451             if (!mRegistered) {
452                 return false;
453             }
454             final MagnificationSpec spec = mCurrentMagnificationSpec;
455             final boolean changed = !spec.isNop();
456             if (changed) {
457                 spec.clear();
458                 onMagnificationChangedLocked();
459             }
460             mIdOfLastServiceToMagnify = INVALID_ID;
461             mForceShowMagnifiableBounds = false;
462             sendSpecToAnimation(spec, animationCallback);
463             return changed;
464         }
465 
466         @GuardedBy("mLock")
setScale(float scale, float pivotX, float pivotY, boolean animate, int id)467         boolean setScale(float scale, float pivotX, float pivotY,
468                 boolean animate, int id) {
469             if (!mRegistered) {
470                 return false;
471             }
472             // Constrain scale immediately for use in the pivot calculations.
473             scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
474 
475             final Rect viewport = mTempRect;
476             mMagnificationRegion.getBounds(viewport);
477             final MagnificationSpec spec = mCurrentMagnificationSpec;
478             final float oldScale = spec.scale;
479             final float oldCenterX =
480                     (viewport.width() / 2.0f - spec.offsetX + viewport.left) / oldScale;
481             final float oldCenterY =
482                     (viewport.height() / 2.0f - spec.offsetY + viewport.top) / oldScale;
483             final float normPivotX = (pivotX - spec.offsetX) / oldScale;
484             final float normPivotY = (pivotY - spec.offsetY) / oldScale;
485             final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
486             final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
487             final float centerX = normPivotX + offsetX;
488             final float centerY = normPivotY + offsetY;
489             mIdOfLastServiceToMagnify = id;
490             return setScaleAndCenter(scale, centerX, centerY, transformToStubCallback(animate), id);
491         }
492 
493         @GuardedBy("mLock")
setScaleAndCenter(float scale, float centerX, float centerY, MagnificationAnimationCallback animationCallback, int id)494         boolean setScaleAndCenter(float scale, float centerX, float centerY,
495                 MagnificationAnimationCallback animationCallback, int id) {
496             if (!mRegistered) {
497                 return false;
498             }
499             if (DEBUG) {
500                 Slog.i(LOG_TAG,
501                         "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
502                                 + ", centerY = " + centerY + ", endCallback = "
503                                 + animationCallback + ", id = " + id + ")");
504             }
505             final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
506             sendSpecToAnimation(mCurrentMagnificationSpec, animationCallback);
507             if (isMagnifying() && (id != INVALID_ID)) {
508                 mIdOfLastServiceToMagnify = id;
509                 mMagnificationInfoChangedCallback.onRequestMagnificationSpec(mDisplayId,
510                         mIdOfLastServiceToMagnify);
511             }
512             return changed;
513         }
514 
515         /**
516          * Updates the current magnification spec.
517          *
518          * @param scale the magnification scale
519          * @param centerX the unscaled, screen-relative X coordinate of the center
520          *                of the viewport, or {@link Float#NaN} to leave unchanged
521          * @param centerY the unscaled, screen-relative Y coordinate of the center
522          *                of the viewport, or {@link Float#NaN} to leave unchanged
523          * @return {@code true} if the magnification spec changed or {@code false}
524          *         otherwise
525          */
updateMagnificationSpecLocked(float scale, float centerX, float centerY)526         boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY) {
527             // Handle defaults.
528             if (Float.isNaN(centerX)) {
529                 centerX = getCenterX();
530             }
531             if (Float.isNaN(centerY)) {
532                 centerY = getCenterY();
533             }
534             if (Float.isNaN(scale)) {
535                 scale = getScale();
536             }
537 
538             // Compute changes.
539             boolean changed = false;
540 
541             final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
542             if (Float.compare(mCurrentMagnificationSpec.scale, normScale) != 0) {
543                 mCurrentMagnificationSpec.scale = normScale;
544                 changed = true;
545             }
546 
547             final float nonNormOffsetX = mMagnificationBounds.width() / 2.0f
548                     + mMagnificationBounds.left - centerX * normScale;
549             final float nonNormOffsetY = mMagnificationBounds.height() / 2.0f
550                     + mMagnificationBounds.top - centerY * normScale;
551             changed |= updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY);
552 
553             if (changed) {
554                 onMagnificationChangedLocked();
555             }
556 
557             return changed;
558         }
559 
560         @GuardedBy("mLock")
offsetMagnifiedRegion(float offsetX, float offsetY, int id)561         void offsetMagnifiedRegion(float offsetX, float offsetY, int id) {
562             if (!mRegistered) {
563                 return;
564             }
565 
566             final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
567             final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
568             if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
569                 onMagnificationChangedLocked();
570             }
571             if (id != INVALID_ID) {
572                 mIdOfLastServiceToMagnify = id;
573             }
574             sendSpecToAnimation(mCurrentMagnificationSpec, null);
575         }
576 
updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY)577         boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
578             if (DEBUG) {
579                 Slog.i(LOG_TAG,
580                         "updateCurrentSpecWithOffsetsLocked(nonNormOffsetX = " + nonNormOffsetX
581                                 + ", nonNormOffsetY = " + nonNormOffsetY + ")");
582             }
583             boolean changed = false;
584             final float offsetX = MathUtils.constrain(
585                     nonNormOffsetX, getMinOffsetXLocked(), getMaxOffsetXLocked());
586             if (Float.compare(mCurrentMagnificationSpec.offsetX, offsetX) != 0) {
587                 mCurrentMagnificationSpec.offsetX = offsetX;
588                 changed = true;
589             }
590             final float offsetY = MathUtils.constrain(
591                     nonNormOffsetY, getMinOffsetYLocked(), getMaxOffsetYLocked());
592             if (Float.compare(mCurrentMagnificationSpec.offsetY, offsetY) != 0) {
593                 mCurrentMagnificationSpec.offsetY = offsetY;
594                 changed = true;
595             }
596             return changed;
597         }
598 
getMinOffsetXLocked()599         float getMinOffsetXLocked() {
600             final float viewportWidth = mMagnificationBounds.width();
601             final float viewportLeft = mMagnificationBounds.left;
602             return (viewportLeft + viewportWidth)
603                     - (viewportLeft + viewportWidth) * mCurrentMagnificationSpec.scale;
604         }
605 
getMaxOffsetXLocked()606         float getMaxOffsetXLocked() {
607             return mMagnificationBounds.left
608                     - mMagnificationBounds.left * mCurrentMagnificationSpec.scale;
609         }
610 
getMinOffsetYLocked()611         float getMinOffsetYLocked() {
612             final float viewportHeight = mMagnificationBounds.height();
613             final float viewportTop = mMagnificationBounds.top;
614             return (viewportTop + viewportHeight)
615                     - (viewportTop + viewportHeight) * mCurrentMagnificationSpec.scale;
616         }
617 
getMaxOffsetYLocked()618         float getMaxOffsetYLocked() {
619             return mMagnificationBounds.top
620                     - mMagnificationBounds.top * mCurrentMagnificationSpec.scale;
621         }
622 
623         @Override
toString()624         public String toString() {
625             return "DisplayMagnification["
626                     + "mCurrentMagnificationSpec=" + mCurrentMagnificationSpec
627                     + ", mMagnificationRegion=" + mMagnificationRegion
628                     + ", mMagnificationBounds=" + mMagnificationBounds
629                     + ", mDisplayId=" + mDisplayId
630                     + ", mIdOfLastServiceToMagnify=" + mIdOfLastServiceToMagnify
631                     + ", mRegistered=" + mRegistered
632                     + ", mUnregisterPending=" + mUnregisterPending
633                     + ']';
634         }
635     }
636 
637     /**
638      * FullScreenMagnificationController Constructor
639      */
FullScreenMagnificationController(@onNull Context context, @NonNull AccessibilityManagerService ams, @NonNull Object lock, @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback)640     public FullScreenMagnificationController(@NonNull Context context,
641             @NonNull AccessibilityManagerService ams, @NonNull Object lock,
642             @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback) {
643         this(new ControllerContext(context, ams,
644                 LocalServices.getService(WindowManagerInternal.class),
645                 new Handler(context.getMainLooper()),
646                 context.getResources().getInteger(R.integer.config_longAnimTime)), lock,
647                 magnificationInfoChangedCallback);
648     }
649 
650     /**
651      * Constructor for tests
652      */
653     @VisibleForTesting
FullScreenMagnificationController(@onNull ControllerContext ctx, @NonNull Object lock, @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback)654     public FullScreenMagnificationController(@NonNull ControllerContext ctx,
655             @NonNull Object lock,
656             @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback) {
657         mControllerCtx = ctx;
658         mLock = lock;
659         mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
660         mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
661         mMagnificationInfoChangedCallback = magnificationInfoChangedCallback;
662     }
663 
664     /**
665      * Start tracking the magnification region for services that control magnification and the
666      * magnification gesture handler.
667      *
668      * This tracking imposes a cost on the system, so we avoid tracking this data unless it's
669      * required.
670      *
671      * @param displayId The logical display id.
672      */
register(int displayId)673     public void register(int displayId) {
674         synchronized (mLock) {
675             DisplayMagnification display = mDisplays.get(displayId);
676             if (display == null) {
677                 display = new DisplayMagnification(displayId);
678             }
679             if (display.isRegistered()) {
680                 return;
681             }
682             if (display.register()) {
683                 mDisplays.put(displayId, display);
684                 mScreenStateObserver.registerIfNecessary();
685             }
686         }
687     }
688 
689     /**
690      * Stop requiring tracking the magnification region. We may remain registered while we
691      * reset magnification.
692      *
693      * @param displayId The logical display id.
694      */
unregister(int displayId)695     public void unregister(int displayId) {
696         synchronized (mLock) {
697             unregisterLocked(displayId, false);
698         }
699     }
700 
701     /**
702      * Stop tracking all displays' magnification region.
703      */
unregisterAll()704     public void unregisterAll() {
705         synchronized (mLock) {
706             // display will be removed from array after unregister, we need to clone it to
707             // prevent error.
708             final SparseArray<DisplayMagnification> displays = mDisplays.clone();
709             for (int i = 0; i < displays.size(); i++) {
710                 unregisterLocked(displays.keyAt(i), false);
711             }
712         }
713     }
714 
715     /**
716      * Remove the display magnification with given id.
717      *
718      * @param displayId The logical display id.
719      */
onDisplayRemoved(int displayId)720     public void onDisplayRemoved(int displayId) {
721         synchronized (mLock) {
722             unregisterLocked(displayId, true);
723         }
724     }
725 
726     /**
727      * Check if we are registered on specified display. Note that we may be planning to unregister
728      * at any moment.
729      *
730      * @return {@code true} if the controller is registered on specified display.
731      * {@code false} otherwise.
732      *
733      * @param displayId The logical display id.
734      */
isRegistered(int displayId)735     public boolean isRegistered(int displayId) {
736         synchronized (mLock) {
737             final DisplayMagnification display = mDisplays.get(displayId);
738             if (display == null) {
739                 return false;
740             }
741             return display.isRegistered();
742         }
743     }
744 
745     /**
746      * @param displayId The logical display id.
747      * @return {@code true} if magnification is active, e.g. the scale
748      *         is > 1, {@code false} otherwise
749      */
isMagnifying(int displayId)750     public boolean isMagnifying(int displayId) {
751         synchronized (mLock) {
752             final DisplayMagnification display = mDisplays.get(displayId);
753             if (display == null) {
754                 return false;
755             }
756             return display.isMagnifying();
757         }
758     }
759 
760     /**
761      * Returns whether the magnification region contains the specified
762      * screen-relative coordinates.
763      *
764      * @param displayId The logical display id.
765      * @param x the screen-relative X coordinate to check
766      * @param y the screen-relative Y coordinate to check
767      * @return {@code true} if the coordinate is contained within the
768      *         magnified region, or {@code false} otherwise
769      */
magnificationRegionContains(int displayId, float x, float y)770     public boolean magnificationRegionContains(int displayId, float x, float y) {
771         synchronized (mLock) {
772             final DisplayMagnification display = mDisplays.get(displayId);
773             if (display == null) {
774                 return false;
775             }
776             return display.magnificationRegionContains(x, y);
777         }
778     }
779 
780     /**
781      * Populates the specified rect with the screen-relative bounds of the
782      * magnification region. If magnification is not enabled, the returned
783      * bounds will be empty.
784      *
785      * @param displayId The logical display id.
786      * @param outBounds rect to populate with the bounds of the magnified
787      *                  region
788      */
getMagnificationBounds(int displayId, @NonNull Rect outBounds)789     public void getMagnificationBounds(int displayId, @NonNull Rect outBounds) {
790         synchronized (mLock) {
791             final DisplayMagnification display = mDisplays.get(displayId);
792             if (display == null) {
793                 return;
794             }
795             display.getMagnificationBounds(outBounds);
796         }
797     }
798 
799     /**
800      * Populates the specified region with the screen-relative magnification
801      * region. If magnification is not enabled, then the returned region
802      * will be empty.
803      *
804      * @param displayId The logical display id.
805      * @param outRegion the region to populate
806      */
getMagnificationRegion(int displayId, @NonNull Region outRegion)807     public void getMagnificationRegion(int displayId, @NonNull Region outRegion) {
808         synchronized (mLock) {
809             final DisplayMagnification display = mDisplays.get(displayId);
810             if (display == null) {
811                 return;
812             }
813             display.getMagnificationRegion(outRegion);
814         }
815     }
816 
817     /**
818      * Returns the magnification scale. If an animation is in progress,
819      * this reflects the end state of the animation.
820      *
821      * @param displayId The logical display id.
822      * @return the scale
823      */
getScale(int displayId)824     public float getScale(int displayId) {
825         synchronized (mLock) {
826             final DisplayMagnification display = mDisplays.get(displayId);
827             if (display == null) {
828                 return 1.0f;
829             }
830             return display.getScale();
831         }
832     }
833 
834     /**
835      * Returns the X offset of the magnification viewport. If an animation
836      * is in progress, this reflects the end state of the animation.
837      *
838      * @param displayId The logical display id.
839      * @return the X offset
840      */
getOffsetX(int displayId)841     public float getOffsetX(int displayId) {
842         synchronized (mLock) {
843             final DisplayMagnification display = mDisplays.get(displayId);
844             if (display == null) {
845                 return 0.0f;
846             }
847             return display.getOffsetX();
848         }
849     }
850 
851     /**
852      * Returns the screen-relative X coordinate of the center of the
853      * magnification viewport.
854      *
855      * @param displayId The logical display id.
856      * @return the X coordinate
857      */
getCenterX(int displayId)858     public float getCenterX(int displayId) {
859         synchronized (mLock) {
860             final DisplayMagnification display = mDisplays.get(displayId);
861             if (display == null) {
862                 return 0.0f;
863             }
864             return display.getCenterX();
865         }
866     }
867 
868     /**
869      * Returns the Y offset of the magnification viewport. If an animation
870      * is in progress, this reflects the end state of the animation.
871      *
872      * @param displayId The logical display id.
873      * @return the Y offset
874      */
getOffsetY(int displayId)875     public float getOffsetY(int displayId) {
876         synchronized (mLock) {
877             final DisplayMagnification display = mDisplays.get(displayId);
878             if (display == null) {
879                 return 0.0f;
880             }
881             return display.getOffsetY();
882         }
883     }
884 
885     /**
886      * Returns the screen-relative Y coordinate of the center of the
887      * magnification viewport.
888      *
889      * @param displayId The logical display id.
890      * @return the Y coordinate
891      */
getCenterY(int displayId)892     public float getCenterY(int displayId) {
893         synchronized (mLock) {
894             final DisplayMagnification display = mDisplays.get(displayId);
895             if (display == null) {
896                 return 0.0f;
897             }
898             return display.getCenterY();
899         }
900     }
901 
902     /**
903      * Resets the magnification scale and center, optionally animating the
904      * transition.
905      *
906      * @param displayId The logical display id.
907      * @param animate {@code true} to animate the transition, {@code false}
908      *                to transition immediately
909      * @return {@code true} if the magnification spec changed, {@code false} if
910      *         the spec did not change
911      */
reset(int displayId, boolean animate)912     public boolean reset(int displayId, boolean animate) {
913         return reset(displayId, animate ? STUB_ANIMATION_CALLBACK : null);
914     }
915 
916     /**
917      * Resets the magnification scale and center, optionally animating the
918      * transition.
919      *
920      * @param displayId The logical display id.
921      * @param animationCallback Called when the animation result is valid.
922      *                    {@code null} to transition immediately
923      * @return {@code true} if the magnification spec changed, {@code false} if
924      *         the spec did not change
925      */
reset(int displayId, MagnificationAnimationCallback animationCallback)926     public boolean reset(int displayId,
927             MagnificationAnimationCallback animationCallback) {
928         synchronized (mLock) {
929             final DisplayMagnification display = mDisplays.get(displayId);
930             if (display == null) {
931                 return false;
932             }
933             return display.reset(animationCallback);
934         }
935     }
936 
937     /**
938      * Scales the magnified region around the specified pivot point,
939      * optionally animating the transition. If animation is disabled, the
940      * transition is immediate.
941      *
942      * @param displayId The logical display id.
943      * @param scale the target scale, must be >= 1
944      * @param pivotX the screen-relative X coordinate around which to scale
945      * @param pivotY the screen-relative Y coordinate around which to scale
946      * @param animate {@code true} to animate the transition, {@code false}
947      *                to transition immediately
948      * @param id the ID of the service requesting the change
949      * @return {@code true} if the magnification spec changed, {@code false} if
950      *         the spec did not change
951      */
setScale(int displayId, float scale, float pivotX, float pivotY, boolean animate, int id)952     public boolean setScale(int displayId, float scale, float pivotX, float pivotY,
953             boolean animate, int id) {
954         synchronized (mLock) {
955             final DisplayMagnification display = mDisplays.get(displayId);
956             if (display == null) {
957                 return false;
958             }
959             return display.setScale(scale, pivotX, pivotY, animate, id);
960         }
961     }
962 
963     /**
964      * Sets the center of the magnified region, optionally animating the
965      * transition. If animation is disabled, the transition is immediate.
966      *
967      * @param displayId The logical display id.
968      * @param centerX the screen-relative X coordinate around which to
969      *                center
970      * @param centerY the screen-relative Y coordinate around which to
971      *                center
972      * @param animate {@code true} to animate the transition, {@code false}
973      *                to transition immediately
974      * @param id      the ID of the service requesting the change
975      * @return {@code true} if the magnification spec changed, {@code false} if
976      * the spec did not change
977      */
setCenter(int displayId, float centerX, float centerY, boolean animate, int id)978     public boolean setCenter(int displayId, float centerX, float centerY, boolean animate, int id) {
979         synchronized (mLock) {
980             final DisplayMagnification display = mDisplays.get(displayId);
981             if (display == null) {
982                 return false;
983             }
984             return display.setScaleAndCenter(Float.NaN, centerX, centerY,
985                     animate ? STUB_ANIMATION_CALLBACK : null, id);
986         }
987     }
988 
989     /**
990      * Sets the scale and center of the magnified region, optionally
991      * animating the transition. If animation is disabled, the transition
992      * is immediate.
993      *
994      * @param displayId The logical display id.
995      * @param scale the target scale, or {@link Float#NaN} to leave unchanged
996      * @param centerX the screen-relative X coordinate around which to
997      *                center and scale, or {@link Float#NaN} to leave unchanged
998      * @param centerY the screen-relative Y coordinate around which to
999      *                center and scale, or {@link Float#NaN} to leave unchanged
1000      * @param animate {@code true} to animate the transition, {@code false}
1001      *                to transition immediately
1002      * @param id the ID of the service requesting the change
1003      * @return {@code true} if the magnification spec changed, {@code false} if
1004      *         the spec did not change
1005      */
setScaleAndCenter(int displayId, float scale, float centerX, float centerY, boolean animate, int id)1006     public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
1007             boolean animate, int id) {
1008         return setScaleAndCenter(displayId, scale, centerX, centerY,
1009                 transformToStubCallback(animate), id);
1010     }
1011 
1012     /**
1013      * Sets the scale and center of the magnified region, optionally
1014      * animating the transition. If animation is disabled, the transition
1015      * is immediate.
1016      *
1017      * @param displayId The logical display id.
1018      * @param scale the target scale, or {@link Float#NaN} to leave unchanged
1019      * @param centerX the screen-relative X coordinate around which to
1020      *                center and scale, or {@link Float#NaN} to leave unchanged
1021      * @param centerY the screen-relative Y coordinate around which to
1022      *                center and scale, or {@link Float#NaN} to leave unchanged
1023      * @param animationCallback Called when the animation result is valid.
1024      *                           {@code null} to transition immediately
1025      * @param id the ID of the service requesting the change
1026      * @return {@code true} if the magnification spec changed, {@code false} if
1027      *         the spec did not change
1028      */
setScaleAndCenter(int displayId, float scale, float centerX, float centerY, MagnificationAnimationCallback animationCallback, int id)1029     public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
1030             MagnificationAnimationCallback animationCallback, int id) {
1031         synchronized (mLock) {
1032             final DisplayMagnification display = mDisplays.get(displayId);
1033             if (display == null) {
1034                 return false;
1035             }
1036             return display.setScaleAndCenter(scale, centerX, centerY, animationCallback, id);
1037         }
1038     }
1039 
1040     /**
1041      * Offsets the magnified region. Note that the offsetX and offsetY values actually move in the
1042      * opposite direction as the offsets passed in here.
1043      *
1044      * @param displayId The logical display id.
1045      * @param offsetX the amount in pixels to offset the region in the X direction, in current
1046      *                screen pixels.
1047      * @param offsetY the amount in pixels to offset the region in the Y direction, in current
1048      *                screen pixels.
1049      * @param id      the ID of the service requesting the change
1050      */
offsetMagnifiedRegion(int displayId, float offsetX, float offsetY, int id)1051     public void offsetMagnifiedRegion(int displayId, float offsetX, float offsetY, int id) {
1052         synchronized (mLock) {
1053             final DisplayMagnification display = mDisplays.get(displayId);
1054             if (display == null) {
1055                 return;
1056             }
1057             display.offsetMagnifiedRegion(offsetX, offsetY, id);
1058         }
1059     }
1060 
1061     /**
1062      * Get the ID of the last service that changed the magnification spec.
1063      *
1064      * @param displayId The logical display id.
1065      * @return The id
1066      */
getIdOfLastServiceToMagnify(int displayId)1067     public int getIdOfLastServiceToMagnify(int displayId) {
1068         synchronized (mLock) {
1069             final DisplayMagnification display = mDisplays.get(displayId);
1070             if (display == null) {
1071                 return -1;
1072             }
1073             return display.getIdOfLastServiceToMagnify();
1074         }
1075     }
1076 
1077     /**
1078      * Persists the default display magnification scale to the current user's settings.
1079      */
persistScale()1080     public void persistScale() {
1081         // TODO: b/123047354, Need support multi-display?
1082         final float scale = getScale(Display.DEFAULT_DISPLAY);
1083         final int userId = mUserId;
1084 
1085         new AsyncTask<Void, Void, Void>() {
1086             @Override
1087             protected Void doInBackground(Void... params) {
1088                 mControllerCtx.putMagnificationScale(scale, userId);
1089                 return null;
1090             }
1091         }.execute();
1092     }
1093 
1094     /**
1095      * Retrieves a previously persisted magnification scale from the current
1096      * user's settings.
1097      *
1098      * @return the previously persisted magnification scale, or the default
1099      *         scale if none is available
1100      */
getPersistedScale()1101     public float getPersistedScale() {
1102         return mControllerCtx.getMagnificationScale(mUserId);
1103     }
1104 
1105     /**
1106      * Sets the currently active user ID.
1107      *
1108      * @param userId the currently active user ID
1109      */
setUserId(int userId)1110     public void setUserId(int userId) {
1111         if (mUserId == userId) {
1112             return;
1113         }
1114         mUserId = userId;
1115         resetAllIfNeeded(false);
1116     }
1117 
1118     /**
1119      * Resets all displays' magnification if last magnifying service is disabled.
1120      *
1121      * @param connectionId
1122      */
resetAllIfNeeded(int connectionId)1123     public void resetAllIfNeeded(int connectionId) {
1124         synchronized (mLock) {
1125             for (int i = 0; i < mDisplays.size(); i++) {
1126                 resetIfNeeded(mDisplays.keyAt(i), connectionId);
1127             }
1128         }
1129     }
1130 
1131     /**
1132      * Resets magnification if magnification and auto-update are both enabled.
1133      *
1134      * @param displayId The logical display id.
1135      * @param animate whether the animate the transition
1136      * @return whether was {@link #isMagnifying(int) magnifying}
1137      */
resetIfNeeded(int displayId, boolean animate)1138     boolean resetIfNeeded(int displayId, boolean animate) {
1139         synchronized (mLock) {
1140             final DisplayMagnification display = mDisplays.get(displayId);
1141             if (display == null || !display.isMagnifying()) {
1142                 return false;
1143             }
1144             display.reset(animate);
1145             return true;
1146         }
1147     }
1148 
1149     /**
1150      * Resets magnification if last magnifying service is disabled.
1151      *
1152      * @param displayId The logical display id.
1153      * @param connectionId the connection ID be disabled.
1154      * @return {@code true} on success, {@code false} on failure
1155      */
resetIfNeeded(int displayId, int connectionId)1156     boolean resetIfNeeded(int displayId, int connectionId) {
1157         synchronized (mLock) {
1158             final DisplayMagnification display = mDisplays.get(displayId);
1159             if (display == null || !display.isMagnifying()
1160                     || connectionId != display.getIdOfLastServiceToMagnify()) {
1161                 return false;
1162             }
1163             display.reset(true);
1164             return true;
1165         }
1166     }
1167 
setForceShowMagnifiableBounds(int displayId, boolean show)1168     void setForceShowMagnifiableBounds(int displayId, boolean show) {
1169         synchronized (mLock) {
1170             final DisplayMagnification display = mDisplays.get(displayId);
1171             if (display == null) {
1172                 return;
1173             }
1174             display.setForceShowMagnifiableBounds(show);
1175         }
1176     }
1177 
1178     /**
1179      * Notifies that the IME window visibility changed.
1180      *
1181      * @param shown {@code true} means the IME window shows on the screen. Otherwise it's
1182      *                           hidden.
1183      */
notifyImeWindowVisibilityChanged(boolean shown)1184     void notifyImeWindowVisibilityChanged(boolean shown) {
1185         mMagnificationInfoChangedCallback.onImeWindowVisibilityChanged(shown);
1186     }
1187 
1188     /**
1189      * Returns {@code true} if the magnifiable regions of the display is forced to be shown.
1190      *
1191      * @param displayId The logical display id.
1192      */
isForceShowMagnifiableBounds(int displayId)1193     public boolean isForceShowMagnifiableBounds(int displayId) {
1194         synchronized (mLock) {
1195             final DisplayMagnification display = mDisplays.get(displayId);
1196             if (display == null) {
1197                 return false;
1198             }
1199             return display.isForceShowMagnifiableBounds();
1200         }
1201     }
1202 
onScreenTurnedOff()1203     private void onScreenTurnedOff() {
1204         final Message m = PooledLambda.obtainMessage(
1205                 FullScreenMagnificationController::resetAllIfNeeded, this, false);
1206         mControllerCtx.getHandler().sendMessage(m);
1207     }
1208 
resetAllIfNeeded(boolean animate)1209     private void resetAllIfNeeded(boolean animate) {
1210         synchronized (mLock) {
1211             for (int i = 0; i < mDisplays.size(); i++) {
1212                 resetIfNeeded(mDisplays.keyAt(i), animate);
1213             }
1214         }
1215     }
1216 
unregisterLocked(int displayId, boolean delete)1217     private void unregisterLocked(int displayId, boolean delete) {
1218         final DisplayMagnification display = mDisplays.get(displayId);
1219         if (display == null) {
1220             return;
1221         }
1222         if (!display.isRegistered()) {
1223             if (delete) {
1224                 mDisplays.remove(displayId);
1225             }
1226             return;
1227         }
1228         if (!display.isMagnifying()) {
1229             display.unregister(delete);
1230         } else {
1231             display.unregisterPending(delete);
1232         }
1233     }
1234 
1235     /**
1236      * Callbacks from DisplayMagnification after display magnification unregistered. It will remove
1237      * DisplayMagnification instance if delete is true, and unregister screen state if
1238      * there is no registered display magnification.
1239      */
unregisterCallbackLocked(int displayId, boolean delete)1240     private void unregisterCallbackLocked(int displayId, boolean delete) {
1241         if (delete) {
1242             mDisplays.remove(displayId);
1243         }
1244         // unregister screen state if necessary
1245         boolean hasRegister = false;
1246         for (int i = 0; i < mDisplays.size(); i++) {
1247             final DisplayMagnification display = mDisplays.valueAt(i);
1248             hasRegister = display.isRegistered();
1249             if (hasRegister) {
1250                 break;
1251             }
1252         }
1253         if (!hasRegister) {
1254             mScreenStateObserver.unregister();
1255         }
1256     }
1257 
1258     @Override
toString()1259     public String toString() {
1260         StringBuilder builder = new StringBuilder();
1261         builder.append("MagnificationController[");
1262         builder.append("mUserId=").append(mUserId);
1263         builder.append(", mDisplays=").append(mDisplays);
1264         builder.append("]");
1265         return builder.toString();
1266     }
1267 
1268     /**
1269      * Class responsible for animating spec on the main thread and sending spec
1270      * updates to the window manager.
1271      */
1272     private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener,
1273             Animator.AnimatorListener {
1274         private final ControllerContext mControllerCtx;
1275 
1276         /**
1277          * The magnification spec that was sent to the window manager. This should
1278          * only be accessed with the lock held.
1279          */
1280         private final MagnificationSpec mSentMagnificationSpec = new MagnificationSpec();
1281 
1282         private final MagnificationSpec mStartMagnificationSpec = new MagnificationSpec();
1283 
1284         private final MagnificationSpec mEndMagnificationSpec = new MagnificationSpec();
1285 
1286         /**
1287          * The animator should only be accessed and modified on the main (e.g. animation) thread.
1288          */
1289         private final ValueAnimator mValueAnimator;
1290 
1291         // Called when the callee wants animating and the sent spec matches the target spec.
1292         private MagnificationAnimationCallback mAnimationCallback;
1293         private final Object mLock;
1294 
1295         private final int mDisplayId;
1296 
1297         @GuardedBy("mLock")
1298         private boolean mEnabled = false;
1299 
SpecAnimationBridge(ControllerContext ctx, Object lock, int displayId)1300         private SpecAnimationBridge(ControllerContext ctx, Object lock, int displayId) {
1301             mControllerCtx = ctx;
1302             mLock = lock;
1303             mDisplayId = displayId;
1304             final long animationDuration = mControllerCtx.getAnimationDuration();
1305             mValueAnimator = mControllerCtx.newValueAnimator();
1306             mValueAnimator.setDuration(animationDuration);
1307             mValueAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
1308             mValueAnimator.setFloatValues(0.0f, 1.0f);
1309             mValueAnimator.addUpdateListener(this);
1310             mValueAnimator.addListener(this);
1311         }
1312 
1313         /**
1314          * Enabled means the bridge will accept input. When not enabled, the output of the animator
1315          * will be ignored
1316          */
setEnabled(boolean enabled)1317         public void setEnabled(boolean enabled) {
1318             synchronized (mLock) {
1319                 if (enabled != mEnabled) {
1320                     mEnabled = enabled;
1321                     if (!mEnabled) {
1322                         mSentMagnificationSpec.clear();
1323                         mControllerCtx.getWindowManager().setMagnificationSpec(
1324                                 mDisplayId, mSentMagnificationSpec);
1325                     }
1326                 }
1327             }
1328         }
1329 
updateSentSpecMainThread(MagnificationSpec spec, MagnificationAnimationCallback animationCallback)1330         void updateSentSpecMainThread(MagnificationSpec spec,
1331                 MagnificationAnimationCallback animationCallback) {
1332             if (mValueAnimator.isRunning()) {
1333                 mValueAnimator.cancel();
1334             }
1335 
1336             mAnimationCallback = animationCallback;
1337             // If the current and sent specs don't match, update the sent spec.
1338             synchronized (mLock) {
1339                 final boolean changed = !mSentMagnificationSpec.equals(spec);
1340                 if (changed) {
1341                     if (mAnimationCallback != null) {
1342                         animateMagnificationSpecLocked(spec);
1343                     } else {
1344                         setMagnificationSpecLocked(spec);
1345                     }
1346                 } else {
1347                     sendEndCallbackMainThread(true);
1348                 }
1349             }
1350         }
1351 
sendEndCallbackMainThread(boolean success)1352         private void sendEndCallbackMainThread(boolean success) {
1353             if (mAnimationCallback != null) {
1354                 if (DEBUG) {
1355                     Slog.d(LOG_TAG, "sendEndCallbackMainThread: " + success);
1356                 }
1357                 mAnimationCallback.onResult(success);
1358                 mAnimationCallback = null;
1359             }
1360         }
1361 
1362         @GuardedBy("mLock")
setMagnificationSpecLocked(MagnificationSpec spec)1363         private void setMagnificationSpecLocked(MagnificationSpec spec) {
1364             if (mEnabled) {
1365                 if (DEBUG_SET_MAGNIFICATION_SPEC) {
1366                     Slog.i(LOG_TAG, "Sending: " + spec);
1367                 }
1368 
1369                 mSentMagnificationSpec.setTo(spec);
1370                 mControllerCtx.getWindowManager().setMagnificationSpec(
1371                         mDisplayId, mSentMagnificationSpec);
1372             }
1373         }
1374 
animateMagnificationSpecLocked(MagnificationSpec toSpec)1375         private void animateMagnificationSpecLocked(MagnificationSpec toSpec) {
1376             mEndMagnificationSpec.setTo(toSpec);
1377             mStartMagnificationSpec.setTo(mSentMagnificationSpec);
1378             mValueAnimator.start();
1379         }
1380 
1381         @Override
onAnimationUpdate(ValueAnimator animation)1382         public void onAnimationUpdate(ValueAnimator animation) {
1383             synchronized (mLock) {
1384                 if (mEnabled) {
1385                     float fract = animation.getAnimatedFraction();
1386                     MagnificationSpec magnificationSpec = new MagnificationSpec();
1387                     magnificationSpec.scale = mStartMagnificationSpec.scale
1388                             + (mEndMagnificationSpec.scale - mStartMagnificationSpec.scale) * fract;
1389                     magnificationSpec.offsetX = mStartMagnificationSpec.offsetX
1390                             + (mEndMagnificationSpec.offsetX - mStartMagnificationSpec.offsetX)
1391                             * fract;
1392                     magnificationSpec.offsetY = mStartMagnificationSpec.offsetY
1393                             + (mEndMagnificationSpec.offsetY - mStartMagnificationSpec.offsetY)
1394                             * fract;
1395                     setMagnificationSpecLocked(magnificationSpec);
1396                 }
1397             }
1398         }
1399 
1400         @Override
onAnimationStart(Animator animation)1401         public void onAnimationStart(Animator animation) {
1402         }
1403 
1404         @Override
onAnimationEnd(Animator animation)1405         public void onAnimationEnd(Animator animation) {
1406             sendEndCallbackMainThread(true);
1407         }
1408 
1409         @Override
onAnimationCancel(Animator animation)1410         public void onAnimationCancel(Animator animation) {
1411             sendEndCallbackMainThread(false);
1412         }
1413 
1414         @Override
onAnimationRepeat(Animator animation)1415         public void onAnimationRepeat(Animator animation) {
1416 
1417         }
1418     }
1419 
1420     private static class ScreenStateObserver extends BroadcastReceiver {
1421         private final Context mContext;
1422         private final FullScreenMagnificationController mController;
1423         private boolean mRegistered = false;
1424 
ScreenStateObserver(Context context, FullScreenMagnificationController controller)1425         ScreenStateObserver(Context context, FullScreenMagnificationController controller) {
1426             mContext = context;
1427             mController = controller;
1428         }
1429 
registerIfNecessary()1430         public void registerIfNecessary() {
1431             if (!mRegistered) {
1432                 mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
1433                 mRegistered = true;
1434             }
1435         }
1436 
unregister()1437         public void unregister() {
1438             if (mRegistered) {
1439                 mContext.unregisterReceiver(this);
1440                 mRegistered = false;
1441             }
1442         }
1443 
1444         @Override
onReceive(Context context, Intent intent)1445         public void onReceive(Context context, Intent intent) {
1446             mController.onScreenTurnedOff();
1447         }
1448     }
1449 
1450     /**
1451      * This class holds resources used between the classes in MagnificationController, and
1452      * functions for tests to mock it.
1453      */
1454     @VisibleForTesting
1455     public static class ControllerContext {
1456         private final Context mContext;
1457         private final AccessibilityManagerService mAms;
1458         private final WindowManagerInternal mWindowManager;
1459         private final Handler mHandler;
1460         private final Long mAnimationDuration;
1461 
1462         /**
1463          * Constructor for ControllerContext.
1464          */
ControllerContext(@onNull Context context, @NonNull AccessibilityManagerService ams, @NonNull WindowManagerInternal windowManager, @NonNull Handler handler, long animationDuration)1465         public ControllerContext(@NonNull Context context,
1466                 @NonNull AccessibilityManagerService ams,
1467                 @NonNull WindowManagerInternal windowManager,
1468                 @NonNull Handler handler,
1469                 long animationDuration) {
1470             mContext = context;
1471             mAms = ams;
1472             mWindowManager = windowManager;
1473             mHandler = handler;
1474             mAnimationDuration = animationDuration;
1475         }
1476 
1477         /**
1478          * @return A context.
1479          */
1480         @NonNull
getContext()1481         public Context getContext() {
1482             return mContext;
1483         }
1484 
1485         /**
1486          * @return AccessibilityManagerService
1487          */
1488         @NonNull
getAms()1489         public AccessibilityManagerService getAms() {
1490             return mAms;
1491         }
1492 
1493         /**
1494          * @return WindowManagerInternal
1495          */
1496         @NonNull
getWindowManager()1497         public WindowManagerInternal getWindowManager() {
1498             return mWindowManager;
1499         }
1500 
1501         /**
1502          * @return Handler for main looper
1503          */
1504         @NonNull
getHandler()1505         public Handler getHandler() {
1506             return mHandler;
1507         }
1508 
1509         /**
1510          * Create a new ValueAnimator.
1511          *
1512          * @return ValueAnimator
1513          */
1514         @NonNull
newValueAnimator()1515         public ValueAnimator newValueAnimator() {
1516             return new ValueAnimator();
1517         }
1518 
1519         /**
1520          * Write Settings of magnification scale.
1521          */
putMagnificationScale(float value, int userId)1522         public void putMagnificationScale(float value, int userId) {
1523             Settings.Secure.putFloatForUser(mContext.getContentResolver(),
1524                     Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, value, userId);
1525         }
1526 
1527         /**
1528          * Get Settings of magnification scale.
1529          */
getMagnificationScale(int userId)1530         public float getMagnificationScale(int userId) {
1531             return Settings.Secure.getFloatForUser(mContext.getContentResolver(),
1532                     Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
1533                     DEFAULT_MAGNIFICATION_SCALE, userId);
1534         }
1535 
1536         /**
1537          * @return Configuration of animation duration.
1538          */
getAnimationDuration()1539         public long getAnimationDuration() {
1540             return mAnimationDuration;
1541         }
1542     }
1543 
1544     @Nullable
transformToStubCallback(boolean animate)1545     private static MagnificationAnimationCallback transformToStubCallback(boolean animate) {
1546         return animate ? STUB_ANIMATION_CALLBACK : null;
1547     }
1548 
1549     interface  MagnificationInfoChangedCallback {
1550 
1551         /**
1552          * Called when the {@link MagnificationSpec} is changed with non-default
1553          * scale by the service.
1554          *
1555          * @param displayId the logical display id
1556          * @param serviceId the ID of the service requesting the change
1557          */
onRequestMagnificationSpec(int displayId, int serviceId)1558         void onRequestMagnificationSpec(int displayId, int serviceId);
1559 
1560         /**
1561          * Called when the state of the magnification activation is changed.
1562          * It is for the logging data of the magnification activation state.
1563          *
1564          * @param activated {@code true} if the magnification is activated, otherwise {@code false}.
1565          */
onFullScreenMagnificationActivationState(boolean activated)1566         void onFullScreenMagnificationActivationState(boolean activated);
1567 
1568         /**
1569          * Called when the IME window visibility changed.
1570          * @param shown {@code true} means the IME window shows on the screen. Otherwise it's
1571          *                           hidden.
1572          */
onImeWindowVisibilityChanged(boolean shown)1573         void onImeWindowVisibilityChanged(boolean shown);
1574     }
1575 }
1576