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