1 /*
2  * Copyright (C) 2020 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.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
20 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
21 import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
22 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
23 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
24 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
25 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
26 
27 import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID;
28 
29 import android.accessibilityservice.MagnificationConfig;
30 import android.annotation.NonNull;
31 import android.annotation.Nullable;
32 import android.annotation.UserIdInt;
33 import android.content.Context;
34 import android.graphics.PointF;
35 import android.graphics.Rect;
36 import android.graphics.Region;
37 import android.os.SystemClock;
38 import android.os.UserHandle;
39 import android.provider.Settings;
40 import android.util.Slog;
41 import android.util.SparseArray;
42 import android.util.SparseBooleanArray;
43 import android.util.SparseIntArray;
44 import android.util.SparseLongArray;
45 import android.view.accessibility.MagnificationAnimationCallback;
46 
47 import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
48 import com.android.internal.annotations.GuardedBy;
49 import com.android.internal.annotations.VisibleForTesting;
50 import com.android.server.LocalServices;
51 import com.android.server.accessibility.AccessibilityManagerService;
52 import com.android.server.wm.WindowManagerInternal;
53 
54 import java.util.concurrent.Executor;
55 
56 /**
57  * Handles all magnification controllers initialization, generic interactions,
58  * magnification mode transition and magnification switch UI show/hide logic
59  * in the following callbacks:
60  *
61  * <ol>
62  *   <li> 1. {@link #onTouchInteractionStart} shows magnification switch UI when
63  *   the user touch interaction starts if magnification capabilities is all. </li>
64  *   <li> 2. {@link #onTouchInteractionEnd} shows magnification switch UI when
65  *   the user touch interaction ends if magnification capabilities is all. </li>
66  *   <li> 3. {@link #onWindowMagnificationActivationState} updates magnification switch UI
67  *   depending on magnification capabilities and magnification active state when window
68  *   magnification activation state change.</li>
69  *   <li> 4. {@link #onFullScreenMagnificationActivationState} updates magnification switch UI
70  *   depending on magnification capabilities and magnification active state when fullscreen
71  *   magnification activation state change.</li>
72  *   <li> 4. {@link #onRequestMagnificationSpec} updates magnification switch UI depending on
73  *   magnification capabilities and magnification active state when new magnification spec is
74  *   changed by external request from calling public APIs. </li>
75  * </ol>
76  *
77  *  <b>Note</b> Updates magnification switch UI when magnification mode transition
78  *  is done and before invoking {@link TransitionCallBack#onResult}.
79  */
80 public class MagnificationController implements WindowMagnificationManager.Callback,
81         MagnificationGestureHandler.Callback,
82         FullScreenMagnificationController.MagnificationInfoChangedCallback,
83         WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks {
84 
85     private static final boolean DEBUG = false;
86     private static final String TAG = "MagnificationController";
87 
88     private final AccessibilityManagerService mAms;
89     private final PointF mTempPoint = new PointF();
90     private final Object mLock;
91     private final Context mContext;
92     @GuardedBy("mLock")
93     private final SparseArray<DisableMagnificationCallback>
94             mMagnificationEndRunnableSparseArray = new SparseArray();
95 
96     private final AlwaysOnMagnificationFeatureFlag mAlwaysOnMagnificationFeatureFlag;
97     private final MagnificationScaleProvider mScaleProvider;
98     private FullScreenMagnificationController mFullScreenMagnificationController;
99     private WindowMagnificationManager mWindowMagnificationMgr;
100     private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
101     /** Whether the platform supports window magnification feature. */
102     private final boolean mSupportWindowMagnification;
103 
104     private final Executor mBackgroundExecutor;
105 
106     @GuardedBy("mLock")
107     private final SparseIntArray mCurrentMagnificationModeArray = new SparseIntArray();
108     @GuardedBy("mLock")
109     private final SparseIntArray mLastMagnificationActivatedModeArray = new SparseIntArray();
110     // Track the active user to reset the magnification and get the associated user settings.
111     private @UserIdInt int mUserId = UserHandle.USER_SYSTEM;
112     @GuardedBy("mLock")
113     private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray();
114     @GuardedBy("mLock")
115     private final SparseLongArray mWindowModeEnabledTimeArray = new SparseLongArray();
116     @GuardedBy("mLock")
117     private final SparseLongArray mFullScreenModeEnabledTimeArray = new SparseLongArray();
118 
119     /**
120      * The transitioning magnification modes on the displays. The controller notifies
121      * magnification change depending on the target config mode.
122      * If the target mode is null, it means the config mode of the display is not
123      * transitioning.
124      */
125     @GuardedBy("mLock")
126     private final SparseArray<Integer> mTransitionModes = new SparseArray();
127 
128     @GuardedBy("mLock")
129     private final SparseArray<WindowManagerInternal.AccessibilityControllerInternal
130             .UiChangesForAccessibilityCallbacks> mAccessibilityCallbacksDelegateArray =
131             new SparseArray<>();
132 
133     /**
134      * A callback to inform the magnification transition result on the given display.
135      */
136     public interface TransitionCallBack {
137         /**
138          * Invoked when the transition ends.
139          *
140          * @param displayId The display id.
141          * @param success {@code true} if the transition success.
142          */
onResult(int displayId, boolean success)143         void onResult(int displayId, boolean success);
144     }
145 
MagnificationController(AccessibilityManagerService ams, Object lock, Context context, MagnificationScaleProvider scaleProvider, Executor backgroundExecutor)146     public MagnificationController(AccessibilityManagerService ams, Object lock,
147             Context context, MagnificationScaleProvider scaleProvider,
148             Executor backgroundExecutor) {
149         mAms = ams;
150         mLock = lock;
151         mContext = context;
152         mScaleProvider = scaleProvider;
153         mBackgroundExecutor = backgroundExecutor;
154         LocalServices.getService(WindowManagerInternal.class)
155                 .getAccessibilityController().setUiChangesForAccessibilityCallbacks(this);
156         mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
157                 FEATURE_WINDOW_MAGNIFICATION);
158 
159         mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context);
160         mAlwaysOnMagnificationFeatureFlag.addOnChangedListener(
161                 mBackgroundExecutor, mAms::updateAlwaysOnMagnification);
162     }
163 
164     @VisibleForTesting
MagnificationController(AccessibilityManagerService ams, Object lock, Context context, FullScreenMagnificationController fullScreenMagnificationController, WindowMagnificationManager windowMagnificationManager, MagnificationScaleProvider scaleProvider, Executor backgroundExecutor)165     public MagnificationController(AccessibilityManagerService ams, Object lock,
166             Context context, FullScreenMagnificationController fullScreenMagnificationController,
167             WindowMagnificationManager windowMagnificationManager,
168             MagnificationScaleProvider scaleProvider, Executor backgroundExecutor) {
169         this(ams, lock, context, scaleProvider, backgroundExecutor);
170         mFullScreenMagnificationController = fullScreenMagnificationController;
171         mWindowMagnificationMgr = windowMagnificationManager;
172     }
173 
174     @Override
onPerformScaleAction(int displayId, float scale, boolean updatePersistence)175     public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
176         if (getFullScreenMagnificationController().isActivated(displayId)) {
177             getFullScreenMagnificationController().setScaleAndCenter(displayId, scale,
178                     Float.NaN, Float.NaN, false, MAGNIFICATION_GESTURE_HANDLER_ID);
179             if (updatePersistence) {
180                 getFullScreenMagnificationController().persistScale(displayId);
181             }
182         } else if (getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId)) {
183             getWindowMagnificationMgr().setScale(displayId, scale);
184             if (updatePersistence) {
185                 getWindowMagnificationMgr().persistScale(displayId);
186             }
187         }
188     }
189 
190     @Override
onAccessibilityActionPerformed(int displayId)191     public void onAccessibilityActionPerformed(int displayId) {
192         updateMagnificationUIControls(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
193     }
194 
195     @Override
onTouchInteractionStart(int displayId, int mode)196     public void onTouchInteractionStart(int displayId, int mode) {
197         handleUserInteractionChanged(displayId, mode);
198     }
199 
200     @Override
onTouchInteractionEnd(int displayId, int mode)201     public void onTouchInteractionEnd(int displayId, int mode) {
202         handleUserInteractionChanged(displayId, mode);
203     }
204 
handleUserInteractionChanged(int displayId, int mode)205     private void handleUserInteractionChanged(int displayId, int mode) {
206         if (mMagnificationCapabilities != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) {
207             return;
208         }
209         updateMagnificationUIControls(displayId, mode);
210     }
211 
updateMagnificationUIControls(int displayId, int mode)212     private void updateMagnificationUIControls(int displayId, int mode) {
213         final boolean isActivated = isActivated(displayId, mode);
214         final boolean showModeSwitchButton;
215         final boolean enableSettingsPanel;
216         synchronized (mLock) {
217             showModeSwitchButton = isActivated
218                     && mMagnificationCapabilities == ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
219             enableSettingsPanel = isActivated
220                     && (mMagnificationCapabilities == ACCESSIBILITY_MAGNIFICATION_MODE_ALL
221                     || mMagnificationCapabilities == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
222         }
223 
224         if (showModeSwitchButton) {
225             getWindowMagnificationMgr().showMagnificationButton(displayId, mode);
226         } else {
227             getWindowMagnificationMgr().removeMagnificationButton(displayId);
228         }
229 
230         if (!enableSettingsPanel) {
231             // Whether the settings panel needs to be shown is controlled in system UI.
232             // Here, we only guarantee that the settings panel is closed when it is not needed.
233             getWindowMagnificationMgr().removeMagnificationSettingsPanel(displayId);
234         }
235     }
236 
237     /** Returns {@code true} if the platform supports window magnification feature. */
supportWindowMagnification()238     public boolean supportWindowMagnification() {
239         return mSupportWindowMagnification;
240     }
241 
242     /**
243      * Transitions to the target Magnification mode with current center of the magnification mode
244      * if it is available.
245      *
246      * @param displayId The logical display
247      * @param targetMode The target magnification mode
248      * @param transitionCallBack The callback invoked when the transition is finished.
249      */
transitionMagnificationModeLocked(int displayId, int targetMode, @NonNull TransitionCallBack transitionCallBack)250     public void transitionMagnificationModeLocked(int displayId, int targetMode,
251             @NonNull TransitionCallBack transitionCallBack) {
252         // check if target mode is already activated
253         if (isActivated(displayId, targetMode)) {
254             transitionCallBack.onResult(displayId, true);
255             return;
256         }
257 
258         final PointF currentCenter = getCurrentMagnificationCenterLocked(displayId, targetMode);
259         final DisableMagnificationCallback animationCallback =
260                 getDisableMagnificationEndRunnableLocked(displayId);
261 
262         if (currentCenter == null && animationCallback == null) {
263             transitionCallBack.onResult(displayId, true);
264             return;
265         }
266 
267         if (animationCallback != null) {
268             if (animationCallback.mCurrentMode == targetMode) {
269                 animationCallback.restoreToCurrentMagnificationMode();
270                 return;
271             } else {
272                 Slog.w(TAG, "discard duplicate request");
273                 return;
274             }
275         }
276 
277         if (currentCenter == null) {
278             Slog.w(TAG, "Invalid center, ignore it");
279             transitionCallBack.onResult(displayId, true);
280             return;
281         }
282 
283         setTransitionState(displayId, targetMode);
284 
285         final FullScreenMagnificationController screenMagnificationController =
286                 getFullScreenMagnificationController();
287         final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr();
288         final float scale = getTargetModeScaleFromCurrentMagnification(displayId, targetMode);
289         final DisableMagnificationCallback animationEndCallback =
290                 new DisableMagnificationCallback(transitionCallBack, displayId, targetMode,
291                         scale, currentCenter, true);
292 
293         setDisableMagnificationCallbackLocked(displayId, animationEndCallback);
294 
295         if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
296             screenMagnificationController.reset(displayId, animationEndCallback);
297         } else {
298             windowMagnificationMgr.disableWindowMagnification(displayId, false,
299                     animationEndCallback);
300         }
301     }
302 
303     /**
304      * Transitions to the targeting magnification config mode with current center of the
305      * magnification mode if it is available. It disables the current magnifier immediately then
306      * transitions to the targeting magnifier.
307      *
308      * @param displayId  The logical display id
309      * @param config The targeting magnification config
310      * @param animate    {@code true} to animate the transition, {@code false}
311      *                   to transition immediately
312      * @param id        The ID of the service requesting the change
313      */
transitionMagnificationConfigMode(int displayId, MagnificationConfig config, boolean animate, int id)314     public void transitionMagnificationConfigMode(int displayId, MagnificationConfig config,
315             boolean animate, int id) {
316         if (DEBUG) {
317             Slog.d(TAG, "transitionMagnificationConfigMode displayId = " + displayId
318                     + ", config = " + config);
319         }
320         synchronized (mLock) {
321             final int targetMode = config.getMode();
322             final boolean targetActivated = config.isActivated();
323             final PointF currentCenter = getCurrentMagnificationCenterLocked(displayId, targetMode);
324             final PointF magnificationCenter = new PointF(config.getCenterX(), config.getCenterY());
325             if (currentCenter != null) {
326                 final float centerX = Float.isNaN(config.getCenterX())
327                         ? currentCenter.x
328                         : config.getCenterX();
329                 final float centerY = Float.isNaN(config.getCenterY())
330                         ? currentCenter.y
331                         : config.getCenterY();
332                 magnificationCenter.set(centerX, centerY);
333             }
334 
335             final DisableMagnificationCallback animationCallback =
336                     getDisableMagnificationEndRunnableLocked(displayId);
337             if (animationCallback != null) {
338                 Slog.w(TAG, "Discard previous animation request");
339                 animationCallback.setExpiredAndRemoveFromListLocked();
340             }
341             final FullScreenMagnificationController screenMagnificationController =
342                     getFullScreenMagnificationController();
343             final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr();
344             final float targetScale = Float.isNaN(config.getScale())
345                     ? getTargetModeScaleFromCurrentMagnification(displayId, targetMode)
346                     : config.getScale();
347             try {
348                 setTransitionState(displayId, targetMode);
349                 final MagnificationAnimationCallback magnificationAnimationCallback = animate
350                         ? success -> mAms.changeMagnificationMode(displayId, targetMode)
351                         : null;
352                 // Activate or deactivate target mode depending on config activated value
353                 if (targetMode == MAGNIFICATION_MODE_WINDOW) {
354                     screenMagnificationController.reset(displayId, false);
355                     if (targetActivated) {
356                         windowMagnificationMgr.enableWindowMagnification(displayId,
357                                 targetScale, magnificationCenter.x, magnificationCenter.y,
358                                 magnificationAnimationCallback, id);
359                     } else {
360                         windowMagnificationMgr.disableWindowMagnification(displayId, false);
361                     }
362                 } else if (targetMode == MAGNIFICATION_MODE_FULLSCREEN) {
363                     windowMagnificationMgr.disableWindowMagnification(displayId, false, null);
364                     if (targetActivated) {
365                         if (!screenMagnificationController.isRegistered(displayId)) {
366                             screenMagnificationController.register(displayId);
367                         }
368                         screenMagnificationController.setScaleAndCenter(displayId, targetScale,
369                                 magnificationCenter.x, magnificationCenter.y,
370                                 magnificationAnimationCallback, id);
371                     } else {
372                         if (screenMagnificationController.isRegistered(displayId)) {
373                             screenMagnificationController.reset(displayId, false);
374                         }
375                     }
376                 }
377             } finally {
378                 if (!animate) {
379                     mAms.changeMagnificationMode(displayId, targetMode);
380                 }
381                 // Reset transition state after enabling target mode.
382                 setTransitionState(displayId, null);
383             }
384         }
385     }
386 
387     /**
388      * Sets magnification config mode transition state. Called when the mode transition starts and
389      * ends. If the targetMode and the display id are null, it resets all
390      * the transition state.
391      *
392      * @param displayId  The logical display id
393      * @param targetMode The transition target mode. It is not transitioning, if the target mode
394      *                   is set null
395      */
setTransitionState(Integer displayId, Integer targetMode)396     private void setTransitionState(Integer displayId, Integer targetMode) {
397         synchronized (mLock) {
398             if (targetMode == null && displayId == null) {
399                 mTransitionModes.clear();
400             } else {
401                 mTransitionModes.put(displayId, targetMode);
402             }
403         }
404     }
405 
406     // We assume the target mode is different from the current mode, and there is only
407     // two modes, so we get the target scale from another mode.
getTargetModeScaleFromCurrentMagnification(int displayId, int targetMode)408     private float getTargetModeScaleFromCurrentMagnification(int displayId, int targetMode) {
409         if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
410             return getFullScreenMagnificationController().getScale(displayId);
411         } else {
412             return getWindowMagnificationMgr().getScale(displayId);
413         }
414     }
415 
416     /**
417      * Return {@code true} if disable magnification animation callback of the display is running.
418      *
419      * @param displayId The logical display id
420      */
hasDisableMagnificationCallback(int displayId)421     public boolean hasDisableMagnificationCallback(int displayId) {
422         synchronized (mLock) {
423             final DisableMagnificationCallback animationCallback =
424                     getDisableMagnificationEndRunnableLocked(displayId);
425             if (animationCallback != null) {
426                 return true;
427             }
428         }
429         return false;
430     }
431 
432     @GuardedBy("mLock")
setCurrentMagnificationModeAndSwitchDelegate(int displayId, int mode)433     private void setCurrentMagnificationModeAndSwitchDelegate(int displayId, int mode) {
434         mCurrentMagnificationModeArray.put(displayId, mode);
435         assignMagnificationWindowManagerDelegateByMode(displayId, mode);
436     }
437 
438     @GuardedBy("mLock")
assignMagnificationWindowManagerDelegateByMode(int displayId, int mode)439     private void assignMagnificationWindowManagerDelegateByMode(int displayId, int mode) {
440         if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
441             mAccessibilityCallbacksDelegateArray.put(displayId,
442                     getFullScreenMagnificationController());
443         } else if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
444             mAccessibilityCallbacksDelegateArray.put(displayId, getWindowMagnificationMgr());
445         } else {
446             mAccessibilityCallbacksDelegateArray.delete(displayId);
447         }
448     }
449 
450     @Override
onRectangleOnScreenRequested(int displayId, int left, int top, int right, int bottom)451     public void onRectangleOnScreenRequested(int displayId, int left, int top, int right,
452             int bottom) {
453         WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks
454                 delegate;
455         synchronized (mLock) {
456             delegate = mAccessibilityCallbacksDelegateArray.get(displayId);
457         }
458         if (delegate != null) {
459             delegate.onRectangleOnScreenRequested(displayId, left, top, right, bottom);
460         }
461     }
462 
463     @Override
onRequestMagnificationSpec(int displayId, int serviceId)464     public void onRequestMagnificationSpec(int displayId, int serviceId) {
465         final WindowMagnificationManager windowMagnificationManager;
466         synchronized (mLock) {
467             updateMagnificationUIControls(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
468             windowMagnificationManager = mWindowMagnificationMgr;
469         }
470         if (windowMagnificationManager != null) {
471             mWindowMagnificationMgr.disableWindowMagnification(displayId, false);
472         }
473     }
474 
475     @Override
onWindowMagnificationActivationState(int displayId, boolean activated)476     public void onWindowMagnificationActivationState(int displayId, boolean activated) {
477         if (activated) {
478             synchronized (mLock) {
479                 mWindowModeEnabledTimeArray.put(displayId, SystemClock.uptimeMillis());
480                 setCurrentMagnificationModeAndSwitchDelegate(displayId,
481                         ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
482                 mLastMagnificationActivatedModeArray.put(displayId,
483                         ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
484             }
485             logMagnificationModeWithImeOnIfNeeded(displayId);
486             disableFullScreenMagnificationIfNeeded(displayId);
487         } else {
488             long duration;
489             float scale;
490             synchronized (mLock) {
491                 setCurrentMagnificationModeAndSwitchDelegate(displayId,
492                         ACCESSIBILITY_MAGNIFICATION_MODE_NONE);
493                 duration = SystemClock.uptimeMillis() - mWindowModeEnabledTimeArray.get(displayId);
494                 scale = mWindowMagnificationMgr.getLastActivatedScale(displayId);
495             }
496             logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, duration, scale);
497         }
498         updateMagnificationUIControls(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
499     }
500 
501     @Override
onChangeMagnificationMode(int displayId, int magnificationMode)502     public void onChangeMagnificationMode(int displayId, int magnificationMode) {
503         mAms.changeMagnificationMode(displayId, magnificationMode);
504     }
505 
506     @Override
onSourceBoundsChanged(int displayId, Rect bounds)507     public void onSourceBoundsChanged(int displayId, Rect bounds) {
508         if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_WINDOW)) {
509             // notify sysui the magnification scale changed on window magnifier
510             mWindowMagnificationMgr.onUserMagnificationScaleChanged(
511                     mUserId, displayId, getWindowMagnificationMgr().getScale(displayId));
512 
513             final MagnificationConfig config = new MagnificationConfig.Builder()
514                     .setMode(MAGNIFICATION_MODE_WINDOW)
515                     .setActivated(getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId))
516                     .setScale(getWindowMagnificationMgr().getScale(displayId))
517                     .setCenterX(bounds.exactCenterX())
518                     .setCenterY(bounds.exactCenterY()).build();
519             mAms.notifyMagnificationChanged(displayId, new Region(bounds), config);
520         }
521     }
522 
523     @Override
onFullScreenMagnificationChanged(int displayId, @NonNull Region region, @NonNull MagnificationConfig config)524     public void onFullScreenMagnificationChanged(int displayId, @NonNull Region region,
525             @NonNull MagnificationConfig config) {
526         if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_FULLSCREEN)) {
527             // notify sysui the magnification scale changed on fullscreen magnifier
528             mWindowMagnificationMgr.onUserMagnificationScaleChanged(
529                     mUserId, displayId, config.getScale());
530 
531             mAms.notifyMagnificationChanged(displayId, region, config);
532         }
533     }
534 
535     /**
536      * Should notify magnification change for the given display under the conditions below
537      *
538      * <ol>
539      *   <li> 1. No mode transitioning and the change mode is active. </li>
540      *   <li> 2. No mode transitioning and all the modes are inactive. </li>
541      *   <li> 3. It is mode transitioning and the change mode is the transition mode. </li>
542      * </ol>
543      *
544      * @param displayId  The logical display id
545      * @param changeMode The mode that has magnification spec change
546      */
shouldNotifyMagnificationChange(int displayId, int changeMode)547     private boolean shouldNotifyMagnificationChange(int displayId, int changeMode) {
548         synchronized (mLock) {
549             final boolean fullScreenActivated = mFullScreenMagnificationController != null
550                     && mFullScreenMagnificationController.isActivated(displayId);
551             final boolean windowEnabled = mWindowMagnificationMgr != null
552                     && mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId);
553             final Integer transitionMode = mTransitionModes.get(displayId);
554             if (((changeMode == MAGNIFICATION_MODE_FULLSCREEN && fullScreenActivated)
555                     || (changeMode == MAGNIFICATION_MODE_WINDOW && windowEnabled))
556                     && (transitionMode == null)) {
557                 return true;
558             }
559             if ((!fullScreenActivated && !windowEnabled)
560                     && (transitionMode == null)) {
561                 return true;
562             }
563             if (transitionMode != null && changeMode == transitionMode) {
564                 return true;
565             }
566         }
567         return false;
568     }
569 
disableFullScreenMagnificationIfNeeded(int displayId)570     private void disableFullScreenMagnificationIfNeeded(int displayId) {
571         final FullScreenMagnificationController fullScreenMagnificationController =
572                 getFullScreenMagnificationController();
573         // Internal request may be for transition, so we just need to check external request.
574         final boolean isMagnifyByExternalRequest =
575                 fullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId) > 0;
576         if (isMagnifyByExternalRequest || isActivated(displayId,
577                 ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)) {
578             fullScreenMagnificationController.reset(displayId, false);
579         }
580     }
581 
582     @Override
onFullScreenMagnificationActivationState(int displayId, boolean activated)583     public void onFullScreenMagnificationActivationState(int displayId, boolean activated) {
584         if (activated) {
585             synchronized (mLock) {
586                 mFullScreenModeEnabledTimeArray.put(displayId, SystemClock.uptimeMillis());
587                 setCurrentMagnificationModeAndSwitchDelegate(displayId,
588                         ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
589                 mLastMagnificationActivatedModeArray.put(displayId,
590                         ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
591             }
592             logMagnificationModeWithImeOnIfNeeded(displayId);
593             disableWindowMagnificationIfNeeded(displayId);
594         } else {
595             long duration;
596             float scale;
597             synchronized (mLock) {
598                 setCurrentMagnificationModeAndSwitchDelegate(displayId,
599                         ACCESSIBILITY_MAGNIFICATION_MODE_NONE);
600                 duration = SystemClock.uptimeMillis()
601                         - mFullScreenModeEnabledTimeArray.get(displayId);
602                 scale = mFullScreenMagnificationController.getLastActivatedScale(displayId);
603             }
604             logMagnificationUsageState(
605                     ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, duration, scale);
606         }
607         updateMagnificationUIControls(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
608     }
609 
disableWindowMagnificationIfNeeded(int displayId)610     private void disableWindowMagnificationIfNeeded(int displayId) {
611         final WindowMagnificationManager windowMagnificationManager =
612                 getWindowMagnificationMgr();
613         if (isActivated(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)) {
614             windowMagnificationManager.disableWindowMagnification(displayId, false);
615         }
616     }
617 
618     @Override
onImeWindowVisibilityChanged(int displayId, boolean shown)619     public void onImeWindowVisibilityChanged(int displayId, boolean shown) {
620         synchronized (mLock) {
621             mIsImeVisibleArray.put(displayId, shown);
622         }
623         getWindowMagnificationMgr().onImeWindowVisibilityChanged(displayId, shown);
624         logMagnificationModeWithImeOnIfNeeded(displayId);
625     }
626 
627     /**
628      * Returns the last activated magnification mode. If there is no activated magnifier before, it
629      * returns fullscreen mode by default.
630      */
getLastMagnificationActivatedMode(int displayId)631     public int getLastMagnificationActivatedMode(int displayId) {
632         synchronized (mLock) {
633             return mLastMagnificationActivatedModeArray.get(displayId,
634                     ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
635         }
636     }
637 
638     /**
639      * Wrapper method of logging the magnification activated mode and its duration of the usage
640      * when the magnification is disabled.
641      *
642      * @param mode The activated magnification mode.
643      * @param duration The duration in milliseconds during the magnification is activated.
644      * @param scale The last magnification scale for the activation
645      */
646     @VisibleForTesting
logMagnificationUsageState(int mode, long duration, float scale)647     public void logMagnificationUsageState(int mode, long duration, float scale) {
648         AccessibilityStatsLogUtils.logMagnificationUsageState(mode, duration, scale);
649     }
650 
651     /**
652      * Wrapper method of logging the activated mode of the magnification when the IME window
653      * is shown on the screen.
654      *
655      * @param mode The activated magnification mode.
656      */
657     @VisibleForTesting
logMagnificationModeWithIme(int mode)658     public void logMagnificationModeWithIme(int mode) {
659         AccessibilityStatsLogUtils.logMagnificationModeWithImeOn(mode);
660     }
661 
662     /**
663      * Updates the active user ID of {@link FullScreenMagnificationController} and {@link
664      * WindowMagnificationManager}.
665      *
666      * @param userId the currently active user ID
667      */
updateUserIdIfNeeded(int userId)668     public void updateUserIdIfNeeded(int userId) {
669         if (mUserId == userId) {
670             return;
671         }
672         mUserId = userId;
673         final FullScreenMagnificationController fullMagnificationController;
674         final WindowMagnificationManager windowMagnificationManager;
675         synchronized (mLock) {
676             fullMagnificationController = mFullScreenMagnificationController;
677             windowMagnificationManager = mWindowMagnificationMgr;
678             mAccessibilityCallbacksDelegateArray.clear();
679             mCurrentMagnificationModeArray.clear();
680             mLastMagnificationActivatedModeArray.clear();
681             mIsImeVisibleArray.clear();
682         }
683 
684         mScaleProvider.onUserChanged(userId);
685         if (fullMagnificationController != null) {
686             fullMagnificationController.resetAllIfNeeded(false);
687         }
688         if (windowMagnificationManager != null) {
689             windowMagnificationManager.disableAllWindowMagnifiers();
690         }
691     }
692 
693     /**
694      * Removes the magnification instance with given id.
695      *
696      * @param displayId The logical display id.
697      */
onDisplayRemoved(int displayId)698     public void onDisplayRemoved(int displayId) {
699         synchronized (mLock) {
700             if (mFullScreenMagnificationController != null) {
701                 mFullScreenMagnificationController.onDisplayRemoved(displayId);
702             }
703             if (mWindowMagnificationMgr != null) {
704                 mWindowMagnificationMgr.onDisplayRemoved(displayId);
705             }
706             mAccessibilityCallbacksDelegateArray.delete(displayId);
707             mCurrentMagnificationModeArray.delete(displayId);
708             mLastMagnificationActivatedModeArray.delete(displayId);
709             mIsImeVisibleArray.delete(displayId);
710         }
711         mScaleProvider.onDisplayRemoved(displayId);
712     }
713 
714     /**
715      * Called when the given user is removed.
716      */
onUserRemoved(int userId)717     public void onUserRemoved(int userId) {
718         mScaleProvider.onUserRemoved(userId);
719     }
720 
setMagnificationCapabilities(int capabilities)721     public void setMagnificationCapabilities(int capabilities) {
722         mMagnificationCapabilities = capabilities;
723     }
724 
725     /**
726      * Called when the following typing focus feature is switched.
727      *
728      * @param enabled Enable the following typing focus feature
729      */
setMagnificationFollowTypingEnabled(boolean enabled)730     public void setMagnificationFollowTypingEnabled(boolean enabled) {
731         getWindowMagnificationMgr().setMagnificationFollowTypingEnabled(enabled);
732         getFullScreenMagnificationController().setMagnificationFollowTypingEnabled(enabled);
733     }
734 
735     /**
736      * Called when the always on magnification feature is switched.
737      *
738      * @param enabled Enable the always on magnification feature
739      */
setAlwaysOnMagnificationEnabled(boolean enabled)740     public void setAlwaysOnMagnificationEnabled(boolean enabled) {
741         getFullScreenMagnificationController().setAlwaysOnMagnificationEnabled(enabled);
742     }
743 
isAlwaysOnMagnificationFeatureFlagEnabled()744     public boolean isAlwaysOnMagnificationFeatureFlagEnabled() {
745         return mAlwaysOnMagnificationFeatureFlag.isFeatureFlagEnabled();
746     }
747 
getDisableMagnificationEndRunnableLocked( int displayId)748     private DisableMagnificationCallback getDisableMagnificationEndRunnableLocked(
749             int displayId) {
750         return mMagnificationEndRunnableSparseArray.get(displayId);
751     }
752 
setDisableMagnificationCallbackLocked(int displayId, @Nullable DisableMagnificationCallback callback)753     private void setDisableMagnificationCallbackLocked(int displayId,
754             @Nullable DisableMagnificationCallback callback) {
755         mMagnificationEndRunnableSparseArray.put(displayId, callback);
756         if (DEBUG) {
757             Slog.d(TAG, "setDisableMagnificationCallbackLocked displayId = " + displayId
758                     + ", callback = " + callback);
759         }
760     }
761 
logMagnificationModeWithImeOnIfNeeded(int displayId)762     private void logMagnificationModeWithImeOnIfNeeded(int displayId) {
763         final int currentActivateMode;
764 
765         synchronized (mLock) {
766             currentActivateMode = mCurrentMagnificationModeArray.get(displayId,
767                     ACCESSIBILITY_MAGNIFICATION_MODE_NONE);
768             if (!mIsImeVisibleArray.get(displayId, false)
769                     || currentActivateMode == ACCESSIBILITY_MAGNIFICATION_MODE_NONE) {
770                 return;
771             }
772         }
773         logMagnificationModeWithIme(currentActivateMode);
774     }
775 
776     /**
777      * Getter of {@link FullScreenMagnificationController}.
778      *
779      * @return {@link FullScreenMagnificationController}.
780      */
getFullScreenMagnificationController()781     public FullScreenMagnificationController getFullScreenMagnificationController() {
782         synchronized (mLock) {
783             if (mFullScreenMagnificationController == null) {
784                 mFullScreenMagnificationController = new FullScreenMagnificationController(
785                         mContext,
786                         mAms.getTraceManager(),
787                         mLock,
788                         this,
789                         mScaleProvider,
790                         mBackgroundExecutor
791                 );
792             }
793         }
794         return mFullScreenMagnificationController;
795     }
796 
797     /**
798      * Is {@link #mFullScreenMagnificationController} is initialized.
799      * @return {code true} if {@link #mFullScreenMagnificationController} is initialized.
800      */
isFullScreenMagnificationControllerInitialized()801     public boolean isFullScreenMagnificationControllerInitialized() {
802         synchronized (mLock) {
803             return mFullScreenMagnificationController != null;
804         }
805     }
806 
807     /**
808      * Getter of {@link WindowMagnificationManager}.
809      *
810      * @return {@link WindowMagnificationManager}.
811      */
getWindowMagnificationMgr()812     public WindowMagnificationManager getWindowMagnificationMgr() {
813         synchronized (mLock) {
814             if (mWindowMagnificationMgr == null) {
815                 mWindowMagnificationMgr = new WindowMagnificationManager(mContext,
816                         mLock, this, mAms.getTraceManager(),
817                         mScaleProvider);
818             }
819             return mWindowMagnificationMgr;
820         }
821     }
822 
getCurrentMagnificationCenterLocked(int displayId, int targetMode)823     private @Nullable PointF getCurrentMagnificationCenterLocked(int displayId, int targetMode) {
824         if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
825             if (mWindowMagnificationMgr == null
826                     || !mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId)) {
827                 return null;
828             }
829             mTempPoint.set(mWindowMagnificationMgr.getCenterX(displayId),
830                     mWindowMagnificationMgr.getCenterY(displayId));
831         } else {
832             if (mFullScreenMagnificationController == null
833                     || !mFullScreenMagnificationController.isActivated(displayId)) {
834                 return null;
835             }
836             mTempPoint.set(mFullScreenMagnificationController.getCenterX(displayId),
837                     mFullScreenMagnificationController.getCenterY(displayId));
838         }
839         return mTempPoint;
840     }
841 
842     /**
843      * Return {@code true} if the specified magnification mode on the given display is activated
844      * or not.
845      *
846      * @param displayId The logical displayId.
847      * @param mode It's either ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN or
848      * ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW.
849      */
isActivated(int displayId, int mode)850     public boolean isActivated(int displayId, int mode) {
851         boolean isActivated = false;
852         if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
853             synchronized (mLock) {
854                 if (mFullScreenMagnificationController == null) {
855                     return false;
856                 }
857                 isActivated = mFullScreenMagnificationController.isActivated(displayId);
858             }
859         } else if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
860             synchronized (mLock) {
861                 if (mWindowMagnificationMgr == null) {
862                     return false;
863                 }
864                 isActivated = mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId);
865             }
866         }
867         return isActivated;
868     }
869 
870     private final class DisableMagnificationCallback implements
871             MagnificationAnimationCallback {
872         private final TransitionCallBack mTransitionCallBack;
873         private boolean mExpired = false;
874         private final int mDisplayId;
875         // The mode the in-progress animation is going to.
876         private final int mTargetMode;
877         // The mode the in-progress animation is going from.
878         private final int mCurrentMode;
879         private final float mCurrentScale;
880         private final PointF mCurrentCenter = new PointF();
881         private final boolean mAnimate;
882 
DisableMagnificationCallback(@ullable TransitionCallBack transitionCallBack, int displayId, int targetMode, float scale, PointF currentCenter, boolean animate)883         DisableMagnificationCallback(@Nullable TransitionCallBack transitionCallBack,
884                 int displayId, int targetMode, float scale, PointF currentCenter, boolean animate) {
885             mTransitionCallBack = transitionCallBack;
886             mDisplayId = displayId;
887             mTargetMode = targetMode;
888             mCurrentMode = mTargetMode ^ ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
889             mCurrentScale = scale;
890             mCurrentCenter.set(currentCenter);
891             mAnimate = animate;
892         }
893 
894         @Override
onResult(boolean success)895         public void onResult(boolean success) {
896             synchronized (mLock) {
897                 if (DEBUG) {
898                     Slog.d(TAG, "onResult success = " + success);
899                 }
900                 if (mExpired) {
901                     return;
902                 }
903                 setExpiredAndRemoveFromListLocked();
904                 setTransitionState(mDisplayId, null);
905 
906                 if (success) {
907                     adjustCurrentCenterIfNeededLocked();
908                     applyMagnificationModeLocked(mTargetMode);
909                 } else {
910                     // Notify magnification change if magnification is inactive when the
911                     // transition is failed. This is for the failed transition from
912                     // full-screen to window mode. Disable magnification callback helps to send
913                     // magnification inactive change since FullScreenMagnificationController
914                     // would not notify magnification change if the spec is not changed.
915                     final FullScreenMagnificationController screenMagnificationController =
916                             getFullScreenMagnificationController();
917                     if (mCurrentMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
918                             && !screenMagnificationController.isActivated(mDisplayId)) {
919                         MagnificationConfig.Builder configBuilder =
920                                 new MagnificationConfig.Builder();
921                         Region region = new Region();
922                         configBuilder.setMode(MAGNIFICATION_MODE_FULLSCREEN)
923                                 .setActivated(screenMagnificationController.isActivated(mDisplayId))
924                                 .setScale(screenMagnificationController.getScale(mDisplayId))
925                                 .setCenterX(screenMagnificationController.getCenterX(mDisplayId))
926                                 .setCenterY(screenMagnificationController.getCenterY(mDisplayId));
927                         screenMagnificationController.getMagnificationRegion(mDisplayId,
928                                 region);
929                         mAms.notifyMagnificationChanged(mDisplayId, region, configBuilder.build());
930                     }
931                 }
932                 updateMagnificationUIControls(mDisplayId, mTargetMode);
933                 if (mTransitionCallBack != null) {
934                     mTransitionCallBack.onResult(mDisplayId, success);
935                 }
936             }
937         }
938 
adjustCurrentCenterIfNeededLocked()939         private void adjustCurrentCenterIfNeededLocked() {
940             if (mTargetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
941                 return;
942             }
943             final Region outRegion = new Region();
944             getFullScreenMagnificationController().getMagnificationRegion(mDisplayId, outRegion);
945             if (outRegion.contains((int) mCurrentCenter.x, (int) mCurrentCenter.y)) {
946                 return;
947             }
948             final Rect bounds = outRegion.getBounds();
949             mCurrentCenter.set(bounds.exactCenterX(), bounds.exactCenterY());
950         }
951 
restoreToCurrentMagnificationMode()952         void restoreToCurrentMagnificationMode() {
953             synchronized (mLock) {
954                 if (mExpired) {
955                     return;
956                 }
957                 setExpiredAndRemoveFromListLocked();
958                 setTransitionState(mDisplayId, null);
959                 applyMagnificationModeLocked(mCurrentMode);
960                 updateMagnificationUIControls(mDisplayId, mCurrentMode);
961                 if (mTransitionCallBack != null) {
962                     mTransitionCallBack.onResult(mDisplayId, true);
963                 }
964             }
965         }
966 
setExpiredAndRemoveFromListLocked()967         void setExpiredAndRemoveFromListLocked() {
968             mExpired = true;
969             setDisableMagnificationCallbackLocked(mDisplayId, null);
970         }
971 
applyMagnificationModeLocked(int mode)972         private void applyMagnificationModeLocked(int mode) {
973             if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
974                 final FullScreenMagnificationController fullScreenMagnificationController =
975                         getFullScreenMagnificationController();
976                 if (!fullScreenMagnificationController.isRegistered(mDisplayId)) {
977                     fullScreenMagnificationController.register(mDisplayId);
978                 }
979                 fullScreenMagnificationController.setScaleAndCenter(mDisplayId, mCurrentScale,
980                         mCurrentCenter.x, mCurrentCenter.y, mAnimate,
981                         MAGNIFICATION_GESTURE_HANDLER_ID);
982             } else {
983                 getWindowMagnificationMgr().enableWindowMagnification(mDisplayId,
984                         mCurrentScale, mCurrentCenter.x,
985                         mCurrentCenter.y, mAnimate ? STUB_ANIMATION_CALLBACK : null,
986                         MAGNIFICATION_GESTURE_HANDLER_ID);
987             }
988         }
989     }
990 }
991