blob: 87fbee71ab59738ef9f659e23380e39790596a32 [file] [log] [blame]
/*
* Copyright (C) 2020 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_FULLSCREEN;
import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID;
import android.accessibilityservice.MagnificationConfig;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.view.accessibility.MagnificationAnimationCallback;
import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.wm.WindowManagerInternal;
import java.util.concurrent.Executor;
/**
* Handles all magnification controllers initialization, generic interactions,
* magnification mode transition and magnification switch UI show/hide logic
* in the following callbacks:
*
* <ol>
* <li> 1. {@link #onTouchInteractionStart} shows magnification switch UI when
* the user touch interaction starts if magnification capabilities is all. </li>
* <li> 2. {@link #onTouchInteractionEnd} shows magnification switch UI when
* the user touch interaction ends if magnification capabilities is all. </li>
* <li> 3. {@link #onWindowMagnificationActivationState} updates magnification switch UI
* depending on magnification capabilities and magnification active state when window
* magnification activation state change.</li>
* <li> 4. {@link #onFullScreenMagnificationActivationState} updates magnification switch UI
* depending on magnification capabilities and magnification active state when fullscreen
* magnification activation state change.</li>
* <li> 4. {@link #onRequestMagnificationSpec} updates magnification switch UI depending on
* magnification capabilities and magnification active state when new magnification spec is
* changed by external request from calling public APIs. </li>
* </ol>
*
* <b>Note</b> Updates magnification switch UI when magnification mode transition
* is done and before invoking {@link TransitionCallBack#onResult}.
*/
public class MagnificationController implements WindowMagnificationManager.Callback,
MagnificationGestureHandler.Callback,
FullScreenMagnificationController.MagnificationInfoChangedCallback,
WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks {
private static final boolean DEBUG = false;
private static final String TAG = "MagnificationController";
private final AccessibilityManagerService mAms;
private final PointF mTempPoint = new PointF();
private final Object mLock;
private final Context mContext;
@GuardedBy("mLock")
private final SparseArray<DisableMagnificationCallback>
mMagnificationEndRunnableSparseArray = new SparseArray();
private final AlwaysOnMagnificationFeatureFlag mAlwaysOnMagnificationFeatureFlag;
private final MagnificationScaleProvider mScaleProvider;
private FullScreenMagnificationController mFullScreenMagnificationController;
private WindowMagnificationManager mWindowMagnificationMgr;
private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
/** Whether the platform supports window magnification feature. */
private final boolean mSupportWindowMagnification;
private final Executor mBackgroundExecutor;
@GuardedBy("mLock")
private final SparseIntArray mCurrentMagnificationModeArray = new SparseIntArray();
@GuardedBy("mLock")
private final SparseIntArray mLastMagnificationActivatedModeArray = new SparseIntArray();
// Track the active user to reset the magnification and get the associated user settings.
private @UserIdInt int mUserId = UserHandle.USER_SYSTEM;
@GuardedBy("mLock")
private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray();
@GuardedBy("mLock")
private final SparseLongArray mWindowModeEnabledTimeArray = new SparseLongArray();
@GuardedBy("mLock")
private final SparseLongArray mFullScreenModeEnabledTimeArray = new SparseLongArray();
/**
* The transitioning magnification modes on the displays. The controller notifies
* magnification change depending on the target config mode.
* If the target mode is null, it means the config mode of the display is not
* transitioning.
*/
@GuardedBy("mLock")
private final SparseArray<Integer> mTransitionModes = new SparseArray();
@GuardedBy("mLock")
private final SparseArray<WindowManagerInternal.AccessibilityControllerInternal
.UiChangesForAccessibilityCallbacks> mAccessibilityCallbacksDelegateArray =
new SparseArray<>();
/**
* A callback to inform the magnification transition result on the given display.
*/
public interface TransitionCallBack {
/**
* Invoked when the transition ends.
*
* @param displayId The display id.
* @param success {@code true} if the transition success.
*/
void onResult(int displayId, boolean success);
}
public MagnificationController(AccessibilityManagerService ams, Object lock,
Context context, MagnificationScaleProvider scaleProvider,
Executor backgroundExecutor) {
mAms = ams;
mLock = lock;
mContext = context;
mScaleProvider = scaleProvider;
mBackgroundExecutor = backgroundExecutor;
LocalServices.getService(WindowManagerInternal.class)
.getAccessibilityController().setUiChangesForAccessibilityCallbacks(this);
mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
FEATURE_WINDOW_MAGNIFICATION);
mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context);
mAlwaysOnMagnificationFeatureFlag.addOnChangedListener(
mBackgroundExecutor, mAms::updateAlwaysOnMagnification);
}
@VisibleForTesting
public MagnificationController(AccessibilityManagerService ams, Object lock,
Context context, FullScreenMagnificationController fullScreenMagnificationController,
WindowMagnificationManager windowMagnificationManager,
MagnificationScaleProvider scaleProvider, Executor backgroundExecutor) {
this(ams, lock, context, scaleProvider, backgroundExecutor);
mFullScreenMagnificationController = fullScreenMagnificationController;
mWindowMagnificationMgr = windowMagnificationManager;
}
@Override
public void onPerformScaleAction(int displayId, float scale) {
if (getFullScreenMagnificationController().isActivated(displayId)) {
getFullScreenMagnificationController().setScaleAndCenter(displayId, scale,
Float.NaN, Float.NaN, false, MAGNIFICATION_GESTURE_HANDLER_ID);
getFullScreenMagnificationController().persistScale(displayId);
} else if (getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId)) {
getWindowMagnificationMgr().setScale(displayId, scale);
getWindowMagnificationMgr().persistScale(displayId);
}
}
@Override
public void onAccessibilityActionPerformed(int displayId) {
updateMagnificationUIControls(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
}
@Override
public void onTouchInteractionStart(int displayId, int mode) {
handleUserInteractionChanged(displayId, mode);
}
@Override
public void onTouchInteractionEnd(int displayId, int mode) {
handleUserInteractionChanged(displayId, mode);
}
private void handleUserInteractionChanged(int displayId, int mode) {
if (mMagnificationCapabilities != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) {
return;
}
updateMagnificationUIControls(displayId, mode);
}
private void updateMagnificationUIControls(int displayId, int mode) {
final boolean isActivated = isActivated(displayId, mode);
final boolean showModeSwitchButton;
final boolean enableSettingsPanel;
synchronized (mLock) {
showModeSwitchButton = isActivated
&& mMagnificationCapabilities == ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
enableSettingsPanel = isActivated
&& (mMagnificationCapabilities == ACCESSIBILITY_MAGNIFICATION_MODE_ALL
|| mMagnificationCapabilities == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
}
if (showModeSwitchButton) {
getWindowMagnificationMgr().showMagnificationButton(displayId, mode);
} else {
getWindowMagnificationMgr().removeMagnificationButton(displayId);
}
if (!enableSettingsPanel) {
// Whether the settings panel needs to be shown is controlled in system UI.
// Here, we only guarantee that the settings panel is closed when it is not needed.
getWindowMagnificationMgr().removeMagnificationSettingsPanel(displayId);
}
}
/** Returns {@code true} if the platform supports window magnification feature. */
public boolean supportWindowMagnification() {
return mSupportWindowMagnification;
}
/**
* Transitions to the target Magnification mode with current center of the magnification mode
* if it is available.
*
* @param displayId The logical display
* @param targetMode The target magnification mode
* @param transitionCallBack The callback invoked when the transition is finished.
*/
public void transitionMagnificationModeLocked(int displayId, int targetMode,
@NonNull TransitionCallBack transitionCallBack) {
// check if target mode is already activated
if (isActivated(displayId, targetMode)) {
transitionCallBack.onResult(displayId, true);
return;
}
final PointF currentCenter = getCurrentMagnificationCenterLocked(displayId, targetMode);
final DisableMagnificationCallback animationCallback =
getDisableMagnificationEndRunnableLocked(displayId);
if (currentCenter == null && animationCallback == null) {
transitionCallBack.onResult(displayId, true);
return;
}
if (animationCallback != null) {
if (animationCallback.mCurrentMode == targetMode) {
animationCallback.restoreToCurrentMagnificationMode();
return;
} else {
Slog.w(TAG, "discard duplicate request");
return;
}
}
if (currentCenter == null) {
Slog.w(TAG, "Invalid center, ignore it");
transitionCallBack.onResult(displayId, true);
return;
}
setTransitionState(displayId, targetMode);
final FullScreenMagnificationController screenMagnificationController =
getFullScreenMagnificationController();
final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr();
final float scale = getTargetModeScaleFromCurrentMagnification(displayId, targetMode);
final DisableMagnificationCallback animationEndCallback =
new DisableMagnificationCallback(transitionCallBack, displayId, targetMode,
scale, currentCenter, true);
setDisableMagnificationCallbackLocked(displayId, animationEndCallback);
if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
screenMagnificationController.reset(displayId, animationEndCallback);
} else {
windowMagnificationMgr.disableWindowMagnification(displayId, false,
animationEndCallback);
}
}
/**
* Transitions to the targeting magnification config mode with current center of the
* magnification mode if it is available. It disables the current magnifier immediately then
* transitions to the targeting magnifier.
*
* @param displayId The logical display id
* @param config The targeting magnification config
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
* @param id The ID of the service requesting the change
*/
public void transitionMagnificationConfigMode(int displayId, MagnificationConfig config,
boolean animate, int id) {
if (DEBUG) {
Slog.d(TAG, "transitionMagnificationConfigMode displayId = " + displayId
+ ", config = " + config);
}
synchronized (mLock) {
final int targetMode = config.getMode();
final boolean targetActivated = config.isActivated();
final PointF currentCenter = getCurrentMagnificationCenterLocked(displayId, targetMode);
final PointF magnificationCenter = new PointF(config.getCenterX(), config.getCenterY());
if (currentCenter != null) {
final float centerX = Float.isNaN(config.getCenterX())
? currentCenter.x
: config.getCenterX();
final float centerY = Float.isNaN(config.getCenterY())
? currentCenter.y
: config.getCenterY();
magnificationCenter.set(centerX, centerY);
}
final DisableMagnificationCallback animationCallback =
getDisableMagnificationEndRunnableLocked(displayId);
if (animationCallback != null) {
Slog.w(TAG, "Discard previous animation request");
animationCallback.setExpiredAndRemoveFromListLocked();
}
final FullScreenMagnificationController screenMagnificationController =
getFullScreenMagnificationController();
final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr();
final float targetScale = Float.isNaN(config.getScale())
? getTargetModeScaleFromCurrentMagnification(displayId, targetMode)
: config.getScale();
try {
setTransitionState(displayId, targetMode);
final MagnificationAnimationCallback magnificationAnimationCallback = animate
? success -> mAms.changeMagnificationMode(displayId, targetMode)
: null;
// Activate or deactivate target mode depending on config activated value
if (targetMode == MAGNIFICATION_MODE_WINDOW) {
screenMagnificationController.reset(displayId, false);
if (targetActivated) {
windowMagnificationMgr.enableWindowMagnification(displayId,
targetScale, magnificationCenter.x, magnificationCenter.y,
magnificationAnimationCallback, id);
} else {
windowMagnificationMgr.disableWindowMagnification(displayId, false);
}
} else if (targetMode == MAGNIFICATION_MODE_FULLSCREEN) {
windowMagnificationMgr.disableWindowMagnification(displayId, false, null);
if (targetActivated) {
if (!screenMagnificationController.isRegistered(displayId)) {
screenMagnificationController.register(displayId);
}
screenMagnificationController.setScaleAndCenter(displayId, targetScale,
magnificationCenter.x, magnificationCenter.y,
magnificationAnimationCallback, id);
} else {
if (screenMagnificationController.isRegistered(displayId)) {
screenMagnificationController.reset(displayId, false);
}
}
}
} finally {
if (!animate) {
mAms.changeMagnificationMode(displayId, targetMode);
}
// Reset transition state after enabling target mode.
setTransitionState(displayId, null);
}
}
}
/**
* Sets magnification config mode transition state. Called when the mode transition starts and
* ends. If the targetMode and the display id are null, it resets all
* the transition state.
*
* @param displayId The logical display id
* @param targetMode The transition target mode. It is not transitioning, if the target mode
* is set null
*/
private void setTransitionState(Integer displayId, Integer targetMode) {
synchronized (mLock) {
if (targetMode == null && displayId == null) {
mTransitionModes.clear();
} else {
mTransitionModes.put(displayId, targetMode);
}
}
}
// We assume the target mode is different from the current mode, and there is only
// two modes, so we get the target scale from another mode.
private float getTargetModeScaleFromCurrentMagnification(int displayId, int targetMode) {
if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
return getFullScreenMagnificationController().getScale(displayId);
} else {
return getWindowMagnificationMgr().getScale(displayId);
}
}
/**
* Return {@code true} if disable magnification animation callback of the display is running.
*
* @param displayId The logical display id
*/
public boolean hasDisableMagnificationCallback(int displayId) {
synchronized (mLock) {
final DisableMagnificationCallback animationCallback =
getDisableMagnificationEndRunnableLocked(displayId);
if (animationCallback != null) {
return true;
}
}
return false;
}
@GuardedBy("mLock")
private void setCurrentMagnificationModeAndSwitchDelegate(int displayId, int mode) {
mCurrentMagnificationModeArray.put(displayId, mode);
assignMagnificationWindowManagerDelegateByMode(displayId, mode);
}
@GuardedBy("mLock")
private void assignMagnificationWindowManagerDelegateByMode(int displayId, int mode) {
if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
mAccessibilityCallbacksDelegateArray.put(displayId,
getFullScreenMagnificationController());
} else if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
mAccessibilityCallbacksDelegateArray.put(displayId, getWindowMagnificationMgr());
} else {
mAccessibilityCallbacksDelegateArray.delete(displayId);
}
}
@Override
public void onRectangleOnScreenRequested(int displayId, int left, int top, int right,
int bottom) {
WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks
delegate;
synchronized (mLock) {
delegate = mAccessibilityCallbacksDelegateArray.get(displayId);
}
if (delegate != null) {
delegate.onRectangleOnScreenRequested(displayId, left, top, right, bottom);
}
}
@Override
public void onRequestMagnificationSpec(int displayId, int serviceId) {
final WindowMagnificationManager windowMagnificationManager;
synchronized (mLock) {
updateMagnificationUIControls(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
windowMagnificationManager = mWindowMagnificationMgr;
}
if (windowMagnificationManager != null) {
mWindowMagnificationMgr.disableWindowMagnification(displayId, false);
}
}
@Override
public void onWindowMagnificationActivationState(int displayId, boolean activated) {
if (activated) {
synchronized (mLock) {
mWindowModeEnabledTimeArray.put(displayId, SystemClock.uptimeMillis());
setCurrentMagnificationModeAndSwitchDelegate(displayId,
ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
mLastMagnificationActivatedModeArray.put(displayId,
ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
}
logMagnificationModeWithImeOnIfNeeded(displayId);
disableFullScreenMagnificationIfNeeded(displayId);
} else {
long duration;
float scale;
synchronized (mLock) {
setCurrentMagnificationModeAndSwitchDelegate(displayId,
ACCESSIBILITY_MAGNIFICATION_MODE_NONE);
duration = SystemClock.uptimeMillis() - mWindowModeEnabledTimeArray.get(displayId);
scale = mWindowMagnificationMgr.getLastActivatedScale(displayId);
}
logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, duration, scale);
}
updateMagnificationUIControls(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
}
@Override
public void onChangeMagnificationMode(int displayId, int magnificationMode) {
mAms.changeMagnificationMode(displayId, magnificationMode);
}
@Override
public void onSourceBoundsChanged(int displayId, Rect bounds) {
if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_WINDOW)) {
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_WINDOW)
.setActivated(getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId))
.setScale(getWindowMagnificationMgr().getScale(displayId))
.setCenterX(bounds.exactCenterX())
.setCenterY(bounds.exactCenterY()).build();
mAms.notifyMagnificationChanged(displayId, new Region(bounds), config);
}
}
@Override
public void onFullScreenMagnificationChanged(int displayId, @NonNull Region region,
@NonNull MagnificationConfig config) {
if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_FULLSCREEN)) {
mAms.notifyMagnificationChanged(displayId, region, config);
}
}
/**
* Should notify magnification change for the given display under the conditions below
*
* <ol>
* <li> 1. No mode transitioning and the change mode is active. </li>
* <li> 2. No mode transitioning and all the modes are inactive. </li>
* <li> 3. It is mode transitioning and the change mode is the transition mode. </li>
* </ol>
*
* @param displayId The logical display id
* @param changeMode The mode that has magnification spec change
*/
private boolean shouldNotifyMagnificationChange(int displayId, int changeMode) {
synchronized (mLock) {
final boolean fullScreenActivated = mFullScreenMagnificationController != null
&& mFullScreenMagnificationController.isActivated(displayId);
final boolean windowEnabled = mWindowMagnificationMgr != null
&& mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId);
final Integer transitionMode = mTransitionModes.get(displayId);
if (((changeMode == MAGNIFICATION_MODE_FULLSCREEN && fullScreenActivated)
|| (changeMode == MAGNIFICATION_MODE_WINDOW && windowEnabled))
&& (transitionMode == null)) {
return true;
}
if ((!fullScreenActivated && !windowEnabled)
&& (transitionMode == null)) {
return true;
}
if (transitionMode != null && changeMode == transitionMode) {
return true;
}
}
return false;
}
private void disableFullScreenMagnificationIfNeeded(int displayId) {
final FullScreenMagnificationController fullScreenMagnificationController =
getFullScreenMagnificationController();
// Internal request may be for transition, so we just need to check external request.
final boolean isMagnifyByExternalRequest =
fullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId) > 0;
if (isMagnifyByExternalRequest || isActivated(displayId,
ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)) {
fullScreenMagnificationController.reset(displayId, false);
}
}
@Override
public void onFullScreenMagnificationActivationState(int displayId, boolean activated) {
if (activated) {
synchronized (mLock) {
mFullScreenModeEnabledTimeArray.put(displayId, SystemClock.uptimeMillis());
setCurrentMagnificationModeAndSwitchDelegate(displayId,
ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
mLastMagnificationActivatedModeArray.put(displayId,
ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
}
logMagnificationModeWithImeOnIfNeeded(displayId);
disableWindowMagnificationIfNeeded(displayId);
} else {
long duration;
float scale;
synchronized (mLock) {
setCurrentMagnificationModeAndSwitchDelegate(displayId,
ACCESSIBILITY_MAGNIFICATION_MODE_NONE);
duration = SystemClock.uptimeMillis()
- mFullScreenModeEnabledTimeArray.get(displayId);
scale = mFullScreenMagnificationController.getLastActivatedScale(displayId);
}
logMagnificationUsageState(
ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, duration, scale);
}
updateMagnificationUIControls(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
}
private void disableWindowMagnificationIfNeeded(int displayId) {
final WindowMagnificationManager windowMagnificationManager =
getWindowMagnificationMgr();
if (isActivated(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)) {
windowMagnificationManager.disableWindowMagnification(displayId, false);
}
}
@Override
public void onImeWindowVisibilityChanged(int displayId, boolean shown) {
synchronized (mLock) {
mIsImeVisibleArray.put(displayId, shown);
}
getWindowMagnificationMgr().onImeWindowVisibilityChanged(displayId, shown);
logMagnificationModeWithImeOnIfNeeded(displayId);
}
/**
* Returns the last activated magnification mode. If there is no activated magnifier before, it
* returns fullscreen mode by default.
*/
public int getLastMagnificationActivatedMode(int displayId) {
synchronized (mLock) {
return mLastMagnificationActivatedModeArray.get(displayId,
ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
}
}
/**
* Wrapper method of logging the magnification activated mode and its duration of the usage
* when the magnification is disabled.
*
* @param mode The activated magnification mode.
* @param duration The duration in milliseconds during the magnification is activated.
* @param scale The last magnification scale for the activation
*/
@VisibleForTesting
public void logMagnificationUsageState(int mode, long duration, float scale) {
AccessibilityStatsLogUtils.logMagnificationUsageState(mode, duration, scale);
}
/**
* Wrapper method of logging the activated mode of the magnification when the IME window
* is shown on the screen.
*
* @param mode The activated magnification mode.
*/
@VisibleForTesting
public void logMagnificationModeWithIme(int mode) {
AccessibilityStatsLogUtils.logMagnificationModeWithImeOn(mode);
}
/**
* Updates the active user ID of {@link FullScreenMagnificationController} and {@link
* WindowMagnificationManager}.
*
* @param userId the currently active user ID
*/
public void updateUserIdIfNeeded(int userId) {
if (mUserId == userId) {
return;
}
mUserId = userId;
final FullScreenMagnificationController fullMagnificationController;
final WindowMagnificationManager windowMagnificationManager;
synchronized (mLock) {
fullMagnificationController = mFullScreenMagnificationController;
windowMagnificationManager = mWindowMagnificationMgr;
mAccessibilityCallbacksDelegateArray.clear();
mCurrentMagnificationModeArray.clear();
mLastMagnificationActivatedModeArray.clear();
mIsImeVisibleArray.clear();
}
mScaleProvider.onUserChanged(userId);
if (fullMagnificationController != null) {
fullMagnificationController.resetAllIfNeeded(false);
}
if (windowMagnificationManager != null) {
windowMagnificationManager.disableAllWindowMagnifiers();
}
}
/**
* Removes the magnification instance with given id.
*
* @param displayId The logical display id.
*/
public void onDisplayRemoved(int displayId) {
synchronized (mLock) {
if (mFullScreenMagnificationController != null) {
mFullScreenMagnificationController.onDisplayRemoved(displayId);
}
if (mWindowMagnificationMgr != null) {
mWindowMagnificationMgr.onDisplayRemoved(displayId);
}
mAccessibilityCallbacksDelegateArray.delete(displayId);
mCurrentMagnificationModeArray.delete(displayId);
mLastMagnificationActivatedModeArray.delete(displayId);
mIsImeVisibleArray.delete(displayId);
}
mScaleProvider.onDisplayRemoved(displayId);
}
/**
* Called when the given user is removed.
*/
public void onUserRemoved(int userId) {
mScaleProvider.onUserRemoved(userId);
}
public void setMagnificationCapabilities(int capabilities) {
mMagnificationCapabilities = capabilities;
}
/**
* Called when the following typing focus feature is switched.
*
* @param enabled Enable the following typing focus feature
*/
public void setMagnificationFollowTypingEnabled(boolean enabled) {
getWindowMagnificationMgr().setMagnificationFollowTypingEnabled(enabled);
getFullScreenMagnificationController().setMagnificationFollowTypingEnabled(enabled);
}
/**
* Called when the always on magnification feature is switched.
*
* @param enabled Enable the always on magnification feature
*/
public void setAlwaysOnMagnificationEnabled(boolean enabled) {
getFullScreenMagnificationController().setAlwaysOnMagnificationEnabled(enabled);
}
public boolean isAlwaysOnMagnificationFeatureFlagEnabled() {
return mAlwaysOnMagnificationFeatureFlag.isFeatureFlagEnabled();
}
private DisableMagnificationCallback getDisableMagnificationEndRunnableLocked(
int displayId) {
return mMagnificationEndRunnableSparseArray.get(displayId);
}
private void setDisableMagnificationCallbackLocked(int displayId,
@Nullable DisableMagnificationCallback callback) {
mMagnificationEndRunnableSparseArray.put(displayId, callback);
if (DEBUG) {
Slog.d(TAG, "setDisableMagnificationCallbackLocked displayId = " + displayId
+ ", callback = " + callback);
}
}
private void logMagnificationModeWithImeOnIfNeeded(int displayId) {
final int currentActivateMode;
synchronized (mLock) {
currentActivateMode = mCurrentMagnificationModeArray.get(displayId,
ACCESSIBILITY_MAGNIFICATION_MODE_NONE);
if (!mIsImeVisibleArray.get(displayId, false)
|| currentActivateMode == ACCESSIBILITY_MAGNIFICATION_MODE_NONE) {
return;
}
}
logMagnificationModeWithIme(currentActivateMode);
}
/**
* Getter of {@link FullScreenMagnificationController}.
*
* @return {@link FullScreenMagnificationController}.
*/
public FullScreenMagnificationController getFullScreenMagnificationController() {
synchronized (mLock) {
if (mFullScreenMagnificationController == null) {
mFullScreenMagnificationController = new FullScreenMagnificationController(
mContext,
mAms.getTraceManager(),
mLock,
this,
mScaleProvider,
mBackgroundExecutor
);
}
}
return mFullScreenMagnificationController;
}
/**
* Is {@link #mFullScreenMagnificationController} is initialized.
* @return {code true} if {@link #mFullScreenMagnificationController} is initialized.
*/
public boolean isFullScreenMagnificationControllerInitialized() {
synchronized (mLock) {
return mFullScreenMagnificationController != null;
}
}
/**
* Getter of {@link WindowMagnificationManager}.
*
* @return {@link WindowMagnificationManager}.
*/
public WindowMagnificationManager getWindowMagnificationMgr() {
synchronized (mLock) {
if (mWindowMagnificationMgr == null) {
mWindowMagnificationMgr = new WindowMagnificationManager(mContext,
mLock, this, mAms.getTraceManager(),
mScaleProvider);
}
return mWindowMagnificationMgr;
}
}
private @Nullable PointF getCurrentMagnificationCenterLocked(int displayId, int targetMode) {
if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
if (mWindowMagnificationMgr == null
|| !mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId)) {
return null;
}
mTempPoint.set(mWindowMagnificationMgr.getCenterX(displayId),
mWindowMagnificationMgr.getCenterY(displayId));
} else {
if (mFullScreenMagnificationController == null
|| !mFullScreenMagnificationController.isActivated(displayId)) {
return null;
}
mTempPoint.set(mFullScreenMagnificationController.getCenterX(displayId),
mFullScreenMagnificationController.getCenterY(displayId));
}
return mTempPoint;
}
/**
* Return {@code true} if the specified magnification mode on the given display is activated
* or not.
*
* @param displayId The logical displayId.
* @param mode It's either ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN or
* ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW.
*/
public boolean isActivated(int displayId, int mode) {
boolean isActivated = false;
if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
synchronized (mLock) {
if (mFullScreenMagnificationController == null) {
return false;
}
isActivated = mFullScreenMagnificationController.isActivated(displayId);
}
} else if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
synchronized (mLock) {
if (mWindowMagnificationMgr == null) {
return false;
}
isActivated = mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId);
}
}
return isActivated;
}
private final class DisableMagnificationCallback implements
MagnificationAnimationCallback {
private final TransitionCallBack mTransitionCallBack;
private boolean mExpired = false;
private final int mDisplayId;
// The mode the in-progress animation is going to.
private final int mTargetMode;
// The mode the in-progress animation is going from.
private final int mCurrentMode;
private final float mCurrentScale;
private final PointF mCurrentCenter = new PointF();
private final boolean mAnimate;
DisableMagnificationCallback(@Nullable TransitionCallBack transitionCallBack,
int displayId, int targetMode, float scale, PointF currentCenter, boolean animate) {
mTransitionCallBack = transitionCallBack;
mDisplayId = displayId;
mTargetMode = targetMode;
mCurrentMode = mTargetMode ^ ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
mCurrentScale = scale;
mCurrentCenter.set(currentCenter);
mAnimate = animate;
}
@Override
public void onResult(boolean success) {
synchronized (mLock) {
if (DEBUG) {
Slog.d(TAG, "onResult success = " + success);
}
if (mExpired) {
return;
}
setExpiredAndRemoveFromListLocked();
setTransitionState(mDisplayId, null);
if (success) {
adjustCurrentCenterIfNeededLocked();
applyMagnificationModeLocked(mTargetMode);
} else {
// Notify magnification change if magnification is inactive when the
// transition is failed. This is for the failed transition from
// full-screen to window mode. Disable magnification callback helps to send
// magnification inactive change since FullScreenMagnificationController
// would not notify magnification change if the spec is not changed.
final FullScreenMagnificationController screenMagnificationController =
getFullScreenMagnificationController();
if (mCurrentMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
&& !screenMagnificationController.isActivated(mDisplayId)) {
MagnificationConfig.Builder configBuilder =
new MagnificationConfig.Builder();
Region region = new Region();
configBuilder.setMode(MAGNIFICATION_MODE_FULLSCREEN)
.setActivated(screenMagnificationController.isActivated(mDisplayId))
.setScale(screenMagnificationController.getScale(mDisplayId))
.setCenterX(screenMagnificationController.getCenterX(mDisplayId))
.setCenterY(screenMagnificationController.getCenterY(mDisplayId));
screenMagnificationController.getMagnificationRegion(mDisplayId,
region);
mAms.notifyMagnificationChanged(mDisplayId, region, configBuilder.build());
}
}
updateMagnificationUIControls(mDisplayId, mTargetMode);
if (mTransitionCallBack != null) {
mTransitionCallBack.onResult(mDisplayId, success);
}
}
}
private void adjustCurrentCenterIfNeededLocked() {
if (mTargetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
return;
}
final Region outRegion = new Region();
getFullScreenMagnificationController().getMagnificationRegion(mDisplayId, outRegion);
if (outRegion.contains((int) mCurrentCenter.x, (int) mCurrentCenter.y)) {
return;
}
final Rect bounds = outRegion.getBounds();
mCurrentCenter.set(bounds.exactCenterX(), bounds.exactCenterY());
}
void restoreToCurrentMagnificationMode() {
synchronized (mLock) {
if (mExpired) {
return;
}
setExpiredAndRemoveFromListLocked();
setTransitionState(mDisplayId, null);
applyMagnificationModeLocked(mCurrentMode);
updateMagnificationUIControls(mDisplayId, mCurrentMode);
if (mTransitionCallBack != null) {
mTransitionCallBack.onResult(mDisplayId, true);
}
}
}
void setExpiredAndRemoveFromListLocked() {
mExpired = true;
setDisableMagnificationCallbackLocked(mDisplayId, null);
}
private void applyMagnificationModeLocked(int mode) {
if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
final FullScreenMagnificationController fullScreenMagnificationController =
getFullScreenMagnificationController();
if (!fullScreenMagnificationController.isRegistered(mDisplayId)) {
fullScreenMagnificationController.register(mDisplayId);
}
fullScreenMagnificationController.setScaleAndCenter(mDisplayId, mCurrentScale,
mCurrentCenter.x, mCurrentCenter.y, mAnimate,
MAGNIFICATION_GESTURE_HANDLER_ID);
} else {
getWindowMagnificationMgr().enableWindowMagnification(mDisplayId,
mCurrentScale, mCurrentCenter.x,
mCurrentCenter.y, mAnimate ? STUB_ANIMATION_CALLBACK : null,
MAGNIFICATION_GESTURE_HANDLER_ID);
}
}
}
}