blob: 5cf2a638fa3e22e0a5a4fbe7abb7ad64bc7f311a [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.accessibility.magnification;
import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_DEFAULT;
import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK;
import android.accessibilityservice.MagnificationConfig;
import android.annotation.NonNull;
import android.graphics.Region;
import android.util.Slog;
import android.view.Display;
import java.io.PrintWriter;
import java.util.ArrayList;
/**
* Processor class for AccessibilityService connection to control magnification on the specified
* display. This wraps the function of magnification controller.
*
* <p>
* If the magnification config uses {@link DEFAULT_MODE}. This processor will control the current
* activated magnifier on the display. If there is no magnifier activated, it controls
* full-screen magnifier by default.
* </p>
*
* <p>
* If the magnification config uses {@link FULLSCREEN_MODE}. This processor will control
* full-screen magnifier on the display.
* </p>
*
* <p>
* If the magnification config uses {@link WINDOW_MODE}. This processor will control
* the activated window magnifier on the display.
* </p>
*
* @see MagnificationController
* @see FullScreenMagnificationController
*/
public class MagnificationProcessor {
private static final String TAG = "MagnificationProcessor";
private static final boolean DEBUG = false;
private final MagnificationController mController;
public MagnificationProcessor(MagnificationController controller) {
mController = controller;
}
/**
* Gets the magnification config of the display.
*
* @param displayId The logical display id
* @return the magnification config
*/
public @NonNull MagnificationConfig getMagnificationConfig(int displayId) {
final int mode = getControllingMode(displayId);
MagnificationConfig.Builder builder = new MagnificationConfig.Builder();
if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
final FullScreenMagnificationController fullScreenMagnificationController =
mController.getFullScreenMagnificationController();
builder.setMode(mode)
.setActivated(mController.isActivated(displayId, MAGNIFICATION_MODE_FULLSCREEN))
.setScale(fullScreenMagnificationController.getScale(displayId))
.setCenterX(fullScreenMagnificationController.getCenterX(displayId))
.setCenterY(fullScreenMagnificationController.getCenterY(displayId));
} else if (mode == MAGNIFICATION_MODE_WINDOW) {
final WindowMagnificationManager windowMagnificationManager =
mController.getWindowMagnificationMgr();
builder.setMode(mode)
.setActivated(mController.isActivated(displayId, MAGNIFICATION_MODE_WINDOW))
.setScale(windowMagnificationManager.getScale(displayId))
.setCenterX(windowMagnificationManager.getCenterX(displayId))
.setCenterY(windowMagnificationManager.getCenterY(displayId));
} else {
// For undefined mode, set enabled to false
builder.setActivated(false);
}
return builder.build();
}
/**
* Sets the magnification config of the display. If animation is disabled, the transition
* is immediate.
*
* @param displayId The logical display id
* @param config The magnification config
* @param animate {@code true} to animate from the current config or
* {@code false} to set the config immediately
* @param id The ID of the service requesting the change
* @return {@code true} if the magnification spec changed, {@code false} if the spec did not
* change
*/
public boolean setMagnificationConfig(int displayId, @NonNull MagnificationConfig config,
boolean animate, int id) {
if (DEBUG) {
Slog.d(TAG, "setMagnificationConfig config=" + config);
}
if (transitionModeIfNeeded(displayId, config, animate, id)) {
return true;
}
int configMode = config.getMode();
if (configMode == MAGNIFICATION_MODE_DEFAULT) {
configMode = getControllingMode(displayId);
}
// Check should activate or deactivate the target mode in config
boolean configActivated = config.isActivated();
if (configMode == MAGNIFICATION_MODE_FULLSCREEN) {
if (configActivated) {
return setScaleAndCenterForFullScreenMagnification(displayId, config.getScale(),
config.getCenterX(), config.getCenterY(),
animate, id);
} else {
return resetFullscreenMagnification(displayId, animate);
}
} else if (configMode == MAGNIFICATION_MODE_WINDOW) {
if (configActivated) {
return mController.getWindowMagnificationMgr().enableWindowMagnification(displayId,
config.getScale(), config.getCenterX(), config.getCenterY(),
animate ? STUB_ANIMATION_CALLBACK : null,
id);
} else {
return mController.getWindowMagnificationMgr()
.disableWindowMagnification(displayId, false);
}
}
return false;
}
private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale,
float centerX, float centerY, boolean animate, int id) {
if (!isRegistered(displayId)) {
register(displayId);
}
return mController.getFullScreenMagnificationController().setScaleAndCenter(
displayId, scale, centerX, centerY, animate, id);
}
/**
* Returns {@code true} if transition magnification mode needed. And it is no need to transition
* mode when the controlling mode is unchanged or the controlling magnifier is not activated.
*/
private boolean transitionModeIfNeeded(int displayId, MagnificationConfig config,
boolean animate, int id) {
int currentMode = getControllingMode(displayId);
if (config.getMode() == MagnificationConfig.MAGNIFICATION_MODE_DEFAULT) {
return false;
}
// Target mode is as same as current mode and is not transitioning.
if (currentMode == config.getMode() && !mController.hasDisableMagnificationCallback(
displayId)) {
return false;
}
mController.transitionMagnificationConfigMode(displayId, config, animate, id);
return true;
}
/**
* Returns the magnification scale of full-screen magnification on the display.
* If an animation is in progress, this reflects the end state of the animation.
*
* @param displayId The logical display id.
* @return the scale
*/
public float getScale(int displayId) {
return mController.getFullScreenMagnificationController().getScale(displayId);
}
/**
* Returns the magnification center in X coordinate of full-screen magnification.
* If the service can control magnification but fullscreen magnifier is not registered, it will
* register the magnifier for this call then unregister the magnifier finally to make the
* magnification center correct.
*
* @param displayId The logical display id
* @param canControlMagnification Whether the service can control magnification
* @return the X coordinate
*/
public float getCenterX(int displayId, boolean canControlMagnification) {
boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
canControlMagnification);
try {
return mController.getFullScreenMagnificationController().getCenterX(displayId);
} finally {
if (registeredJustForThisCall) {
unregister(displayId);
}
}
}
/**
* Returns the magnification center in Y coordinate of full-screen magnification.
* If the service can control magnification but fullscreen magnifier is not registered, it will
* register the magnifier for this call then unregister the magnifier finally to make the
* magnification center correct.
*
* @param displayId The logical display id
* @param canControlMagnification Whether the service can control magnification
* @return the Y coordinate
*/
public float getCenterY(int displayId, boolean canControlMagnification) {
boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
canControlMagnification);
try {
return mController.getFullScreenMagnificationController().getCenterY(displayId);
} finally {
if (registeredJustForThisCall) {
unregister(displayId);
}
}
}
/**
* Returns the region of the screen currently active for magnification if the
* controlling magnification is {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN}.
* Returns the region of screen projected on the magnification window if the controlling
* magnification is {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW}.
* <p>
* If the controlling mode is {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN},
* the returned region will be empty if the magnification is
* not active. And the magnification is active if magnification gestures are enabled
* or if a service is running that can control magnification.
* </p><p>
* If the controlling mode is {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW},
* the returned region will be empty if the magnification is not activated.
* </p>
*
* @param displayId The logical display id
* @param outRegion the region to populate
* @param canControlMagnification Whether the service can control magnification
*/
public void getCurrentMagnificationRegion(int displayId, @NonNull Region outRegion,
boolean canControlMagnification) {
int currentMode = getControllingMode(displayId);
if (currentMode == MAGNIFICATION_MODE_FULLSCREEN) {
getFullscreenMagnificationRegion(displayId, outRegion, canControlMagnification);
} else if (currentMode == MAGNIFICATION_MODE_WINDOW) {
mController.getWindowMagnificationMgr().getMagnificationSourceBounds(displayId,
outRegion);
}
}
/**
* Returns the magnification bounds of full-screen magnification on the given display.
*
* @param displayId The logical display id
* @param outRegion the region to populate
* @param canControlMagnification Whether the service can control magnification
*/
public void getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion,
boolean canControlMagnification) {
boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
canControlMagnification);
try {
mController.getFullScreenMagnificationController().getMagnificationRegion(displayId,
outRegion);
} finally {
if (registeredJustForThisCall) {
unregister(displayId);
}
}
}
/**
* Resets the controlling magnifier on the given display.
* For resetting window magnifier, it disables the magnifier by setting the scale to 1.
*
* @param displayId The logical display id.
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
public boolean resetCurrentMagnification(int displayId, boolean animate) {
int mode = getControllingMode(displayId);
if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
return mController.getFullScreenMagnificationController().reset(displayId, animate);
} else if (mode == MAGNIFICATION_MODE_WINDOW) {
return mController.getWindowMagnificationMgr().disableWindowMagnification(displayId,
false, animate ? STUB_ANIMATION_CALLBACK : null);
}
return false;
}
/**
* Resets the full-screen magnification on the given display.
*
* @param displayId The logical display id.
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
public boolean resetFullscreenMagnification(int displayId, boolean animate) {
return mController.getFullScreenMagnificationController().reset(displayId, animate);
}
/**
* Resets all the magnifiers on all the displays.
* Called when the a11y service connection that has changed the current magnification spec is
* unbound or the binder died.
*
* @param connectionId The connection id
*/
public void resetAllIfNeeded(int connectionId) {
mController.getFullScreenMagnificationController().resetAllIfNeeded(connectionId);
mController.getWindowMagnificationMgr().resetAllIfNeeded(connectionId);
}
/**
* {@link FullScreenMagnificationController#isActivated(int)}
* {@link WindowMagnificationManager#isWindowMagnifierEnabled(int)}
*/
public boolean isMagnifying(int displayId) {
int mode = getControllingMode(displayId);
if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
return mController.getFullScreenMagnificationController().isActivated(displayId);
} else if (mode == MAGNIFICATION_MODE_WINDOW) {
return mController.getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId);
}
return false;
}
/**
* Returns the current controlling magnification mode on the given display.
* If there is no magnifier activated, it fallbacks to the last activated mode.
* And the last activated mode is {@link FULLSCREEN_MODE} by default.
*
* @param displayId The logical display id
*/
public int getControllingMode(int displayId) {
if (mController.isActivated(displayId,
ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)) {
return MAGNIFICATION_MODE_WINDOW;
} else if (mController.isActivated(displayId,
ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)) {
return MAGNIFICATION_MODE_FULLSCREEN;
} else {
return (mController.getLastMagnificationActivatedMode(displayId)
== ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)
? MAGNIFICATION_MODE_WINDOW
: MAGNIFICATION_MODE_FULLSCREEN;
}
}
private boolean registerDisplayMagnificationIfNeeded(int displayId,
boolean canControlMagnification) {
if (!isRegistered(displayId) && canControlMagnification) {
register(displayId);
return true;
}
return false;
}
private boolean isRegistered(int displayId) {
return mController.getFullScreenMagnificationController().isRegistered(displayId);
}
/**
* {@link FullScreenMagnificationController#register(int)}
*/
private void register(int displayId) {
mController.getFullScreenMagnificationController().register(displayId);
}
/**
* {@link FullScreenMagnificationController#unregister(int)} (int)}
*/
private void unregister(int displayId) {
mController.getFullScreenMagnificationController().unregister(displayId);
}
/**
* Dumps magnification configuration {@link MagnificationConfig} and state for each
* {@link Display}
*/
public void dump(final PrintWriter pw, ArrayList<Display> displaysList) {
for (int i = 0; i < displaysList.size(); i++) {
final int displayId = displaysList.get(i).getDisplayId();
final MagnificationConfig config = getMagnificationConfig(displayId);
pw.println("Magnifier on display#" + displayId);
pw.append(" " + config).println();
final Region region = new Region();
getCurrentMagnificationRegion(displayId, region, true);
if (!region.isEmpty()) {
pw.append(" Magnification region=").append(region.toString()).println();
}
pw.append(" IdOfLastServiceToMagnify="
+ getIdOfLastServiceToMagnify(config.getMode(), displayId)).println();
dumpTrackingTypingFocusEnabledState(pw, displayId, config.getMode());
}
pw.append(" SupportWindowMagnification="
+ mController.supportWindowMagnification()).println();
pw.append(" WindowMagnificationConnectionState="
+ mController.getWindowMagnificationMgr().getConnectionState()).println();
}
private int getIdOfLastServiceToMagnify(int mode, int displayId) {
return (mode == MAGNIFICATION_MODE_FULLSCREEN)
? mController.getFullScreenMagnificationController()
.getIdOfLastServiceToMagnify(displayId)
: mController.getWindowMagnificationMgr().getIdOfLastServiceToMagnify(
displayId);
}
private void dumpTrackingTypingFocusEnabledState(final PrintWriter pw, int displayId,
int mode) {
if (mode == MAGNIFICATION_MODE_WINDOW) {
pw.append(" TrackingTypingFocusEnabled=" + mController
.getWindowMagnificationMgr().isTrackingTypingFocusEnabled(displayId))
.println();
}
}
}