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 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
21 import static android.util.MathUtils.abs;
22 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
23 import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK;
24 
25 import static com.android.server.accessibility.AccessibilityManagerService.INVALID_SERVICE_ID;
26 
27 import android.accessibilityservice.MagnificationConfig;
28 import android.animation.Animator;
29 import android.animation.TimeAnimator;
30 import android.animation.ValueAnimator;
31 import android.annotation.MainThread;
32 import android.annotation.NonNull;
33 import android.annotation.Nullable;
34 import android.content.BroadcastReceiver;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.IntentFilter;
38 import android.content.res.CompatibilityInfo;
39 import android.graphics.Rect;
40 import android.graphics.Region;
41 import android.hardware.display.DisplayManagerInternal;
42 import android.os.Handler;
43 import android.os.Message;
44 import android.text.TextUtils;
45 import android.util.DisplayMetrics;
46 import android.util.MathUtils;
47 import android.util.Slog;
48 import android.util.SparseArray;
49 import android.util.TypedValue;
50 import android.view.Display;
51 import android.view.DisplayInfo;
52 import android.view.MagnificationSpec;
53 import android.view.View;
54 import android.view.WindowManager;
55 import android.view.accessibility.MagnificationAnimationCallback;
56 import android.view.animation.DecelerateInterpolator;
57 import android.widget.Scroller;
58 
59 import com.android.internal.R;
60 import com.android.internal.accessibility.common.MagnificationConstants;
61 import com.android.internal.annotations.GuardedBy;
62 import com.android.internal.annotations.VisibleForTesting;
63 import com.android.internal.util.function.pooled.PooledLambda;
64 import com.android.server.LocalServices;
65 import com.android.server.accessibility.AccessibilityManagerService;
66 import com.android.server.accessibility.AccessibilityTraceManager;
67 import com.android.server.accessibility.Flags;
68 import com.android.server.wm.WindowManagerInternal;
69 
70 import java.util.ArrayList;
71 import java.util.Locale;
72 import java.util.concurrent.Executor;
73 import java.util.function.Supplier;
74 
75 /**
76  * This class is used to control and query the state of display magnification
77  * from the accessibility manager and related classes. It is responsible for
78  * holding the current state of magnification and animation, and it handles
79  * communication between the accessibility manager and window manager.
80  *
81  * Magnification is limited to the range controlled by
82  * {@link MagnificationScaleProvider#constrainScale(float)}, and can only occur inside the
83  * magnification region. If a value is out of bounds, it will be adjusted to guarantee these
84  * constraints.
85  */
86 public class FullScreenMagnificationController implements
87         WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks {
88     private static final boolean DEBUG = false;
89     private static final String LOG_TAG = "FullScreenMagnificationController";
90 
91     private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
92 
93     private final Object mLock;
94     private final Supplier<Scroller> mScrollerSupplier;
95     private final Supplier<TimeAnimator> mTimeAnimatorSupplier;
96 
97     private final ControllerContext mControllerCtx;
98 
99     private final ScreenStateObserver mScreenStateObserver;
100 
101     @GuardedBy("mLock")
102     private final ArrayList<MagnificationInfoChangedCallback>
103             mMagnificationInfoChangedCallbacks = new ArrayList<>();
104 
105     private final MagnificationScaleProvider mScaleProvider;
106 
107     private final long mMainThreadId;
108 
109     /** List of display Magnification, mapping from displayId -> DisplayMagnification. */
110     @GuardedBy("mLock")
111     private final SparseArray<DisplayMagnification> mDisplays = new SparseArray<>(0);
112 
113     private final Rect mTempRect = new Rect();
114     // Whether the following typing focus feature for magnification is enabled.
115     private boolean mMagnificationFollowTypingEnabled = true;
116     // Whether the always on magnification feature is enabled.
117     private boolean mAlwaysOnMagnificationEnabled = false;
118     private final DisplayManagerInternal mDisplayManagerInternal;
119 
120     private final MagnificationThumbnailFeatureFlag mMagnificationThumbnailFeatureFlag;
121     @NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier;
122     @NonNull private final Supplier<Boolean> mMagnificationConnectionStateSupplier;
123 
124     /**
125      * This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds
126      * magnification information per display.
127      */
128     private final class DisplayMagnification implements
129             WindowManagerInternal.MagnificationCallbacks {
130         /**
131          * The current magnification spec. If an animation is running, this
132          * reflects the end state.
133          */
134         private final MagnificationSpec mCurrentMagnificationSpec = new MagnificationSpec();
135 
136         private final Region mMagnificationRegion = Region.obtain();
137         private final Rect mMagnificationBounds = new Rect();
138 
139         private final Rect mTempRect = new Rect();
140         private final Rect mTempRect1 = new Rect();
141 
142         private final SpecAnimationBridge mSpecAnimationBridge;
143 
144         // Flag indicating that we are registered with window manager.
145         private boolean mRegistered;
146         private boolean mUnregisterPending;
147         private boolean mDeleteAfterUnregister;
148 
149         private final int mDisplayId;
150 
151         private int mIdOfLastServiceToMagnify = INVALID_SERVICE_ID;
152         private boolean mMagnificationActivated = false;
153 
154         private boolean mZoomedOutFromService = false;
155 
156         @GuardedBy("mLock") @Nullable private MagnificationThumbnail mMagnificationThumbnail;
157 
DisplayMagnification(int displayId)158         DisplayMagnification(int displayId) {
159             mDisplayId = displayId;
160             mSpecAnimationBridge =
161                     new SpecAnimationBridge(
162                             mControllerCtx,
163                             mLock,
164                             mDisplayId,
165                             mScrollerSupplier,
166                             mTimeAnimatorSupplier);
167         }
168 
169         /**
170          * Registers magnification callback and get current magnification region from
171          * window manager.
172          *
173          * @return true if callback registers successful.
174          */
175         @GuardedBy("mLock")
register()176         boolean register() {
177             if (traceEnabled()) {
178                 logTrace("setMagnificationCallbacks",
179                         "displayID=" + mDisplayId + ";callback=" + this);
180             }
181             mRegistered = mControllerCtx.getWindowManager().setMagnificationCallbacks(
182                     mDisplayId, this);
183             if (!mRegistered) {
184                 Slog.w(LOG_TAG, "set magnification callbacks fail, displayId:" + mDisplayId);
185                 return false;
186             }
187             mSpecAnimationBridge.setEnabled(true);
188             if (traceEnabled()) {
189                 logTrace("getMagnificationRegion",
190                         "displayID=" + mDisplayId + ";region=" + mMagnificationRegion);
191             }
192             // Obtain initial state.
193             mControllerCtx.getWindowManager().getMagnificationRegion(
194                     mDisplayId, mMagnificationRegion);
195             mMagnificationRegion.getBounds(mMagnificationBounds);
196 
197             createThumbnailIfSupported();
198 
199             return true;
200         }
201 
202         /**
203          * Unregisters magnification callback from window manager. Callbacks to
204          * {@link FullScreenMagnificationController#unregisterCallbackLocked(int, boolean)} after
205          * unregistered.
206          *
207          * @param delete true if this instance should be removed from the SparseArray in
208          *               FullScreenMagnificationController after unregistered, for example,
209          *               display removed.
210          */
211         @GuardedBy("mLock")
unregister(boolean delete)212         void unregister(boolean delete) {
213             if (mRegistered) {
214                 mSpecAnimationBridge.setEnabled(false);
215                 if (traceEnabled()) {
216                     logTrace("setMagnificationCallbacks",
217                             "displayID=" + mDisplayId + ";callback=null");
218                 }
219                 mControllerCtx.getWindowManager().setMagnificationCallbacks(
220                         mDisplayId, null);
221                 mMagnificationRegion.setEmpty();
222                 mRegistered = false;
223                 unregisterCallbackLocked(mDisplayId, delete);
224 
225                 destroyThumbnail();
226             }
227             mUnregisterPending = false;
228         }
229 
230         /**
231          * Reset magnification status with animation enabled. {@link #unregister(boolean)} will be
232          * called after animation finished.
233          *
234          * @param delete true if this instance should be removed from the SparseArray in
235          *               FullScreenMagnificationController after unregistered, for example,
236          *               display removed.
237          */
238         @GuardedBy("mLock")
unregisterPending(boolean delete)239         void unregisterPending(boolean delete) {
240             mDeleteAfterUnregister = delete;
241             mUnregisterPending = true;
242             reset(true);
243         }
244 
isRegistered()245         boolean isRegistered() {
246             return mRegistered;
247         }
248 
isActivated()249         boolean isActivated() {
250             return mMagnificationActivated;
251         }
252 
getScale()253         float getScale() {
254             return mCurrentMagnificationSpec.scale;
255         }
256 
getOffsetX()257         float getOffsetX() {
258             return mCurrentMagnificationSpec.offsetX;
259         }
260 
getOffsetY()261         float getOffsetY() {
262             return mCurrentMagnificationSpec.offsetY;
263         }
264 
265         @GuardedBy("mLock")
isAtEdge()266         boolean isAtEdge() {
267             return isAtLeftEdge(0f) || isAtRightEdge(0f) || isAtTopEdge(0f) || isAtBottomEdge(0f);
268         }
269 
270         @GuardedBy("mLock")
isAtLeftEdge(float slop)271         boolean isAtLeftEdge(float slop) {
272             return abs(getOffsetX() - getMaxOffsetXLocked()) <= slop;
273         }
274 
275         @GuardedBy("mLock")
isAtRightEdge(float slop)276         boolean isAtRightEdge(float slop) {
277             return abs(getOffsetX() - getMinOffsetXLocked()) <= slop;
278         }
279 
280         @GuardedBy("mLock")
isAtTopEdge(float slop)281         boolean isAtTopEdge(float slop) {
282             return abs(getOffsetY() - getMaxOffsetYLocked()) <= slop;
283         }
284 
285         @GuardedBy("mLock")
isAtBottomEdge(float slop)286         boolean isAtBottomEdge(float slop) {
287             return abs(getOffsetY() - getMinOffsetYLocked()) <= slop;
288         }
289 
290         @GuardedBy("mLock")
getCenterX()291         float getCenterX() {
292             return (mMagnificationBounds.width() / 2.0f
293                     + mMagnificationBounds.left - getOffsetX()) / getScale();
294         }
295 
296         @GuardedBy("mLock")
getCenterY()297         float getCenterY() {
298             return (mMagnificationBounds.height() / 2.0f
299                     + mMagnificationBounds.top - getOffsetY()) / getScale();
300         }
301 
302         /**
303          * Returns the scale currently used by the window manager. If an
304          * animation is in progress, this reflects the current state of the
305          * animation.
306          *
307          * @return the scale currently used by the window manager
308          */
getSentScale()309         float getSentScale() {
310             return mSpecAnimationBridge.mSentMagnificationSpec.scale;
311         }
312 
313         /**
314          * Returns the X offset currently used by the window manager. If an
315          * animation is in progress, this reflects the current state of the
316          * animation.
317          *
318          * @return the X offset currently used by the window manager
319          */
getSentOffsetX()320         float getSentOffsetX() {
321             return mSpecAnimationBridge.mSentMagnificationSpec.offsetX;
322         }
323 
324         /**
325          * Returns the Y offset currently used by the window manager. If an
326          * animation is in progress, this reflects the current state of the
327          * animation.
328          *
329          * @return the Y offset currently used by the window manager
330          */
getSentOffsetY()331         float getSentOffsetY() {
332             return mSpecAnimationBridge.mSentMagnificationSpec.offsetY;
333         }
334 
335         @Override
onMagnificationRegionChanged(Region magnificationRegion)336         public void onMagnificationRegionChanged(Region magnificationRegion) {
337             final Message m = PooledLambda.obtainMessage(
338                     DisplayMagnification::updateMagnificationRegion, this,
339                     Region.obtain(magnificationRegion));
340             mControllerCtx.getHandler().sendMessage(m);
341         }
342 
343         @Override
onRectangleOnScreenRequested(int left, int top, int right, int bottom)344         public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
345             final Message m = PooledLambda.obtainMessage(
346                     DisplayMagnification::requestRectangleOnScreen, this,
347                     left, top, right, bottom);
348             mControllerCtx.getHandler().sendMessage(m);
349         }
350 
351         @Override
onDisplaySizeChanged()352         public void onDisplaySizeChanged() {
353             // Treat as context change
354             onUserContextChanged();
355         }
356 
357         @Override
onUserContextChanged()358         public void onUserContextChanged() {
359             final Message m = PooledLambda.obtainMessage(
360                     FullScreenMagnificationController::onUserContextChanged,
361                     FullScreenMagnificationController.this, mDisplayId);
362             mControllerCtx.getHandler().sendMessage(m);
363 
364             synchronized (mLock) {
365                 refreshThumbnail();
366             }
367         }
368 
369         @Override
onImeWindowVisibilityChanged(boolean shown)370         public void onImeWindowVisibilityChanged(boolean shown) {
371             final Message m = PooledLambda.obtainMessage(
372                     FullScreenMagnificationController::notifyImeWindowVisibilityChanged,
373                     FullScreenMagnificationController.this, mDisplayId, shown);
374             mControllerCtx.getHandler().sendMessage(m);
375         }
376 
377         /**
378          * Update our copy of the current magnification region
379          *
380          * @param magnified the magnified region
381          */
updateMagnificationRegion(Region magnified)382         void updateMagnificationRegion(Region magnified) {
383             synchronized (mLock) {
384                 if (!mRegistered) {
385                     // Don't update if we've unregistered
386                     return;
387                 }
388                 if (!mMagnificationRegion.equals(magnified)) {
389                     mMagnificationRegion.set(magnified);
390                     mMagnificationRegion.getBounds(mMagnificationBounds);
391 
392                     refreshThumbnail();
393 
394                     // It's possible that our magnification spec is invalid with the new bounds.
395                     // Adjust the current spec's offsets if necessary.
396                     if (updateCurrentSpecWithOffsetsLocked(
397                             mCurrentMagnificationSpec.offsetX, mCurrentMagnificationSpec.offsetY)) {
398                         sendSpecToAnimation(mCurrentMagnificationSpec, null);
399                     }
400                     onMagnificationChangedLocked();
401                 }
402                 magnified.recycle();
403             }
404         }
405 
sendSpecToAnimation(MagnificationSpec spec, MagnificationAnimationCallback animationCallback)406         void sendSpecToAnimation(MagnificationSpec spec,
407                 MagnificationAnimationCallback animationCallback) {
408             if (DEBUG) {
409                 Slog.i(LOG_TAG,
410                         "sendSpecToAnimation(spec = " + spec + ", animationCallback = "
411                                 + animationCallback + ")");
412             }
413             if (Thread.currentThread().getId() == mMainThreadId) {
414                 mSpecAnimationBridge.updateSentSpecMainThread(spec, animationCallback);
415             } else {
416                 final Message m = PooledLambda.obtainMessage(
417                         SpecAnimationBridge::updateSentSpecMainThread,
418                         mSpecAnimationBridge, spec, animationCallback);
419                 mControllerCtx.getHandler().sendMessage(m);
420             }
421         }
422 
startFlingAnimation( float xPixelsPerSecond, float yPixelsPerSecond, MagnificationAnimationCallback animationCallback )423         void startFlingAnimation(
424                 float xPixelsPerSecond,
425                 float yPixelsPerSecond,
426                 MagnificationAnimationCallback animationCallback
427         ) {
428             if (DEBUG) {
429                 Slog.i(LOG_TAG,
430                         "startFlingAnimation(spec = " + xPixelsPerSecond + ", animationCallback = "
431                                 + animationCallback + ")");
432             }
433             if (Thread.currentThread().getId() == mMainThreadId) {
434                 mSpecAnimationBridge.startFlingAnimation(
435                         xPixelsPerSecond,
436                         yPixelsPerSecond,
437                         getMinOffsetXLocked(),
438                         getMaxOffsetXLocked(),
439                         getMinOffsetYLocked(),
440                         getMaxOffsetYLocked(),
441                         animationCallback);
442             } else {
443                 final Message m =
444                         PooledLambda.obtainMessage(
445                                 SpecAnimationBridge::startFlingAnimation,
446                                 mSpecAnimationBridge,
447                                 xPixelsPerSecond,
448                                 yPixelsPerSecond,
449                                 getMinOffsetXLocked(),
450                                 getMaxOffsetXLocked(),
451                                 getMinOffsetYLocked(),
452                                 getMaxOffsetYLocked(),
453                                 animationCallback);
454                 mControllerCtx.getHandler().sendMessage(m);
455             }
456         }
457 
cancelFlingAnimation()458         void cancelFlingAnimation() {
459             if (DEBUG) {
460                 Slog.i(LOG_TAG, "cancelFlingAnimation()");
461             }
462             if (Thread.currentThread().getId() == mMainThreadId) {
463                 mSpecAnimationBridge.cancelFlingAnimation();
464             } else {
465                 mControllerCtx.getHandler().post(mSpecAnimationBridge::cancelFlingAnimation);
466             }
467         }
468 
469         /**
470          * Get the ID of the last service that changed the magnification spec.
471          *
472          * @return The id
473          */
getIdOfLastServiceToMagnify()474         int getIdOfLastServiceToMagnify() {
475             return mIdOfLastServiceToMagnify;
476         }
477 
478         @GuardedBy("mLock")
onMagnificationChangedLocked()479         void onMagnificationChangedLocked() {
480             final float scale = getScale();
481             final float centerX = getCenterX();
482             final float centerY = getCenterY();
483             final MagnificationConfig config = new MagnificationConfig.Builder()
484                     .setMode(MAGNIFICATION_MODE_FULLSCREEN)
485                     .setActivated(mMagnificationActivated)
486                     .setScale(scale)
487                     .setCenterX(centerX)
488                     .setCenterY(centerY).build();
489             mMagnificationInfoChangedCallbacks.forEach(callback -> {
490                 callback.onFullScreenMagnificationChanged(mDisplayId,
491                         mMagnificationRegion, config);
492             });
493             if (mUnregisterPending && !isActivated()) {
494                 unregister(mDeleteAfterUnregister);
495             }
496 
497             if (isActivated()) {
498                 updateThumbnail(scale, centerX, centerY);
499             } else {
500                 hideThumbnail();
501             }
502         }
503 
504         @GuardedBy("mLock")
magnificationRegionContains(float x, float y)505         boolean magnificationRegionContains(float x, float y) {
506             return mMagnificationRegion.contains((int) x, (int) y);
507         }
508 
509         @GuardedBy("mLock")
getMagnificationBounds(@onNull Rect outBounds)510         void getMagnificationBounds(@NonNull Rect outBounds) {
511             outBounds.set(mMagnificationBounds);
512         }
513 
514         @GuardedBy("mLock")
getMagnificationRegion(@onNull Region outRegion)515         void getMagnificationRegion(@NonNull Region outRegion) {
516             outRegion.set(mMagnificationRegion);
517         }
518 
getDisplayMetricsForId()519         private DisplayMetrics getDisplayMetricsForId() {
520             final DisplayMetrics outMetrics = new DisplayMetrics();
521             final DisplayInfo displayInfo = mDisplayManagerInternal.getDisplayInfo(mDisplayId);
522             if (displayInfo != null) {
523                 displayInfo.getLogicalMetrics(outMetrics,
524                         CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
525             } else {
526                 outMetrics.setToDefaults();
527             }
528             return outMetrics;
529         }
530 
requestRectangleOnScreen(int left, int top, int right, int bottom)531         void requestRectangleOnScreen(int left, int top, int right, int bottom) {
532             synchronized (mLock) {
533                 final Rect magnifiedFrame = mTempRect;
534                 getMagnificationBounds(magnifiedFrame);
535                 if (!magnifiedFrame.intersects(left, top, right, bottom)) {
536                     return;
537                 }
538 
539                 final Rect magnifFrameInScreenCoords = mTempRect1;
540                 getMagnifiedFrameInContentCoordsLocked(magnifFrameInScreenCoords);
541 
542                 final float scrollX;
543                 final float scrollY;
544                 // We offset an additional distance for a user to know the surrounding context.
545                 DisplayMetrics metrics = getDisplayMetricsForId();
546                 final float offsetViewportX = (float) magnifFrameInScreenCoords.width() / 4;
547                 final float offsetViewportY =
548                         TypedValue.applyDimension(COMPLEX_UNIT_DIP, 10, metrics);
549 
550                 if (right - left > magnifFrameInScreenCoords.width()) {
551                     final int direction = TextUtils
552                             .getLayoutDirectionFromLocale(Locale.getDefault());
553                     if (direction == View.LAYOUT_DIRECTION_LTR) {
554                         scrollX = left - magnifFrameInScreenCoords.left;
555                     } else {
556                         scrollX = right - magnifFrameInScreenCoords.right;
557                     }
558                 } else if (left < magnifFrameInScreenCoords.left) {
559                     scrollX = left - magnifFrameInScreenCoords.left - offsetViewportX;
560                 } else if (right > magnifFrameInScreenCoords.right) {
561                     scrollX = right - magnifFrameInScreenCoords.right + offsetViewportX;
562                 } else {
563                     scrollX = 0;
564                 }
565 
566                 if (bottom - top > magnifFrameInScreenCoords.height()) {
567                     scrollY = top - magnifFrameInScreenCoords.top;
568                 } else if (top < magnifFrameInScreenCoords.top) {
569                     scrollY = top - magnifFrameInScreenCoords.top - offsetViewportY;
570                 } else if (bottom > magnifFrameInScreenCoords.bottom) {
571                     scrollY = bottom - magnifFrameInScreenCoords.bottom + offsetViewportY;
572                 } else {
573                     scrollY = 0;
574                 }
575 
576                 final float scale = getScale();
577                 offsetMagnifiedRegion(scrollX * scale, scrollY * scale, INVALID_SERVICE_ID);
578             }
579         }
580 
getMagnifiedFrameInContentCoordsLocked(Rect outFrame)581         void getMagnifiedFrameInContentCoordsLocked(Rect outFrame) {
582             final float scale = getSentScale();
583             final float offsetX = getSentOffsetX();
584             final float offsetY = getSentOffsetY();
585             getMagnificationBounds(outFrame);
586             outFrame.offset((int) -offsetX, (int) -offsetY);
587             outFrame.scale(1.0f / scale);
588         }
589 
590         @GuardedBy("mLock")
setActivated(boolean activated)591         private boolean setActivated(boolean activated) {
592             if (DEBUG) {
593                 Slog.i(LOG_TAG, "setActivated(activated = " + activated + ")");
594             }
595 
596             final boolean changed = (mMagnificationActivated != activated);
597 
598             if (changed) {
599                 mMagnificationActivated = activated;
600                 mMagnificationInfoChangedCallbacks.forEach(callback -> {
601                     callback.onFullScreenMagnificationActivationState(
602                             mDisplayId, mMagnificationActivated);
603                 });
604                 mControllerCtx.getWindowManager().setFullscreenMagnificationActivated(
605                         mDisplayId, activated);
606             }
607 
608             return changed;
609         }
610 
611         /**
612          * Directly Zooms out the scale to 1f with animating the transition. This method is
613          * triggered only by service automatically, such as when user context changed.
614          */
zoomOutFromService()615         void zoomOutFromService() {
616             setScaleAndCenter(1.0f, Float.NaN, Float.NaN,
617                     transformToStubCallback(true),
618                     AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
619             mZoomedOutFromService = true;
620         }
621 
622         /**
623          * Whether the zooming out is triggered by {@link #zoomOutFromService}.
624          */
isZoomedOutFromService()625         boolean isZoomedOutFromService() {
626             return mZoomedOutFromService;
627         }
628 
629         @GuardedBy("mLock")
reset(boolean animate)630         boolean reset(boolean animate) {
631             return reset(transformToStubCallback(animate));
632         }
633 
634         @GuardedBy("mLock")
reset(MagnificationAnimationCallback animationCallback)635         boolean reset(MagnificationAnimationCallback animationCallback) {
636             if (!mRegistered) {
637                 return false;
638             }
639             final MagnificationSpec spec = mCurrentMagnificationSpec;
640             final boolean changed = isActivated();
641             setActivated(false);
642             if (changed) {
643                 spec.clear();
644                 onMagnificationChangedLocked();
645             }
646             mIdOfLastServiceToMagnify = INVALID_SERVICE_ID;
647             sendSpecToAnimation(spec, animationCallback);
648 
649             hideThumbnail();
650 
651             return changed;
652         }
653 
654         @GuardedBy("mLock")
setScale(float scale, float pivotX, float pivotY, boolean animate, int id)655         boolean setScale(float scale, float pivotX, float pivotY,
656                 boolean animate, int id) {
657             if (!mRegistered) {
658                 return false;
659             }
660             // Constrain scale immediately for use in the pivot calculations.
661             scale = MagnificationScaleProvider.constrainScale(scale);
662 
663             final Rect viewport = mTempRect;
664             mMagnificationRegion.getBounds(viewport);
665             final MagnificationSpec spec = mCurrentMagnificationSpec;
666             final float oldScale = spec.scale;
667             final float oldCenterX =
668                     (viewport.width() / 2.0f - spec.offsetX + viewport.left) / oldScale;
669             final float oldCenterY =
670                     (viewport.height() / 2.0f - spec.offsetY + viewport.top) / oldScale;
671             final float normPivotX = (pivotX - spec.offsetX) / oldScale;
672             final float normPivotY = (pivotY - spec.offsetY) / oldScale;
673             final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
674             final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
675             final float centerX = normPivotX + offsetX;
676             final float centerY = normPivotY + offsetY;
677             mIdOfLastServiceToMagnify = id;
678             return setScaleAndCenter(scale, centerX, centerY, transformToStubCallback(animate), id);
679         }
680 
681         @GuardedBy("mLock")
setScaleAndCenter(float scale, float centerX, float centerY, MagnificationAnimationCallback animationCallback, int id)682         boolean setScaleAndCenter(float scale, float centerX, float centerY,
683                 MagnificationAnimationCallback animationCallback, int id) {
684             if (!mRegistered) {
685                 return false;
686             }
687             // If the border implementation is on system ui side but the connection is not
688             // established, the fullscreen magnification should not work.
689             if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
690                     && !mMagnificationConnectionStateSupplier.get()) {
691                 return false;
692             }
693             if (DEBUG) {
694                 Slog.i(LOG_TAG,
695                         "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
696                                 + ", centerY = " + centerY + ", endCallback = "
697                                 + animationCallback + ", id = " + id + ")");
698             }
699             boolean changed = setActivated(true);
700             changed |= updateMagnificationSpecLocked(scale, centerX, centerY);
701             sendSpecToAnimation(mCurrentMagnificationSpec, animationCallback);
702             if (isActivated() && (id != INVALID_SERVICE_ID)) {
703                 mIdOfLastServiceToMagnify = id;
704                 mMagnificationInfoChangedCallbacks.forEach(callback -> {
705                     callback.onRequestMagnificationSpec(mDisplayId,
706                             mIdOfLastServiceToMagnify);
707                 });
708             }
709             // the zoom scale would be changed so we reset the flag
710             mZoomedOutFromService = false;
711             return changed;
712         }
713 
714         @GuardedBy("mLock")
updateThumbnail(float scale, float centerX, float centerY)715         void updateThumbnail(float scale, float centerX, float centerY) {
716             if (mMagnificationThumbnail != null) {
717                 mMagnificationThumbnail.updateThumbnail(scale, centerX, centerY);
718             }
719         }
720 
721         @GuardedBy("mLock")
refreshThumbnail()722         void refreshThumbnail() {
723             if (mMagnificationThumbnail != null) {
724                 mMagnificationThumbnail.setThumbnailBounds(
725                         mMagnificationBounds,
726                         getScale(),
727                         getCenterX(),
728                         getCenterY()
729                 );
730             }
731         }
732 
733         @GuardedBy("mLock")
hideThumbnail()734         void hideThumbnail() {
735             if (mMagnificationThumbnail != null) {
736                 mMagnificationThumbnail.hideThumbnail();
737             }
738         }
739 
740         @GuardedBy("mLock")
createThumbnailIfSupported()741         void createThumbnailIfSupported() {
742             if (mMagnificationThumbnail == null) {
743                 mMagnificationThumbnail = mThumbnailSupplier.get();
744                 // We call refreshThumbnail when the thumbnail is just created to set current
745                 // magnification bounds to thumbnail. It to prevent the thumbnail size has not yet
746                 // updated properly and thus shows with huge size. (b/276314641)
747                 refreshThumbnail();
748             }
749         }
750 
751         @GuardedBy("mLock")
destroyThumbnail()752         void destroyThumbnail() {
753             if (mMagnificationThumbnail != null) {
754                 hideThumbnail();
755                 mMagnificationThumbnail = null;
756             }
757         }
758 
onThumbnailFeatureFlagChanged()759         void onThumbnailFeatureFlagChanged() {
760             synchronized (mLock) {
761                 destroyThumbnail();
762                 createThumbnailIfSupported();
763             }
764         }
765 
766         /**
767          * Updates the current magnification spec.
768          *
769          * @param scale the magnification scale
770          * @param centerX the unscaled, screen-relative X coordinate of the center
771          *                of the viewport, or {@link Float#NaN} to leave unchanged
772          * @param centerY the unscaled, screen-relative Y coordinate of the center
773          *                of the viewport, or {@link Float#NaN} to leave unchanged
774          * @return {@code true} if the magnification spec changed or {@code false}
775          *         otherwise
776          */
updateMagnificationSpecLocked(float scale, float centerX, float centerY)777         boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY) {
778             // Handle defaults.
779             if (Float.isNaN(centerX)) {
780                 centerX = getCenterX();
781             }
782             if (Float.isNaN(centerY)) {
783                 centerY = getCenterY();
784             }
785             if (Float.isNaN(scale)) {
786                 scale = getScale();
787             }
788 
789             // Compute changes.
790             boolean changed = false;
791 
792             final float normScale = MagnificationScaleProvider.constrainScale(scale);
793             if (Float.compare(mCurrentMagnificationSpec.scale, normScale) != 0) {
794                 mCurrentMagnificationSpec.scale = normScale;
795                 changed = true;
796             }
797 
798             final float nonNormOffsetX = mMagnificationBounds.width() / 2.0f
799                     + mMagnificationBounds.left - centerX * normScale;
800             final float nonNormOffsetY = mMagnificationBounds.height() / 2.0f
801                     + mMagnificationBounds.top - centerY * normScale;
802             changed |= updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY);
803 
804             if (changed) {
805                 onMagnificationChangedLocked();
806             }
807 
808             return changed;
809         }
810 
811         @GuardedBy("mLock")
offsetMagnifiedRegion(float offsetX, float offsetY, int id)812         void offsetMagnifiedRegion(float offsetX, float offsetY, int id) {
813             if (!mRegistered) {
814                 return;
815             }
816 
817             final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
818             final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
819             if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
820                 onMagnificationChangedLocked();
821             }
822             if (id != INVALID_SERVICE_ID) {
823                 mIdOfLastServiceToMagnify = id;
824             }
825             sendSpecToAnimation(mCurrentMagnificationSpec, null);
826         }
827 
828         @GuardedBy("mLock")
startFling(float xPixelsPerSecond, float yPixelsPerSecond, int id)829         void startFling(float xPixelsPerSecond, float yPixelsPerSecond, int id) {
830             if (!mRegistered) {
831                 return;
832             }
833             if (!isActivated()) {
834                 return;
835             }
836 
837             if (id != INVALID_SERVICE_ID) {
838                 mIdOfLastServiceToMagnify = id;
839             }
840 
841             startFlingAnimation(
842                     xPixelsPerSecond,
843                     yPixelsPerSecond,
844                     new MagnificationAnimationCallback() {
845                         @Override
846                         public void onResult(boolean success) {
847                             // never called
848                         }
849 
850                         @Override
851                         public void onResult(boolean success, MagnificationSpec lastSpecSent) {
852                             if (DEBUG) {
853                                 Slog.i(
854                                         LOG_TAG,
855                                         "startFlingAnimation finished( "
856                                                 + success
857                                                 + " = "
858                                                 + lastSpecSent.offsetX
859                                                 + ", "
860                                                 + lastSpecSent.offsetY
861                                                 + ")");
862                             }
863                             synchronized (mLock) {
864                                 mCurrentMagnificationSpec.setTo(lastSpecSent);
865                                 onMagnificationChangedLocked();
866                             }
867                         }
868                     });
869         }
870 
871 
872         @GuardedBy("mLock")
cancelFling(int id)873         void cancelFling(int id) {
874             if (!mRegistered) {
875                 return;
876             }
877 
878             if (id != INVALID_SERVICE_ID) {
879                 mIdOfLastServiceToMagnify = id;
880             }
881 
882             cancelFlingAnimation();
883         }
884 
updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY)885         boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
886             if (DEBUG) {
887                 Slog.i(LOG_TAG,
888                         "updateCurrentSpecWithOffsetsLocked(nonNormOffsetX = " + nonNormOffsetX
889                                 + ", nonNormOffsetY = " + nonNormOffsetY + ")");
890             }
891             boolean changed = false;
892             final float offsetX = MathUtils.constrain(
893                     nonNormOffsetX, getMinOffsetXLocked(), getMaxOffsetXLocked());
894             if (Float.compare(mCurrentMagnificationSpec.offsetX, offsetX) != 0) {
895                 mCurrentMagnificationSpec.offsetX = offsetX;
896                 changed = true;
897             }
898             final float offsetY = MathUtils.constrain(
899                     nonNormOffsetY, getMinOffsetYLocked(), getMaxOffsetYLocked());
900             if (Float.compare(mCurrentMagnificationSpec.offsetY, offsetY) != 0) {
901                 mCurrentMagnificationSpec.offsetY = offsetY;
902                 changed = true;
903             }
904             return changed;
905         }
906 
getMinOffsetXLocked()907         float getMinOffsetXLocked() {
908             final float viewportWidth = mMagnificationBounds.width();
909             final float viewportLeft = mMagnificationBounds.left;
910             return (viewportLeft + viewportWidth)
911                     - (viewportLeft + viewportWidth) * mCurrentMagnificationSpec.scale;
912         }
913 
getMaxOffsetXLocked()914         float getMaxOffsetXLocked() {
915             return mMagnificationBounds.left
916                     - mMagnificationBounds.left * mCurrentMagnificationSpec.scale;
917         }
918 
getMinOffsetYLocked()919         float getMinOffsetYLocked() {
920             final float viewportHeight = mMagnificationBounds.height();
921             final float viewportTop = mMagnificationBounds.top;
922             return (viewportTop + viewportHeight)
923                     - (viewportTop + viewportHeight) * mCurrentMagnificationSpec.scale;
924         }
925 
getMaxOffsetYLocked()926         float getMaxOffsetYLocked() {
927             return mMagnificationBounds.top
928                     - mMagnificationBounds.top * mCurrentMagnificationSpec.scale;
929         }
930 
931         @Override
toString()932         public String toString() {
933             return "DisplayMagnification["
934                     + "mCurrentMagnificationSpec=" + mCurrentMagnificationSpec
935                     + ", mMagnificationRegion=" + mMagnificationRegion
936                     + ", mMagnificationBounds=" + mMagnificationBounds
937                     + ", mDisplayId=" + mDisplayId
938                     + ", mIdOfLastServiceToMagnify=" + mIdOfLastServiceToMagnify
939                     + ", mRegistered=" + mRegistered
940                     + ", mUnregisterPending=" + mUnregisterPending
941                     + ']';
942         }
943     }
944 
945     /**
946      * FullScreenMagnificationController Constructor
947      */
FullScreenMagnificationController(@onNull Context context, @NonNull AccessibilityTraceManager traceManager, @NonNull Object lock, @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback, @NonNull MagnificationScaleProvider scaleProvider, @NonNull Executor backgroundExecutor, @NonNull Supplier<Boolean> magnificationConnectionStateSupplier)948     public FullScreenMagnificationController(@NonNull Context context,
949             @NonNull AccessibilityTraceManager traceManager, @NonNull Object lock,
950             @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
951             @NonNull MagnificationScaleProvider scaleProvider,
952             @NonNull Executor backgroundExecutor,
953             @NonNull Supplier<Boolean> magnificationConnectionStateSupplier) {
954         this(
955                 new ControllerContext(
956                         context,
957                         traceManager,
958                         LocalServices.getService(WindowManagerInternal.class),
959                         new Handler(context.getMainLooper()),
960                         context.getResources().getInteger(R.integer.config_longAnimTime)),
961                 lock,
962                 magnificationInfoChangedCallback,
963                 scaleProvider,
964                 /* thumbnailSupplier= */ null,
965                 backgroundExecutor,
966                 () -> new Scroller(context),
967                 TimeAnimator::new,
968                 magnificationConnectionStateSupplier);
969     }
970 
971     /** Constructor for tests */
972     @VisibleForTesting
FullScreenMagnificationController( @onNull ControllerContext ctx, @NonNull Object lock, @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback, @NonNull MagnificationScaleProvider scaleProvider, Supplier<MagnificationThumbnail> thumbnailSupplier, @NonNull Executor backgroundExecutor, Supplier<Scroller> scrollerSupplier, Supplier<TimeAnimator> timeAnimatorSupplier, @NonNull Supplier<Boolean> magnificationConnectionStateSupplier)973     public FullScreenMagnificationController(
974             @NonNull ControllerContext ctx,
975             @NonNull Object lock,
976             @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
977             @NonNull MagnificationScaleProvider scaleProvider,
978             Supplier<MagnificationThumbnail> thumbnailSupplier,
979             @NonNull Executor backgroundExecutor,
980             Supplier<Scroller> scrollerSupplier,
981             Supplier<TimeAnimator> timeAnimatorSupplier,
982             @NonNull Supplier<Boolean> magnificationConnectionStateSupplier) {
983         mControllerCtx = ctx;
984         mLock = lock;
985         mScrollerSupplier = scrollerSupplier;
986         mTimeAnimatorSupplier = timeAnimatorSupplier;
987         mMagnificationConnectionStateSupplier = magnificationConnectionStateSupplier;
988         mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
989         mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
990         addInfoChangedCallback(magnificationInfoChangedCallback);
991         mScaleProvider = scaleProvider;
992         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
993         mMagnificationThumbnailFeatureFlag = new MagnificationThumbnailFeatureFlag();
994         mMagnificationThumbnailFeatureFlag.addOnChangedListener(
995                 backgroundExecutor, this::onMagnificationThumbnailFeatureFlagChanged);
996         if (thumbnailSupplier != null) {
997             mThumbnailSupplier = thumbnailSupplier;
998         } else {
999             mThumbnailSupplier = () -> {
1000                 if (mMagnificationThumbnailFeatureFlag.isFeatureFlagEnabled()) {
1001                     return new MagnificationThumbnail(
1002                             ctx.getContext(),
1003                             ctx.getContext().getSystemService(WindowManager.class),
1004                             new Handler(ctx.getContext().getMainLooper())
1005                     );
1006                 }
1007                 return null;
1008             };
1009         }
1010     }
1011 
onMagnificationThumbnailFeatureFlagChanged()1012     private void onMagnificationThumbnailFeatureFlagChanged() {
1013         synchronized (mLock) {
1014             for (int i = 0; i < mDisplays.size(); i++) {
1015                 onMagnificationThumbnailFeatureFlagChanged(mDisplays.keyAt(i));
1016             }
1017         }
1018     }
1019 
onMagnificationThumbnailFeatureFlagChanged(int displayId)1020     private void onMagnificationThumbnailFeatureFlagChanged(int displayId) {
1021         synchronized (mLock) {
1022             final DisplayMagnification display = mDisplays.get(displayId);
1023             if (display == null) {
1024                 return;
1025             }
1026             display.onThumbnailFeatureFlagChanged();
1027         }
1028     }
1029 
1030     /**
1031      * Start tracking the magnification region for services that control magnification and the
1032      * magnification gesture handler.
1033      *
1034      * This tracking imposes a cost on the system, so we avoid tracking this data unless it's
1035      * required.
1036      *
1037      * @param displayId The logical display id.
1038      */
register(int displayId)1039     public void register(int displayId) {
1040         synchronized (mLock) {
1041             DisplayMagnification display = mDisplays.get(displayId);
1042             if (display == null) {
1043                 display = new DisplayMagnification(displayId);
1044             }
1045             if (display.isRegistered()) {
1046                 return;
1047             }
1048             if (display.register()) {
1049                 mDisplays.put(displayId, display);
1050                 mScreenStateObserver.registerIfNecessary();
1051             }
1052         }
1053     }
1054 
1055     /**
1056      * Stop requiring tracking the magnification region. We may remain registered while we
1057      * reset magnification.
1058      *
1059      * @param displayId The logical display id.
1060      */
unregister(int displayId)1061     public void unregister(int displayId) {
1062         synchronized (mLock) {
1063             unregisterLocked(displayId, false);
1064         }
1065     }
1066 
1067     /**
1068      * Stop tracking all displays' magnification region.
1069      */
unregisterAll()1070     public void unregisterAll() {
1071         synchronized (mLock) {
1072             // display will be removed from array after unregister, we need to clone it to
1073             // prevent error.
1074             final SparseArray<DisplayMagnification> displays = mDisplays.clone();
1075             for (int i = 0; i < displays.size(); i++) {
1076                 unregisterLocked(displays.keyAt(i), false);
1077             }
1078         }
1079     }
1080 
1081     @Override
onRectangleOnScreenRequested(int displayId, int left, int top, int right, int bottom)1082     public void onRectangleOnScreenRequested(int displayId, int left, int top, int right,
1083             int bottom) {
1084         synchronized (mLock) {
1085             if (!mMagnificationFollowTypingEnabled) {
1086                 return;
1087             }
1088             final DisplayMagnification display = mDisplays.get(displayId);
1089             if (display == null) {
1090                 return;
1091             }
1092             if (!display.isActivated()) {
1093                 return;
1094             }
1095             final Rect magnifiedRegionBounds = mTempRect;
1096             display.getMagnifiedFrameInContentCoordsLocked(magnifiedRegionBounds);
1097             if (magnifiedRegionBounds.contains(left, top, right, bottom)) {
1098                 return;
1099             }
1100             display.onRectangleOnScreenRequested(left, top, right, bottom);
1101         }
1102     }
1103 
setMagnificationFollowTypingEnabled(boolean enabled)1104     void setMagnificationFollowTypingEnabled(boolean enabled) {
1105         mMagnificationFollowTypingEnabled = enabled;
1106     }
1107 
isMagnificationFollowTypingEnabled()1108     boolean isMagnificationFollowTypingEnabled() {
1109         return mMagnificationFollowTypingEnabled;
1110     }
1111 
setAlwaysOnMagnificationEnabled(boolean enabled)1112     void setAlwaysOnMagnificationEnabled(boolean enabled) {
1113         mAlwaysOnMagnificationEnabled = enabled;
1114     }
1115 
isAlwaysOnMagnificationEnabled()1116     boolean isAlwaysOnMagnificationEnabled() {
1117         return mAlwaysOnMagnificationEnabled;
1118     }
1119 
1120     /**
1121      * if the magnifier with given displayId is activated:
1122      * 1. if {@link #isAlwaysOnMagnificationEnabled()}, zoom the magnifier to 100%,
1123      * 2. otherwise, reset the magnification.
1124      *
1125      * @param displayId The logical display id.
1126      */
onUserContextChanged(int displayId)1127     void onUserContextChanged(int displayId) {
1128         synchronized (mLock) {
1129             if (!isActivated(displayId)) {
1130                 return;
1131             }
1132 
1133             if (isAlwaysOnMagnificationEnabled()) {
1134                 zoomOutFromService(displayId);
1135             } else {
1136                 reset(displayId, true);
1137             }
1138         }
1139     }
1140 
1141     /**
1142      * Remove the display magnification with given id.
1143      *
1144      * @param displayId The logical display id.
1145      */
onDisplayRemoved(int displayId)1146     public void onDisplayRemoved(int displayId) {
1147         synchronized (mLock) {
1148             unregisterLocked(displayId, true);
1149         }
1150     }
1151 
1152     /**
1153      * Check if we are registered on specified display. Note that we may be planning to unregister
1154      * at any moment.
1155      *
1156      * @return {@code true} if the controller is registered on specified display.
1157      * {@code false} otherwise.
1158      *
1159      * @param displayId The logical display id.
1160      */
isRegistered(int displayId)1161     public boolean isRegistered(int displayId) {
1162         synchronized (mLock) {
1163             final DisplayMagnification display = mDisplays.get(displayId);
1164             if (display == null) {
1165                 return false;
1166             }
1167             return display.isRegistered();
1168         }
1169     }
1170 
1171     /**
1172      * @param displayId The logical display id.
1173      * @return {@code true} if magnification is activated,
1174      *         {@code false} otherwise
1175      */
isActivated(int displayId)1176     public boolean isActivated(int displayId) {
1177         synchronized (mLock) {
1178             final DisplayMagnification display = mDisplays.get(displayId);
1179             if (display == null) {
1180                 return false;
1181             }
1182             return display.isActivated();
1183         }
1184     }
1185 
1186     /**
1187      * Returns whether the magnification region contains the specified
1188      * screen-relative coordinates.
1189      *
1190      * @param displayId The logical display id.
1191      * @param x the screen-relative X coordinate to check
1192      * @param y the screen-relative Y coordinate to check
1193      * @return {@code true} if the coordinate is contained within the
1194      *         magnified region, or {@code false} otherwise
1195      */
magnificationRegionContains(int displayId, float x, float y)1196     public boolean magnificationRegionContains(int displayId, float x, float y) {
1197         synchronized (mLock) {
1198             final DisplayMagnification display = mDisplays.get(displayId);
1199             if (display == null) {
1200                 return false;
1201             }
1202             return display.magnificationRegionContains(x, y);
1203         }
1204     }
1205 
1206     /**
1207      * Populates the specified rect with the screen-relative bounds of the
1208      * magnification region. If magnification is not enabled, the returned
1209      * bounds will be empty.
1210      *
1211      * @param displayId The logical display id.
1212      * @param outBounds rect to populate with the bounds of the magnified
1213      *                  region
1214      */
getMagnificationBounds(int displayId, @NonNull Rect outBounds)1215     public void getMagnificationBounds(int displayId, @NonNull Rect outBounds) {
1216         synchronized (mLock) {
1217             final DisplayMagnification display = mDisplays.get(displayId);
1218             if (display == null) {
1219                 return;
1220             }
1221             display.getMagnificationBounds(outBounds);
1222         }
1223     }
1224 
1225     /**
1226      * Populates the specified region with the screen-relative magnification
1227      * region. If magnification is not enabled, then the returned region
1228      * will be empty.
1229      *
1230      * @param displayId The logical display id.
1231      * @param outRegion the region to populate
1232      */
getMagnificationRegion(int displayId, @NonNull Region outRegion)1233     public void getMagnificationRegion(int displayId, @NonNull Region outRegion) {
1234         synchronized (mLock) {
1235             final DisplayMagnification display = mDisplays.get(displayId);
1236             if (display == null) {
1237                 return;
1238             }
1239             display.getMagnificationRegion(outRegion);
1240         }
1241     }
1242 
1243     /**
1244      * Returns the magnification scale. If an animation is in progress,
1245      * this reflects the end state of the animation.
1246      *
1247      * @param displayId The logical display id.
1248      * @return the scale
1249      */
getScale(int displayId)1250     public float getScale(int displayId) {
1251         synchronized (mLock) {
1252             final DisplayMagnification display = mDisplays.get(displayId);
1253             if (display == null) {
1254                 return 1.0f;
1255             }
1256             return display.getScale();
1257         }
1258     }
1259 
getLastActivatedScale(int displayId)1260     protected float getLastActivatedScale(int displayId) {
1261         return getScale(displayId);
1262     }
1263 
1264     /**
1265      * Returns the X offset of the magnification viewport. If an animation
1266      * is in progress, this reflects the end state of the animation.
1267      *
1268      * @param displayId The logical display id.
1269      * @return the X offset
1270      */
getOffsetX(int displayId)1271     public float getOffsetX(int displayId) {
1272         synchronized (mLock) {
1273             final DisplayMagnification display = mDisplays.get(displayId);
1274             if (display == null) {
1275                 return 0.0f;
1276             }
1277             return display.getOffsetX();
1278         }
1279     }
1280 
1281     /**
1282      * Returns the screen-relative X coordinate of the center of the
1283      * magnification viewport.
1284      *
1285      * @param displayId The logical display id.
1286      * @return the X coordinate
1287      */
getCenterX(int displayId)1288     public float getCenterX(int displayId) {
1289         synchronized (mLock) {
1290             final DisplayMagnification display = mDisplays.get(displayId);
1291             if (display == null) {
1292                 return 0.0f;
1293             }
1294             return display.getCenterX();
1295         }
1296     }
1297 
1298     /**
1299      * Returns whether the user is at one of the edges (left, right, top, bottom)
1300      * of the magnification viewport
1301      *
1302      * @param displayId The logical display id.
1303      * @return if user is at the edge of the view
1304      */
isAtEdge(int displayId)1305     public boolean isAtEdge(int displayId) {
1306         synchronized (mLock) {
1307             final DisplayMagnification display = mDisplays.get(displayId);
1308             if (display == null) {
1309                 return false;
1310             }
1311             return display.isAtEdge();
1312         }
1313     }
1314 
1315     /**
1316      * Returns whether the user is at the left edge of the viewport
1317      *
1318      * @param displayId The logical display id.
1319      * @param slop The buffer distance in pixels from the left edge within that will be considered
1320      *             to be at the edge.
1321      * @return if user is considered at left edge of view
1322      */
isAtLeftEdge(int displayId, float slop)1323     public boolean isAtLeftEdge(int displayId, float slop) {
1324         synchronized (mLock) {
1325             final DisplayMagnification display = mDisplays.get(displayId);
1326             if (display == null) {
1327                 return false;
1328             }
1329             return display.isAtLeftEdge(slop);
1330         }
1331     }
1332 
1333     /**
1334      * Returns whether the user is at the right edge of the viewport
1335      *
1336      * @param displayId The logical display id.
1337      * @param slop The buffer distance in pixels from the right edge within that will be considered
1338      *             to be at the edge.
1339      * @return if user is considered at right edge of view
1340      */
isAtRightEdge(int displayId, float slop)1341     public boolean isAtRightEdge(int displayId, float slop) {
1342         synchronized (mLock) {
1343             final DisplayMagnification display = mDisplays.get(displayId);
1344             if (display == null) {
1345                 return false;
1346             }
1347             return display.isAtRightEdge(slop);
1348         }
1349     }
1350 
1351     /**
1352      * Returns whether the user is at the top edge of the viewport
1353      *
1354      * @param displayId The logical display id.
1355      * @param slop The buffer distance in pixels from the top edge within that will be considered
1356      *             to be at the edge.
1357      * @return if user is considered at top edge of view
1358      */
isAtTopEdge(int displayId, float slop)1359     public boolean isAtTopEdge(int displayId, float slop) {
1360         synchronized (mLock) {
1361             final DisplayMagnification display = mDisplays.get(displayId);
1362             if (display == null) {
1363                 return false;
1364             }
1365             return display.isAtTopEdge(slop);
1366         }
1367     }
1368 
1369     /**
1370      * Returns whether the user is at the bottom edge of the viewport
1371      *
1372      * @param displayId The logical display id.
1373      * @param slop The buffer distance in pixels from the bottom edge within that will be considered
1374      *             to be at the edge.
1375      * @return if user is considered at bottom edge of view
1376      */
isAtBottomEdge(int displayId, float slop)1377     public boolean isAtBottomEdge(int displayId, float slop) {
1378         synchronized (mLock) {
1379             final DisplayMagnification display = mDisplays.get(displayId);
1380             if (display == null) {
1381                 return false;
1382             }
1383             return display.isAtBottomEdge(slop);
1384         }
1385     }
1386 
1387     /**
1388      * Returns the Y offset of the magnification viewport. If an animation
1389      * is in progress, this reflects the end state of the animation.
1390      *
1391      * @param displayId The logical display id.
1392      * @return the Y offset
1393      */
getOffsetY(int displayId)1394     public float getOffsetY(int displayId) {
1395         synchronized (mLock) {
1396             final DisplayMagnification display = mDisplays.get(displayId);
1397             if (display == null) {
1398                 return 0.0f;
1399             }
1400             return display.getOffsetY();
1401         }
1402     }
1403 
1404     /**
1405      * Returns the screen-relative Y coordinate of the center of the
1406      * magnification viewport.
1407      *
1408      * @param displayId The logical display id.
1409      * @return the Y coordinate
1410      */
getCenterY(int displayId)1411     public float getCenterY(int displayId) {
1412         synchronized (mLock) {
1413             final DisplayMagnification display = mDisplays.get(displayId);
1414             if (display == null) {
1415                 return 0.0f;
1416             }
1417             return display.getCenterY();
1418         }
1419     }
1420 
1421     /**
1422      * Resets the magnification scale and center, optionally animating the
1423      * transition.
1424      *
1425      * @param displayId The logical display id.
1426      * @param animate {@code true} to animate the transition, {@code false}
1427      *                to transition immediately
1428      * @return {@code true} if the magnification spec changed, {@code false} if
1429      *         the spec did not change
1430      */
reset(int displayId, boolean animate)1431     public boolean reset(int displayId, boolean animate) {
1432         return reset(displayId, animate ? STUB_ANIMATION_CALLBACK : null);
1433     }
1434 
1435     /**
1436      * Resets the magnification scale and center, optionally animating the
1437      * transition.
1438      *
1439      * @param displayId The logical display id.
1440      * @param animationCallback Called when the animation result is valid.
1441      *                    {@code null} to transition immediately
1442      * @return {@code true} if the magnification spec changed, {@code false} if
1443      *         the spec did not change
1444      */
reset(int displayId, MagnificationAnimationCallback animationCallback)1445     public boolean reset(int displayId,
1446             MagnificationAnimationCallback animationCallback) {
1447         synchronized (mLock) {
1448             final DisplayMagnification display = mDisplays.get(displayId);
1449             if (display == null) {
1450                 return false;
1451             }
1452             return display.reset(animationCallback);
1453         }
1454     }
1455 
1456     /**
1457      * Scales the magnified region around the specified pivot point,
1458      * optionally animating the transition. If animation is disabled, the
1459      * transition is immediate.
1460      *
1461      * @param displayId The logical display id.
1462      * @param scale the target scale, must be >= 1
1463      * @param pivotX the screen-relative X coordinate around which to scale
1464      * @param pivotY the screen-relative Y coordinate around which to scale
1465      * @param animate {@code true} to animate the transition, {@code false}
1466      *                to transition immediately
1467      * @param id the ID of the service requesting the change
1468      * @return {@code true} if the magnification spec changed, {@code false} if
1469      *         the spec did not change
1470      */
setScale(int displayId, float scale, float pivotX, float pivotY, boolean animate, int id)1471     public boolean setScale(int displayId, float scale, float pivotX, float pivotY,
1472             boolean animate, int id) {
1473         synchronized (mLock) {
1474             final DisplayMagnification display = mDisplays.get(displayId);
1475             if (display == null) {
1476                 return false;
1477             }
1478             return display.setScale(scale, pivotX, pivotY, animate, id);
1479         }
1480     }
1481 
1482     /**
1483      * Sets the center of the magnified region, optionally animating the
1484      * transition. If animation is disabled, the transition is immediate.
1485      *
1486      * @param displayId The logical display id.
1487      * @param centerX the screen-relative X coordinate around which to
1488      *                center
1489      * @param centerY the screen-relative Y coordinate around which to
1490      *                center
1491      * @param animate {@code true} to animate the transition, {@code false}
1492      *                to transition immediately
1493      * @param id      the ID of the service requesting the change
1494      * @return {@code true} if the magnification spec changed, {@code false} if
1495      * the spec did not change
1496      */
setCenter(int displayId, float centerX, float centerY, boolean animate, int id)1497     public boolean setCenter(int displayId, float centerX, float centerY, boolean animate, int id) {
1498         synchronized (mLock) {
1499             final DisplayMagnification display = mDisplays.get(displayId);
1500             if (display == null) {
1501                 return false;
1502             }
1503             return display.setScaleAndCenter(Float.NaN, centerX, centerY,
1504                     animate ? STUB_ANIMATION_CALLBACK : null, id);
1505         }
1506     }
1507 
1508     /**
1509      * Sets the scale and center of the magnified region, optionally
1510      * animating the transition. If animation is disabled, the transition
1511      * is immediate.
1512      *
1513      * @param displayId The logical display id.
1514      * @param scale the target scale, or {@link Float#NaN} to leave unchanged
1515      * @param centerX the screen-relative X coordinate around which to
1516      *                center and scale, or {@link Float#NaN} to leave unchanged
1517      * @param centerY the screen-relative Y coordinate around which to
1518      *                center and scale, or {@link Float#NaN} to leave unchanged
1519      * @param animate {@code true} to animate the transition, {@code false}
1520      *                to transition immediately
1521      * @param id the ID of the service requesting the change
1522      * @return {@code true} if the magnification spec changed, {@code false} if
1523      *         the spec did not change
1524      */
setScaleAndCenter(int displayId, float scale, float centerX, float centerY, boolean animate, int id)1525     public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
1526             boolean animate, int id) {
1527         return setScaleAndCenter(displayId, scale, centerX, centerY,
1528                 transformToStubCallback(animate), id);
1529     }
1530 
1531     /**
1532      * Sets the scale and center of the magnified region, optionally
1533      * animating the transition. If animation is disabled, the transition
1534      * is immediate.
1535      *
1536      * @param displayId The logical display id.
1537      * @param scale the target scale, or {@link Float#NaN} to leave unchanged
1538      * @param centerX the screen-relative X coordinate around which to
1539      *                center and scale, or {@link Float#NaN} to leave unchanged
1540      * @param centerY the screen-relative Y coordinate around which to
1541      *                center and scale, or {@link Float#NaN} to leave unchanged
1542      * @param animationCallback Called when the animation result is valid.
1543      *                           {@code null} to transition immediately
1544      * @param id the ID of the service requesting the change
1545      * @return {@code true} if the magnification spec changed, {@code false} if
1546      *         the spec did not change
1547      */
setScaleAndCenter(int displayId, float scale, float centerX, float centerY, MagnificationAnimationCallback animationCallback, int id)1548     public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
1549             MagnificationAnimationCallback animationCallback, int id) {
1550         synchronized (mLock) {
1551             final DisplayMagnification display = mDisplays.get(displayId);
1552             if (display == null) {
1553                 return false;
1554             }
1555             return display.setScaleAndCenter(scale, centerX, centerY, animationCallback, id);
1556         }
1557     }
1558 
1559     /**
1560      * Offsets the magnified region. Note that the offsetX and offsetY values actually move in the
1561      * opposite direction as the offsets passed in here.
1562      *
1563      * @param displayId The logical display id.
1564      * @param offsetX the amount in pixels to offset the region in the X direction, in current
1565      *                screen pixels.
1566      * @param offsetY the amount in pixels to offset the region in the Y direction, in current
1567      *                screen pixels.
1568      * @param id      the ID of the service requesting the change
1569      */
offsetMagnifiedRegion(int displayId, float offsetX, float offsetY, int id)1570     public void offsetMagnifiedRegion(int displayId, float offsetX, float offsetY, int id) {
1571         synchronized (mLock) {
1572             final DisplayMagnification display = mDisplays.get(displayId);
1573             if (display == null) {
1574                 return;
1575             }
1576             display.offsetMagnifiedRegion(offsetX, offsetY, id);
1577         }
1578     }
1579 
1580     /**
1581      * Call after a pan ends, if the velocity has passed the threshold, to start a fling animation.
1582      *
1583      * @param displayId The logical display id.
1584      * @param xPixelsPerSecond the velocity of the last pan gesture in the X direction, in current
1585      *     screen pixels per second.
1586      * @param yPixelsPerSecond the velocity of the last pan gesture in the Y direction, in current
1587      *     screen pixels per second.
1588      * @param id the ID of the service requesting the change
1589      */
startFling(int displayId, float xPixelsPerSecond, float yPixelsPerSecond, int id)1590     public void startFling(int displayId, float xPixelsPerSecond, float yPixelsPerSecond, int id) {
1591         synchronized (mLock) {
1592             final DisplayMagnification display = mDisplays.get(displayId);
1593             if (display == null) {
1594                 return;
1595             }
1596             display.startFling(xPixelsPerSecond, yPixelsPerSecond, id);
1597         }
1598     }
1599 
1600     /**
1601      * Call to cancel the fling animation if it is running. Call this on any ACTION_DOWN event.
1602      *
1603      * @param displayId The logical display id.
1604      * @param id the ID of the service requesting the change
1605      */
cancelFling(int displayId, int id)1606     public void cancelFling(int displayId, int id) {
1607         synchronized (mLock) {
1608             final DisplayMagnification display = mDisplays.get(displayId);
1609             if (display == null) {
1610                 return;
1611             }
1612             display.cancelFling(id);
1613         }
1614     }
1615 
1616     /**
1617      * Get the ID of the last service that changed the magnification spec.
1618      *
1619      * @param displayId The logical display id.
1620      * @return The id
1621      */
getIdOfLastServiceToMagnify(int displayId)1622     public int getIdOfLastServiceToMagnify(int displayId) {
1623         synchronized (mLock) {
1624             final DisplayMagnification display = mDisplays.get(displayId);
1625             if (display == null) {
1626                 return -1;
1627             }
1628             return display.getIdOfLastServiceToMagnify();
1629         }
1630     }
1631 
1632     /**
1633      * Persists the default display magnification scale to the current user's settings
1634      * <strong>if scale is >= {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}</strong>.
1635      * We assume if the scale is < {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}, there
1636      * will be no obvious magnification effect.
1637      */
persistScale(int displayId)1638     public void persistScale(int displayId) {
1639         final float scale = getScale(Display.DEFAULT_DISPLAY);
1640         if (scale < MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
1641             return;
1642         }
1643         mScaleProvider.putScale(scale, displayId);
1644     }
1645 
1646     /**
1647      * Retrieves a previously persisted magnification scale from the current
1648      * user's settings.
1649      *
1650      * @return the previously persisted magnification scale, or the default
1651      *         scale if none is available
1652      */
getPersistedScale(int displayId)1653     public float getPersistedScale(int displayId) {
1654         return MathUtils.constrain(mScaleProvider.getScale(displayId),
1655                 MagnificationConstants.PERSISTED_SCALE_MIN_VALUE,
1656                 MagnificationScaleProvider.MAX_SCALE);
1657     }
1658 
1659     /**
1660      * Directly Zooms out the scale to 1f with animating the transition. This method is
1661      * triggered only by service automatically, such as when user context changed.
1662      *
1663      * @param displayId The logical display id.
1664      */
zoomOutFromService(int displayId)1665     private void zoomOutFromService(int displayId) {
1666         synchronized (mLock) {
1667             final DisplayMagnification display = mDisplays.get(displayId);
1668             if (display == null || !display.isActivated()) {
1669                 return;
1670             }
1671             display.zoomOutFromService();
1672         }
1673     }
1674 
1675     /**
1676      * Whether the magnification is zoomed out by {@link #zoomOutFromService(int)}.
1677      *
1678      * @param displayId The logical display id.
1679      */
isZoomedOutFromService(int displayId)1680     public boolean isZoomedOutFromService(int displayId) {
1681         synchronized (mLock) {
1682             final DisplayMagnification display = mDisplays.get(displayId);
1683             if (display == null || !display.isActivated()) {
1684                 return false;
1685             }
1686             return display.isZoomedOutFromService();
1687         }
1688     }
1689 
1690     /**
1691      * Resets all displays' magnification if last magnifying service is disabled.
1692      *
1693      * @param connectionId
1694      */
resetAllIfNeeded(int connectionId)1695     public void resetAllIfNeeded(int connectionId) {
1696         synchronized (mLock) {
1697             for (int i = 0; i < mDisplays.size(); i++) {
1698                 resetIfNeeded(mDisplays.keyAt(i), connectionId);
1699             }
1700         }
1701     }
1702 
1703     /**
1704      * Resets magnification if magnification and auto-update are both enabled.
1705      *
1706      * @param displayId The logical display id.
1707      * @param animate whether the animate the transition
1708      * @return whether was {@link #isActivated(int)}  activated}
1709      */
resetIfNeeded(int displayId, boolean animate)1710     boolean resetIfNeeded(int displayId, boolean animate) {
1711         synchronized (mLock) {
1712             final DisplayMagnification display = mDisplays.get(displayId);
1713             if (display == null || !display.isActivated()) {
1714                 return false;
1715             }
1716             display.reset(animate);
1717             return true;
1718         }
1719     }
1720 
1721     /**
1722      * Resets magnification if last magnifying service is disabled.
1723      *
1724      * @param displayId The logical display id.
1725      * @param connectionId the connection ID be disabled.
1726      * @return {@code true} on success, {@code false} on failure
1727      */
resetIfNeeded(int displayId, int connectionId)1728     boolean resetIfNeeded(int displayId, int connectionId) {
1729         synchronized (mLock) {
1730             final DisplayMagnification display = mDisplays.get(displayId);
1731             if (display == null || !display.isActivated()
1732                     || connectionId != display.getIdOfLastServiceToMagnify()) {
1733                 return false;
1734             }
1735             display.reset(true);
1736             return true;
1737         }
1738     }
1739 
1740     /**
1741      * Notifies that the IME window visibility changed.
1742      *
1743      * @param displayId the logical display id
1744      * @param shown {@code true} means the IME window shows on the screen. Otherwise it's
1745      *                           hidden.
1746      */
notifyImeWindowVisibilityChanged(int displayId, boolean shown)1747     void notifyImeWindowVisibilityChanged(int displayId, boolean shown) {
1748         synchronized (mLock) {
1749             mMagnificationInfoChangedCallbacks.forEach(callback -> {
1750                 callback.onImeWindowVisibilityChanged(displayId, shown);
1751             });
1752         }
1753     }
1754 
onScreenTurnedOff()1755     private void onScreenTurnedOff() {
1756         final Message m = PooledLambda.obtainMessage(
1757                 FullScreenMagnificationController::resetAllIfNeeded, this, false);
1758         mControllerCtx.getHandler().sendMessage(m);
1759     }
1760 
1761     /**
1762      * Resets magnification on all displays.
1763      * @param animate reset the magnification with animation
1764      */
resetAllIfNeeded(boolean animate)1765     void resetAllIfNeeded(boolean animate) {
1766         synchronized (mLock) {
1767             for (int i = 0; i < mDisplays.size(); i++) {
1768                 resetIfNeeded(mDisplays.keyAt(i), animate);
1769             }
1770         }
1771     }
1772 
unregisterLocked(int displayId, boolean delete)1773     private void unregisterLocked(int displayId, boolean delete) {
1774         final DisplayMagnification display = mDisplays.get(displayId);
1775         if (display == null) {
1776             return;
1777         }
1778         if (!display.isRegistered()) {
1779             if (delete) {
1780                 mDisplays.remove(displayId);
1781             }
1782             return;
1783         }
1784         if (!display.isActivated()) {
1785             display.unregister(delete);
1786         } else {
1787             display.unregisterPending(delete);
1788         }
1789     }
1790 
1791     /**
1792      * Callbacks from DisplayMagnification after display magnification unregistered. It will remove
1793      * DisplayMagnification instance if delete is true, and unregister screen state if
1794      * there is no registered display magnification.
1795      */
unregisterCallbackLocked(int displayId, boolean delete)1796     private void unregisterCallbackLocked(int displayId, boolean delete) {
1797         if (delete) {
1798             mDisplays.remove(displayId);
1799         }
1800         // unregister screen state if necessary
1801         boolean hasRegister = false;
1802         for (int i = 0; i < mDisplays.size(); i++) {
1803             final DisplayMagnification display = mDisplays.valueAt(i);
1804             hasRegister = display.isRegistered();
1805             if (hasRegister) {
1806                 break;
1807             }
1808         }
1809         if (!hasRegister) {
1810             mScreenStateObserver.unregister();
1811         }
1812     }
1813 
addInfoChangedCallback(@onNull MagnificationInfoChangedCallback callback)1814     void addInfoChangedCallback(@NonNull MagnificationInfoChangedCallback callback) {
1815         synchronized (mLock) {
1816             mMagnificationInfoChangedCallbacks.add(callback);
1817         }
1818     }
1819 
removeInfoChangedCallback(@onNull MagnificationInfoChangedCallback callback)1820     void removeInfoChangedCallback(@NonNull MagnificationInfoChangedCallback callback) {
1821         synchronized (mLock) {
1822             mMagnificationInfoChangedCallbacks.remove(callback);
1823         }
1824     }
1825 
traceEnabled()1826     private boolean traceEnabled() {
1827         return mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes(
1828                 FLAGS_WINDOW_MANAGER_INTERNAL);
1829     }
1830 
logTrace(String methodName, String params)1831     private void logTrace(String methodName, String params) {
1832         mControllerCtx.getTraceManager().logTrace(
1833                 "WindowManagerInternal." + methodName, FLAGS_WINDOW_MANAGER_INTERNAL, params);
1834     }
1835 
1836     @Override
toString()1837     public String toString() {
1838         StringBuilder builder = new StringBuilder();
1839         builder.append("MagnificationController[");
1840         builder.append(", mDisplays=").append(mDisplays);
1841         builder.append(", mScaleProvider=").append(mScaleProvider);
1842         builder.append("]");
1843         return builder.toString();
1844     }
1845 
1846     /**
1847      * Class responsible for animating spec on the main thread and sending spec
1848      * updates to the window manager.
1849      */
1850     private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener,
1851             Animator.AnimatorListener {
1852         private final ControllerContext mControllerCtx;
1853 
1854         /**
1855          * The magnification spec that was sent to the window manager. This should
1856          * only be accessed with the lock held.
1857          */
1858         private final MagnificationSpec mSentMagnificationSpec = new MagnificationSpec();
1859 
1860         private final MagnificationSpec mStartMagnificationSpec = new MagnificationSpec();
1861 
1862         private final MagnificationSpec mEndMagnificationSpec = new MagnificationSpec();
1863 
1864         /**
1865          * The animator should only be accessed and modified on the main (e.g. animation) thread.
1866          */
1867         private final ValueAnimator mValueAnimator;
1868 
1869         // Called when the callee wants animating and the sent spec matches the target spec.
1870         private MagnificationAnimationCallback mAnimationCallback;
1871         private final Object mLock;
1872 
1873         private final int mDisplayId;
1874 
1875         @GuardedBy("mLock")
1876         private boolean mEnabled = false;
1877 
1878         private final Scroller mScroller;
1879         private final TimeAnimator mScrollAnimator;
1880 
SpecAnimationBridge( ControllerContext ctx, Object lock, int displayId, Supplier<Scroller> scrollerSupplier, Supplier<TimeAnimator> timeAnimatorSupplier)1881         private SpecAnimationBridge(
1882                 ControllerContext ctx,
1883                 Object lock,
1884                 int displayId,
1885                 Supplier<Scroller> scrollerSupplier,
1886                 Supplier<TimeAnimator> timeAnimatorSupplier) {
1887             mControllerCtx = ctx;
1888             mLock = lock;
1889             mDisplayId = displayId;
1890             final long animationDuration = mControllerCtx.getAnimationDuration();
1891             mValueAnimator = mControllerCtx.newValueAnimator();
1892             mValueAnimator.setDuration(animationDuration);
1893             mValueAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
1894             mValueAnimator.setFloatValues(0.0f, 1.0f);
1895             mValueAnimator.addUpdateListener(this);
1896             mValueAnimator.addListener(this);
1897 
1898             if (Flags.fullscreenFlingGesture()) {
1899                 mScroller = scrollerSupplier.get();
1900                 mScrollAnimator = timeAnimatorSupplier.get();
1901                 mScrollAnimator.addListener(this);
1902                 mScrollAnimator.setTimeListener(
1903                         (animation, totalTime, deltaTime) -> {
1904                             synchronized (mLock) {
1905                                 if (DEBUG) {
1906                                     Slog.v(
1907                                             LOG_TAG,
1908                                             "onScrollAnimationUpdate: "
1909                                                     + mEnabled + " : " + totalTime);
1910                                 }
1911 
1912                                 if (mEnabled) {
1913                                     if (!mScroller.computeScrollOffset()) {
1914                                         animation.end();
1915                                         return;
1916                                     }
1917 
1918                                     mEndMagnificationSpec.offsetX = mScroller.getCurrX();
1919                                     mEndMagnificationSpec.offsetY = mScroller.getCurrY();
1920                                     setMagnificationSpecLocked(mEndMagnificationSpec);
1921                                 }
1922                             }
1923                         });
1924             } else {
1925                 mScroller = null;
1926                 mScrollAnimator = null;
1927             }
1928         }
1929 
1930         /**
1931          * Enabled means the bridge will accept input. When not enabled, the output of the animator
1932          * will be ignored
1933          */
setEnabled(boolean enabled)1934         public void setEnabled(boolean enabled) {
1935             synchronized (mLock) {
1936                 if (enabled != mEnabled) {
1937                     mEnabled = enabled;
1938                     if (!mEnabled) {
1939                         mSentMagnificationSpec.clear();
1940                         if (mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes(
1941                                 FLAGS_WINDOW_MANAGER_INTERNAL)) {
1942                             mControllerCtx.getTraceManager().logTrace(
1943                                     "WindowManagerInternal.setMagnificationSpec",
1944                                     FLAGS_WINDOW_MANAGER_INTERNAL,
1945                                     "displayID=" + mDisplayId + ";spec=" + mSentMagnificationSpec);
1946                         }
1947                         mControllerCtx.getWindowManager().setMagnificationSpec(
1948                                 mDisplayId, mSentMagnificationSpec);
1949                     }
1950                 }
1951             }
1952         }
1953 
1954         @MainThread
updateSentSpecMainThread( MagnificationSpec spec, MagnificationAnimationCallback animationCallback)1955         void updateSentSpecMainThread(
1956                 MagnificationSpec spec, MagnificationAnimationCallback animationCallback) {
1957             cancelAnimations();
1958 
1959             mAnimationCallback = animationCallback;
1960             // If the current and sent specs don't match, update the sent spec.
1961             synchronized (mLock) {
1962                 final boolean changed = !mSentMagnificationSpec.equals(spec);
1963                 if (DEBUG_SET_MAGNIFICATION_SPEC) {
1964                     Slog.d(
1965                             LOG_TAG,
1966                             "updateSentSpecMainThread: " + mEnabled + " : changed: " + changed);
1967                 }
1968                 if (changed) {
1969                     if (mAnimationCallback != null) {
1970                         animateMagnificationSpecLocked(spec);
1971                     } else {
1972                         setMagnificationSpecLocked(spec);
1973                     }
1974                 } else {
1975                     sendEndCallbackMainThread(true);
1976                 }
1977             }
1978         }
1979 
1980         @MainThread
sendEndCallbackMainThread(boolean success)1981         private void sendEndCallbackMainThread(boolean success) {
1982             if (mAnimationCallback != null) {
1983                 if (DEBUG) {
1984                     Slog.d(LOG_TAG, "sendEndCallbackMainThread: " + success);
1985                 }
1986                 mAnimationCallback.onResult(success, mSentMagnificationSpec);
1987                 mAnimationCallback = null;
1988             }
1989         }
1990 
1991         @GuardedBy("mLock")
setMagnificationSpecLocked(MagnificationSpec spec)1992         private void setMagnificationSpecLocked(MagnificationSpec spec) {
1993             if (mEnabled) {
1994                 if (DEBUG_SET_MAGNIFICATION_SPEC) {
1995                     Slog.i(LOG_TAG, "Sending: " + spec);
1996                 }
1997 
1998                 mSentMagnificationSpec.setTo(spec);
1999                 if (mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes(
2000                         FLAGS_WINDOW_MANAGER_INTERNAL)) {
2001                     mControllerCtx.getTraceManager().logTrace(
2002                             "WindowManagerInternal.setMagnificationSpec",
2003                             FLAGS_WINDOW_MANAGER_INTERNAL,
2004                             "displayID=" + mDisplayId + ";spec=" + mSentMagnificationSpec);
2005                 }
2006                 mControllerCtx.getWindowManager().setMagnificationSpec(
2007                         mDisplayId, mSentMagnificationSpec);
2008             }
2009         }
2010 
animateMagnificationSpecLocked(MagnificationSpec toSpec)2011         private void animateMagnificationSpecLocked(MagnificationSpec toSpec) {
2012             mEndMagnificationSpec.setTo(toSpec);
2013             mStartMagnificationSpec.setTo(mSentMagnificationSpec);
2014             mValueAnimator.start();
2015         }
2016 
2017         @Override
onAnimationUpdate(ValueAnimator animation)2018         public void onAnimationUpdate(ValueAnimator animation) {
2019             synchronized (mLock) {
2020                 if (mEnabled) {
2021                     float fract = animation.getAnimatedFraction();
2022                     MagnificationSpec magnificationSpec = new MagnificationSpec();
2023                     magnificationSpec.scale = mStartMagnificationSpec.scale
2024                             + (mEndMagnificationSpec.scale - mStartMagnificationSpec.scale) * fract;
2025                     magnificationSpec.offsetX = mStartMagnificationSpec.offsetX
2026                             + (mEndMagnificationSpec.offsetX - mStartMagnificationSpec.offsetX)
2027                             * fract;
2028                     magnificationSpec.offsetY = mStartMagnificationSpec.offsetY
2029                             + (mEndMagnificationSpec.offsetY - mStartMagnificationSpec.offsetY)
2030                             * fract;
2031                     setMagnificationSpecLocked(magnificationSpec);
2032                 }
2033             }
2034         }
2035 
2036         @Override
onAnimationStart(Animator animation)2037         public void onAnimationStart(Animator animation) {
2038         }
2039 
2040         @Override
onAnimationEnd(Animator animation)2041         public void onAnimationEnd(Animator animation) {
2042             sendEndCallbackMainThread(true);
2043         }
2044 
2045         @Override
onAnimationCancel(Animator animation)2046         public void onAnimationCancel(Animator animation) {
2047             sendEndCallbackMainThread(false);
2048         }
2049 
2050         @Override
onAnimationRepeat(Animator animation)2051         public void onAnimationRepeat(Animator animation) {
2052 
2053         }
2054 
2055         /**
2056          * Call after a pan ends, if the velocity has passed the threshold, to start a fling
2057          * animation.
2058          */
2059         @MainThread
startFlingAnimation( float xPixelsPerSecond, float yPixelsPerSecond, float minX, float maxX, float minY, float maxY, MagnificationAnimationCallback animationCallback )2060         public void startFlingAnimation(
2061                 float xPixelsPerSecond,
2062                 float yPixelsPerSecond,
2063                 float minX,
2064                 float maxX,
2065                 float minY,
2066                 float maxY,
2067                 MagnificationAnimationCallback animationCallback
2068         ) {
2069             if (!Flags.fullscreenFlingGesture()) {
2070                 return;
2071             }
2072             cancelAnimations();
2073 
2074             mAnimationCallback = animationCallback;
2075 
2076             // We use this as a temp object to send updates every animation frame, so make sure it
2077             // matches the current spec before we start.
2078             mEndMagnificationSpec.setTo(mSentMagnificationSpec);
2079 
2080             if (DEBUG) {
2081                 Slog.d(LOG_TAG, "startFlingAnimation: "
2082                         + "offsetX " + mSentMagnificationSpec.offsetX
2083                         + "offsetY " + mSentMagnificationSpec.offsetY
2084                         + "xPixelsPerSecond " + xPixelsPerSecond
2085                         + "yPixelsPerSecond " + yPixelsPerSecond
2086                         + "minX " + minX
2087                         + "maxX " + maxX
2088                         + "minY " + minY
2089                         + "maxY " + maxY
2090                 );
2091             }
2092 
2093             mScroller.fling(
2094                     (int) mSentMagnificationSpec.offsetX,
2095                     (int) mSentMagnificationSpec.offsetY,
2096                     (int) xPixelsPerSecond,
2097                     (int) yPixelsPerSecond,
2098                     (int) minX,
2099                     (int) maxX,
2100                     (int) minY,
2101                     (int) maxY);
2102 
2103             mScrollAnimator.start();
2104         }
2105 
2106         @MainThread
cancelAnimations()2107         void cancelAnimations() {
2108             if (mValueAnimator.isRunning()) {
2109                 mValueAnimator.cancel();
2110             }
2111 
2112             cancelFlingAnimation();
2113         }
2114 
2115         @MainThread
cancelFlingAnimation()2116         void cancelFlingAnimation() {
2117             if (!Flags.fullscreenFlingGesture()) {
2118                 return;
2119             }
2120             if (mScrollAnimator.isRunning()) {
2121                 mScrollAnimator.cancel();
2122             }
2123             mScroller.forceFinished(true);
2124         }
2125     }
2126 
2127     private static class ScreenStateObserver extends BroadcastReceiver {
2128         private final Context mContext;
2129         private final FullScreenMagnificationController mController;
2130         private boolean mRegistered = false;
2131 
ScreenStateObserver(Context context, FullScreenMagnificationController controller)2132         ScreenStateObserver(Context context, FullScreenMagnificationController controller) {
2133             mContext = context;
2134             mController = controller;
2135         }
2136 
registerIfNecessary()2137         public void registerIfNecessary() {
2138             if (!mRegistered) {
2139                 mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
2140                 mRegistered = true;
2141             }
2142         }
2143 
unregister()2144         public void unregister() {
2145             if (mRegistered) {
2146                 mContext.unregisterReceiver(this);
2147                 mRegistered = false;
2148             }
2149         }
2150 
2151         @Override
onReceive(Context context, Intent intent)2152         public void onReceive(Context context, Intent intent) {
2153             mController.onScreenTurnedOff();
2154         }
2155     }
2156 
2157     /**
2158      * This class holds resources used between the classes in MagnificationController, and
2159      * functions for tests to mock it.
2160      */
2161     @VisibleForTesting
2162     public static class ControllerContext {
2163         private final Context mContext;
2164         private final AccessibilityTraceManager mTrace;
2165         private final WindowManagerInternal mWindowManager;
2166         private final Handler mHandler;
2167         private final Long mAnimationDuration;
2168 
2169         /**
2170          * Constructor for ControllerContext.
2171          */
ControllerContext(@onNull Context context, @NonNull AccessibilityTraceManager traceManager, @NonNull WindowManagerInternal windowManager, @NonNull Handler handler, long animationDuration)2172         public ControllerContext(@NonNull Context context,
2173                 @NonNull AccessibilityTraceManager traceManager,
2174                 @NonNull WindowManagerInternal windowManager,
2175                 @NonNull Handler handler,
2176                 long animationDuration) {
2177             mContext = context;
2178             mTrace = traceManager;
2179             mWindowManager = windowManager;
2180             mHandler = handler;
2181             mAnimationDuration = animationDuration;
2182         }
2183 
2184         /**
2185          * @return A context.
2186          */
2187         @NonNull
getContext()2188         public Context getContext() {
2189             return mContext;
2190         }
2191 
2192         /**
2193          * @return AccessibilityTraceManager
2194          */
2195         @NonNull
getTraceManager()2196         public AccessibilityTraceManager getTraceManager() {
2197             return mTrace;
2198         }
2199 
2200         /**
2201          * @return WindowManagerInternal
2202          */
2203         @NonNull
getWindowManager()2204         public WindowManagerInternal getWindowManager() {
2205             return mWindowManager;
2206         }
2207 
2208         /**
2209          * @return Handler for main looper
2210          */
2211         @NonNull
getHandler()2212         public Handler getHandler() {
2213             return mHandler;
2214         }
2215 
2216         /**
2217          * Create a new ValueAnimator.
2218          *
2219          * @return ValueAnimator
2220          */
2221         @NonNull
newValueAnimator()2222         public ValueAnimator newValueAnimator() {
2223             return new ValueAnimator();
2224         }
2225 
2226         /**
2227          * @return Configuration of animation duration.
2228          */
getAnimationDuration()2229         public long getAnimationDuration() {
2230             return mAnimationDuration;
2231         }
2232     }
2233 
2234     @Nullable
transformToStubCallback(boolean animate)2235     private static MagnificationAnimationCallback transformToStubCallback(boolean animate) {
2236         return animate ? STUB_ANIMATION_CALLBACK : null;
2237     }
2238 
2239     interface MagnificationInfoChangedCallback {
2240 
2241         /**
2242          * Called when the {@link MagnificationSpec} is changed with non-default
2243          * scale by the service.
2244          *
2245          * @param displayId the logical display id
2246          * @param serviceId the ID of the service requesting the change
2247          */
onRequestMagnificationSpec(int displayId, int serviceId)2248         void onRequestMagnificationSpec(int displayId, int serviceId);
2249 
2250         /**
2251          * Called when the state of the magnification activation is changed.
2252          *
2253          * @param displayId the logical display id
2254          * @param activated {@code true} if the magnification is activated, otherwise {@code false}.
2255          */
onFullScreenMagnificationActivationState(int displayId, boolean activated)2256         void onFullScreenMagnificationActivationState(int displayId, boolean activated);
2257 
2258         /**
2259          * Called when the IME window visibility changed.
2260          *
2261          * @param displayId the logical display id
2262          * @param shown {@code true} means the IME window shows on the screen. Otherwise it's
2263          *                           hidden.
2264          */
onImeWindowVisibilityChanged(int displayId, boolean shown)2265         void onImeWindowVisibilityChanged(int displayId, boolean shown);
2266 
2267         /**
2268          * Called when the magnification spec changed.
2269          *
2270          * @param displayId The logical display id
2271          * @param region    The region of the screen currently active for magnification.
2272          *                  The returned region will be empty if the magnification is not active.
2273          * @param config    The magnification config. That has magnification mode, the new scale and
2274          *                  the new screen-relative center position
2275          */
onFullScreenMagnificationChanged(int displayId, @NonNull Region region, @NonNull MagnificationConfig config)2276         void onFullScreenMagnificationChanged(int displayId, @NonNull Region region,
2277                 @NonNull MagnificationConfig config);
2278     }
2279 }
2280