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