| /* |
| * Copyright (C) 2019 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.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION; |
| import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION_CALLBACK; |
| import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK; |
| |
| import static com.android.server.accessibility.AccessibilityManagerService.INVALID_SERVICE_ID; |
| import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID; |
| |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.graphics.PointF; |
| import android.graphics.Rect; |
| import android.graphics.Region; |
| import android.os.Binder; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.os.SystemClock; |
| import android.util.MathUtils; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| import android.util.SparseBooleanArray; |
| import android.view.MotionEvent; |
| import android.view.accessibility.IMagnificationConnection; |
| import android.view.accessibility.IMagnificationConnectionCallback; |
| import android.view.accessibility.MagnificationAnimationCallback; |
| |
| import com.android.internal.accessibility.common.MagnificationConstants; |
| 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.AccessibilityTraceManager; |
| import com.android.server.statusbar.StatusBarManagerInternal; |
| import com.android.server.wm.WindowManagerInternal; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.concurrent.atomic.AtomicLongFieldUpdater; |
| |
| /** |
| * A class to manipulate magnification through {@link MagnificationConnectionWrapper} |
| * create by {@link #setConnection(IMagnificationConnection)}. To set the connection with |
| * SysUI, call {@code StatusBarManagerInternal#requestMagnificationConnection(boolean)}. |
| * The applied magnification scale is constrained by |
| * {@link MagnificationScaleProvider#constrainScale(float)} |
| */ |
| public class MagnificationConnectionManager implements |
| PanningScalingHandler.MagnificationDelegate, |
| WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks { |
| |
| private static final boolean DBG = false; |
| |
| private static final String TAG = "MagnificationConnectionManager"; |
| |
| /** |
| * Indicate that the magnification window is at the magnification center. |
| */ |
| public static final int WINDOW_POSITION_AT_CENTER = 0; |
| |
| /** |
| * Indicate that the magnification window is at the top-left side of the magnification |
| * center. The offset is equal to a half of MirrorSurfaceView. So, the bottom-right corner |
| * of the window is at the magnification center. |
| */ |
| public static final int WINDOW_POSITION_AT_TOP_LEFT = 1; |
| |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef(prefix = { "WINDOW_POSITION_AT_" }, value = { |
| WINDOW_POSITION_AT_CENTER, |
| WINDOW_POSITION_AT_TOP_LEFT |
| }) |
| public @interface WindowPosition {} |
| |
| /** Magnification connection is connecting. */ |
| private static final int CONNECTING = 0; |
| /** Magnification connection is connected. */ |
| private static final int CONNECTED = 1; |
| /** Magnification connection is disconnecting. */ |
| private static final int DISCONNECTING = 2; |
| /** Magnification connection is disconnected. */ |
| private static final int DISCONNECTED = 3; |
| |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef(prefix = {"CONNECTION_STATE"}, value = { |
| CONNECTING, |
| CONNECTED, |
| DISCONNECTING, |
| DISCONNECTED |
| }) |
| private @interface ConnectionState { |
| } |
| |
| private static String connectionStateToString(@ConnectionState int state) { |
| switch (state) { |
| case CONNECTING: return "CONNECTING"; |
| case CONNECTED: return "CONNECTED"; |
| case DISCONNECTING: return "DISCONNECTING"; |
| case DISCONNECTED: return "DISCONNECTED"; |
| default: |
| return "UNKNOWN:" + state; |
| } |
| } |
| |
| @ConnectionState |
| private int mConnectionState = DISCONNECTED; |
| |
| private static final int WAIT_CONNECTION_TIMEOUT_MILLIS = 100; |
| |
| private final Object mLock; |
| private final Context mContext; |
| @VisibleForTesting |
| @GuardedBy("mLock") |
| @Nullable |
| MagnificationConnectionWrapper mConnectionWrapper; |
| @GuardedBy("mLock") |
| private ConnectionCallback mConnectionCallback; |
| @GuardedBy("mLock") |
| private SparseArray<WindowMagnifier> mWindowMagnifiers = new SparseArray<>(); |
| // Whether the following typing focus feature for magnification is enabled. |
| private boolean mMagnificationFollowTypingEnabled = true; |
| @GuardedBy("mLock") |
| private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray(); |
| @GuardedBy("mLock") |
| private final SparseArray<Float> mLastActivatedScale = new SparseArray<>(); |
| |
| private boolean mReceiverRegistered = false; |
| @VisibleForTesting |
| protected final BroadcastReceiver mScreenStateReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| final int displayId = context.getDisplayId(); |
| removeMagnificationButton(displayId); |
| disableWindowMagnification(displayId, false, null); |
| } |
| }; |
| |
| /** |
| * Callback to handle magnification actions from system UI. |
| */ |
| public interface Callback { |
| |
| /** |
| * Called when the accessibility action of scale requests to be performed. |
| * It is invoked from System UI. And the action is provided by the mirror window. |
| * |
| * @param displayId The logical display id. |
| * @param scale the target scale, or {@link Float#NaN} to leave unchanged |
| * @param updatePersistence whether the scale should be persisted |
| */ |
| void onPerformScaleAction(int displayId, float scale, boolean updatePersistence); |
| |
| /** |
| * Called when the accessibility action is performed. |
| * |
| * @param displayId The logical display id. |
| */ |
| void onAccessibilityActionPerformed(int displayId); |
| |
| /** |
| * Called when the state of the magnification activation is changed. |
| * |
| * @param displayId The logical display id. |
| * @param activated {@code true} if the magnification is activated, otherwise {@code false}. |
| */ |
| void onWindowMagnificationActivationState(int displayId, boolean activated); |
| |
| /** |
| * Called when the magnification source bounds are changed. |
| * |
| * @param displayId The logical display id. |
| * @param bounds The magnified source bounds on the display. |
| */ |
| void onSourceBoundsChanged(int displayId, Rect bounds); |
| |
| /** |
| * Called from {@link IMagnificationConnection} to request changing the magnification |
| * mode on the given display. |
| * |
| * @param displayId the logical display id |
| * @param magnificationMode the target magnification mode |
| */ |
| void onChangeMagnificationMode(int displayId, int magnificationMode); |
| } |
| |
| private final Callback mCallback; |
| private final AccessibilityTraceManager mTrace; |
| private final MagnificationScaleProvider mScaleProvider; |
| |
| public MagnificationConnectionManager(Context context, Object lock, @NonNull Callback callback, |
| AccessibilityTraceManager trace, MagnificationScaleProvider scaleProvider) { |
| mContext = context; |
| mLock = lock; |
| mCallback = callback; |
| mTrace = trace; |
| mScaleProvider = scaleProvider; |
| } |
| |
| /** |
| * Sets {@link IMagnificationConnection}. |
| * |
| * @param connection {@link IMagnificationConnection} |
| */ |
| public void setConnection(@Nullable IMagnificationConnection connection) { |
| if (DBG) { |
| Slog.d(TAG, "setConnection :" + connection + ", mConnectionState=" |
| + connectionStateToString(mConnectionState)); |
| } |
| synchronized (mLock) { |
| // Reset connectionWrapper. |
| if (mConnectionWrapper != null) { |
| mConnectionWrapper.setConnectionCallback(null); |
| if (mConnectionCallback != null) { |
| mConnectionCallback.mExpiredDeathRecipient = true; |
| } |
| mConnectionWrapper.unlinkToDeath(mConnectionCallback); |
| mConnectionWrapper = null; |
| // The connection is still connecting so it is no need to reset the |
| // connection state to disconnected. |
| // TODO b/220086369 will reset the connection immediately when requestConnection |
| // is called |
| if (mConnectionState != CONNECTING) { |
| setConnectionState(DISCONNECTED); |
| } |
| } |
| if (connection != null) { |
| mConnectionWrapper = new MagnificationConnectionWrapper(connection, mTrace); |
| } |
| |
| if (mConnectionWrapper != null) { |
| try { |
| mConnectionCallback = new ConnectionCallback(); |
| mConnectionWrapper.linkToDeath(mConnectionCallback); |
| mConnectionWrapper.setConnectionCallback(mConnectionCallback); |
| setConnectionState(CONNECTED); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "setConnection failed", e); |
| mConnectionWrapper = null; |
| setConnectionState(DISCONNECTED); |
| } finally { |
| mLock.notify(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @return {@code true} if {@link IMagnificationConnection} is available |
| */ |
| public boolean isConnected() { |
| synchronized (mLock) { |
| return mConnectionWrapper != null; |
| } |
| } |
| |
| /** |
| * Requests {@link IMagnificationConnection} through |
| * {@link StatusBarManagerInternal#requestMagnificationConnection(boolean)} and |
| * destroys all window magnifications if necessary. |
| * |
| * @param connect {@code true} if needs connection, otherwise set the connection to null and |
| * destroy all window magnifications. |
| * @return {@code true} if {@link IMagnificationConnection} state is going to change. |
| */ |
| public boolean requestConnection(boolean connect) { |
| if (DBG) { |
| Slog.d(TAG, "requestConnection :" + connect); |
| } |
| if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) { |
| mTrace.logTrace(TAG + ".requestMagnificationConnection", |
| FLAGS_MAGNIFICATION_CONNECTION, "connect=" + connect); |
| } |
| synchronized (mLock) { |
| if ((connect && (mConnectionState == CONNECTED || mConnectionState == CONNECTING)) |
| || (!connect && (mConnectionState == DISCONNECTED |
| || mConnectionState == DISCONNECTING))) { |
| Slog.w(TAG, "requestConnection duplicated request: connect=" + connect |
| + ", mConnectionState=" + connectionStateToString(mConnectionState)); |
| return false; |
| } |
| |
| if (connect) { |
| final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF); |
| if (!mReceiverRegistered) { |
| mContext.registerReceiver(mScreenStateReceiver, intentFilter); |
| mReceiverRegistered = true; |
| } |
| } else { |
| disableAllWindowMagnifiers(); |
| if (mReceiverRegistered) { |
| mContext.unregisterReceiver(mScreenStateReceiver); |
| mReceiverRegistered = false; |
| } |
| } |
| } |
| if (requestConnectionInternal(connect)) { |
| setConnectionState(connect ? CONNECTING : DISCONNECTING); |
| return true; |
| } else { |
| setConnectionState(DISCONNECTED); |
| return false; |
| } |
| } |
| |
| private boolean requestConnectionInternal(boolean connect) { |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| final StatusBarManagerInternal service = LocalServices.getService( |
| StatusBarManagerInternal.class); |
| if (service != null) { |
| return service.requestMagnificationConnection(connect); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| return false; |
| } |
| |
| /** |
| * Returns window magnification connection state. |
| */ |
| public String getConnectionState() { |
| return connectionStateToString(mConnectionState); |
| } |
| |
| private void setConnectionState(@ConnectionState int state) { |
| if (DBG) { |
| Slog.d(TAG, "setConnectionState : state=" + state + ", mConnectionState=" |
| + connectionStateToString(mConnectionState)); |
| } |
| mConnectionState = state; |
| } |
| |
| /** |
| * Disables window magnifier on all displays without animation. |
| */ |
| void disableAllWindowMagnifiers() { |
| synchronized (mLock) { |
| for (int i = 0; i < mWindowMagnifiers.size(); i++) { |
| final WindowMagnifier magnifier = mWindowMagnifiers.valueAt(i); |
| magnifier.disableWindowMagnificationInternal(null); |
| } |
| mWindowMagnifiers.clear(); |
| } |
| } |
| |
| /** |
| * Resets the window magnifier on all displays that had been controlled by the |
| * specified service connection. Called when the service connection is unbound |
| * or binder died. |
| * |
| * @param connectionId The connection id |
| */ |
| public void resetAllIfNeeded(int connectionId) { |
| synchronized (mLock) { |
| for (int i = 0; i < mWindowMagnifiers.size(); i++) { |
| final WindowMagnifier magnifier = mWindowMagnifiers.valueAt(i); |
| if (magnifier != null |
| && magnifier.mEnabled |
| && connectionId == magnifier.getIdOfLastServiceToControl()) { |
| magnifier.disableWindowMagnificationInternal(null); |
| } |
| } |
| } |
| } |
| |
| private void resetWindowMagnifiers() { |
| synchronized (mLock) { |
| for (int i = 0; i < mWindowMagnifiers.size(); i++) { |
| WindowMagnifier magnifier = mWindowMagnifiers.valueAt(i); |
| magnifier.reset(); |
| } |
| } |
| } |
| |
| @Override |
| public void onRectangleOnScreenRequested(int displayId, int left, int top, int right, |
| int bottom) { |
| if (!mMagnificationFollowTypingEnabled) { |
| return; |
| } |
| |
| float toCenterX = (float) (left + right) / 2; |
| float toCenterY = (float) (top + bottom) / 2; |
| |
| synchronized (mLock) { |
| if (mIsImeVisibleArray.get(displayId, false) |
| && !isPositionInSourceBounds(displayId, toCenterX, toCenterY) |
| && isTrackingTypingFocusEnabled(displayId)) { |
| moveWindowMagnifierToPositionInternal(displayId, toCenterX, toCenterY, |
| STUB_ANIMATION_CALLBACK); |
| } |
| } |
| } |
| |
| void setMagnificationFollowTypingEnabled(boolean enabled) { |
| mMagnificationFollowTypingEnabled = enabled; |
| } |
| |
| boolean isMagnificationFollowTypingEnabled() { |
| return mMagnificationFollowTypingEnabled; |
| } |
| |
| /** |
| * Get the ID of the last service that changed the magnification config. |
| * |
| * @param displayId The logical display id. |
| * @return The id |
| */ |
| public int getIdOfLastServiceToMagnify(int displayId) { |
| synchronized (mLock) { |
| final WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier != null) { |
| return magnifier.mIdOfLastServiceToControl; |
| } |
| } |
| return INVALID_SERVICE_ID; |
| } |
| |
| /** |
| * Enable or disable tracking typing focus for the specific magnification window. |
| * |
| * The tracking typing focus should be set to enabled with the following conditions: |
| * 1. IME is shown. |
| * |
| * The tracking typing focus should be set to disabled with the following conditions: |
| * 1. A user drags the magnification window by 1 finger. |
| * 2. A user scroll the magnification window by 2 fingers. |
| * |
| * @param displayId The logical display id. |
| * @param trackingTypingFocusEnabled Enabled or disable the function of tracking typing focus. |
| */ |
| void setTrackingTypingFocusEnabled(int displayId, boolean trackingTypingFocusEnabled) { |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null) { |
| return; |
| } |
| magnifier.setTrackingTypingFocusEnabled(trackingTypingFocusEnabled); |
| } |
| } |
| |
| /** |
| * Enable tracking typing focus function for all magnifications. |
| */ |
| private void enableAllTrackingTypingFocus() { |
| synchronized (mLock) { |
| for (int i = 0; i < mWindowMagnifiers.size(); i++) { |
| WindowMagnifier magnifier = mWindowMagnifiers.valueAt(i); |
| magnifier.setTrackingTypingFocusEnabled(true); |
| } |
| } |
| } |
| |
| private void pauseTrackingTypingFocusRecord(int displayId) { |
| WindowMagnifier magnifier; |
| synchronized (mLock) { |
| magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null) { |
| return; |
| } |
| } |
| magnifier.pauseTrackingTypingFocusRecord(); |
| } |
| |
| /** |
| * Called when the IME window visibility changed. |
| * |
| * @param shown {@code true} means the IME window shows on the screen. Otherwise, it's hidden. |
| */ |
| void onImeWindowVisibilityChanged(int displayId, boolean shown) { |
| synchronized (mLock) { |
| mIsImeVisibleArray.put(displayId, shown); |
| } |
| if (shown) { |
| enableAllTrackingTypingFocus(); |
| } else { |
| pauseTrackingTypingFocusRecord(displayId); |
| } |
| } |
| |
| boolean isImeVisible(int displayId) { |
| synchronized (mLock) { |
| return mIsImeVisibleArray.get(displayId); |
| } |
| } |
| |
| void logTrackingTypingFocus(long duration) { |
| AccessibilityStatsLogUtils.logMagnificationFollowTypingFocusSession(duration); |
| } |
| |
| @Override |
| public boolean processScroll(int displayId, float distanceX, float distanceY) { |
| moveWindowMagnification(displayId, -distanceX, -distanceY); |
| setTrackingTypingFocusEnabled(displayId, false); |
| return /* event consumed: */ true; |
| } |
| |
| /** |
| * Scales the magnified region on the specified display if window magnification is initiated. |
| * |
| * @param displayId The logical display id. |
| * @param scale The target scale, must be >= 1 |
| */ |
| @Override |
| public void setScale(int displayId, float scale) { |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null) { |
| return; |
| } |
| magnifier.setScale(scale); |
| mLastActivatedScale.put(displayId, scale); |
| } |
| } |
| |
| /** |
| * Enables window magnification with specified center and scale on the given display and |
| * animating the transition. |
| * |
| * @param displayId The logical display id. |
| * @param scale The target scale, must be >= 1. |
| * @param centerX The screen-relative X coordinate around which to center, |
| * or {@link Float#NaN} to leave unchanged. |
| * @param centerY The screen-relative Y coordinate around which to center, |
| * or {@link Float#NaN} to leave unchanged. |
| * @return {@code true} if the magnification is enabled successfully. |
| */ |
| public boolean enableWindowMagnification(int displayId, float scale, float centerX, |
| float centerY) { |
| return enableWindowMagnification(displayId, scale, centerX, centerY, |
| STUB_ANIMATION_CALLBACK, MAGNIFICATION_GESTURE_HANDLER_ID); |
| } |
| |
| /** |
| * Enables window magnification with specified center and scale on the given display and |
| * animating the transition. |
| * |
| * @param displayId The logical display id. |
| * @param scale The target scale, must be >= 1. |
| * @param centerX The screen-relative X coordinate around which to center for magnification, |
| * or {@link Float#NaN} to leave unchanged. |
| * @param centerY The screen-relative Y coordinate around which to center for magnification, |
| * or {@link Float#NaN} to leave unchanged. |
| * @param animationCallback Called when the animation result is valid. |
| * @param id The connection ID |
| * @return {@code true} if the magnification is enabled successfully. |
| */ |
| public boolean enableWindowMagnification(int displayId, float scale, float centerX, |
| float centerY, @Nullable MagnificationAnimationCallback animationCallback, int id) { |
| return enableWindowMagnification(displayId, scale, centerX, centerY, animationCallback, |
| WINDOW_POSITION_AT_CENTER, id); |
| } |
| |
| /** |
| * Enables window magnification with specified center and scale on the given display and |
| * animating the transition. |
| * |
| * @param displayId The logical display id. |
| * @param scale The target scale, must be >= 1. |
| * @param centerX The screen-relative X coordinate around which to center for magnification, |
| * or {@link Float#NaN} to leave unchanged. |
| * @param centerY The screen-relative Y coordinate around which to center for magnification, |
| * or {@link Float#NaN} to leave unchanged. |
| * @param windowPosition Indicate the offset between window position and (centerX, centerY). |
| * @return {@code true} if the magnification is enabled successfully. |
| */ |
| public boolean enableWindowMagnification(int displayId, float scale, float centerX, |
| float centerY, @WindowPosition int windowPosition) { |
| return enableWindowMagnification(displayId, scale, centerX, centerY, |
| STUB_ANIMATION_CALLBACK, windowPosition, MAGNIFICATION_GESTURE_HANDLER_ID); |
| } |
| |
| /** |
| * Enables window magnification with specified center and scale on the given display and |
| * animating the transition. |
| * |
| * @param displayId The logical display id. |
| * @param scale The target scale, must be >= 1. |
| * @param centerX The screen-relative X coordinate around which to center for |
| * magnification, or {@link Float#NaN} to leave unchanged. |
| * @param centerY The screen-relative Y coordinate around which to center for |
| * magnification, or {@link Float#NaN} to leave unchanged. |
| * @param animationCallback Called when the animation result is valid. |
| * @param windowPosition Indicate the offset between window position and (centerX, centerY). |
| * @return {@code true} if the magnification is enabled successfully. |
| */ |
| public boolean enableWindowMagnification(int displayId, float scale, float centerX, |
| float centerY, @Nullable MagnificationAnimationCallback animationCallback, |
| @WindowPosition int windowPosition, int id) { |
| final boolean enabled; |
| boolean previousEnabled; |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null) { |
| magnifier = createWindowMagnifier(displayId); |
| } |
| previousEnabled = magnifier.mEnabled; |
| enabled = magnifier.enableWindowMagnificationInternal(scale, centerX, centerY, |
| animationCallback, windowPosition, id); |
| if (enabled) { |
| mLastActivatedScale.put(displayId, getScale(displayId)); |
| } |
| } |
| |
| if (enabled) { |
| setTrackingTypingFocusEnabled(displayId, true); |
| if (!previousEnabled) { |
| mCallback.onWindowMagnificationActivationState(displayId, true); |
| } |
| } |
| return enabled; |
| } |
| |
| /** |
| * Disables window magnification on the given display. |
| * |
| * @param displayId The logical display id. |
| * @param clear {@true} Clears the state of window magnification. |
| * @return {@code true} if the magnification is turned to be disabled successfully |
| */ |
| public boolean disableWindowMagnification(int displayId, boolean clear) { |
| return disableWindowMagnification(displayId, clear, STUB_ANIMATION_CALLBACK); |
| } |
| |
| /** |
| * Disables window magnification on the specified display and animating the transition. |
| * |
| * @param displayId The logical display id. |
| * @param clear {@true} Clears the state of window magnification. |
| * @param animationCallback Called when the animation result is valid. |
| * @return {@code true} if the magnification is turned to be disabled successfully |
| */ |
| public boolean disableWindowMagnification(int displayId, boolean clear, |
| MagnificationAnimationCallback animationCallback) { |
| final boolean disabled; |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null) { |
| return false; |
| } |
| |
| disabled = magnifier.disableWindowMagnificationInternal(animationCallback); |
| if (clear) { |
| mWindowMagnifiers.delete(displayId); |
| } |
| } |
| |
| if (disabled) { |
| mCallback.onWindowMagnificationActivationState(displayId, false); |
| } |
| return disabled; |
| } |
| |
| /** |
| * Calculates the number of fingers in the window. |
| * |
| * @param displayId The logical display id. |
| * @param motionEvent The motion event |
| * @return the number of fingers in the window. |
| */ |
| int pointersInWindow(int displayId, MotionEvent motionEvent) { |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null) { |
| return 0; |
| } |
| return magnifier.pointersInWindow(motionEvent); |
| } |
| } |
| |
| @GuardedBy("mLock") |
| boolean isPositionInSourceBounds(int displayId, float x, float y) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null) { |
| return false; |
| } |
| return magnifier.isPositionInSourceBounds(x, y); |
| } |
| |
| /** |
| * Indicates whether window magnification is enabled on specified display. |
| * |
| * @param displayId The logical display id. |
| * @return {@code true} if the window magnification is enabled. |
| */ |
| public boolean isWindowMagnifierEnabled(int displayId) { |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null) { |
| return false; |
| } |
| return magnifier.isEnabled(); |
| } |
| } |
| |
| /** |
| * Retrieves a previously magnification scale from the current |
| * user's settings. Only the value of the default display is persisted. |
| * |
| * @return the previously magnification scale, or the default |
| * scale if none is available |
| */ |
| float getPersistedScale(int displayId) { |
| return MathUtils.constrain(mScaleProvider.getScale(displayId), |
| MagnificationConstants.PERSISTED_SCALE_MIN_VALUE, |
| MagnificationScaleProvider.MAX_SCALE); |
| } |
| |
| /** |
| * Persists the default display magnification scale to the current user's settings |
| * <strong>if scale is >= {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}</strong>. |
| * We assume if the scale is < {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}, there |
| * will be no obvious magnification effect. |
| * Only the value of the default display is persisted in user's settings. |
| */ |
| void persistScale(int displayId) { |
| float scale = getScale(displayId); |
| if (scale < MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) { |
| return; |
| } |
| mScaleProvider.putScale(scale, displayId); |
| } |
| |
| /** |
| * Returns the magnification scale. |
| * |
| * @param displayId The logical display id. |
| * @return the scale |
| */ |
| public float getScale(int displayId) { |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null || !magnifier.mEnabled) { |
| return 1.0f; |
| } |
| return magnifier.getScale(); |
| } |
| } |
| |
| protected float getLastActivatedScale(int displayId) { |
| synchronized (mLock) { |
| if (!mLastActivatedScale.contains(displayId)) { |
| return -1.0f; |
| } |
| return mLastActivatedScale.get(displayId); |
| } |
| } |
| |
| /** |
| * Moves window magnification on the specified display with the specified offset. |
| * |
| * @param displayId The logical display id. |
| * @param offsetX the amount in pixels to offset the region in the X direction, in current |
| * screen pixels. |
| * @param offsetY the amount in pixels to offset the region in the Y direction, in current |
| * screen pixels. |
| */ |
| void moveWindowMagnification(int displayId, float offsetX, float offsetY) { |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null) { |
| return; |
| } |
| magnifier.move(offsetX, offsetY); |
| } |
| } |
| |
| /** |
| * Requests System UI show magnification mode button UI on the specified display. |
| * |
| * @param displayId The logical display id. |
| * @param magnificationMode the current magnification mode. |
| * @return {@code true} if the event was handled, {@code false} otherwise |
| */ |
| public boolean showMagnificationButton(int displayId, int magnificationMode) { |
| synchronized (mLock) { |
| return mConnectionWrapper != null |
| && mConnectionWrapper.showMagnificationButton(displayId, magnificationMode); |
| } |
| } |
| |
| /** |
| * Requests System UI remove magnification mode button UI on the specified display. |
| * |
| * @param displayId The logical display id. |
| * @return {@code true} if the event was handled, {@code false} otherwise |
| */ |
| public boolean removeMagnificationButton(int displayId) { |
| synchronized (mLock) { |
| return mConnectionWrapper != null |
| && mConnectionWrapper.removeMagnificationButton(displayId); |
| } |
| } |
| |
| /** |
| * Requests System UI remove magnification settings panel on the specified display. |
| * |
| * @param displayId The logical display id. |
| * @return {@code true} if the event was handled, {@code false} otherwise |
| */ |
| public boolean removeMagnificationSettingsPanel(int displayId) { |
| synchronized (mLock) { |
| return mConnectionWrapper != null |
| && mConnectionWrapper.removeMagnificationSettingsPanel(displayId); |
| } |
| } |
| |
| /** |
| * Notify System UI the magnification scale on the specified display for userId is changed. |
| * |
| * @param userId the user id. |
| * @param displayId the logical display id. |
| * @param scale magnification scale. |
| */ |
| public boolean onUserMagnificationScaleChanged(int userId, int displayId, float scale) { |
| synchronized (mLock) { |
| return mConnectionWrapper != null |
| && mConnectionWrapper.onUserMagnificationScaleChanged(userId, displayId, scale); |
| } |
| } |
| |
| /** |
| * Returns the screen-relative X coordinate of the center of the magnified bounds. |
| * |
| * @param displayId The logical display id |
| * @return the X coordinate. {@link Float#NaN} if the window magnification is not enabled. |
| */ |
| public float getCenterX(int displayId) { |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null || !magnifier.mEnabled) { |
| return Float.NaN; |
| } |
| return magnifier.getCenterX(); |
| } |
| } |
| |
| /** |
| * Returns the screen-relative Y coordinate of the center of the magnified bounds. |
| * |
| * @param displayId The logical display id |
| * @return the Y coordinate. {@link Float#NaN} if the window magnification is not enabled. |
| */ |
| public float getCenterY(int displayId) { |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null || !magnifier.mEnabled) { |
| return Float.NaN; |
| } |
| return magnifier.getCenterY(); |
| } |
| } |
| |
| boolean isTrackingTypingFocusEnabled(int displayId) { |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null) { |
| return false; |
| } |
| return magnifier.isTrackingTypingFocusEnabled(); |
| } |
| } |
| |
| /** |
| * Populates magnified bounds on the screen. And the populated magnified bounds would be |
| * empty If window magnifier is not activated. |
| * |
| * @param displayId The logical display id. |
| * @param outRegion the region to populate |
| */ |
| public void getMagnificationSourceBounds(int displayId, @NonNull Region outRegion) { |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null || !magnifier.mEnabled) { |
| outRegion.setEmpty(); |
| } else { |
| outRegion.set(magnifier.mSourceBounds); |
| } |
| } |
| } |
| |
| /** |
| * Creates the windowMagnifier based on the specified display and stores it. |
| * |
| * @param displayId logical display id. |
| */ |
| @GuardedBy("mLock") |
| private WindowMagnifier createWindowMagnifier(int displayId) { |
| final WindowMagnifier magnifier = new WindowMagnifier(displayId, this); |
| mWindowMagnifiers.put(displayId, magnifier); |
| return magnifier; |
| } |
| |
| /** |
| * Removes the window magnifier with given id. |
| * |
| * @param displayId The logical display id. |
| */ |
| public void onDisplayRemoved(int displayId) { |
| disableWindowMagnification(displayId, true); |
| } |
| |
| @SuppressWarnings("MissingPermissionAnnotation") |
| private class ConnectionCallback extends IMagnificationConnectionCallback.Stub implements |
| IBinder.DeathRecipient { |
| private boolean mExpiredDeathRecipient = false; |
| |
| @Override |
| public void onWindowMagnifierBoundsChanged(int displayId, Rect bounds) { |
| if (mTrace.isA11yTracingEnabledForTypes( |
| FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) { |
| mTrace.logTrace(TAG + "ConnectionCallback.onWindowMagnifierBoundsChanged", |
| FLAGS_MAGNIFICATION_CONNECTION_CALLBACK, |
| "displayId=" + displayId + ";bounds=" + bounds); |
| } |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null) { |
| magnifier = createWindowMagnifier(displayId); |
| } |
| if (DBG) { |
| Slog.i(TAG, |
| "onWindowMagnifierBoundsChanged -" + displayId + " bounds = " + bounds); |
| } |
| magnifier.setMagnifierLocation(bounds); |
| } |
| } |
| |
| @Override |
| public void onChangeMagnificationMode(int displayId, int magnificationMode) |
| throws RemoteException { |
| if (mTrace.isA11yTracingEnabledForTypes( |
| FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) { |
| mTrace.logTrace(TAG + "ConnectionCallback.onChangeMagnificationMode", |
| FLAGS_MAGNIFICATION_CONNECTION_CALLBACK, |
| "displayId=" + displayId + ";mode=" + magnificationMode); |
| } |
| mCallback.onChangeMagnificationMode(displayId, magnificationMode); |
| } |
| |
| @Override |
| public void onSourceBoundsChanged(int displayId, Rect sourceBounds) { |
| if (mTrace.isA11yTracingEnabledForTypes( |
| FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) { |
| mTrace.logTrace(TAG + "ConnectionCallback.onSourceBoundsChanged", |
| FLAGS_MAGNIFICATION_CONNECTION_CALLBACK, |
| "displayId=" + displayId + ";source=" + sourceBounds); |
| } |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null) { |
| magnifier = createWindowMagnifier(displayId); |
| } |
| magnifier.onSourceBoundsChanged(sourceBounds); |
| } |
| mCallback.onSourceBoundsChanged(displayId, sourceBounds); |
| } |
| |
| @Override |
| public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) { |
| if (mTrace.isA11yTracingEnabledForTypes( |
| FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) { |
| mTrace.logTrace(TAG + "ConnectionCallback.onPerformScaleAction", |
| FLAGS_MAGNIFICATION_CONNECTION_CALLBACK, |
| "displayId=" + displayId + ";scale=" + scale |
| + ";updatePersistence=" + updatePersistence); |
| } |
| mCallback.onPerformScaleAction(displayId, scale, updatePersistence); |
| } |
| |
| @Override |
| public void onAccessibilityActionPerformed(int displayId) { |
| if (mTrace.isA11yTracingEnabledForTypes( |
| FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) { |
| mTrace.logTrace(TAG + "ConnectionCallback.onAccessibilityActionPerformed", |
| FLAGS_MAGNIFICATION_CONNECTION_CALLBACK, |
| "displayId=" + displayId); |
| } |
| mCallback.onAccessibilityActionPerformed(displayId); |
| } |
| |
| @Override |
| public void onMove(int displayId) { |
| if (mTrace.isA11yTracingEnabledForTypes( |
| FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) { |
| mTrace.logTrace(TAG + "ConnectionCallback.onMove", |
| FLAGS_MAGNIFICATION_CONNECTION_CALLBACK, |
| "displayId=" + displayId); |
| } |
| setTrackingTypingFocusEnabled(displayId, false); |
| } |
| |
| @Override |
| public void binderDied() { |
| synchronized (mLock) { |
| Slog.w(TAG, "binderDied DeathRecipient :" + mExpiredDeathRecipient); |
| if (mExpiredDeathRecipient) { |
| return; |
| } |
| mConnectionWrapper.unlinkToDeath(this); |
| mConnectionWrapper = null; |
| mConnectionCallback = null; |
| setConnectionState(DISCONNECTED); |
| resetWindowMagnifiers(); |
| } |
| } |
| } |
| |
| /** |
| * A class manipulates window magnification per display and contains the magnification |
| * information. |
| * <p> |
| * This class requires to hold the lock when controlling the magnifier. |
| * </p> |
| */ |
| private static class WindowMagnifier { |
| |
| private final int mDisplayId; |
| private float mScale = MagnificationScaleProvider.MIN_SCALE; |
| private boolean mEnabled; |
| |
| private final MagnificationConnectionManager mMagnificationConnectionManager; |
| // Records the bounds of window magnification. |
| private final Rect mBounds = new Rect(); |
| // The magnified bounds on the screen. |
| private final Rect mSourceBounds = new Rect(); |
| |
| private int mIdOfLastServiceToControl = INVALID_SERVICE_ID; |
| |
| private final PointF mMagnificationFrameOffsetRatio = new PointF(0f, 0f); |
| |
| private boolean mTrackingTypingFocusEnabled = true; |
| |
| private volatile long mTrackingTypingFocusStartTime = 0; |
| private static final AtomicLongFieldUpdater<WindowMagnifier> SUM_TIME_UPDATER = |
| AtomicLongFieldUpdater.newUpdater(WindowMagnifier.class, |
| "mTrackingTypingFocusSumTime"); |
| private volatile long mTrackingTypingFocusSumTime = 0; |
| |
| WindowMagnifier(int displayId, |
| MagnificationConnectionManager magnificationConnectionManager) { |
| mDisplayId = displayId; |
| mMagnificationConnectionManager = magnificationConnectionManager; |
| } |
| |
| // TODO(b/312324808): Investigating whether |
| // mMagnificationConnectionManager#enableWindowMagnificationInternal requires a sync lock |
| @SuppressWarnings("GuardedBy") |
| boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY, |
| @Nullable MagnificationAnimationCallback animationCallback, |
| @WindowPosition int windowPosition, int id) { |
| // Handle defaults. The scale may be NAN when just updating magnification center. |
| if (Float.isNaN(scale)) { |
| scale = getScale(); |
| } |
| final float normScale = MagnificationScaleProvider.constrainScale(scale); |
| setMagnificationFrameOffsetRatioByWindowPosition(windowPosition); |
| if (mMagnificationConnectionManager.enableWindowMagnificationInternal(mDisplayId, |
| normScale, centerX, centerY, mMagnificationFrameOffsetRatio.x, |
| mMagnificationFrameOffsetRatio.y, animationCallback)) { |
| mScale = normScale; |
| mEnabled = true; |
| mIdOfLastServiceToControl = id; |
| return true; |
| } |
| return false; |
| } |
| |
| void setMagnificationFrameOffsetRatioByWindowPosition(@WindowPosition int windowPosition) { |
| switch (windowPosition) { |
| case WINDOW_POSITION_AT_CENTER: { |
| mMagnificationFrameOffsetRatio.set(0f, 0f); |
| } |
| break; |
| case WINDOW_POSITION_AT_TOP_LEFT: { |
| mMagnificationFrameOffsetRatio.set(-1f, -1f); |
| } |
| break; |
| } |
| } |
| |
| // TODO(b/312324808): Investigating whether |
| // mMagnificationConnectionManager#disableWindowMagnificationInternal requires a sync lock |
| @SuppressWarnings("GuardedBy") |
| boolean disableWindowMagnificationInternal( |
| @Nullable MagnificationAnimationCallback animationResultCallback) { |
| if (!mEnabled) { |
| return false; |
| } |
| if (mMagnificationConnectionManager.disableWindowMagnificationInternal( |
| mDisplayId, animationResultCallback)) { |
| mEnabled = false; |
| mIdOfLastServiceToControl = INVALID_SERVICE_ID; |
| mTrackingTypingFocusEnabled = false; |
| pauseTrackingTypingFocusRecord(); |
| return true; |
| } |
| return false; |
| } |
| |
| // ErrorProne says the access of mMagnificationConnectionManager#setScaleInternal should |
| // be guarded by 'this.mMagnificationConnectionManager.mLock' which is the same one as |
| // 'mLock'. Therefore, we'll put @SuppressWarnings here. |
| @SuppressWarnings("GuardedBy") |
| @GuardedBy("mLock") |
| void setScale(float scale) { |
| if (!mEnabled) { |
| return; |
| } |
| final float normScale = MagnificationScaleProvider.constrainScale(scale); |
| if (Float.compare(mScale, normScale) != 0 |
| && mMagnificationConnectionManager |
| .setScaleForWindowMagnificationInternal(mDisplayId, scale)) { |
| mScale = normScale; |
| } |
| } |
| |
| @GuardedBy("mLock") |
| float getScale() { |
| return mScale; |
| } |
| |
| @GuardedBy("mLock") |
| void setMagnifierLocation(Rect rect) { |
| mBounds.set(rect); |
| } |
| |
| /** |
| * Returns the ID of the last service that changed the magnification config. |
| */ |
| int getIdOfLastServiceToControl() { |
| return mIdOfLastServiceToControl; |
| } |
| |
| int pointersInWindow(MotionEvent motionEvent) { |
| int count = 0; |
| final int pointerCount = motionEvent.getPointerCount(); |
| for (int i = 0; i < pointerCount; i++) { |
| final float x = motionEvent.getX(i); |
| final float y = motionEvent.getY(i); |
| if (mBounds.contains((int) x, (int) y)) { |
| count++; |
| } |
| } |
| return count; |
| } |
| |
| boolean isPositionInSourceBounds(float x, float y) { |
| return mSourceBounds.contains((int) x, (int) y); |
| } |
| |
| void setTrackingTypingFocusEnabled(boolean trackingTypingFocusEnabled) { |
| if (mMagnificationConnectionManager.isWindowMagnifierEnabled(mDisplayId) |
| && mMagnificationConnectionManager.isImeVisible(mDisplayId) |
| && trackingTypingFocusEnabled) { |
| startTrackingTypingFocusRecord(); |
| } |
| if (mTrackingTypingFocusEnabled && !trackingTypingFocusEnabled) { |
| stopAndLogTrackingTypingFocusRecordIfNeeded(); |
| } |
| mTrackingTypingFocusEnabled = trackingTypingFocusEnabled; |
| } |
| |
| boolean isTrackingTypingFocusEnabled() { |
| return mTrackingTypingFocusEnabled; |
| } |
| |
| void startTrackingTypingFocusRecord() { |
| if (mTrackingTypingFocusStartTime == 0) { |
| mTrackingTypingFocusStartTime = SystemClock.uptimeMillis(); |
| if (DBG) { |
| Slog.d(TAG, "start: mTrackingTypingFocusStartTime = " |
| + mTrackingTypingFocusStartTime); |
| } |
| } |
| } |
| |
| void pauseTrackingTypingFocusRecord() { |
| if (mTrackingTypingFocusStartTime != 0) { |
| final long elapsed = (SystemClock.uptimeMillis() - mTrackingTypingFocusStartTime); |
| // update mTrackingTypingFocusSumTime value in an atomic operation |
| SUM_TIME_UPDATER.addAndGet(this, elapsed); |
| mTrackingTypingFocusStartTime = 0; |
| if (DBG) { |
| Slog.d(TAG, "pause: mTrackingTypingFocusSumTime = " |
| + mTrackingTypingFocusSumTime + ", elapsed = " + elapsed); |
| } |
| } |
| } |
| |
| void stopAndLogTrackingTypingFocusRecordIfNeeded() { |
| if (mTrackingTypingFocusStartTime != 0 || mTrackingTypingFocusSumTime != 0) { |
| final long elapsed = mTrackingTypingFocusStartTime != 0 |
| ? (SystemClock.uptimeMillis() - mTrackingTypingFocusStartTime) : 0; |
| final long duration = mTrackingTypingFocusSumTime + elapsed; |
| if (DBG) { |
| Slog.d(TAG, "stop and log: session duration = " + duration |
| + ", elapsed = " + elapsed); |
| } |
| mMagnificationConnectionManager.logTrackingTypingFocus(duration); |
| mTrackingTypingFocusStartTime = 0; |
| mTrackingTypingFocusSumTime = 0; |
| } |
| } |
| |
| boolean isEnabled() { |
| return mEnabled; |
| } |
| |
| // ErrorProne says the access of mMagnificationConnectionManager#moveWindowMagnifierInternal |
| // should be guarded by 'this.mMagnificationConnectionManager.mLock' which is the same one |
| // as 'mLock'. Therefore, we'll put @SuppressWarnings here. |
| @SuppressWarnings("GuardedBy") |
| @GuardedBy("mLock") |
| void move(float offsetX, float offsetY) { |
| mMagnificationConnectionManager.moveWindowMagnifierInternal( |
| mDisplayId, offsetX, offsetY); |
| } |
| |
| @GuardedBy("mLock") |
| void reset() { |
| mEnabled = false; |
| mIdOfLastServiceToControl = INVALID_SERVICE_ID; |
| mSourceBounds.setEmpty(); |
| } |
| |
| @GuardedBy("mLock") |
| public void onSourceBoundsChanged(Rect sourceBounds) { |
| mSourceBounds.set(sourceBounds); |
| } |
| |
| @GuardedBy("mLock") |
| float getCenterX() { |
| return mSourceBounds.exactCenterX(); |
| } |
| |
| @GuardedBy("mLock") |
| float getCenterY() { |
| return mSourceBounds.exactCenterY(); |
| } |
| } |
| |
| @GuardedBy("mLock") |
| private boolean enableWindowMagnificationInternal(int displayId, float scale, float centerX, |
| float centerY, float magnificationFrameOffsetRatioX, |
| float magnificationFrameOffsetRatioY, |
| MagnificationAnimationCallback animationCallback) { |
| // Wait for the connection with a timeout. |
| final long endMillis = SystemClock.uptimeMillis() + WAIT_CONNECTION_TIMEOUT_MILLIS; |
| while (mConnectionState == CONNECTING && (SystemClock.uptimeMillis() < endMillis)) { |
| try { |
| mLock.wait(endMillis - SystemClock.uptimeMillis()); |
| } catch (InterruptedException ie) { |
| /* ignore */ |
| } |
| } |
| if (mConnectionWrapper == null) { |
| Slog.w(TAG, |
| "enableWindowMagnificationInternal mConnectionWrapper is null. " |
| + "mConnectionState=" + connectionStateToString(mConnectionState)); |
| return false; |
| } |
| return mConnectionWrapper.enableWindowMagnification( |
| displayId, scale, centerX, centerY, |
| magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY, |
| animationCallback); |
| } |
| |
| @GuardedBy("mLock") |
| private boolean setScaleForWindowMagnificationInternal(int displayId, float scale) { |
| return mConnectionWrapper != null |
| && mConnectionWrapper.setScaleForWindowMagnification(displayId, scale); |
| } |
| |
| @GuardedBy("mLock") |
| private boolean disableWindowMagnificationInternal(int displayId, |
| MagnificationAnimationCallback animationCallback) { |
| if (mConnectionWrapper == null) { |
| Slog.w(TAG, "mConnectionWrapper is null"); |
| return false; |
| } |
| return mConnectionWrapper.disableWindowMagnification( |
| displayId, animationCallback); |
| } |
| |
| @GuardedBy("mLock") |
| private boolean moveWindowMagnifierInternal(int displayId, float offsetX, float offsetY) { |
| return mConnectionWrapper != null && mConnectionWrapper.moveWindowMagnifier( |
| displayId, offsetX, offsetY); |
| } |
| |
| @GuardedBy("mLock") |
| private boolean moveWindowMagnifierToPositionInternal(int displayId, float positionX, |
| float positionY, MagnificationAnimationCallback animationCallback) { |
| return mConnectionWrapper != null && mConnectionWrapper.moveWindowMagnifierToPosition( |
| displayId, positionX, positionY, animationCallback); |
| } |
| } |