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