• 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.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
20 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
21 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
22 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.content.Context;
27 import android.graphics.PointF;
28 import android.graphics.Rect;
29 import android.graphics.Region;
30 import android.os.SystemClock;
31 import android.provider.Settings;
32 import android.util.Slog;
33 import android.util.SparseArray;
34 import android.view.accessibility.MagnificationAnimationCallback;
35 
36 import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
37 import com.android.internal.annotations.GuardedBy;
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.server.accessibility.AccessibilityManagerService;
40 
41 /**
42  * Handles all magnification controllers initialization, generic interactions,
43  * magnification mode transition and magnification switch UI show/hide logic
44  * in the following callbacks:
45  *
46  * <ol>
47  *   <li> 1. {@link #onTouchInteractionStart} shows magnification switch UI when
48  *   the user touch interaction starts if magnification capabilities is all. </li>
49  *   <li> 2. {@link #onTouchInteractionEnd} shows magnification switch UI when
50  *   the user touch interaction ends if magnification capabilities is all. </li>
51  *   <li> 3. {@link #onShortcutTriggered} updates magnification switch UI depending on
52  *   magnification capabilities and magnification active state when magnification shortcut
53  *   is triggered.</li>
54  *   <li> 4. {@link #onTripleTapped} updates magnification switch UI depending on magnification
55  *   capabilities and magnification active state when triple-tap gesture is detected. </li>
56  *   <li> 4. {@link #onRequestMagnificationSpec} updates magnification switch UI depending on
57  *   magnification capabilities and magnification active state when new magnification spec is
58  *   changed by external request from calling public APIs. </li>
59  * </ol>
60  *
61  *  <b>Note</b> Updates magnification switch UI when magnification mode transition
62  *  is done and before invoking {@link TransitionCallBack#onResult}.
63  */
64 public class MagnificationController implements WindowMagnificationManager.Callback,
65         MagnificationGestureHandler.Callback,
66         FullScreenMagnificationController.MagnificationInfoChangedCallback {
67 
68     private static final boolean DEBUG = false;
69     private static final String TAG = "MagnificationController";
70     private final AccessibilityManagerService mAms;
71     private final PointF mTempPoint = new PointF();
72     private final Object mLock;
73     private final Context mContext;
74     private final SparseArray<DisableMagnificationCallback>
75             mMagnificationEndRunnableSparseArray = new SparseArray();
76 
77     private FullScreenMagnificationController mFullScreenMagnificationController;
78     private WindowMagnificationManager mWindowMagnificationMgr;
79     private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
80 
81     @GuardedBy("mLock")
82     private int mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
83     @GuardedBy("mLock")
84     private boolean mImeWindowVisible = false;
85     private long mWindowModeEnabledTime = 0;
86     private long mFullScreenModeEnabledTime = 0;
87 
88     /**
89      * A callback to inform the magnification transition result.
90      */
91     public interface TransitionCallBack {
92         /**
93          * Invoked when the transition ends.
94          * @param success {@code true} if the transition success.
95          */
onResult(boolean success)96         void onResult(boolean success);
97     }
98 
MagnificationController(AccessibilityManagerService ams, Object lock, Context context)99     public MagnificationController(AccessibilityManagerService ams, Object lock,
100             Context context) {
101         mAms = ams;
102         mLock = lock;
103         mContext = context;
104     }
105 
106     @VisibleForTesting
MagnificationController(AccessibilityManagerService ams, Object lock, Context context, FullScreenMagnificationController fullScreenMagnificationController, WindowMagnificationManager windowMagnificationManager)107     public MagnificationController(AccessibilityManagerService ams, Object lock,
108             Context context, FullScreenMagnificationController fullScreenMagnificationController,
109             WindowMagnificationManager windowMagnificationManager) {
110         this(ams, lock, context);
111         mFullScreenMagnificationController = fullScreenMagnificationController;
112         mWindowMagnificationMgr = windowMagnificationManager;
113     }
114 
115     @Override
onPerformScaleAction(int displayId, float scale)116     public void onPerformScaleAction(int displayId, float scale) {
117         getWindowMagnificationMgr().setScale(displayId, scale);
118         getWindowMagnificationMgr().persistScale(displayId);
119     }
120 
121     @Override
onAccessibilityActionPerformed(int displayId)122     public void onAccessibilityActionPerformed(int displayId) {
123         updateMagnificationButton(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
124     }
125 
126     @Override
onTouchInteractionStart(int displayId, int mode)127     public void onTouchInteractionStart(int displayId, int mode) {
128         handleUserInteractionChanged(displayId, mode);
129     }
130 
131     @Override
onTouchInteractionEnd(int displayId, int mode)132     public void onTouchInteractionEnd(int displayId, int mode) {
133         handleUserInteractionChanged(displayId, mode);
134     }
135 
handleUserInteractionChanged(int displayId, int mode)136     private void handleUserInteractionChanged(int displayId, int mode) {
137         if (mMagnificationCapabilities != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) {
138             return;
139         }
140         if (isActivated(displayId, mode)) {
141             getWindowMagnificationMgr().showMagnificationButton(displayId, mode);
142         }
143     }
144 
145     @Override
onShortcutTriggered(int displayId, int mode)146     public void onShortcutTriggered(int displayId, int mode) {
147         updateMagnificationButton(displayId, mode);
148     }
149 
150     @Override
onTripleTapped(int displayId, int mode)151     public void onTripleTapped(int displayId, int mode) {
152         updateMagnificationButton(displayId, mode);
153     }
154 
updateMagnificationButton(int displayId, int mode)155     private void updateMagnificationButton(int displayId, int mode) {
156         final boolean isActivated = isActivated(displayId, mode);
157         final boolean showButton;
158         synchronized (mLock) {
159             showButton = isActivated && mMagnificationCapabilities
160                     == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
161         }
162         if (showButton) {
163             getWindowMagnificationMgr().showMagnificationButton(displayId, mode);
164         } else {
165             getWindowMagnificationMgr().removeMagnificationButton(displayId);
166         }
167     }
168 
169     /**
170      * Transitions to the target Magnification mode with current center of the magnification mode
171      * if it is available.
172      *
173      * @param displayId The logical display
174      * @param targetMode The target magnification mode
175      * @param transitionCallBack The callback invoked when the transition is finished.
176      */
transitionMagnificationModeLocked(int displayId, int targetMode, @NonNull TransitionCallBack transitionCallBack)177     public void transitionMagnificationModeLocked(int displayId, int targetMode,
178             @NonNull TransitionCallBack transitionCallBack) {
179         final PointF magnificationCenter = getCurrentMagnificationBoundsCenterLocked(displayId,
180                 targetMode);
181         final DisableMagnificationCallback animationCallback =
182                 getDisableMagnificationEndRunnableLocked(displayId);
183         if (magnificationCenter == null && animationCallback == null) {
184             transitionCallBack.onResult(true);
185             return;
186         }
187 
188         if (animationCallback != null) {
189             if (animationCallback.mCurrentMode == targetMode) {
190                 animationCallback.restoreToCurrentMagnificationMode();
191                 return;
192             }
193             Slog.w(TAG, "request during transition, abandon current:"
194                     + animationCallback.mTargetMode);
195             animationCallback.setExpiredAndRemoveFromListLocked();
196         }
197 
198         if (magnificationCenter == null) {
199             Slog.w(TAG, "Invalid center, ignore it");
200             transitionCallBack.onResult(true);
201             return;
202         }
203         final FullScreenMagnificationController screenMagnificationController =
204                 getFullScreenMagnificationController();
205         final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr();
206         final float scale = windowMagnificationMgr.getPersistedScale();
207         final DisableMagnificationCallback animationEndCallback =
208                 new DisableMagnificationCallback(transitionCallBack, displayId, targetMode,
209                         scale, magnificationCenter);
210         if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
211             screenMagnificationController.reset(displayId, animationEndCallback);
212         } else {
213             windowMagnificationMgr.disableWindowMagnification(displayId, false,
214                     animationEndCallback);
215         }
216         setDisableMagnificationCallbackLocked(displayId, animationEndCallback);
217     }
218 
219     @Override
onRequestMagnificationSpec(int displayId, int serviceId)220     public void onRequestMagnificationSpec(int displayId, int serviceId) {
221         final WindowMagnificationManager windowMagnificationManager;
222         synchronized (mLock) {
223             if (serviceId == AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID) {
224                 return;
225             }
226             updateMagnificationButton(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
227             windowMagnificationManager = mWindowMagnificationMgr;
228         }
229         if (windowMagnificationManager != null) {
230             mWindowMagnificationMgr.disableWindowMagnification(displayId, false);
231         }
232     }
233 
234     // TODO : supporting multi-display (b/182227245).
235     @Override
onWindowMagnificationActivationState(int displayId, boolean activated)236     public void onWindowMagnificationActivationState(int displayId, boolean activated) {
237         if (activated) {
238             mWindowModeEnabledTime = SystemClock.uptimeMillis();
239 
240             synchronized (mLock) {
241                 mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
242             }
243             logMagnificationModeWithImeOnIfNeeded();
244             disableFullScreenMagnificationIfNeeded(displayId);
245         } else {
246             logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
247                     SystemClock.uptimeMillis() - mWindowModeEnabledTime);
248 
249             synchronized (mLock) {
250                 mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
251             }
252         }
253     }
254 
disableFullScreenMagnificationIfNeeded(int displayId)255     private void disableFullScreenMagnificationIfNeeded(int displayId) {
256         final FullScreenMagnificationController fullScreenMagnificationController =
257                 getFullScreenMagnificationController();
258         // Internal request may be for transition, so we just need to check external request.
259         final boolean isMagnifyByExternalRequest =
260                 fullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId) > 0;
261         if (isMagnifyByExternalRequest) {
262             fullScreenMagnificationController.reset(displayId, false);
263         }
264     }
265 
266     @Override
onFullScreenMagnificationActivationState(boolean activated)267     public void onFullScreenMagnificationActivationState(boolean activated) {
268         if (activated) {
269             mFullScreenModeEnabledTime = SystemClock.uptimeMillis();
270 
271             synchronized (mLock) {
272                 mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
273             }
274             logMagnificationModeWithImeOnIfNeeded();
275         } else {
276             logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
277                     SystemClock.uptimeMillis() - mFullScreenModeEnabledTime);
278 
279             synchronized (mLock) {
280                 mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
281             }
282         }
283     }
284 
285     @Override
onImeWindowVisibilityChanged(boolean shown)286     public void onImeWindowVisibilityChanged(boolean shown) {
287         synchronized (mLock) {
288             mImeWindowVisible = shown;
289         }
290         logMagnificationModeWithImeOnIfNeeded();
291     }
292 
293     /**
294      * Wrapper method of logging the magnification activated mode and its duration of the usage
295      * when the magnification is disabled.
296      *
297      * @param mode The activated magnification mode.
298      * @param duration The duration in milliseconds during the magnification is activated.
299      */
300     @VisibleForTesting
logMagnificationUsageState(int mode, long duration)301     public void logMagnificationUsageState(int mode, long duration) {
302         AccessibilityStatsLogUtils.logMagnificationUsageState(mode, duration);
303     }
304 
305     /**
306      * Wrapper method of logging the activated mode of the magnification when the IME window
307      * is shown on the screen.
308      *
309      * @param mode The activated magnification mode.
310      */
311     @VisibleForTesting
logMagnificationModeWithIme(int mode)312     public void logMagnificationModeWithIme(int mode) {
313         AccessibilityStatsLogUtils.logMagnificationModeWithImeOn(mode);
314     }
315 
316     /**
317      * Updates the active user ID of {@link FullScreenMagnificationController} and {@link
318      * WindowMagnificationManager}.
319      *
320      * @param userId the currently active user ID
321      */
updateUserIdIfNeeded(int userId)322     public void updateUserIdIfNeeded(int userId) {
323         synchronized (mLock) {
324             if (mFullScreenMagnificationController != null) {
325                 mFullScreenMagnificationController.setUserId(userId);
326             }
327             if (mWindowMagnificationMgr != null) {
328                 mWindowMagnificationMgr.setUserId(userId);
329             }
330         }
331     }
332 
333     /**
334      * Removes the magnification instance with given id.
335      *
336      * @param displayId The logical display id.
337      */
onDisplayRemoved(int displayId)338     public void onDisplayRemoved(int displayId) {
339         synchronized (mLock) {
340             if (mFullScreenMagnificationController != null) {
341                 mFullScreenMagnificationController.onDisplayRemoved(displayId);
342             }
343             if (mWindowMagnificationMgr != null) {
344                 mWindowMagnificationMgr.onDisplayRemoved(displayId);
345             }
346         }
347     }
348 
setMagnificationCapabilities(int capabilities)349     public void setMagnificationCapabilities(int capabilities) {
350         mMagnificationCapabilities = capabilities;
351     }
352 
getDisableMagnificationEndRunnableLocked( int displayId)353     private DisableMagnificationCallback getDisableMagnificationEndRunnableLocked(
354             int displayId) {
355         return mMagnificationEndRunnableSparseArray.get(displayId);
356     }
357 
setDisableMagnificationCallbackLocked(int displayId, @Nullable DisableMagnificationCallback callback)358     private void setDisableMagnificationCallbackLocked(int displayId,
359             @Nullable DisableMagnificationCallback callback) {
360         mMagnificationEndRunnableSparseArray.put(displayId, callback);
361         if (DEBUG) {
362             Slog.d(TAG, "setDisableMagnificationCallbackLocked displayId = " + displayId
363                     + ", callback = " + callback);
364         }
365     }
366 
logMagnificationModeWithImeOnIfNeeded()367     private void logMagnificationModeWithImeOnIfNeeded() {
368         final int mode;
369 
370         synchronized (mLock) {
371             if (!mImeWindowVisible || mActivatedMode == ACCESSIBILITY_MAGNIFICATION_MODE_NONE) {
372                 return;
373             }
374             mode = mActivatedMode;
375         }
376         logMagnificationModeWithIme(mode);
377     }
378 
379     /**
380      * Getter of {@link FullScreenMagnificationController}.
381      *
382      * @return {@link FullScreenMagnificationController}.
383      */
getFullScreenMagnificationController()384     public FullScreenMagnificationController getFullScreenMagnificationController() {
385         synchronized (mLock) {
386             if (mFullScreenMagnificationController == null) {
387                 mFullScreenMagnificationController = new FullScreenMagnificationController(mContext,
388                         mAms, mLock, this);
389                 mFullScreenMagnificationController.setUserId(mAms.getCurrentUserIdLocked());
390             }
391         }
392         return mFullScreenMagnificationController;
393     }
394 
395     /**
396      * Is {@link #mFullScreenMagnificationController} is initialized.
397      * @return {code true} if {@link #mFullScreenMagnificationController} is initialized.
398      */
isFullScreenMagnificationControllerInitialized()399     public boolean isFullScreenMagnificationControllerInitialized() {
400         synchronized (mLock) {
401             return mFullScreenMagnificationController != null;
402         }
403     }
404 
405     /**
406      * Getter of {@link WindowMagnificationManager}.
407      *
408      * @return {@link WindowMagnificationManager}.
409      */
getWindowMagnificationMgr()410     public WindowMagnificationManager getWindowMagnificationMgr() {
411         synchronized (mLock) {
412             if (mWindowMagnificationMgr == null) {
413                 mWindowMagnificationMgr = new WindowMagnificationManager(mContext,
414                         mAms.getCurrentUserIdLocked(),
415                         this);
416             }
417             return mWindowMagnificationMgr;
418         }
419     }
420 
421     private @Nullable
getCurrentMagnificationBoundsCenterLocked(int displayId, int targetMode)422             PointF getCurrentMagnificationBoundsCenterLocked(int displayId, int targetMode) {
423         if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
424             if (mWindowMagnificationMgr == null
425                     || !mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId)) {
426                 return null;
427             }
428             mTempPoint.set(mWindowMagnificationMgr.getCenterX(displayId),
429                     mWindowMagnificationMgr.getCenterY(displayId));
430         } else {
431             if (mFullScreenMagnificationController == null
432                     || !mFullScreenMagnificationController.isMagnifying(displayId)) {
433                 return null;
434             }
435             mTempPoint.set(mFullScreenMagnificationController.getCenterX(displayId),
436                     mFullScreenMagnificationController.getCenterY(displayId));
437         }
438         return mTempPoint;
439     }
440 
isActivated(int displayId, int mode)441     private boolean isActivated(int displayId, int mode) {
442         boolean isActivated = false;
443         if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
444             synchronized (mLock) {
445                 if (mFullScreenMagnificationController == null) {
446                     return false;
447                 }
448                 isActivated = mFullScreenMagnificationController.isMagnifying(displayId)
449                         || mFullScreenMagnificationController.isForceShowMagnifiableBounds(
450                         displayId);
451             }
452         } else if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
453             synchronized (mLock) {
454                 if (mWindowMagnificationMgr == null) {
455                     return false;
456                 }
457                 isActivated = mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId);
458             }
459         }
460         return isActivated;
461     }
462 
463     private final class DisableMagnificationCallback implements
464             MagnificationAnimationCallback {
465         private final TransitionCallBack mTransitionCallBack;
466         private boolean mExpired = false;
467         private final int mDisplayId;
468         private final int mTargetMode;
469         private final int mCurrentMode;
470         private final float mCurrentScale;
471         private final PointF mCurrentCenter = new PointF();
472 
DisableMagnificationCallback(TransitionCallBack transitionCallBack, int displayId, int targetMode, float scale, PointF currentCenter)473         DisableMagnificationCallback(TransitionCallBack transitionCallBack,
474                 int displayId, int targetMode, float scale, PointF currentCenter) {
475             mTransitionCallBack = transitionCallBack;
476             mDisplayId = displayId;
477             mTargetMode = targetMode;
478             mCurrentMode = mTargetMode ^ ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
479             mCurrentScale = scale;
480             mCurrentCenter.set(currentCenter);
481         }
482 
483         @Override
onResult(boolean success)484         public void onResult(boolean success) {
485             synchronized (mLock) {
486                 if (DEBUG) {
487                     Slog.d(TAG, "onResult success = " + success);
488                 }
489                 if (mExpired) {
490                     return;
491                 }
492                 setExpiredAndRemoveFromListLocked();
493                 if (success) {
494                     adjustCurrentCenterIfNeededLocked();
495                     applyMagnificationModeLocked(mTargetMode);
496                 }
497                 updateMagnificationButton(mDisplayId, mTargetMode);
498                 mTransitionCallBack.onResult(success);
499             }
500         }
501 
adjustCurrentCenterIfNeededLocked()502         private void adjustCurrentCenterIfNeededLocked() {
503             if (mTargetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
504                 return;
505             }
506             final Region outRegion = new Region();
507             getFullScreenMagnificationController().getMagnificationRegion(mDisplayId, outRegion);
508             if (outRegion.contains((int) mCurrentCenter.x, (int) mCurrentCenter.y)) {
509                 return;
510             }
511             final Rect bounds = outRegion.getBounds();
512             mCurrentCenter.set(bounds.exactCenterX(), bounds.exactCenterY());
513         }
514 
restoreToCurrentMagnificationMode()515         void restoreToCurrentMagnificationMode() {
516             synchronized (mLock) {
517                 if (mExpired) {
518                     return;
519                 }
520                 setExpiredAndRemoveFromListLocked();
521                 applyMagnificationModeLocked(mCurrentMode);
522                 updateMagnificationButton(mDisplayId, mCurrentMode);
523                 mTransitionCallBack.onResult(true);
524             }
525         }
526 
setExpiredAndRemoveFromListLocked()527         void setExpiredAndRemoveFromListLocked() {
528             mExpired = true;
529             setDisableMagnificationCallbackLocked(mDisplayId, null);
530         }
531 
applyMagnificationModeLocked(int mode)532         private void applyMagnificationModeLocked(int mode) {
533             if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
534                 getFullScreenMagnificationController().setScaleAndCenter(mDisplayId,
535                         mCurrentScale, mCurrentCenter.x,
536                         mCurrentCenter.y, true,
537                         AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
538             } else {
539                 getWindowMagnificationMgr().enableWindowMagnification(mDisplayId,
540                         mCurrentScale, mCurrentCenter.x,
541                         mCurrentCenter.y);
542             }
543         }
544     }
545 }
546