1 /*
2  * Copyright (C) 2021 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_DEFAULT;
20 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
21 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
22 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
23 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
24 import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK;
25 
26 import android.accessibilityservice.MagnificationConfig;
27 import android.annotation.NonNull;
28 import android.graphics.Region;
29 import android.util.Slog;
30 import android.view.Display;
31 
32 import java.io.PrintWriter;
33 import java.util.ArrayList;
34 
35 /**
36  * Processor class for AccessibilityService connection to control magnification on the specified
37  * display. This wraps the function of magnification controller.
38  *
39  * <p>
40  * If the magnification config uses {@link DEFAULT_MODE}. This processor will control the current
41  * activated magnifier on the display. If there is no magnifier activated, it controls
42  * full-screen magnifier by default.
43  * </p>
44  *
45  * <p>
46  * If the magnification config uses {@link FULLSCREEN_MODE}. This processor will control
47  * full-screen magnifier on the display.
48  * </p>
49  *
50  * <p>
51  * If the magnification config uses {@link WINDOW_MODE}. This processor will control
52  * the activated window magnifier on the display.
53  * </p>
54  *
55  * @see MagnificationController
56  * @see FullScreenMagnificationController
57  */
58 public class MagnificationProcessor {
59 
60     private static final String TAG = "MagnificationProcessor";
61     private static final boolean DEBUG = false;
62 
63     private final MagnificationController mController;
64 
MagnificationProcessor(MagnificationController controller)65     public MagnificationProcessor(MagnificationController controller) {
66         mController = controller;
67     }
68 
69     /**
70      * Gets the magnification config of the display.
71      *
72      * @param displayId The logical display id
73      * @return the magnification config
74      */
getMagnificationConfig(int displayId)75     public @NonNull MagnificationConfig getMagnificationConfig(int displayId) {
76         final int mode = getControllingMode(displayId);
77         MagnificationConfig.Builder builder = new MagnificationConfig.Builder();
78         if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
79             final FullScreenMagnificationController fullScreenMagnificationController =
80                     mController.getFullScreenMagnificationController();
81             builder.setMode(mode)
82                     .setActivated(mController.isActivated(displayId, MAGNIFICATION_MODE_FULLSCREEN))
83                     .setScale(fullScreenMagnificationController.getScale(displayId))
84                     .setCenterX(fullScreenMagnificationController.getCenterX(displayId))
85                     .setCenterY(fullScreenMagnificationController.getCenterY(displayId));
86         } else if (mode == MAGNIFICATION_MODE_WINDOW) {
87             final MagnificationConnectionManager magnificationConnectionManager =
88                     mController.getMagnificationConnectionManager();
89             builder.setMode(mode)
90                     .setActivated(mController.isActivated(displayId, MAGNIFICATION_MODE_WINDOW))
91                     .setScale(magnificationConnectionManager.getScale(displayId))
92                     .setCenterX(magnificationConnectionManager.getCenterX(displayId))
93                     .setCenterY(magnificationConnectionManager.getCenterY(displayId));
94         } else {
95             // For undefined mode, set enabled to false
96             builder.setActivated(false);
97         }
98         return builder.build();
99     }
100 
101     /**
102      * Sets the magnification config of the display. If animation is disabled, the transition
103      * is immediate.
104      *
105      * @param displayId The logical display id
106      * @param config    The magnification config
107      * @param animate   {@code true} to animate from the current config or
108      *                  {@code false} to set the config immediately
109      * @param id        The ID of the service requesting the change
110      * @return {@code true} if the magnification spec changed, {@code false} if the spec did not
111      * change
112      */
setMagnificationConfig(int displayId, @NonNull MagnificationConfig config, boolean animate, int id)113     public boolean setMagnificationConfig(int displayId, @NonNull MagnificationConfig config,
114             boolean animate, int id) {
115         if (DEBUG) {
116             Slog.d(TAG, "setMagnificationConfig config=" + config);
117         }
118         if (transitionModeIfNeeded(displayId, config, animate, id)) {
119             return true;
120         }
121 
122         int configMode = config.getMode();
123         if (configMode == MAGNIFICATION_MODE_DEFAULT) {
124             configMode = getControllingMode(displayId);
125         }
126         // Check should activate or deactivate the target mode in config
127         boolean configActivated = config.isActivated();
128         if (configMode == MAGNIFICATION_MODE_FULLSCREEN) {
129             if (configActivated) {
130                 return setScaleAndCenterForFullScreenMagnification(displayId, config.getScale(),
131                         config.getCenterX(), config.getCenterY(),
132                         animate, id);
133             } else {
134                 return resetFullscreenMagnification(displayId, animate);
135             }
136         } else if (configMode == MAGNIFICATION_MODE_WINDOW) {
137             if (configActivated) {
138                 return mController.getMagnificationConnectionManager().enableWindowMagnification(
139                         displayId, config.getScale(), config.getCenterX(), config.getCenterY(),
140                         animate ? STUB_ANIMATION_CALLBACK : null,
141                         id);
142             } else {
143                 return mController.getMagnificationConnectionManager()
144                         .disableWindowMagnification(displayId, false);
145             }
146         }
147         return false;
148     }
149 
setScaleAndCenterForFullScreenMagnification(int displayId, float scale, float centerX, float centerY, boolean animate, int id)150     private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale,
151             float centerX, float centerY, boolean animate, int id) {
152 
153         if (!isRegistered(displayId)) {
154             register(displayId);
155         }
156         return mController.getFullScreenMagnificationController().setScaleAndCenter(
157                 displayId, scale, centerX, centerY, animate, id);
158     }
159 
160     /**
161      * Returns {@code true} if transition magnification mode needed. And it is no need to transition
162      * mode when the controlling mode is unchanged or the controlling magnifier is not activated.
163      */
transitionModeIfNeeded(int displayId, MagnificationConfig config, boolean animate, int id)164     private boolean transitionModeIfNeeded(int displayId, MagnificationConfig config,
165             boolean animate, int id) {
166         int currentMode = getControllingMode(displayId);
167         if (config.getMode() == MagnificationConfig.MAGNIFICATION_MODE_DEFAULT) {
168             return false;
169         }
170         // Target mode is as same as current mode and is not transitioning.
171         if (currentMode == config.getMode() && !mController.hasDisableMagnificationCallback(
172                 displayId)) {
173             return false;
174         }
175         mController.transitionMagnificationConfigMode(displayId, config, animate, id);
176         return true;
177     }
178 
179     /**
180      * Returns the magnification scale of full-screen magnification on the display.
181      * If an animation is in progress, this reflects the end state of the animation.
182      *
183      * @param displayId The logical display id.
184      * @return the scale
185      */
getScale(int displayId)186     public float getScale(int displayId) {
187         return mController.getFullScreenMagnificationController().getScale(displayId);
188     }
189 
190     /**
191      * Returns the magnification center in X coordinate of full-screen magnification.
192      * If the service can control magnification but fullscreen magnifier is not registered, it will
193      * register the magnifier for this call then unregister the magnifier finally to make the
194      * magnification center correct.
195      *
196      * @param displayId The logical display id
197      * @param canControlMagnification Whether the service can control magnification
198      * @return the X coordinate
199      */
getCenterX(int displayId, boolean canControlMagnification)200     public float getCenterX(int displayId, boolean canControlMagnification) {
201         boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
202                 canControlMagnification);
203         try {
204             return mController.getFullScreenMagnificationController().getCenterX(displayId);
205         } finally {
206             if (registeredJustForThisCall) {
207                 unregister(displayId);
208             }
209         }
210     }
211 
212     /**
213      * Returns the magnification center in Y coordinate of full-screen magnification.
214      * If the service can control magnification but fullscreen magnifier is not registered, it will
215      * register the magnifier for this call then unregister the magnifier finally to make the
216      * magnification center correct.
217      *
218      * @param displayId The logical display id
219      * @param canControlMagnification Whether the service can control magnification
220      * @return the Y coordinate
221      */
getCenterY(int displayId, boolean canControlMagnification)222     public float getCenterY(int displayId, boolean canControlMagnification) {
223         boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
224                 canControlMagnification);
225         try {
226             return mController.getFullScreenMagnificationController().getCenterY(displayId);
227         } finally {
228             if (registeredJustForThisCall) {
229                 unregister(displayId);
230             }
231         }
232     }
233 
234     /**
235      * Returns the region of the screen currently active for magnification if the
236      * controlling magnification is {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN}.
237      * Returns the region of screen projected on the magnification window if the controlling
238      * magnification is {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW}.
239      * <p>
240      * If the controlling mode is {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN},
241      * the returned region will be empty if the magnification is
242      * not active. And the magnification is active if magnification gestures are enabled
243      * or if a service is running that can control magnification.
244      * </p><p>
245      * If the controlling mode is {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW},
246      * the returned region will be empty if the magnification is not activated.
247      * </p>
248      *
249      * @param displayId The logical display id
250      * @param outRegion the region to populate
251      * @param canControlMagnification Whether the service can control magnification
252      */
getCurrentMagnificationRegion(int displayId, @NonNull Region outRegion, boolean canControlMagnification)253     public void getCurrentMagnificationRegion(int displayId, @NonNull Region outRegion,
254             boolean canControlMagnification) {
255         int currentMode = getControllingMode(displayId);
256         if (currentMode == MAGNIFICATION_MODE_FULLSCREEN) {
257             getFullscreenMagnificationRegion(displayId, outRegion, canControlMagnification);
258         } else if (currentMode == MAGNIFICATION_MODE_WINDOW) {
259             mController.getMagnificationConnectionManager().getMagnificationSourceBounds(displayId,
260                     outRegion);
261         }
262     }
263 
264     /**
265      * Returns the magnification bounds of full-screen magnification on the given display.
266      *
267      * @param displayId The logical display id
268      * @param outRegion the region to populate
269      * @param canControlMagnification Whether the service can control magnification
270      */
getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion, boolean canControlMagnification)271     public void getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion,
272             boolean canControlMagnification) {
273         boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
274                 canControlMagnification);
275         try {
276             mController.getFullScreenMagnificationController().getMagnificationRegion(displayId,
277                     outRegion);
278         } finally {
279             if (registeredJustForThisCall) {
280                 unregister(displayId);
281             }
282         }
283     }
284 
285     /**
286      * Resets the controlling magnifier on the given display.
287      * For resetting window magnifier, it disables the magnifier by setting the scale to 1.
288      *
289      * @param displayId The logical display id.
290      * @param animate   {@code true} to animate the transition, {@code false}
291      *                  to transition immediately
292      * @return {@code true} if the magnification spec changed, {@code false} if
293      * the spec did not change
294      */
resetCurrentMagnification(int displayId, boolean animate)295     public boolean resetCurrentMagnification(int displayId, boolean animate) {
296         int mode = getControllingMode(displayId);
297         if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
298             return mController.getFullScreenMagnificationController().reset(displayId, animate);
299         } else if (mode == MAGNIFICATION_MODE_WINDOW) {
300             return mController.getMagnificationConnectionManager().disableWindowMagnification(
301                     displayId, false, animate ? STUB_ANIMATION_CALLBACK : null);
302         }
303         return false;
304     }
305 
306     /**
307      * Resets the full-screen magnification on the given display.
308      *
309      * @param displayId The logical display id.
310      * @param animate   {@code true} to animate the transition, {@code false}
311      *                  to transition immediately
312      * @return {@code true} if the magnification spec changed, {@code false} if
313      * the spec did not change
314      */
resetFullscreenMagnification(int displayId, boolean animate)315     public boolean resetFullscreenMagnification(int displayId, boolean animate) {
316         return mController.getFullScreenMagnificationController().reset(displayId, animate);
317     }
318 
319     /**
320      * Resets all the magnifiers on all the displays.
321      * Called when the a11y service connection that has changed the current magnification spec is
322      * unbound or the binder died.
323      *
324      * @param connectionId The connection id
325      */
resetAllIfNeeded(int connectionId)326     public void resetAllIfNeeded(int connectionId) {
327         mController.getFullScreenMagnificationController().resetAllIfNeeded(connectionId);
328         mController.getMagnificationConnectionManager().resetAllIfNeeded(connectionId);
329     }
330 
331     /**
332      * {@link FullScreenMagnificationController#isActivated(int)}
333      * {@link MagnificationConnectionManager#isWindowMagnifierEnabled(int)}
334      */
isMagnifying(int displayId)335     public boolean isMagnifying(int displayId) {
336         int mode = getControllingMode(displayId);
337         if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
338             return mController.getFullScreenMagnificationController().isActivated(displayId);
339         } else if (mode == MAGNIFICATION_MODE_WINDOW) {
340             return mController.getMagnificationConnectionManager().isWindowMagnifierEnabled(
341                     displayId);
342         }
343         return false;
344     }
345 
346     /**
347      * Returns the current controlling magnification mode on the given display.
348      * If there is no magnifier activated, it fallbacks to the last activated mode.
349      * And the last activated mode is {@link FULLSCREEN_MODE} by default.
350      *
351      * @param displayId The logical display id
352      */
getControllingMode(int displayId)353     public int getControllingMode(int displayId) {
354         if (mController.isActivated(displayId,
355                 ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)) {
356             return MAGNIFICATION_MODE_WINDOW;
357         } else if (mController.isActivated(displayId,
358                 ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)) {
359             return MAGNIFICATION_MODE_FULLSCREEN;
360         } else {
361             return (mController.getLastMagnificationActivatedMode(displayId)
362                     == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)
363                     ? MAGNIFICATION_MODE_WINDOW
364                     : MAGNIFICATION_MODE_FULLSCREEN;
365         }
366     }
367 
registerDisplayMagnificationIfNeeded(int displayId, boolean canControlMagnification)368     private boolean registerDisplayMagnificationIfNeeded(int displayId,
369             boolean canControlMagnification) {
370         if (!isRegistered(displayId) && canControlMagnification) {
371             register(displayId);
372             return true;
373         }
374         return false;
375     }
376 
isRegistered(int displayId)377     private boolean isRegistered(int displayId) {
378         return mController.getFullScreenMagnificationController().isRegistered(displayId);
379     }
380 
381     /**
382      * {@link FullScreenMagnificationController#register(int)}
383      */
register(int displayId)384     private void register(int displayId) {
385         mController.getFullScreenMagnificationController().register(displayId);
386     }
387 
388     /**
389      * {@link FullScreenMagnificationController#unregister(int)} (int)}
390      */
unregister(int displayId)391     private void unregister(int displayId) {
392         mController.getFullScreenMagnificationController().unregister(displayId);
393     }
394 
395     /**
396      * Dumps magnification configuration {@link MagnificationConfig} and state for each
397      * {@link Display}
398      */
dump(final PrintWriter pw, ArrayList<Display> displaysList)399     public void dump(final PrintWriter pw, ArrayList<Display> displaysList) {
400         for (int i = 0; i < displaysList.size(); i++) {
401             final int displayId = displaysList.get(i).getDisplayId();
402 
403             final MagnificationConfig config = getMagnificationConfig(displayId);
404             pw.println("Magnifier on display#" + displayId);
405             pw.append("    " + config).println();
406 
407             final Region region = new Region();
408             getCurrentMagnificationRegion(displayId, region, true);
409             if (!region.isEmpty()) {
410                 pw.append("    Magnification region=").append(region.toString()).println();
411             }
412             pw.append("    IdOfLastServiceToMagnify="
413                     + getIdOfLastServiceToMagnify(config.getMode(), displayId)).println();
414 
415             dumpTrackingTypingFocusEnabledState(pw, displayId, config.getMode());
416         }
417         pw.append("    SupportWindowMagnification="
418                 + mController.supportWindowMagnification()).println();
419         pw.append("    WindowMagnificationConnectionState="
420                 + mController.getMagnificationConnectionManager().getConnectionState()).println();
421     }
422 
getIdOfLastServiceToMagnify(int mode, int displayId)423     private int getIdOfLastServiceToMagnify(int mode, int displayId) {
424         return (mode == MAGNIFICATION_MODE_FULLSCREEN)
425                 ? mController.getFullScreenMagnificationController()
426                 .getIdOfLastServiceToMagnify(displayId)
427                 : mController.getMagnificationConnectionManager().getIdOfLastServiceToMagnify(
428                         displayId);
429     }
430 
dumpTrackingTypingFocusEnabledState(final PrintWriter pw, int displayId, int mode)431     private void dumpTrackingTypingFocusEnabledState(final PrintWriter pw, int displayId,
432             int mode) {
433         if (mode == MAGNIFICATION_MODE_WINDOW) {
434             pw.append("    TrackingTypingFocusEnabled="
435                             + mController.getMagnificationConnectionManager()
436                                 .isTrackingTypingFocusEnabled(displayId))
437                     .println();
438         }
439     }
440 }
441