| /* |
| * 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 android.annotation.Nullable; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.graphics.Rect; |
| import android.os.Binder; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.provider.Settings; |
| import android.util.MathUtils; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| import android.view.MotionEvent; |
| import android.view.accessibility.IWindowMagnificationConnection; |
| import android.view.accessibility.IWindowMagnificationConnectionCallback; |
| import android.view.accessibility.MagnificationAnimationCallback; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.os.BackgroundThread; |
| import com.android.server.LocalServices; |
| import com.android.server.statusbar.StatusBarManagerInternal; |
| |
| /** |
| * A class to manipulate window magnification through {@link WindowMagnificationConnectionWrapper} |
| * create by {@link #setConnection(IWindowMagnificationConnection)}. To set the connection with |
| * SysUI, call {@code StatusBarManagerInternal#requestWindowMagnificationConnection(boolean)}. |
| */ |
| public class WindowMagnificationManager implements |
| PanningScalingHandler.MagnificationDelegate { |
| |
| private static final boolean DBG = false; |
| |
| private static final String TAG = "WindowMagnificationMgr"; |
| |
| //Ensure the range has consistency with full screen. |
| static final float MAX_SCALE = FullScreenMagnificationController.MAX_SCALE; |
| static final float MIN_SCALE = FullScreenMagnificationController.MIN_SCALE; |
| |
| private final Object mLock = new Object(); |
| private final Context mContext; |
| @VisibleForTesting |
| @GuardedBy("mLock") |
| @Nullable |
| WindowMagnificationConnectionWrapper mConnectionWrapper; |
| @GuardedBy("mLock") |
| private ConnectionCallback mConnectionCallback; |
| @GuardedBy("mLock") |
| private SparseArray<WindowMagnifier> mWindowMagnifiers = new SparseArray<>(); |
| private int mUserId; |
| |
| @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); |
| } |
| }; |
| |
| public WindowMagnificationManager(Context context, int userId) { |
| mContext = context; |
| mUserId = userId; |
| } |
| |
| /** |
| * Sets {@link IWindowMagnificationConnection}. |
| * |
| * @param connection {@link IWindowMagnificationConnection} |
| */ |
| public void setConnection(@Nullable IWindowMagnificationConnection connection) { |
| synchronized (mLock) { |
| //Reset connectionWrapper. |
| if (mConnectionWrapper != null) { |
| mConnectionWrapper.setConnectionCallback(null); |
| if (mConnectionCallback != null) { |
| mConnectionCallback.mExpiredDeathRecipient = true; |
| } |
| mConnectionWrapper.unlinkToDeath(mConnectionCallback); |
| mConnectionWrapper = null; |
| } |
| if (connection != null) { |
| mConnectionWrapper = new WindowMagnificationConnectionWrapper(connection); |
| } |
| |
| if (mConnectionWrapper != null) { |
| try { |
| mConnectionCallback = new ConnectionCallback(); |
| mConnectionWrapper.linkToDeath(mConnectionCallback); |
| mConnectionWrapper.setConnectionCallback(mConnectionCallback); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "setConnection failed", e); |
| mConnectionWrapper = null; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Sets the currently active user ID. |
| * |
| * @param userId the currently active user ID |
| */ |
| public void setUserId(int userId) { |
| mUserId = userId; |
| } |
| |
| /** |
| * @return {@code true} if {@link IWindowMagnificationConnection} is available |
| */ |
| public boolean isConnected() { |
| synchronized (mLock) { |
| return mConnectionWrapper != null; |
| } |
| } |
| |
| /** |
| * Requests {@link IWindowMagnificationConnection} through |
| * {@link StatusBarManagerInternal#requestWindowMagnificationConnection(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 IWindowMagnificationConnection} state is going to change. |
| */ |
| public boolean requestConnection(boolean connect) { |
| synchronized (mLock) { |
| if (connect == isConnected()) { |
| return false; |
| } |
| if (connect) { |
| final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF); |
| mContext.registerReceiver(mScreenStateReceiver, intentFilter); |
| } else { |
| disableAllWindowMagnifiers(); |
| mContext.unregisterReceiver(mScreenStateReceiver); |
| } |
| } |
| |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| final StatusBarManagerInternal service = LocalServices.getService( |
| StatusBarManagerInternal.class); |
| service.requestWindowMagnificationConnection(connect); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| return true; |
| } |
| |
| @GuardedBy("mLock") |
| private void disableAllWindowMagnifiers() { |
| for (int i = 0; i < mWindowMagnifiers.size(); i++) { |
| final WindowMagnifier magnifier = mWindowMagnifiers.valueAt(i); |
| magnifier.disableWindowMagnificationInternal(null); |
| } |
| mWindowMagnifiers.clear(); |
| } |
| |
| private void resetWindowMagnifiers() { |
| synchronized (mLock) { |
| for (int i = 0; i < mWindowMagnifiers.size(); i++) { |
| WindowMagnifier magnifier = mWindowMagnifiers.valueAt(i); |
| magnifier.reset(); |
| } |
| } |
| } |
| |
| @Override |
| public boolean processScroll(int displayId, float distanceX, float distanceY) { |
| moveWindowMagnification(displayId, -distanceX, -distanceY); |
| 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); |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| void enableWindowMagnification(int displayId, float scale, float centerX, float centerY) { |
| enableWindowMagnification(displayId, scale, centerX, centerY, null); |
| } |
| |
| /** |
| * Enables window magnification with specified center and scale on the specified 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. |
| * @param animationCallback Called when the animation result is valid. |
| */ |
| void enableWindowMagnification(int displayId, float scale, float centerX, float centerY, |
| @Nullable MagnificationAnimationCallback animationCallback) { |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null) { |
| magnifier = createWindowMagnifier(displayId); |
| } |
| magnifier.enableWindowMagnificationInternal(scale, centerX, centerY, |
| animationCallback); |
| } |
| } |
| |
| /** |
| * Disables window magnification on the given display. |
| * |
| * @param displayId The logical display id. |
| * @param clear {@true} Clears the state of window magnification. |
| */ |
| void disableWindowMagnification(int displayId, boolean clear) { |
| disableWindowMagnification(displayId, clear, null); |
| } |
| |
| /** |
| * 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. |
| */ |
| void disableWindowMagnification(int displayId, boolean clear, |
| MagnificationAnimationCallback animationCallback) { |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null) { |
| return; |
| } |
| magnifier.disableWindowMagnificationInternal(animationCallback); |
| if (clear) { |
| mWindowMagnifiers.delete(displayId); |
| } |
| } |
| } |
| |
| /** |
| * 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); |
| } |
| } |
| |
| /** |
| * Indicates whether window magnification is enabled on specified display. |
| * |
| * @param displayId The logical display id. |
| * @return {@code true} if the window magnification is enabled. |
| */ |
| boolean isWindowMagnifierEnabled(int displayId) { |
| synchronized (mLock) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null) { |
| return false; |
| } |
| return magnifier.isEnabled(); |
| } |
| } |
| |
| /** |
| * Retrieves a previously persisted magnification scale from the current |
| * user's settings. |
| * |
| * @return the previously persisted magnification scale, or the default |
| * scale if none is available |
| */ |
| float getPersistedScale() { |
| return Settings.Secure.getFloatForUser(mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, |
| MIN_SCALE, mUserId); |
| } |
| |
| /** |
| * Persists the default display magnification scale to the current user's settings. |
| */ |
| void persistScale(int displayId) { |
| |
| float scale = getScale(displayId); |
| if (scale != 1.0f) { |
| BackgroundThread.getHandler().post(() -> { |
| Settings.Secure.putFloatForUser(mContext.getContentResolver(), |
| Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, mUserId); |
| }); |
| } |
| } |
| |
| /** |
| * 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) { |
| return 1.0f; |
| } |
| return magnifier.getScale(); |
| } |
| } |
| |
| /** |
| * 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) { |
| 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) { |
| return mConnectionWrapper != null && mConnectionWrapper.removeMagnificationButton( |
| displayId); |
| } |
| |
| /** |
| * 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; |
| } |
| |
| private class ConnectionCallback extends IWindowMagnificationConnectionCallback.Stub implements |
| IBinder.DeathRecipient { |
| private boolean mExpiredDeathRecipient = false; |
| |
| @Override |
| public void onWindowMagnifierBoundsChanged(int displayId, Rect 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 { |
| //TODO: Uses this method to change the magnification mode on non-default display. |
| } |
| |
| @Override |
| public void onSourceBoundsChanged(int displayId, Rect sourceBounds) { |
| WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); |
| if (magnifier == null) { |
| magnifier = createWindowMagnifier(displayId); |
| } |
| magnifier.onSourceBoundsChanged(sourceBounds); |
| } |
| |
| @Override |
| public void binderDied() { |
| synchronized (mLock) { |
| Slog.w(TAG, "binderDied DeathRecipient :" + mExpiredDeathRecipient); |
| if (mExpiredDeathRecipient) { |
| return; |
| } |
| mConnectionWrapper.unlinkToDeath(this); |
| mConnectionWrapper = null; |
| mConnectionCallback = null; |
| resetWindowMagnifiers(); |
| } |
| } |
| } |
| |
| /** |
| * A class manipulates window magnification per display and contains the magnification |
| * information. |
| */ |
| private static class WindowMagnifier { |
| |
| private final int mDisplayId; |
| private float mScale = MIN_SCALE; |
| private boolean mEnabled; |
| |
| private final WindowMagnificationManager mWindowMagnificationManager; |
| //Records the bounds of window magnification. |
| private final Rect mBounds = new Rect(); |
| //The magnified bounds on the screen. |
| private final Rect mSourceBounds = new Rect(); |
| |
| WindowMagnifier(int displayId, WindowMagnificationManager windowMagnificationManager) { |
| mDisplayId = displayId; |
| mWindowMagnificationManager = windowMagnificationManager; |
| } |
| |
| @GuardedBy("mLock") |
| void enableWindowMagnificationInternal(float scale, float centerX, float centerY, |
| @Nullable MagnificationAnimationCallback animationCallback) { |
| if (mEnabled) { |
| return; |
| } |
| final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE); |
| if (mWindowMagnificationManager.enableWindowMagnificationInternal(mDisplayId, normScale, |
| centerX, centerY, animationCallback)) { |
| mScale = normScale; |
| mEnabled = true; |
| } |
| } |
| |
| @GuardedBy("mLock") |
| void disableWindowMagnificationInternal( |
| @Nullable MagnificationAnimationCallback animationResultCallback) { |
| if (mEnabled && mWindowMagnificationManager.disableWindowMagnificationInternal( |
| mDisplayId, animationResultCallback)) { |
| mEnabled = false; |
| } |
| } |
| |
| @GuardedBy("mLock") |
| void setScale(float scale) { |
| if (!mEnabled) { |
| return; |
| } |
| final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE); |
| if (Float.compare(mScale, normScale) != 0 |
| && mWindowMagnificationManager.setScaleInternal(mDisplayId, scale)) { |
| mScale = normScale; |
| } |
| } |
| |
| @GuardedBy("mLock") |
| float getScale() { |
| return mScale; |
| } |
| |
| @GuardedBy("mLock") |
| void setMagnifierLocation(Rect rect) { |
| mBounds.set(rect); |
| } |
| |
| @GuardedBy("mLock") |
| 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; |
| } |
| |
| @GuardedBy("mLock") |
| boolean isEnabled() { |
| return mEnabled; |
| } |
| |
| @GuardedBy("mLock") |
| void move(float offsetX, float offsetY) { |
| mWindowMagnificationManager.moveWindowMagnifierInternal(mDisplayId, offsetX, offsetY); |
| } |
| |
| @GuardedBy("mLock") |
| void reset() { |
| mEnabled = false; |
| } |
| |
| public void onSourceBoundsChanged(Rect sourceBounds) { |
| mSourceBounds.set(sourceBounds); |
| } |
| } |
| |
| private boolean enableWindowMagnificationInternal(int displayId, float scale, float centerX, |
| float centerY, MagnificationAnimationCallback animationCallback) { |
| return mConnectionWrapper != null && mConnectionWrapper.enableWindowMagnification( |
| displayId, scale, centerX, centerY, animationCallback); |
| } |
| |
| private boolean setScaleInternal(int displayId, float scale) { |
| return mConnectionWrapper != null && mConnectionWrapper.setScale(displayId, scale); |
| } |
| |
| private boolean disableWindowMagnificationInternal(int displayId, |
| MagnificationAnimationCallback animationCallback) { |
| return mConnectionWrapper != null && mConnectionWrapper.disableWindowMagnification( |
| displayId, animationCallback); |
| } |
| |
| private boolean moveWindowMagnifierInternal(int displayId, float offsetX, float offsetY) { |
| return mConnectionWrapper != null && mConnectionWrapper.moveWindowMagnifier( |
| displayId, offsetX, offsetY); |
| } |
| } |