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