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