Merge "Move DecorView out of PhoneWindow into its own class file."
diff --git a/api/current.txt b/api/current.txt
index de9abd0..cca8293 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -332,6 +332,7 @@
field public static final int calendarTextColor = 16843931; // 0x101049b
field public static final int calendarViewShown = 16843596; // 0x101034c
field public static final int calendarViewStyle = 16843613; // 0x101035d
+ field public static final int canControlMagnification = 16844040; // 0x1010508
field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
field public static final int canRequestFilterKeyEvents = 16843737; // 0x10103d9
field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
@@ -2619,6 +2620,7 @@
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+ method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
@@ -2656,6 +2658,23 @@
field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice";
}
+ public static final class AccessibilityService.MagnificationController {
+ method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
+ method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, android.os.Handler);
+ method public float getCenterX();
+ method public float getCenterY();
+ method public android.graphics.Region getMagnifiedRegion();
+ method public float getScale();
+ method public boolean removeListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
+ method public boolean reset(boolean);
+ method public boolean setCenter(float, float, boolean);
+ method public boolean setScale(float, boolean);
+ }
+
+ public static abstract interface AccessibilityService.MagnificationController.OnMagnificationChangedListener {
+ method public abstract void onMagnificationChanged(android.accessibilityservice.AccessibilityService.MagnificationController, android.graphics.Region, float, float, float);
+ }
+
public class AccessibilityServiceInfo implements android.os.Parcelable {
ctor public AccessibilityServiceInfo();
method public static java.lang.String capabilityToString(int);
@@ -2670,6 +2689,7 @@
method public java.lang.String getSettingsActivityName();
method public java.lang.String loadDescription(android.content.pm.PackageManager);
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8
field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
diff --git a/api/system-current.txt b/api/system-current.txt
index 65ecdc1..cd6c58e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -424,6 +424,7 @@
field public static final int calendarTextColor = 16843931; // 0x101049b
field public static final int calendarViewShown = 16843596; // 0x101034c
field public static final int calendarViewStyle = 16843613; // 0x101035d
+ field public static final int canControlMagnification = 16844040; // 0x1010508
field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
field public static final int canRequestFilterKeyEvents = 16843737; // 0x10103d9
field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
@@ -2718,6 +2719,7 @@
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+ method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
@@ -2755,6 +2757,23 @@
field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice";
}
+ public static final class AccessibilityService.MagnificationController {
+ method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
+ method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, android.os.Handler);
+ method public float getCenterX();
+ method public float getCenterY();
+ method public android.graphics.Region getMagnifiedRegion();
+ method public float getScale();
+ method public boolean removeListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
+ method public boolean reset(boolean);
+ method public boolean setCenter(float, float, boolean);
+ method public boolean setScale(float, boolean);
+ }
+
+ public static abstract interface AccessibilityService.MagnificationController.OnMagnificationChangedListener {
+ method public abstract void onMagnificationChanged(android.accessibilityservice.AccessibilityService.MagnificationController, android.graphics.Region, float, float, float);
+ }
+
public class AccessibilityServiceInfo implements android.os.Parcelable {
ctor public AccessibilityServiceInfo();
method public static java.lang.String capabilityToString(int);
@@ -2769,6 +2788,7 @@
method public java.lang.String getSettingsActivityName();
method public java.lang.String loadDescription(android.content.pm.PackageManager);
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8
field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 9d6aa13..273483a 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -17,14 +17,19 @@
package android.accessibilityservice;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Region;
+import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.util.ArrayMap;
import android.util.Log;
+import android.util.Slog;
import android.view.KeyEvent;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
@@ -36,7 +41,10 @@
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
/**
* An accessibility service runs in the background and receives callbacks by the system
@@ -373,6 +381,8 @@
public void init(int connectionId, IBinder windowToken);
public boolean onGesture(int gestureId);
public boolean onKeyEvent(KeyEvent event);
+ public void onMagnificationChanged(@NonNull Region region,
+ float scale, float centerX, float centerY);
}
private int mConnectionId;
@@ -383,6 +393,8 @@
private WindowManager mWindowManager;
+ private MagnificationController mMagnificationController;
+
/**
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
*
@@ -396,6 +408,20 @@
public abstract void onInterrupt();
/**
+ * Dispatches service connection to internal components first, then the
+ * client code.
+ */
+ private void dispatchServiceConnected() {
+ if (mMagnificationController != null) {
+ mMagnificationController.onServiceConnected();
+ }
+
+ // The client gets to handle service connection last, after we've set
+ // up any state upon which their code may rely.
+ onServiceConnected();
+ }
+
+ /**
* This method is a part of the {@link AccessibilityService} lifecycle and is
* called after the system has successfully bound to the service. If is
* convenient to use this method for setting the {@link AccessibilityServiceInfo}.
@@ -513,6 +539,385 @@
}
/**
+ * Returns the magnification controller, which may be used to query and
+ * modify the state of display magnification.
+ * <p>
+ * <strong>Note:</strong> In order to control magnification, your service
+ * must declare the capability by setting the
+ * {@link android.R.styleable#AccessibilityService_canControlMagnification}
+ * property in its meta-data. For more information, see
+ * {@link #SERVICE_META_DATA}.
+ *
+ * @return the magnification controller
+ */
+ @NonNull
+ public final MagnificationController getMagnificationController() {
+ if (mMagnificationController == null) {
+ mMagnificationController = new MagnificationController(this);
+ }
+ return mMagnificationController;
+ }
+
+ private void onMagnificationChanged(@NonNull Region region, float scale,
+ float centerX, float centerY) {
+ if (mMagnificationController != null) {
+ mMagnificationController.dispatchMagnificationChanged(
+ region, scale, centerX, centerY);
+ }
+ }
+
+ /**
+ * Used to control and query the state of display magnification.
+ */
+ public static final class MagnificationController {
+ private final AccessibilityService mService;
+
+ /**
+ * Map of listeners to their handlers. Lazily created when adding the
+ * first magnification listener.
+ */
+ private ArrayMap<OnMagnificationChangedListener, Handler> mListeners;
+
+ MagnificationController(@NonNull AccessibilityService service) {
+ mService = service;
+ }
+
+ /**
+ * Called when the service is connected.
+ */
+ void onServiceConnected() {
+ if (mListeners != null && !mListeners.isEmpty()) {
+ setMagnificationCallbackEnabled(true);
+ }
+ }
+
+ /**
+ * Adds the specified change listener to the list of magnification
+ * change listeners. The callback will occur on the service's main
+ * thread.
+ *
+ * @param listener the listener to add, must be non-{@code null}
+ */
+ public void addListener(@NonNull OnMagnificationChangedListener listener) {
+ addListener(listener, null);
+ }
+
+ /**
+ * Adds the specified change listener to the list of magnification
+ * change listeners. The callback will occur on the specified
+ * {@link Handler}'s thread, or on the service's main thread if the
+ * handler is {@code null}.
+ *
+ * @param listener the listener to add, must be non-null
+ * @param handler the handler on which the callback should execute, or
+ * {@code null} to execute on the service's main thread
+ */
+ public void addListener(@NonNull OnMagnificationChangedListener listener,
+ @Nullable Handler handler) {
+ if (mListeners == null) {
+ mListeners = new ArrayMap<>();
+ }
+
+ final boolean shouldEnableCallback = mListeners.isEmpty();
+ mListeners.put(listener, handler);
+
+ if (shouldEnableCallback) {
+ // This may fail if the service is not connected yet, but if we
+ // still have listeners when it connects then we can try again.
+ setMagnificationCallbackEnabled(true);
+ }
+ }
+
+ /**
+ * Removes all instances of the specified change listener from the list
+ * of magnification change listeners.
+ *
+ * @param listener the listener to remove, must be non-null
+ * @return {@code true} if at least one instance of the listener was
+ * removed
+ */
+ public boolean removeListener(@NonNull OnMagnificationChangedListener listener) {
+ if (mListeners == null) {
+ return false;
+ }
+
+ final int keyIndex = mListeners.indexOfKey(listener);
+ final boolean hasKey = keyIndex >= 0;
+ if (hasKey) {
+ mListeners.removeAt(keyIndex);
+ }
+
+ if (hasKey && mListeners.isEmpty()) {
+ // We just removed the last listener, so we don't need
+ // callbacks from the service anymore.
+ setMagnificationCallbackEnabled(false);
+ }
+
+ return hasKey;
+ }
+
+ private void setMagnificationCallbackEnabled(boolean enabled) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setMagnificationCallbackEnabled(enabled);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
+ * Dispatches magnification changes to any registered listeners. This
+ * should be called on the service's main thread.
+ */
+ void dispatchMagnificationChanged(final @NonNull Region region, final float scale,
+ final float centerX, final float centerY) {
+ if (mListeners == null || mListeners.isEmpty()) {
+ Slog.d(LOG_TAG, "Received magnification changed "
+ + "callback with no listeners registered!");
+ setMagnificationCallbackEnabled(false);
+ return;
+ }
+
+ // Listeners may remove themselves. Perform a shallow copy to avoid
+ // concurrent modification.
+ final ArrayMap<OnMagnificationChangedListener, Handler> entries =
+ new ArrayMap<>(mListeners);
+
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final OnMagnificationChangedListener listener = entries.keyAt(i);
+ final Handler handler = entries.valueAt(i);
+ if (handler != null) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onMagnificationChanged(MagnificationController.this,
+ region, scale, centerX, centerY);
+ }
+ });
+ } else {
+ // We're already on the main thread, just run the listener.
+ listener.onMagnificationChanged(this, region, scale, centerX, centerY);
+ }
+ }
+ }
+
+ /**
+ * Returns the current magnification scale.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return a default value of {@code 1.0f}.
+ *
+ * @return the current magnification scale
+ */
+ public float getScale() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnificationScale();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain scale", re);
+ }
+ }
+ return 1.0f;
+ }
+
+ /**
+ * Returns the unscaled screen-relative X coordinate of the focal
+ * center of the magnified region. This is the point around which
+ * zooming occurs and is guaranteed to lie within the magnified
+ * region.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return a default value of {@code 0.0f}.
+ *
+ * @return the unscaled screen-relative X coordinate of the center of
+ * the magnified region
+ */
+ public float getCenterX() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnificationCenterX();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain center X", re);
+ }
+ }
+ return 0.0f;
+ }
+
+ /**
+ * Returns the unscaled screen-relative Y coordinate of the focal
+ * center of the magnified region. This is the point around which
+ * zooming occurs and is guaranteed to lie within the magnified
+ * region.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return a default value of {@code 0.0f}.
+ *
+ * @return the unscaled screen-relative Y coordinate of the center of
+ * the magnified region
+ */
+ public float getCenterY() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnificationCenterY();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain center Y", re);
+ }
+ }
+ return 0.0f;
+ }
+
+ /**
+ * Returns the region of the screen currently being magnified. If
+ * magnification is not enabled, the returned region will be empty.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will
+ * return an empty region.
+ *
+ * @return the screen-relative bounds of the magnified region
+ */
+ @NonNull
+ public Region getMagnifiedRegion() {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getMagnifiedRegion();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to obtain magnified region", re);
+ }
+ }
+ return Region.obtain();
+ }
+
+ /**
+ * Resets magnification scale and center to their default (e.g. no
+ * magnification) values.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will have
+ * no effect and return {@code false}.
+ *
+ * @param animate {@code true} to animate from the current scale and
+ * center or {@code false} to reset the scale and center
+ * immediately
+ * @return {@code true} on success, {@code false} on failure
+ */
+ public boolean reset(boolean animate) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.resetMagnification(animate);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to reset", re);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets the magnification scale.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will have
+ * no effect and return {@code false}.
+ *
+ * @param scale the magnification scale to set, must be >= 1 and <= 5
+ * @param animate {@code true} to animate from the current scale or
+ * {@code false} to set the scale immediately
+ * @return {@code true} on success, {@code false} on failure
+ */
+ public boolean setScale(float scale, boolean animate) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.setMagnificationScaleAndCenter(
+ scale, Float.NaN, Float.NaN, animate);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to set scale", re);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets the center of the magnified viewport.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been
+ * called) or the service has been disconnected, this method will have
+ * no effect and return {@code false}.
+ *
+ * @param centerX the unscaled screen-relative X coordinate on which to
+ * center the viewport
+ * @param centerY the unscaled screen-relative Y coordinate on which to
+ * center the viewport
+ * @param animate {@code true} to animate from the current viewport
+ * center or {@code false} to set the center immediately
+ * @return {@code true} on success, {@code false} on failure
+ */
+ public boolean setCenter(float centerX, float centerY, boolean animate) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.setMagnificationScaleAndCenter(
+ Float.NaN, centerX, centerY, animate);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to set center", re);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Listener for changes in the state of magnification.
+ */
+ public interface OnMagnificationChangedListener {
+ /**
+ * Called when the magnified region, scale, or center changes.
+ *
+ * @param controller the magnification controller
+ * @param region the new magnified region, may be empty if
+ * magnification is not enabled (e.g. scale is 1)
+ * @param scale the new scale
+ * @param centerX the new X coordinate around which magnification is focused
+ * @param centerY the new Y coordinate around which magnification is focused
+ */
+ void onMagnificationChanged(@NonNull MagnificationController controller,
+ @NonNull Region region, float scale, float centerX, float centerY);
+ }
+ }
+
+ /**
* Performs a global action. Such an action can be performed
* at any moment regardless of the current application or user
* location in that application. For example going back, going
@@ -645,7 +1050,7 @@
return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
@Override
public void onServiceConnected() {
- AccessibilityService.this.onServiceConnected();
+ AccessibilityService.this.dispatchServiceConnected();
}
@Override
@@ -678,6 +1083,12 @@
public boolean onKeyEvent(KeyEvent event) {
return AccessibilityService.this.onKeyEvent(event);
}
+
+ @Override
+ public void onMagnificationChanged(@NonNull Region region,
+ float scale, float centerX, float centerY) {
+ AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY);
+ }
});
}
@@ -695,6 +1106,7 @@
private static final int DO_ON_GESTURE = 4;
private static final int DO_CLEAR_ACCESSIBILITY_CACHE = 5;
private static final int DO_ON_KEY_EVENT = 6;
+ private static final int DO_ON_MAGNIFICATION_CHANGED = 7;
private final HandlerCaller mCaller;
@@ -741,6 +1153,18 @@
mCaller.sendMessage(message);
}
+ public void onMagnificationChanged(@NonNull Region region,
+ float scale, float centerX, float centerY) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = region;
+ args.arg2 = scale;
+ args.arg3 = centerX;
+ args.arg4 = centerY;
+
+ final Message message = mCaller.obtainMessageO(DO_ON_MAGNIFICATION_CHANGED, args);
+ mCaller.sendMessage(message);
+ }
+
@Override
public void executeMessage(Message message) {
switch (message.what) {
@@ -816,6 +1240,15 @@
}
} return;
+ case DO_ON_MAGNIFICATION_CHANGED: {
+ final SomeArgs args = (SomeArgs) message.obj;
+ final Region region = (Region) args.arg1;
+ final float scale = (float) args.arg2;
+ final float centerX = (float) args.arg3;
+ final float centerY = (float) args.arg4;
+ mCallback.onMagnificationChanged(region, scale, centerX, centerY);
+ } return;
+
default :
Log.w(LOG_TAG, "Unknown message type " + message.what);
}
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 4edb0c6..2c98fef 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -104,6 +104,12 @@
*/
public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 0x00000008;
+ /**
+ * Capability: This accessibility service can control display magnification.
+ * @see android.R.styleable#AccessibilityService_canControlMagnification
+ */
+ public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 0x00000010;
+
private static final SparseArray<CapabilityInfo> sAvailableCapabilityInfos =
new SparseArray<CapabilityInfo>();
static {
@@ -123,6 +129,10 @@
new CapabilityInfo(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
R.string.capability_title_canRequestFilterKeyEvents,
R.string.capability_desc_canRequestFilterKeyEvents));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
+ new CapabilityInfo(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
+ R.string.capability_title_canControlMagnification,
+ R.string.capability_desc_canControlMagnification));
}
/**
@@ -502,6 +512,10 @@
.AccessibilityService_canRequestFilterKeyEvents, false)) {
mCapabilities |= CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS;
}
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canControlMagnification, false)) {
+ mCapabilities |= CAPABILITY_CAN_CONTROL_MAGNIFICATION;
+ }
TypedValue peekedValue = asAttributes.peekValue(
com.android.internal.R.styleable.AccessibilityService_description);
if (peekedValue != null) {
@@ -917,6 +931,8 @@
return "CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
case CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS:
return "CAPABILITY_CAN_FILTER_KEY_EVENTS";
+ case CAPABILITY_CAN_CONTROL_MAGNIFICATION:
+ return "CAPABILITY_CAN_CONTROL_MAGNIFICATION";
default:
return "UNKNOWN";
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 8b503dd..15666bf 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -17,6 +17,7 @@
package android.accessibilityservice;
import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.graphics.Region;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.KeyEvent;
@@ -39,4 +40,6 @@
void clearAccessibilityCache();
void onKeyEvent(in KeyEvent event, int sequence);
+
+ void onMagnificationChanged(in Region region, float scale, float centerX, float centerY);
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 5f7a17d..6ac50bd 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -18,6 +18,7 @@
import android.os.Bundle;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.graphics.Region;
import android.view.MagnificationSpec;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
@@ -63,4 +64,19 @@
boolean performGlobalAction(int action);
oneway void setOnKeyEventResult(boolean handled, int sequence);
+
+ float getMagnificationScale();
+
+ float getMagnificationCenterX();
+
+ float getMagnificationCenterY();
+
+ Region getMagnifiedRegion();
+
+ boolean resetMagnification(boolean animate);
+
+ boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
+ boolean animate);
+
+ void setMagnificationCallbackEnabled(boolean enabled);
}
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index efed2e0..f7848f9 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -21,9 +21,11 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.NonNull;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Point;
+import android.graphics.Region;
import android.hardware.display.DisplayManagerGlobal;
import android.os.IBinder;
import android.os.Looper;
@@ -1020,6 +1022,12 @@
public boolean onKeyEvent(KeyEvent event) {
return false;
}
+
+ @Override
+ public void onMagnificationChanged(@NonNull Region region,
+ float scale, float centerX, float centerY) {
+ /* do nothing */
+ }
});
}
}
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 25d9aa9..09a15de 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -685,6 +685,48 @@
}
/**
+ * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any
+ * audio to the HF unless explicitly told to.
+ * This method should be used in cases where the SCO channel is shared between multiple profiles
+ * and must be delegated by a source knowledgeable
+ * Note: This is an internal function and shouldn't be exposed
+ *
+ * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise.
+ *
+ * @hide
+ */
+ public void setAudioRouteAllowed(boolean allowed) {
+ if (VDBG) log("setAudioRouteAllowed");
+ if (mService != null && isEnabled()) {
+ try {
+ mService.setAudioRouteAllowed(allowed);
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ }
+
+ /**
+ * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}.
+ * Note: This is an internal function and shouldn't be exposed
+ *
+ * @hide
+ */
+ public boolean getAudioRouteAllowed() {
+ if (VDBG) log("getAudioRouteAllowed");
+ if (mService != null && isEnabled()) {
+ try {
+ return mService.getAudioRouteAllowed();
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
* Check if Bluetooth SCO audio is connected.
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
index 0e23fad..0bb4088 100755
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -50,6 +50,8 @@
boolean isAudioOn();
boolean connectAudio();
boolean disconnectAudio();
+ void setAudioRouteAllowed(boolean allowed);
+ boolean getAudioRouteAllowed();
boolean startScoUsingVirtualVoiceCall(in BluetoothDevice device);
boolean stopScoUsingVirtualVoiceCall(in BluetoothDevice device);
void phoneStateChanged(int numActive, int numHeld, int callState, String number, int type);
diff --git a/core/java/android/view/MagnificationSpec.java b/core/java/android/view/MagnificationSpec.java
index 0ee6714..49242bb 100644
--- a/core/java/android/view/MagnificationSpec.java
+++ b/core/java/android/view/MagnificationSpec.java
@@ -28,10 +28,21 @@
public class MagnificationSpec implements Parcelable {
private static final int MAX_POOL_SIZE = 20;
private static final SynchronizedPool<MagnificationSpec> sPool =
- new SynchronizedPool<MagnificationSpec>(MAX_POOL_SIZE);
+ new SynchronizedPool<>(MAX_POOL_SIZE);
+ /** The magnification scaling factor. */
public float scale = 1.0f;
+
+ /**
+ * The X coordinate, in unscaled screen-relative pixels, around which
+ * magnification is focused.
+ */
public float offsetX;
+
+ /**
+ * The Y coordinate, in unscaled screen-relative pixels, around which
+ * magnification is focused.
+ */
public float offsetY;
private MagnificationSpec() {
@@ -75,6 +86,12 @@
offsetY = 0.0f;
}
+ public void setTo(MagnificationSpec other) {
+ scale = other.scale;
+ offsetX = other.offsetX;
+ offsetY = other.offsetY;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -89,6 +106,28 @@
}
@Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+
+ final MagnificationSpec s = (MagnificationSpec) other;
+ return scale == s.scale && offsetX == s.offsetX && offsetY == s.offsetY;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (scale != +0.0f ? Float.floatToIntBits(scale) : 0);
+ result = 31 * result + (offsetX != +0.0f ? Float.floatToIntBits(offsetX) : 0);
+ result = 31 * result + (offsetY != +0.0f ? Float.floatToIntBits(offsetY) : 0);
+ return result;
+ }
+
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("<scale:");
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index 3882bca..89b1eb9 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.Nullable;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManagerInternal;
@@ -55,9 +56,10 @@
* Called when the bounds of the screen content that is magnified changed.
* Note that not the entire screen is magnified.
*
- * @param bounds The bounds.
+ * @param magnifiedBounds the currently magnified region
+ * @param availableBounds the region available for magnification
*/
- public void onMagnifedBoundsChanged(Region bounds);
+ public void onMagnifiedBoundsChanged(Region magnifiedBounds, Region availableBounds);
/**
* Called when an application requests a rectangle on the screen to allow
@@ -142,7 +144,7 @@
*
* @param callbacks The callbacks to invoke.
*/
- public abstract void setMagnificationCallbacks(MagnificationCallbacks callbacks);
+ public abstract void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks);
/**
* Set by the accessibility layer to specify the magnification and panning to
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 50cf302..90fc22b 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3242,6 +3242,14 @@
</p>
-->
<attr name="canRequestFilterKeyEvents" format="boolean" />
+ <!-- Attribute whether the accessibility service wants to be able to control
+ display magnification.
+ <p>
+ Required to allow setting the {@link android.accessibilityservice
+ #AccessibilityServiceInfo#FLAG_CAN_CONTROL_MAGNIFICATION} flag.
+ </p>
+ -->
+ <attr name="canControlMagnification" format="boolean" />
<!-- Short description of the accessibility serivce purpose or behavior.-->
<attr name="description" />
</declare-styleable>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 54e43c8..b6b2e20 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2682,6 +2682,7 @@
<public type="attr" name="forceDeviceEncrypted" />
<public type="attr" name="encryptionAware" />
<public type="attr" name="preferenceFragmentStyle" />
+ <public type="attr" name="canControlMagnification" />
<public type="style" name="Theme.Material.DayNight" />
<public type="style" name="Theme.Material.DayNight.DarkActionBar" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index faa76f2..00c0fe8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -613,6 +613,12 @@
<string name="capability_desc_canRequestFilterKeyEvents">Includes personal data such as credit
card numbers and passwords.</string>
+ <!-- Title for the capability of an accessibility service to control display magnification. -->
+ <string name="capability_title_canControlMagnification">Control display magnification</string>
+ <!-- Description for the capability of an accessibility service to control display magnification. -->
+ <string name="capability_desc_canControlMagnification">Control the display\'s zoom level and
+ positioning.</string>
+
<!-- Permissions -->
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6820c25..e8f6b46 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -564,6 +564,8 @@
<java-symbol type="string" name="capability_desc_canRequestFilterKeyEvents" />
<java-symbol type="string" name="capability_title_canRequestTouchExploration" />
<java-symbol type="string" name="capability_title_canRetrieveWindowContent" />
+ <java-symbol type="string" name="capability_desc_canControlMagnification" />
+ <java-symbol type="string" name="capability_title_canControlMagnification" />
<java-symbol type="string" name="cfTemplateForwarded" />
<java-symbol type="string" name="cfTemplateForwardedTime" />
<java-symbol type="string" name="cfTemplateNotForwarded" />
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index b52687a..5f6cbf9 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -21,7 +21,6 @@
import android.util.Pools.SimplePool;
import android.util.Slog;
import android.view.Choreographer;
-import android.view.Display;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputFilter;
@@ -103,7 +102,7 @@
private TouchExplorer mTouchExplorer;
- private ScreenMagnifier mScreenMagnifier;
+ private MagnificationGestureHandler mMagnificationGestureHandler;
private AutoclickController mAutoclickController;
@@ -363,14 +362,13 @@
}
if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) {
- mScreenMagnifier = new ScreenMagnifier(mContext, mUserId,
- Display.DEFAULT_DISPLAY, mAms);
- addFirstEventHandler(mScreenMagnifier);
+ mMagnificationGestureHandler = new MagnificationGestureHandler(mContext, mAms);
+ addFirstEventHandler(mMagnificationGestureHandler);
}
if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) {
- mKeyboardInterceptor = new KeyboardInterceptor(mAms);
- addFirstEventHandler(mKeyboardInterceptor);
+ mKeyboardInterceptor = new KeyboardInterceptor(mAms);
+ addFirstEventHandler(mKeyboardInterceptor);
}
}
@@ -398,9 +396,9 @@
mTouchExplorer.onDestroy();
mTouchExplorer = null;
}
- if (mScreenMagnifier != null) {
- mScreenMagnifier.onDestroy();
- mScreenMagnifier = null;
+ if (mMagnificationGestureHandler != null) {
+ mMagnificationGestureHandler.onDestroy();
+ mMagnificationGestureHandler = null;
}
if (mKeyboardInterceptor != null) {
mKeyboardInterceptor.onDestroy();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 535a8ef..9f1dc0a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -23,6 +23,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.NonNull;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.StatusBarManager;
@@ -90,6 +91,7 @@
import com.android.internal.R;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.SomeArgs;
import com.android.internal.statusbar.IStatusBarService;
import com.android.server.LocalServices;
@@ -181,6 +183,8 @@
private MagnificationController mMagnificationController;
+ private boolean mUnregisterMagnificationOnReset;
+
private InteractionBridge mInteractionBridge;
private AlertDialog mEnableTouchExplorationDialog;
@@ -762,6 +766,28 @@
}
/**
+ * Called by the MagnificationController when the state of display
+ * magnification changes.
+ *
+ * @param region the new magnified region, may be empty if
+ * magnification is not enabled (e.g. scale is 1)
+ * @param scale the new scale
+ * @param centerX the new screen-relative center X coordinate
+ * @param centerY the new screen-relative center Y coordinate
+ */
+ void notifyMagnificationChanged(@NonNull Region region,
+ float scale, float centerX, float centerY) {
+ synchronized (mLock) {
+ notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+
+ if (mUnregisterMagnificationOnReset && scale == 1.0f) {
+ mUnregisterMagnificationOnReset = false;
+ mMagnificationController.unregister();
+ }
+ }
+ }
+
+ /**
* Gets a point within the accessibility focused node where we can send down
* and up events to perform a click.
*
@@ -942,6 +968,15 @@
}
}
+ private void notifyMagnificationChangedLocked(@NonNull Region region,
+ float scale, float centerX, float centerY) {
+ final UserState state = getCurrentUserStateLocked();
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ final Service service = state.mBoundServices.get(i);
+ service.notifyMagnificationChanged(region, scale, centerX, centerY);
+ }
+ }
+
/**
* Removes an AccessibilityInteractionConnection.
*
@@ -1363,6 +1398,11 @@
}
}
+ /**
+ * Called when any property of the user state has changed.
+ *
+ * @param userState the new user state
+ */
private void onUserStateChangedLocked(UserState userState) {
// TODO: Remove this hack
mInitialized = true;
@@ -1374,6 +1414,7 @@
updateTouchExplorationLocked(userState);
updateEnhancedWebAccessibilityLocked(userState);
updateDisplayColorAdjustmentSettingsLocked(userState);
+ updateMagnificationLocked(userState);
scheduleUpdateInputFilter(userState);
scheduleUpdateClientsIfNeededLocked(userState);
}
@@ -1663,6 +1704,44 @@
DisplayAdjustmentUtils.applyAdjustments(mContext, userState.mUserId);
}
+ private void updateMagnificationLocked(UserState userState) {
+ final int userId = userState.mUserId;
+ if (userId == mCurrentUserId && mMagnificationController != null) {
+ if (userHasMagnificationServicesLocked(userState)) {
+ mMagnificationController.setUserId(userState.mUserId);
+ } else {
+ // If the user no longer has any magnification-controlling
+ // services and is not using magnification gestures, then
+ // reset the state to normal.
+ if (!userState.mIsDisplayMagnificationEnabled
+ && mMagnificationController.resetIfNeeded(true)) {
+ // Animations are still running, so wait until we receive a
+ // callback verifying that we've reset magnification.
+ mUnregisterMagnificationOnReset = true;
+ } else {
+ mUnregisterMagnificationOnReset = false;
+ mMagnificationController.unregister();
+ mMagnificationController = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns whether the specified user has any services that are capable of
+ * controlling magnification.
+ */
+ private boolean userHasMagnificationServicesLocked(UserState userState) {
+ final List<Service> services = userState.mBoundServices;
+ for (int i = 0, count = services.size(); i < count; i++) {
+ final Service service = services.get(i);
+ if (mSecurityPolicy.canControlMagnification(service)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) {
IBinder windowToken = mGlobalWindowTokens.get(windowId);
if (windowToken == null) {
@@ -1938,10 +2017,14 @@
}
MagnificationController getMagnificationController() {
- if (mMagnificationController == null) {
- mMagnificationController = new MagnificationController(mContext, this);
+ synchronized (mLock) {
+ if (mMagnificationController == null) {
+ mMagnificationController = new MagnificationController(mContext, this);
+ mMagnificationController.register();
+ mMagnificationController.setUserId(mCurrentUserId);
+ }
+ return mMagnificationController;
}
- return mMagnificationController;
}
/**
@@ -2625,6 +2708,149 @@
}
@Override
+ public float getMagnificationScale() {
+ synchronized (mLock) {
+ // We treat calls from a profile as if made by its parent as profiles
+ // share the accessibility state of the parent. The call below
+ // performs the current profile parent resolution.
+ final int resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(
+ UserHandle.USER_CURRENT);
+ if (resolvedUserId != mCurrentUserId) {
+ return 1.0f;
+ }
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return getMagnificationController().getScale();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public Region getMagnifiedRegion() {
+ synchronized (mLock) {
+ // We treat calls from a profile as if made by its parent as profiles
+ // share the accessibility state of the parent. The call below
+ // performs the current profile parent resolution.
+ final int resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(
+ UserHandle.USER_CURRENT);
+ if (resolvedUserId != mCurrentUserId) {
+ return Region.obtain();
+ }
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final Region region = Region.obtain();
+ getMagnificationController().getMagnifiedRegion(region);
+ return region;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public float getMagnificationCenterX() {
+ synchronized (mLock) {
+ // We treat calls from a profile as if made by its parent as profiles
+ // share the accessibility state of the parent. The call below
+ // performs the current profile parent resolution.
+ final int resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(
+ UserHandle.USER_CURRENT);
+ if (resolvedUserId != mCurrentUserId) {
+ return 0.0f;
+ }
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return getMagnificationController().getCenterX();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public float getMagnificationCenterY() {
+ synchronized (mLock) {
+ // We treat calls from a profile as if made by its parent as profiles
+ // share the accessibility state of the parent. The call below
+ // performs the current profile parent resolution.
+ final int resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(
+ UserHandle.USER_CURRENT);
+ if (resolvedUserId != mCurrentUserId) {
+ return 0.0f;
+ }
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return getMagnificationController().getCenterY();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public boolean resetMagnification(boolean animate) {
+ synchronized (mLock) {
+ // We treat calls from a profile as if made by its parent as profiles
+ // share the accessibility state of the parent. The call below
+ // performs the current profile parent resolution.
+ final int resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(
+ UserHandle.USER_CURRENT);
+ if (resolvedUserId != mCurrentUserId) {
+ return false;
+ }
+ final boolean permissionGranted = mSecurityPolicy.canControlMagnification(this);
+ if (!permissionGranted) {
+ return false;
+ }
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return getMagnificationController().reset(animate);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
+ boolean animate) {
+ synchronized (mLock) {
+ // We treat calls from a profile as if made by its parent as profiles
+ // share the accessibility state of the parent. The call below
+ // performs the current profile parent resolution.
+ final int resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(
+ UserHandle.USER_CURRENT);
+ if (resolvedUserId != mCurrentUserId) {
+ return false;
+ }
+ final boolean permissionGranted = mSecurityPolicy.canControlMagnification(this);
+ if (!permissionGranted) {
+ return false;
+ }
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return getMagnificationController().setScaleAndCenter(
+ scale, centerX, centerY, animate);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void setMagnificationCallbackEnabled(boolean enabled) {
+ mInvocationHandler.setMagnificationCallbackEnabled(enabled);
+ }
+
+ @Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP);
synchronized (mLock) {
@@ -2819,6 +3045,30 @@
InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
}
+ public void notifyMagnificationChanged(@NonNull Region region,
+ float scale, float centerX, float centerY) {
+ mInvocationHandler.notifyMagnificationChanged(region, scale, centerX, centerY);
+ }
+
+ /**
+ * Called by the invocation handler to notify the service that the
+ * state of magnification has changed.
+ */
+ private void notifyMagnificationChangedInternal(@NonNull Region region,
+ float scale, float centerX, float centerY) {
+ final IAccessibilityServiceClient listener;
+ synchronized (mLock) {
+ listener = mServiceInterface;
+ }
+ if (listener != null) {
+ try {
+ listener.onMagnificationChanged(region, scale, centerX, centerY);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
+ }
+ }
+ }
+
private void notifyGestureInternal(int gestureId) {
final IAccessibilityServiceClient listener;
synchronized (mLock) {
@@ -2959,6 +3209,10 @@
public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 3;
public static final int MSG_ON_KEY_EVENT_TIMEOUT = 4;
+ private static final int MSG_ON_MAGNIFICATION_CHANGED = 5;
+
+ private boolean mIsMagnificationCallbackEnabled = false;
+
public InvocationHandler(Looper looper) {
super(looper, null, true);
}
@@ -2987,11 +3241,41 @@
setOnKeyEventResult(false, eventState.sequence);
} break;
+ case MSG_ON_MAGNIFICATION_CHANGED: {
+ final SomeArgs args = (SomeArgs) message.obj;
+ final Region region = (Region) args.arg1;
+ final float scale = (float) args.arg2;
+ final float centerX = (float) args.arg3;
+ final float centerY = (float) args.arg4;
+ notifyMagnificationChangedInternal(region, scale, centerX, centerY);
+ } break;
+
default: {
throw new IllegalArgumentException("Unknown message: " + type);
}
}
}
+
+ public void notifyMagnificationChanged(@NonNull Region region, float scale,
+ float centerX, float centerY) {
+ if (!mIsMagnificationCallbackEnabled) {
+ // Callback is disabled, don't bother packing args.
+ return;
+ }
+
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = region;
+ args.arg2 = scale;
+ args.arg3 = centerX;
+ args.arg4 = centerY;
+
+ final Message msg = obtainMessage(MSG_ON_MAGNIFICATION_CHANGED, args);
+ msg.sendToTarget();
+ }
+
+ public void setMagnificationCallbackEnabled(boolean enabled) {
+ mIsMagnificationCallbackEnabled = enabled;
+ }
}
private final class KeyEventDispatcher {
@@ -3660,6 +3944,11 @@
& AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
}
+ public boolean canControlMagnification(Service service) {
+ return (service.mAccessibilityServiceInfo.getCapabilities()
+ & AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION) != 0;
+ }
+
private int resolveProfileParentLocked(int userId) {
if (userId != mCurrentUserId) {
final long identity = Binder.clearCallingIdentity();
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index 781d134..a093d92 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -17,20 +17,36 @@
package com.android.server.accessibility;
import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
import com.android.server.LocalServices;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.MathUtils;
import android.util.Property;
import android.util.Slog;
import android.view.MagnificationSpec;
+import android.view.View;
import android.view.WindowManagerInternal;
import android.view.animation.DecelerateInterpolator;
+import java.util.Locale;
+
/**
* This class is used to control and query the state of display magnification
* from the accessibility manager and related classes. It is responsible for
@@ -38,37 +54,71 @@
* communication between the accessibility manager and window manager.
*/
class MagnificationController {
- private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
+ private static final String LOG_TAG = "MagnificationController";
private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
- private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false;
- private static final String PROPERTY_NAME_MAGNIFICATION_SPEC = "magnificationSpec";
+ private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
- private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
+ private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
+
+ private static final float MIN_SCALE = 1.0f;
+ private static final float MAX_SCALE = 5.0f;
+
+ /**
+ * The minimum scaling factor that can be persisted to secure settings.
+ * This must be > 1.0 to ensure that magnification is actually set to an
+ * enabled state when the scaling factor is restored from settings.
+ */
+ private static final float MIN_PERSISTED_SCALE = 2.0f;
+
+ private final Object mLock = new Object();
+
+ /**
+ * The current magnification spec. If an animation is running, this
+ * reflects the end state.
+ */
private final MagnificationSpec mCurrentMagnificationSpec = MagnificationSpec.obtain();
- private final Region mMagnifiedBounds = new Region();
+ private final Region mMagnifiedRegion = Region.obtain();
+ private final Region mAvailableRegion = Region.obtain();
+ private final Rect mMagnifiedBounds = new Rect();
+
private final Rect mTempRect = new Rect();
+ private final Rect mTempRect1 = new Rect();
private final AccessibilityManagerService mAms;
- private final WindowManagerInternal mWindowManager;
- private final ValueAnimator mTransformationAnimator;
+ private final ContentResolver mContentResolver;
+
+ private final ScreenStateObserver mScreenStateObserver;
+ private final WindowStateObserver mWindowStateObserver;
+
+ private final SpecAnimationBridge mSpecAnimationBridge;
+
+ private int mUserId;
public MagnificationController(Context context, AccessibilityManagerService ams) {
mAms = ams;
- mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+ mContentResolver = context.getContentResolver();
+ mScreenStateObserver = new ScreenStateObserver(context, this);
+ mWindowStateObserver = new WindowStateObserver(context, this);
+ mSpecAnimationBridge = new SpecAnimationBridge(context);
+ }
- final Property<MagnificationController, MagnificationSpec> property =
- Property.of(MagnificationController.class, MagnificationSpec.class,
- PROPERTY_NAME_MAGNIFICATION_SPEC);
- final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator();
- final long animationDuration = context.getResources().getInteger(
- R.integer.config_longAnimTime);
- mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator,
- mSentMagnificationSpec, mCurrentMagnificationSpec);
- mTransformationAnimator.setDuration(animationDuration);
- mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
+ /**
+ * Registers magnification-related observers.
+ */
+ public void register() {
+ mScreenStateObserver.register();
+ mWindowStateObserver.register();
+ }
+
+ /**
+ * Unregisters magnification-related observers.
+ */
+ public void unregister() {
+ mScreenStateObserver.unregister();
+ mWindowStateObserver.unregister();
}
/**
@@ -80,26 +130,33 @@
}
/**
- * Sets the magnified region.
+ * Sets the magnified and available regions.
*
- * @param region the region to set
- * @param updateSpec {@code true} to update the scale and center based on
- * the region bounds, {@code false} to leave them as-is
+ * @param magnified the magnified region
+ * @param available the region available for magnification
+ * @param updateSpec {@code true} to update the scale and center based on
+ * the region bounds, {@code false} to leave them as-is
*/
- public void setMagnifiedRegion(Region region, boolean updateSpec) {
- mMagnifiedBounds.set(region);
+ public void setMagnifiedRegion(Region magnified, Region available, boolean updateSpec) {
+ synchronized (mLock) {
+ mMagnifiedRegion.set(magnified);
+ mMagnifiedRegion.getBounds(mMagnifiedBounds);
+ mAvailableRegion.set(available);
- if (updateSpec) {
- final Rect magnifiedFrame = mTempRect;
- region.getBounds(magnifiedFrame);
- final float scale = mSentMagnificationSpec.scale;
- final float offsetX = mSentMagnificationSpec.offsetX;
- final float offsetY = mSentMagnificationSpec.offsetY;
- final float centerX = (-offsetX + magnifiedFrame.width() / 2) / scale;
- final float centerY = (-offsetY + magnifiedFrame.height() / 2) / scale;
- setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, false);
- } else {
- mAms.onMagnificationStateChanged();
+ final MagnificationSpec sentSpec = mSpecAnimationBridge.mSentMagnificationSpec;
+ final float scale = sentSpec.scale;
+ final float offsetX = sentSpec.offsetX;
+ final float offsetY = sentSpec.offsetY;
+
+ // Compute the new center and update spec as needed.
+ final float centerX = (mMagnifiedBounds.width() / 2.0f - offsetX) / scale;
+ final float centerY = (mMagnifiedBounds.height() / 2.0f - offsetY) / scale;
+ if (updateSpec) {
+ setScaleAndCenter(scale, centerX, centerY, false);
+ } else {
+ mAms.onMagnificationStateChanged();
+ mAms.notifyMagnificationChanged(mMagnifiedRegion, scale, centerX, centerY);
+ }
}
}
@@ -113,18 +170,51 @@
* magnified region, or {@code false} otherwise
*/
public boolean magnifiedRegionContains(float x, float y) {
- return mMagnifiedBounds.contains((int) x, (int) y);
+ synchronized (mLock) {
+ return mMagnifiedRegion.contains((int) x, (int) y);
+ }
}
/**
- * Populates the specified rect with the bounds of the magnified
- * region.
+ * Returns whether the region available for magnification contains the
+ * specified screen-relative coordinates.
+ *
+ * @param x the screen-relative X coordinate to check
+ * @param y the screen-relative Y coordinate to check
+ * @return {@code true} if the coordinate is contained within the
+ * region available for magnification, or {@code false} otherwise
+ */
+ private boolean availableRegionContains(float x, float y) {
+ synchronized (mLock) {
+ return mAvailableRegion.contains((int) x, (int) y);
+ }
+ }
+
+ /**
+ * Populates the specified rect with the screen-relative bounds of the
+ * magnified region. If magnification is not enabled, the returned
+ * bounds will be empty.
*
* @param outBounds rect to populate with the bounds of the magnified
* region
*/
- public void getMagnifiedBounds(Rect outBounds) {
- mMagnifiedBounds.getBounds(outBounds);
+ public void getMagnifiedBounds(@NonNull Rect outBounds) {
+ synchronized (mLock) {
+ outBounds.set(mMagnifiedBounds);
+ }
+ }
+
+ /**
+ * Populates the specified region with the screen-relative magnified
+ * region. If magnification is not enabled, then the returned region
+ * will be empty.
+ *
+ * @param outRegion the region to populate
+ */
+ public void getMagnifiedRegion(@NonNull Region outRegion) {
+ synchronized (mLock) {
+ outRegion.set(mMagnifiedRegion);
+ }
}
/**
@@ -147,6 +237,19 @@
return mCurrentMagnificationSpec.offsetX;
}
+
+ /**
+ * Returns the screen-relative X coordinate of the center of the
+ * magnification viewport.
+ *
+ * @return the X coordinate
+ */
+ public float getCenterX() {
+ synchronized (mLock) {
+ return (mMagnifiedBounds.width() / 2.0f - getOffsetX()) / getScale();
+ }
+ }
+
/**
* Returns the Y offset of the magnification viewport. If an animation
* is in progress, this reflects the end state of the animation.
@@ -158,6 +261,18 @@
}
/**
+ * Returns the screen-relative Y coordinate of the center of the
+ * magnification viewport.
+ *
+ * @return the Y coordinate
+ */
+ public float getCenterY() {
+ synchronized (mLock) {
+ return (mMagnifiedBounds.height() / 2.0f - getOffsetY()) / getScale();
+ }
+ }
+
+ /**
* Returns the scale currently used by the window manager. If an
* animation is in progress, this reflects the current state of the
* animation.
@@ -165,7 +280,7 @@
* @return the scale currently used by the window manager
*/
public float getSentScale() {
- return mSentMagnificationSpec.scale;
+ return mSpecAnimationBridge.mSentMagnificationSpec.scale;
}
/**
@@ -176,7 +291,7 @@
* @return the X offset currently used by the window manager
*/
public float getSentOffsetX() {
- return mSentMagnificationSpec.offsetX;
+ return mSpecAnimationBridge.mSentMagnificationSpec.offsetX;
}
/**
@@ -187,7 +302,7 @@
* @return the Y offset currently used by the window manager
*/
public float getSentOffsetY() {
- return mSentMagnificationSpec.offsetY;
+ return mSpecAnimationBridge.mSentMagnificationSpec.offsetY;
}
/**
@@ -196,21 +311,24 @@
*
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
+ * @return {@code true} if the magnification spec changed, {@code false} if
+ * the spec did not change
*/
- public void reset(boolean animate) {
- if (mTransformationAnimator.isRunning()) {
- mTransformationAnimator.cancel();
+ public boolean reset(boolean animate) {
+ synchronized (mLock) {
+ return resetLocked(animate);
}
- mCurrentMagnificationSpec.clear();
- if (animate) {
- animateMagnificationSpec(mSentMagnificationSpec,
- mCurrentMagnificationSpec);
- } else {
- setMagnificationSpec(mCurrentMagnificationSpec);
+ }
+
+ private boolean resetLocked(boolean animate) {
+ final MagnificationSpec spec = mCurrentMagnificationSpec;
+ final boolean changed = !spec.isNop();
+ if (changed) {
+ spec.clear();
}
- final Rect bounds = mTempRect;
- bounds.setEmpty();
- mAms.onMagnificationStateChanged();
+
+ mSpecAnimationBridge.updateSentSpec(spec, animate);
+ return changed;
}
/**
@@ -219,23 +337,32 @@
* transition is immediate.
*
* @param scale the target scale, must be >= 1
+ * @param pivotX the screen-relative X coordinate around which to scale
+ * @param pivotY the screen-relative Y coordinate around which to scale
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
+ * @return {@code true} if the magnification spec changed, {@code false} if
+ * the spec did not change
*/
- public void setScale(float scale, float pivotX, float pivotY, boolean animate) {
- final Rect magnifiedFrame = mTempRect;
- mMagnifiedBounds.getBounds(magnifiedFrame);
- final MagnificationSpec spec = mCurrentMagnificationSpec;
- final float oldScale = spec.scale;
- final float oldCenterX = (-spec.offsetX + magnifiedFrame.width() / 2) / oldScale;
- final float oldCenterY = (-spec.offsetY + magnifiedFrame.height() / 2) / oldScale;
- final float normPivotX = (-spec.offsetX + pivotX) / oldScale;
- final float normPivotY = (-spec.offsetY + pivotY) / oldScale;
- final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
- final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
- final float centerX = normPivotX + offsetX;
- final float centerY = normPivotY + offsetY;
- setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate);
+ public boolean setScale(float scale, float pivotX, float pivotY, boolean animate) {
+ synchronized (mLock) {
+ // Constrain scale immediately for use in the pivot calculations.
+ scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+
+ final Rect viewport = mTempRect;
+ mMagnifiedRegion.getBounds(viewport);
+ final MagnificationSpec spec = mCurrentMagnificationSpec;
+ final float oldScale = spec.scale;
+ final float oldCenterX = (viewport.width() / 2.0f - spec.offsetX) / oldScale;
+ final float oldCenterY = (viewport.height() / 2.0f - spec.offsetY) / oldScale;
+ final float normPivotX = (pivotX - spec.offsetX) / oldScale;
+ final float normPivotY = (pivotY - spec.offsetY) / oldScale;
+ final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
+ final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
+ final float centerX = normPivotX + offsetX;
+ final float centerY = normPivotY + offsetY;
+ return setScaleAndCenterLocked(scale, centerX, centerY, animate);
+ }
}
/**
@@ -248,10 +375,13 @@
* center
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
+ * @return {@code true} if the magnification spec changed, {@code false} if
+ * the spec did not change
*/
- public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) {
- setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.scale, centerX, centerY,
- animate);
+ public boolean setCenter(float centerX, float centerY, boolean animate) {
+ synchronized (mLock) {
+ return setScaleAndCenterLocked(Float.NaN, centerX, centerY, animate);
+ }
}
/**
@@ -259,35 +389,27 @@
* animating the transition. If animation is disabled, the transition
* is immediate.
*
- * @param scale the target scale, must be >= 1
+ * @param scale the target scale, or {@link Float#NaN} to leave unchanged
* @param centerX the screen-relative X coordinate around which to
- * center and scale
+ * center and scale, or {@link Float#NaN} to leave unchanged
* @param centerY the screen-relative Y coordinate around which to
- * center and scale
+ * center and scale, or {@link Float#NaN} to leave unchanged
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
+ * @return {@code true} if the magnification spec changed, {@code false} if
+ * the spec did not change
*/
- public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY,
- boolean animate) {
- if (Float.compare(mCurrentMagnificationSpec.scale, scale) == 0
- && Float.compare(mCurrentMagnificationSpec.offsetX, centerX) == 0
- && Float.compare(mCurrentMagnificationSpec.offsetY, centerY) == 0) {
- return;
+ public boolean setScaleAndCenter(float scale, float centerX, float centerY, boolean animate) {
+ synchronized (mLock) {
+ return setScaleAndCenterLocked(scale, centerX, centerY, animate);
}
- if (mTransformationAnimator.isRunning()) {
- mTransformationAnimator.cancel();
- }
- if (DEBUG_MAGNIFICATION_CONTROLLER) {
- Slog.i(LOG_TAG, "scale: " + scale + " offsetX: " + centerX + " offsetY: " + centerY);
- }
- updateMagnificationSpec(scale, centerX, centerY);
- if (animate) {
- animateMagnificationSpec(mSentMagnificationSpec,
- mCurrentMagnificationSpec);
- } else {
- setMagnificationSpec(mCurrentMagnificationSpec);
- }
- mAms.onMagnificationStateChanged();
+ }
+
+ private boolean setScaleAndCenterLocked(
+ float scale, float centerX, float centerY, boolean animate) {
+ final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
+ mSpecAnimationBridge.updateSentSpec(mCurrentMagnificationSpec, animate);
+ return changed;
}
/**
@@ -297,75 +419,504 @@
* @param offsetY the amount in pixels to offset the Y center
*/
public void offsetMagnifiedRegionCenter(float offsetX, float offsetY) {
- final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
- mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
- getMinOffsetX()), 0);
- final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
- mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
- getMinOffsetY()), 0);
- setMagnificationSpec(mCurrentMagnificationSpec);
+ synchronized (mLock) {
+ final MagnificationSpec currSpec = mCurrentMagnificationSpec;
+ final float nonNormOffsetX = currSpec.offsetX - offsetX;
+ currSpec.offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
+ final float nonNormOffsetY = currSpec.offsetY - offsetY;
+ currSpec.offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
+ mSpecAnimationBridge.updateSentSpec(currSpec, false);
+ }
}
- private void updateMagnificationSpec(float scale, float magnifiedCenterX,
- float magnifiedCenterY) {
- final Rect magnifiedFrame = mTempRect;
- mMagnifiedBounds.getBounds(magnifiedFrame);
- mCurrentMagnificationSpec.scale = scale;
- final int viewportWidth = magnifiedFrame.width();
- final float nonNormOffsetX = viewportWidth / 2 - magnifiedCenterX * scale;
- mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
- getMinOffsetX()), 0);
- final int viewportHeight = magnifiedFrame.height();
- final float nonNormOffsetY = viewportHeight / 2 - magnifiedCenterY * scale;
- mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
- getMinOffsetY()), 0);
+ /**
+ * Persists the current magnification scale to the current user's settings.
+ */
+ public void persistScale() {
+ final float scale = mCurrentMagnificationSpec.scale;
+ final int userId = mUserId;
+
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ Settings.Secure.putFloatForUser(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, userId);
+ return null;
+ }
+ }.execute();
}
- private float getMinOffsetX() {
- final Rect magnifiedFrame = mTempRect;
- mMagnifiedBounds.getBounds(magnifiedFrame);
- final float viewportWidth = magnifiedFrame.width();
+ /**
+ * 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
+ */
+ public float getPersistedScale() {
+ return Settings.Secure.getFloatForUser(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+ DEFAULT_MAGNIFICATION_SCALE, mUserId);
+ }
+
+ /**
+ * Updates the current magnification spec.
+ *
+ * @param scale the magnification scale
+ * @param centerX the unscaled, screen-relative X coordinate of the center
+ * of the viewport, or {@link Float#NaN} to leave unchanged
+ * @param centerY the unscaled, screen-relative Y coordinate of the center
+ * of the viewport, or {@link Float#NaN} to leave unchanged
+ * @return {@code true} if the magnification spec changed or {@code false}
+ * otherwise
+ */
+ private boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY) {
+ if (!availableRegionContains(centerX, centerY)) {
+ return false;
+ }
+
+ boolean changed = false;
+
+ final MagnificationSpec currSpec = mCurrentMagnificationSpec;
+
+ // Handle scale.
+ if (Float.isNaN(scale)) {
+ scale = getScale();
+ }
+
+ final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+ if (Float.compare(currSpec.scale, normScale) != 0) {
+ currSpec.scale = normScale;
+ changed = true;
+ }
+
+ // Handle X offset.
+ if (Float.isNaN(centerX)) {
+ centerX = getCenterX();
+ }
+
+ final float nonNormOffsetX = mMagnifiedBounds.width() / 2.0f - centerX * scale;
+ final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
+ if (Float.compare(currSpec.offsetX, offsetX) != 0) {
+ currSpec.offsetX = offsetX;
+ changed = true;
+ }
+
+ // Handle Y offset.
+ if (Float.isNaN(centerY)) {
+ centerY = getCenterY();
+ }
+
+ final float nonNormOffsetY = mMagnifiedBounds.height() / 2.0f - centerY * scale;
+ final float offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
+ if (Float.compare(currSpec.offsetY, offsetY) != 0) {
+ currSpec.offsetY = offsetY;
+ changed = true;
+ }
+
+ return changed;
+ }
+
+ private float getMinOffsetXLocked() {
+ final float viewportWidth = mMagnifiedBounds.width();
return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale;
}
- private float getMinOffsetY() {
- final Rect magnifiedFrame = mTempRect;
- mMagnifiedBounds.getBounds(magnifiedFrame);
- final float viewportHeight = magnifiedFrame.height();
+ private float getMinOffsetYLocked() {
+ final float viewportHeight = mMagnifiedBounds.height();
return viewportHeight - viewportHeight * mCurrentMagnificationSpec.scale;
}
- private void animateMagnificationSpec(MagnificationSpec fromSpec,
- MagnificationSpec toSpec) {
- mTransformationAnimator.setObjectValues(fromSpec, toSpec);
- mTransformationAnimator.start();
- }
+ /**
+ * Sets the currently active user ID.
+ *
+ * @param userId the currently active user ID
+ */
+ public void setUserId(int userId) {
+ if (mUserId != userId) {
+ mUserId = userId;
- public void setMagnificationSpec(MagnificationSpec spec) {
- if (DEBUG_SET_MAGNIFICATION_SPEC) {
- Slog.i(LOG_TAG, "Sending: " + spec);
+ synchronized (mLock) {
+ if (isMagnifying()) {
+ reset(false);
+ }
+ }
}
- mSentMagnificationSpec.scale = spec.scale;
- mSentMagnificationSpec.offsetX = spec.offsetX;
- mSentMagnificationSpec.offsetY = spec.offsetY;
- mWindowManager.setMagnificationSpec(MagnificationSpec.obtain(spec));
}
- public MagnificationSpec getMagnificationSpec() {
- return mSentMagnificationSpec;
+ private boolean isScreenMagnificationAutoUpdateEnabled() {
+ return (Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
+ DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
}
- private static class MagnificationSpecEvaluator implements TypeEvaluator<MagnificationSpec> {
- private final MagnificationSpec mTempTransformationSpec = MagnificationSpec.obtain();
+ /**
+ * Resets magnification if magnification and auto-update are both enabled.
+ *
+ * @param animate whether the animate the transition
+ * @return {@code true} if magnification was reset to the disabled state,
+ * {@code false} if magnification is still active
+ */
+ boolean resetIfNeeded(boolean animate) {
+ synchronized (mLock) {
+ if (isMagnifying() && isScreenMagnificationAutoUpdateEnabled()) {
+ reset(animate);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private void getMagnifiedFrameInContentCoordsLocked(Rect outFrame) {
+ final float scale = getSentScale();
+ final float offsetX = getSentOffsetX();
+ final float offsetY = getSentOffsetY();
+ getMagnifiedBounds(outFrame);
+ outFrame.offset((int) -offsetX, (int) -offsetY);
+ outFrame.scale(1.0f / scale);
+ }
+
+ private void requestRectangleOnScreen(int left, int top, int right, int bottom) {
+ synchronized (mLock) {
+ final Rect magnifiedFrame = mTempRect;
+ getMagnifiedBounds(magnifiedFrame);
+ if (!magnifiedFrame.intersects(left, top, right, bottom)) {
+ return;
+ }
+
+ final Rect magnifFrameInScreenCoords = mTempRect1;
+ getMagnifiedFrameInContentCoordsLocked(magnifFrameInScreenCoords);
+
+ final float scrollX;
+ final float scrollY;
+ if (right - left > magnifFrameInScreenCoords.width()) {
+ final int direction = TextUtils
+ .getLayoutDirectionFromLocale(Locale.getDefault());
+ if (direction == View.LAYOUT_DIRECTION_LTR) {
+ scrollX = left - magnifFrameInScreenCoords.left;
+ } else {
+ scrollX = right - magnifFrameInScreenCoords.right;
+ }
+ } else if (left < magnifFrameInScreenCoords.left) {
+ scrollX = left - magnifFrameInScreenCoords.left;
+ } else if (right > magnifFrameInScreenCoords.right) {
+ scrollX = right - magnifFrameInScreenCoords.right;
+ } else {
+ scrollX = 0;
+ }
+
+ if (bottom - top > magnifFrameInScreenCoords.height()) {
+ scrollY = top - magnifFrameInScreenCoords.top;
+ } else if (top < magnifFrameInScreenCoords.top) {
+ scrollY = top - magnifFrameInScreenCoords.top;
+ } else if (bottom > magnifFrameInScreenCoords.bottom) {
+ scrollY = bottom - magnifFrameInScreenCoords.bottom;
+ } else {
+ scrollY = 0;
+ }
+
+ final float scale = getScale();
+ offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale);
+ }
+ }
+
+ /**
+ * Class responsible for animating spec on the main thread and sending spec
+ * updates to the window manager.
+ */
+ private static class SpecAnimationBridge {
+ private static final int ACTION_UPDATE_SPEC = 1;
+
+ private final Handler mHandler;
+ private final WindowManagerInternal mWindowManager;
+
+ /**
+ * The magnification spec that was sent to the window manager. This should
+ * only be accessed and modified on the main (e.g. animation) thread.
+ */
+ private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
+
+ /**
+ * The animator that updates the sent spec. This should only be accessed
+ * and modified on the main (e.g. animation) thread.
+ */
+ private final ValueAnimator mTransformationAnimator;
+
+ private final long mMainThreadId;
+
+ private SpecAnimationBridge(Context context) {
+ final Looper mainLooper = context.getMainLooper();
+ mMainThreadId = mainLooper.getThread().getId();
+
+ mHandler = new UpdateHandler(context);
+ mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+
+ final MagnificationSpecProperty property = new MagnificationSpecProperty();
+ final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator();
+ final long animationDuration = context.getResources().getInteger(
+ R.integer.config_longAnimTime);
+ mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator,
+ mSentMagnificationSpec);
+ mTransformationAnimator.setDuration(animationDuration);
+ mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
+ }
+
+ public void updateSentSpec(MagnificationSpec spec, boolean animate) {
+ if (Thread.currentThread().getId() == mMainThreadId) {
+ // Already on the main thread, don't bother proxying.
+ updateSentSpecInternal(spec, animate);
+ } else {
+ mHandler.obtainMessage(ACTION_UPDATE_SPEC,
+ animate ? 1 : 0, 0, spec).sendToTarget();
+ }
+ }
+
+ /**
+ * Updates the sent spec.
+ */
+ private void updateSentSpecInternal(MagnificationSpec spec, boolean animate) {
+ if (mTransformationAnimator.isRunning()) {
+ mTransformationAnimator.cancel();
+ }
+
+ // If the current and sent specs don't match, update the sent spec.
+ final boolean changed = !mSentMagnificationSpec.equals(spec);
+ if (changed) {
+ if (animate) {
+ animateMagnificationSpec(spec);
+ } else {
+ setMagnificationSpec(spec);
+ }
+ }
+ }
+
+ private void animateMagnificationSpec(MagnificationSpec toSpec) {
+ mTransformationAnimator.setObjectValues(mSentMagnificationSpec, toSpec);
+ mTransformationAnimator.start();
+ }
+
+ private void setMagnificationSpec(MagnificationSpec spec) {
+ if (DEBUG_SET_MAGNIFICATION_SPEC) {
+ Slog.i(LOG_TAG, "Sending: " + spec);
+ }
+
+ mSentMagnificationSpec.setTo(spec);
+ mWindowManager.setMagnificationSpec(spec);
+ }
+
+ private class UpdateHandler extends Handler {
+ public UpdateHandler(Context context) {
+ super(context.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case ACTION_UPDATE_SPEC:
+ final boolean animate = msg.arg1 == 1;
+ final MagnificationSpec spec = (MagnificationSpec) msg.obj;
+ updateSentSpecInternal(spec, animate);
+ break;
+ }
+ }
+ }
+
+ private static class MagnificationSpecProperty
+ extends Property<SpecAnimationBridge, MagnificationSpec> {
+ public MagnificationSpecProperty() {
+ super(MagnificationSpec.class, "spec");
+ }
+
+ @Override
+ public MagnificationSpec get(SpecAnimationBridge object) {
+ return object.mSentMagnificationSpec;
+ }
+
+ @Override
+ public void set(SpecAnimationBridge object, MagnificationSpec value) {
+ object.setMagnificationSpec(value);
+ }
+ }
+
+ private static class MagnificationSpecEvaluator
+ implements TypeEvaluator<MagnificationSpec> {
+ private final MagnificationSpec mTempSpec = MagnificationSpec.obtain();
+
+ @Override
+ public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
+ MagnificationSpec toSpec) {
+ final MagnificationSpec result = mTempSpec;
+ result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction;
+ result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction;
+ result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction;
+ return result;
+ }
+ }
+ }
+
+ private static class ScreenStateObserver extends BroadcastReceiver {
+ private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
+
+ private final Context mContext;
+ private final MagnificationController mController;
+ private final Handler mHandler;
+
+ public ScreenStateObserver(Context context, MagnificationController controller) {
+ mContext = context;
+ mController = controller;
+ mHandler = new StateChangeHandler(context);
+ }
+
+ public void register() {
+ mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+ }
+
+ public void unregister() {
+ mContext.unregisterReceiver(this);
+ }
@Override
- public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
- MagnificationSpec toSpec) {
- final MagnificationSpec result = mTempTransformationSpec;
- result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction;
- result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction;
- result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction;
- return result;
+ public void onReceive(Context context, Intent intent) {
+ mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
+ intent.getAction()).sendToTarget();
+ }
+
+ private void handleOnScreenStateChange() {
+ mController.resetIfNeeded(false);
+ }
+
+ private class StateChangeHandler extends Handler {
+ public StateChangeHandler(Context context) {
+ super(context.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MESSAGE_ON_SCREEN_STATE_CHANGE:
+ handleOnScreenStateChange();
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * This class handles the screen magnification when accessibility is enabled.
+ */
+ private static class WindowStateObserver
+ implements WindowManagerInternal.MagnificationCallbacks {
+ private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1;
+ private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2;
+ private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3;
+ private static final int MESSAGE_ON_ROTATION_CHANGED = 4;
+
+ private final Rect mTempRect = new Rect();
+ private final Rect mTempRect1 = new Rect();
+
+ private final MagnificationController mController;
+ private final WindowManagerInternal mWindowManager;
+ private final Handler mHandler;
+
+ private boolean mSpecIsDirty;
+
+ public WindowStateObserver(Context context, MagnificationController controller) {
+ mController = controller;
+ mWindowManager = LocalServices.getService(WindowManagerInternal.class);
+ mHandler = new CallbackHandler(context);
+ }
+
+ public void register() {
+ mWindowManager.setMagnificationCallbacks(this);
+ }
+
+ public void unregister() {
+ mWindowManager.setMagnificationCallbacks(null);
+ }
+
+ @Override
+ public void onMagnifiedBoundsChanged(Region magnified, Region available) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = Region.obtain(magnified);
+ args.arg2 = Region.obtain(available);
+ mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget();
+ }
+
+ private void handleOnMagnifiedBoundsChanged(Region magnified, Region available) {
+ mController.setMagnifiedRegion(magnified, available, mSpecIsDirty);
+ mSpecIsDirty = false;
+ }
+
+ @Override
+ public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.argi1 = left;
+ args.argi2 = top;
+ args.argi3 = right;
+ args.argi4 = bottom;
+ mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget();
+ }
+
+ private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) {
+ mController.requestRectangleOnScreen(left, top, right, bottom);
+ }
+
+ @Override
+ public void onRotationChanged(int rotation) {
+ mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget();
+ }
+
+ private void handleOnRotationChanged() {
+ // If there was a rotation and magnification is still enabled,
+ // we'll need to rewrite the spec to reflect the new screen
+ // configuration. Conveniently, we'll receive a callback from
+ // the window manager with updated bounds for the magnified
+ // region.
+ mSpecIsDirty = !mController.resetIfNeeded(true);
+ }
+
+ @Override
+ public void onUserContextChanged() {
+ mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED);
+ }
+
+ private void handleOnUserContextChanged() {
+ mController.resetIfNeeded(true);
+ }
+
+ private class CallbackHandler extends Handler {
+ public CallbackHandler(Context context) {
+ super(context.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: {
+ final SomeArgs args = (SomeArgs) message.obj;
+ final Region magnifiedBounds = (Region) args.arg1;
+ final Region availableBounds = (Region) args.arg2;
+ handleOnMagnifiedBoundsChanged(magnifiedBounds, availableBounds);
+ magnifiedBounds.recycle();
+ availableBounds.recycle();
+ } break;
+ case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
+ final SomeArgs args = (SomeArgs) message.obj;
+ final int left = args.argi1;
+ final int top = args.argi2;
+ final int right = args.argi3;
+ final int bottom = args.argi4;
+ handleOnRectangleOnScreenRequested(left, top, right, bottom);
+ args.recycle();
+ } break;
+ case MESSAGE_ON_USER_CONTEXT_CHANGED: {
+ handleOnUserContextChanged();
+ } break;
+ case MESSAGE_ON_ROTATION_CHANGED: {
+ handleOnRotationChanged();
+ } break;
+ }
+ }
}
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
similarity index 65%
rename from services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
rename to services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 8feb167..51c8ab5 100644
--- a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2015 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.
@@ -16,18 +16,10 @@
package com.android.server.accessibility;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.AsyncTask;
-import android.os.Binder;
import android.os.Handler;
import android.os.Message;
-import android.provider.Settings;
-import android.text.TextUtils;
+import android.util.MathUtils;
import android.util.Slog;
import android.util.TypedValue;
import android.view.GestureDetector;
@@ -39,18 +31,12 @@
import android.view.MotionEvent.PointerProperties;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
-import android.view.View;
import android.view.ViewConfiguration;
-import android.view.WindowManagerInternal;
import android.view.accessibility.AccessibilityEvent;
-import com.android.internal.os.SomeArgs;
-import com.android.server.LocalServices;
-
-import java.util.Locale;
-
/**
- * This class handles the screen magnification when accessibility is enabled.
+ * This class handles magnification in response to touch events.
+ *
* The behavior is as follows:
*
* 1. Triple tap toggles permanent screen magnification which is magnifying
@@ -88,10 +74,8 @@
*
* 6. The magnification scale will be persisted in settings and in the cloud.
*/
-public final class ScreenMagnifier implements WindowManagerInternal.MagnificationCallbacks,
- EventStreamTransformation {
-
- private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
+class MagnificationGestureHandler implements EventStreamTransformation {
+ private static final String LOG_TAG = "MagnificationEventHandler";
private static final boolean DEBUG_STATE_TRANSITIONS = false;
private static final boolean DEBUG_DETECTING = false;
@@ -103,40 +87,19 @@
private static final int STATE_VIEWPORT_DRAGGING = 3;
private static final int STATE_MAGNIFIED_INTERACTION = 4;
- private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
+ private static final float MIN_SCALE = 2.0f;
+ private static final float MAX_SCALE = 5.0f;
- private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1;
- private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2;
- private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3;
- private static final int MESSAGE_ON_ROTATION_CHANGED = 4;
-
- private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
-
- private static final int MY_PID = android.os.Process.myPid();
-
- private final Rect mTempRect = new Rect();
- private final Rect mTempRect1 = new Rect();
-
- private final Context mContext;
- private final WindowManagerInternal mWindowManager;
private final MagnificationController mMagnificationController;
- private final ScreenStateObserver mScreenStateObserver;
-
private final DetectingStateHandler mDetectingStateHandler;
- private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler;
+ private final MagnifiedContentInteractionStateHandler mMagnifiedContentInteractionStateHandler;
private final StateViewportDraggingHandler mStateViewportDraggingHandler;
- private final int mUserId;
-
- private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout();
- private final int mMultiTapTimeSlop;
- private final int mTapDistanceSlop;
- private final int mMultiTapDistanceSlop;
-
private EventStreamTransformation mNext;
private int mCurrentState;
private int mPreviousState;
+
private boolean mTranslationEnabledBeforePan;
private PointerCoords[] mTempPointerCoords;
@@ -144,189 +107,44 @@
private long mDelegatingStateDownTime;
- private boolean mUpdateMagnificationSpecOnNextBoundsChange;
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: {
- Region bounds = (Region) message.obj;
- handleOnMagnifiedBoundsChanged(bounds);
- bounds.recycle();
- } break;
- case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
- SomeArgs args = (SomeArgs) message.obj;
- final int left = args.argi1;
- final int top = args.argi2;
- final int right = args.argi3;
- final int bottom = args.argi4;
- handleOnRectangleOnScreenRequested(left, top, right, bottom);
- args.recycle();
- } break;
- case MESSAGE_ON_USER_CONTEXT_CHANGED: {
- handleOnUserContextChanged();
- } break;
- case MESSAGE_ON_ROTATION_CHANGED: {
- final int rotation = message.arg1;
- handleOnRotationChanged(rotation);
- } break;
- }
- }
- };
-
- public ScreenMagnifier(Context context, int userId, int displayId,
- AccessibilityManagerService service) {
- mContext = context;
- mUserId = userId;
- mWindowManager = LocalServices.getService(WindowManagerInternal.class);
-
- mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout()
- + mContext.getResources().getInteger(
- com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment);
- mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
- mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
-
- mDetectingStateHandler = new DetectingStateHandler();
+ public MagnificationGestureHandler(Context context, AccessibilityManagerService ams) {
+ mMagnificationController = ams.getMagnificationController();
+ mDetectingStateHandler = new DetectingStateHandler(context);
mStateViewportDraggingHandler = new StateViewportDraggingHandler();
- mMagnifiedContentInteractonStateHandler = new MagnifiedContentInteractonStateHandler(
- context);
-
- mMagnificationController = service.getMagnificationController();
- mScreenStateObserver = new ScreenStateObserver(context, mMagnificationController);
-
- mWindowManager.setMagnificationCallbacks(this);
+ mMagnifiedContentInteractionStateHandler =
+ new MagnifiedContentInteractionStateHandler(context);
transitionToState(STATE_DETECTING);
}
@Override
- public void onMagnifedBoundsChanged(Region bounds) {
- Region newBounds = Region.obtain(bounds);
- mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, newBounds).sendToTarget();
- if (MY_PID != Binder.getCallingPid()) {
- bounds.recycle();
- }
- }
-
- private void handleOnMagnifiedBoundsChanged(Region bounds) {
- // If there was a rotation we have to update the center of the magnified
- // region since the old offset X/Y may be out of its acceptable range for
- // the new display width and height.
- mMagnificationController.setMagnifiedRegion(
- bounds, mUpdateMagnificationSpecOnNextBoundsChange);
- mUpdateMagnificationSpecOnNextBoundsChange = false;
- }
-
- @Override
- public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = left;
- args.argi2 = top;
- args.argi3 = right;
- args.argi4 = bottom;
- mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget();
- }
-
- private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) {
- Rect magnifiedFrame = mTempRect;
- mMagnificationController.getMagnifiedBounds(magnifiedFrame);
- if (!magnifiedFrame.intersects(left, top, right, bottom)) {
- return;
- }
- Rect magnifFrameInScreenCoords = mTempRect1;
- getMagnifiedFrameInContentCoords(magnifFrameInScreenCoords);
- final float scrollX;
- final float scrollY;
- if (right - left > magnifFrameInScreenCoords.width()) {
- final int direction = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
- if (direction == View.LAYOUT_DIRECTION_LTR) {
- scrollX = left - magnifFrameInScreenCoords.left;
- } else {
- scrollX = right - magnifFrameInScreenCoords.right;
- }
- } else if (left < magnifFrameInScreenCoords.left) {
- scrollX = left - magnifFrameInScreenCoords.left;
- } else if (right > magnifFrameInScreenCoords.right) {
- scrollX = right - magnifFrameInScreenCoords.right;
- } else {
- scrollX = 0;
- }
- if (bottom - top > magnifFrameInScreenCoords.height()) {
- scrollY = top - magnifFrameInScreenCoords.top;
- } else if (top < magnifFrameInScreenCoords.top) {
- scrollY = top - magnifFrameInScreenCoords.top;
- } else if (bottom > magnifFrameInScreenCoords.bottom) {
- scrollY = bottom - magnifFrameInScreenCoords.bottom;
- } else {
- scrollY = 0;
- }
- final float scale = mMagnificationController.getScale();
- mMagnificationController.offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale);
- }
-
- @Override
- public void onRotationChanged(int rotation) {
- mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget();
- }
-
- private void handleOnRotationChanged(int rotation) {
- resetMagnificationIfNeeded();
- if (mMagnificationController.isMagnifying()) {
- mUpdateMagnificationSpecOnNextBoundsChange = true;
- }
- }
-
- @Override
- public void onUserContextChanged() {
- mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED);
- }
-
- private void handleOnUserContextChanged() {
- resetMagnificationIfNeeded();
- }
-
- private void getMagnifiedFrameInContentCoords(Rect rect) {
- final float scale = mMagnificationController.getSentScale();
- final float offsetX = mMagnificationController.getSentOffsetX();
- final float offsetY = mMagnificationController.getSentOffsetY();
- mMagnificationController.getMagnifiedBounds(rect);
- rect.offset((int) -offsetX, (int) -offsetY);
- rect.scale(1.0f / scale);
- }
-
- private void resetMagnificationIfNeeded() {
- if (mMagnificationController.isMagnifying()
- && isScreenMagnificationAutoUpdateEnabled(mContext)) {
- mMagnificationController.reset(true);
- }
- }
-
- @Override
- public void onMotionEvent(MotionEvent event, MotionEvent rawEvent,
- int policyFlags) {
+ public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
if (mNext != null) {
mNext.onMotionEvent(event, rawEvent, policyFlags);
}
return;
}
- mMagnifiedContentInteractonStateHandler.onMotionEvent(event);
+ mMagnifiedContentInteractionStateHandler.onMotionEvent(event, rawEvent, policyFlags);
switch (mCurrentState) {
case STATE_DELEGATING: {
handleMotionEventStateDelegating(event, rawEvent, policyFlags);
- } break;
+ }
+ break;
case STATE_DETECTING: {
mDetectingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
- } break;
+ }
+ break;
case STATE_VIEWPORT_DRAGGING: {
- mStateViewportDraggingHandler.onMotionEvent(event, policyFlags);
- } break;
+ mStateViewportDraggingHandler.onMotionEvent(event, rawEvent, policyFlags);
+ }
+ break;
case STATE_MAGNIFIED_INTERACTION: {
// mMagnifiedContentInteractonStateHandler handles events only
// if this is the current state since it uses ScaleGestureDetecotr
// and a GestureDetector which need well formed event stream.
- } break;
+ }
+ break;
default: {
throw new IllegalStateException("Unknown state: " + mCurrentState);
}
@@ -336,7 +154,7 @@
@Override
public void onKeyEvent(KeyEvent event, int policyFlags) {
if (mNext != null) {
- mNext.onKeyEvent(event, policyFlags);
+ mNext.onKeyEvent(event, policyFlags);
}
}
@@ -366,15 +184,13 @@
@Override
public void onDestroy() {
clear();
- mScreenStateObserver.destroy();
- mWindowManager.setMagnificationCallbacks(null);
}
private void clear() {
mCurrentState = STATE_DETECTING;
mDetectingStateHandler.clear();
mStateViewportDraggingHandler.clear();
- mMagnifiedContentInteractonStateHandler.clear();
+ mMagnifiedContentInteractionStateHandler.clear();
}
private void handleMotionEventStateDelegating(MotionEvent event,
@@ -382,12 +198,14 @@
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mDelegatingStateDownTime = event.getDownTime();
- } break;
+ }
+ break;
case MotionEvent.ACTION_UP: {
if (mDetectingStateHandler.mDelayedEventQueue == null) {
transitionToState(STATE_DETECTING);
}
- } break;
+ }
+ break;
}
if (mNext != null) {
// If the event is within the magnified portion of the screen we have
@@ -402,7 +220,8 @@
final float scaledOffsetY = mMagnificationController.getOffsetY();
final int pointerCount = event.getPointerCount();
PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount);
- PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount);
+ PointerProperties[] properties = getTempPointerPropertiesWithMinSize(
+ pointerCount);
for (int i = 0; i < pointerCount; i++) {
event.getPointerCoords(i, coords[i]);
coords[i].x = (coords[i].x - scaledOffsetX) / scale;
@@ -441,12 +260,14 @@
}
private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) {
- final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length : 0;
+ final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length
+ : 0;
if (oldSize < size) {
PointerProperties[] oldTempPointerProperties = mTempPointerProperties;
mTempPointerProperties = new PointerProperties[size];
if (oldTempPointerProperties != null) {
- System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, oldSize);
+ System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0,
+ oldSize);
}
}
for (int i = oldSize; i < size; i++) {
@@ -460,16 +281,20 @@
switch (state) {
case STATE_DELEGATING: {
Slog.i(LOG_TAG, "mCurrentState: STATE_DELEGATING");
- } break;
+ }
+ break;
case STATE_DETECTING: {
Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING");
- } break;
+ }
+ break;
case STATE_VIEWPORT_DRAGGING: {
Slog.i(LOG_TAG, "mCurrentState: STATE_VIEWPORT_DRAGGING");
- } break;
+ }
+ break;
case STATE_MAGNIFIED_INTERACTION: {
Slog.i(LOG_TAG, "mCurrentState: STATE_MAGNIFIED_INTERACTION");
- } break;
+ }
+ break;
default: {
throw new IllegalArgumentException("Unknown state: " + state);
}
@@ -479,20 +304,30 @@
mCurrentState = state;
}
- private final class MagnifiedContentInteractonStateHandler
- extends SimpleOnGestureListener implements OnScaleGestureListener {
- private static final float MIN_SCALE = 1.3f;
- private static final float MAX_SCALE = 5.0f;
+ private interface MotionEventHandler {
+
+ void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+
+ void clear();
+ }
+
+ /**
+ * This class determines if the user is performing a scale or pan gesture.
+ */
+ private final class MagnifiedContentInteractionStateHandler extends SimpleOnGestureListener
+ implements OnScaleGestureListener, MotionEventHandler {
private final ScaleGestureDetector mScaleGestureDetector;
+
private final GestureDetector mGestureDetector;
private final float mScalingThreshold;
private float mInitialScaleFactor = -1;
+
private boolean mScaling;
- public MagnifiedContentInteractonStateHandler(Context context) {
+ public MagnifiedContentInteractionStateHandler(Context context) {
final TypedValue scaleValue = new TypedValue();
context.getResources().getValue(
com.android.internal.R.dimen.config_screen_magnification_scaling_threshold,
@@ -503,7 +338,8 @@
mGestureDetector = new GestureDetector(context, this);
}
- public void onMotionEvent(MotionEvent event) {
+ @Override
+ public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
mScaleGestureDetector.onTouchEvent(event);
mGestureDetector.onTouchEvent(event);
if (mCurrentState != STATE_MAGNIFIED_INTERACTION) {
@@ -511,11 +347,7 @@
}
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
clear();
- final float scale = Math.min(Math.max(mMagnificationController.getScale(),
- MIN_SCALE), MAX_SCALE);
- if (scale != getPersistedScale()) {
- persistScale(scale);
- }
+ mMagnificationController.persistScale();
if (mPreviousState == STATE_VIEWPORT_DRAGGING) {
transitionToState(STATE_VIEWPORT_DRAGGING);
} else {
@@ -552,14 +384,29 @@
}
return false;
}
- final float newScale = mMagnificationController.getScale()
- * detector.getScaleFactor();
- final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE), MAX_SCALE);
- if (DEBUG_SCALING) {
- Slog.i(LOG_TAG, "normalizedNewScale: " + normalizedNewScale);
+
+ final float initialScale = mMagnificationController.getScale();
+ final float targetScale = initialScale * detector.getScaleFactor();
+
+ // Don't allow a gesture to move the user further outside the
+ // desired bounds for gesture-controlled scaling.
+ final float scale;
+ if (targetScale > MAX_SCALE && targetScale > initialScale) {
+ // The target scale is too big and getting bigger.
+ scale = MAX_SCALE;
+ } else if (targetScale < MIN_SCALE && targetScale < initialScale) {
+ // The target scale is too small and getting smaller.
+ scale = MIN_SCALE;
+ } else {
+ // The target scale may be outside our bounds, but at least
+ // it's moving in the right direction. This avoids a "jump" if
+ // we're at odds with some other service's desired bounds.
+ scale = targetScale;
}
- mMagnificationController.setScale(normalizedNewScale, detector.getFocusX(),
- detector.getFocusY(), false);
+
+ final float pivotX = detector.getFocusX();
+ final float pivotY = detector.getFocusY();
+ mMagnificationController.setScale(scale, pivotX, pivotY, false);
return true;
}
@@ -573,16 +420,24 @@
clear();
}
- private void clear() {
+ @Override
+ public void clear() {
mInitialScaleFactor = -1;
mScaling = false;
}
}
- private final class StateViewportDraggingHandler {
+ /**
+ * This class handles motion events when the event dispatcher has
+ * determined that the user is performing a single-finger drag of the
+ * magnification viewport.
+ */
+ private final class StateViewportDraggingHandler implements MotionEventHandler {
+
private boolean mLastMoveOutsideMagnifiedRegion;
- private void onMotionEvent(MotionEvent event, int policyFlags) {
+ @Override
+ public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
final int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
@@ -591,7 +446,8 @@
case MotionEvent.ACTION_POINTER_DOWN: {
clear();
transitionToState(STATE_MAGNIFIED_INTERACTION);
- } break;
+ }
+ break;
case MotionEvent.ACTION_MOVE: {
if (event.getPointerCount() != 1) {
throw new IllegalStateException("Should have one pointer down.");
@@ -601,35 +457,43 @@
if (mMagnificationController.magnifiedRegionContains(eventX, eventY)) {
if (mLastMoveOutsideMagnifiedRegion) {
mLastMoveOutsideMagnifiedRegion = false;
- mMagnificationController.setMagnifiedRegionCenter(eventX,
+ mMagnificationController.setCenter(eventX,
eventY, true);
} else {
- mMagnificationController.setMagnifiedRegionCenter(eventX,
+ mMagnificationController.setCenter(eventX,
eventY, false);
}
} else {
mLastMoveOutsideMagnifiedRegion = true;
}
- } break;
+ }
+ break;
case MotionEvent.ACTION_UP: {
if (!mTranslationEnabledBeforePan) {
mMagnificationController.reset(true);
}
clear();
transitionToState(STATE_DETECTING);
- } break;
+ }
+ break;
case MotionEvent.ACTION_POINTER_UP: {
- throw new IllegalArgumentException("Unexpected event type: ACTION_POINTER_UP");
+ throw new IllegalArgumentException(
+ "Unexpected event type: ACTION_POINTER_UP");
}
}
}
+ @Override
public void clear() {
mLastMoveOutsideMagnifiedRegion = false;
}
}
- private final class DetectingStateHandler {
+ /**
+ * This class handles motion events when the event dispatch has not yet
+ * determined what the user is doing. It watches for various tap events.
+ */
+ private final class DetectingStateHandler implements MotionEventHandler {
private static final int MESSAGE_ON_ACTION_TAP_AND_HOLD = 1;
@@ -637,12 +501,30 @@
private static final int ACTION_TAP_COUNT = 3;
+ private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout();
+
+ private final int mMultiTapTimeSlop;
+
+ private final int mTapDistanceSlop;
+
+ private final int mMultiTapDistanceSlop;
+
private MotionEventInfo mDelayedEventQueue;
private MotionEvent mLastDownEvent;
+
private MotionEvent mLastTapUpEvent;
+
private int mTapCount;
+ public DetectingStateHandler(Context context) {
+ mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout()
+ + context.getResources().getInteger(
+ com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment);
+ mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+ }
+
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message message) {
@@ -652,12 +534,14 @@
MotionEvent event = (MotionEvent) message.obj;
final int policyFlags = message.arg1;
onActionTapAndHold(event, policyFlags);
- } break;
+ }
+ break;
case MESSAGE_TRANSITION_TO_DELEGATING_STATE: {
transitionToState(STATE_DELEGATING);
sendDelayedMotionEvents();
clear();
- } break;
+ }
+ break;
default: {
throw new IllegalArgumentException("Unknown message type: " + type);
}
@@ -665,6 +549,7 @@
}
};
+ @Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
cacheDelayedMotionEvent(event, rawEvent, policyFlags);
final int action = event.getActionMasked();
@@ -678,7 +563,7 @@
}
if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null
&& GestureUtils.isMultiTap(mLastDownEvent, event,
- mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
+ mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD,
policyFlags, 0, event);
mHandler.sendMessageDelayed(message,
@@ -690,7 +575,8 @@
}
clearLastDownEvent();
mLastDownEvent = MotionEvent.obtain(event);
- } break;
+ }
+ break;
case MotionEvent.ACTION_POINTER_DOWN: {
if (mMagnificationController.isMagnifying()) {
transitionToState(STATE_MAGNIFIED_INTERACTION);
@@ -698,7 +584,8 @@
} else {
transitionToDelegatingStateAndClear();
}
- } break;
+ }
+ break;
case MotionEvent.ACTION_MOVE: {
if (mLastDownEvent != null && mTapCount < ACTION_TAP_COUNT - 1) {
final double distance = GestureUtils.computeDistance(mLastDownEvent,
@@ -707,7 +594,8 @@
transitionToDelegatingStateAndClear();
}
}
- } break;
+ }
+ break;
case MotionEvent.ACTION_UP: {
if (mLastDownEvent == null) {
return;
@@ -715,8 +603,8 @@
mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
if (!mMagnificationController.magnifiedRegionContains(
event.getX(), event.getY())) {
- transitionToDelegatingStateAndClear();
- return;
+ transitionToDelegatingStateAndClear();
+ return;
}
if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop,
mTapDistanceSlop, 0)) {
@@ -739,13 +627,16 @@
}
clearLastTapUpEvent();
mLastTapUpEvent = MotionEvent.obtain(event);
- } break;
+ }
+ break;
case MotionEvent.ACTION_POINTER_UP: {
/* do nothing */
- } break;
+ }
+ break;
}
}
+ @Override
public void clear() {
mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
@@ -792,7 +683,7 @@
while (mDelayedEventQueue != null) {
MotionEventInfo info = mDelayedEventQueue;
mDelayedEventQueue = info.mNext;
- ScreenMagnifier.this.onMotionEvent(info.mEvent, info.mRawEvent,
+ MagnificationGestureHandler.this.onMotionEvent(info.mEvent, info.mRawEvent,
info.mPolicyFlags);
info.recycle();
}
@@ -816,9 +707,11 @@
if (DEBUG_DETECTING) {
Slog.i(LOG_TAG, "onActionTap()");
}
+
if (!mMagnificationController.isMagnifying()) {
- mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
- up.getX(), up.getY(), true);
+ final float targetScale = mMagnificationController.getPersistedScale();
+ final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE);
+ mMagnificationController.setScaleAndCenter(scale, up.getX(), up.getY(), true);
} else {
mMagnificationController.reset(true);
}
@@ -828,50 +721,36 @@
if (DEBUG_DETECTING) {
Slog.i(LOG_TAG, "onActionTapAndHold()");
}
+
clear();
mTranslationEnabledBeforePan = mMagnificationController.isMagnifying();
- mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
- down.getX(), down.getY(), true);
+
+ final float targetScale = mMagnificationController.getPersistedScale();
+ final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE);
+ mMagnificationController.setScaleAndCenter(scale, down.getX(), down.getY(), true);
+
transitionToState(STATE_VIEWPORT_DRAGGING);
}
}
- private void persistScale(final float scale) {
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- Settings.Secure.putFloatForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, mUserId);
- return null;
- }
- }.execute();
- }
-
- private float getPersistedScale() {
- return Settings.Secure.getFloatForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
- DEFAULT_MAGNIFICATION_SCALE, mUserId);
- }
-
- private static boolean isScreenMagnificationAutoUpdateEnabled(Context context) {
- return (Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
- DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
- }
-
private static final class MotionEventInfo {
private static final int MAX_POOL_SIZE = 10;
private static final Object sLock = new Object();
+
private static MotionEventInfo sPool;
+
private static int sPoolSize;
private MotionEventInfo mNext;
+
private boolean mInPool;
public MotionEvent mEvent;
+
public MotionEvent mRawEvent;
+
public int mPolicyFlags;
public static MotionEventInfo obtain(MotionEvent event, MotionEvent rawEvent,
@@ -922,47 +801,4 @@
mPolicyFlags = 0;
}
}
-
- private final class ScreenStateObserver extends BroadcastReceiver {
- private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
-
- private final Context mContext;
- private final MagnificationController mMagnificationController;
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MESSAGE_ON_SCREEN_STATE_CHANGE: {
- String action = (String) message.obj;
- handleOnScreenStateChange(action);
- } break;
- }
- }
- };
-
- public ScreenStateObserver(Context context,
- MagnificationController magnificationController) {
- mContext = context;
- mMagnificationController = magnificationController;
- mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
- }
-
- public void destroy() {
- mContext.unregisterReceiver(this);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
- intent.getAction()).sendToTarget();
- }
-
- private void handleOnScreenStateChange(String action) {
- if (mMagnificationController.isMagnifying()
- && isScreenMagnificationAutoUpdateEnabled(mContext)) {
- mMagnificationController.reset(false);
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index d713751..c246609 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -409,6 +409,7 @@
private final Region mMagnifiedBounds = new Region();
private final Region mOldMagnifiedBounds = new Region();
+ private final Region mOldAvailableBounds = new Region();
private final Path mCircularPath;
@@ -537,29 +538,39 @@
screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset,
Region.Op.INTERSECT);
- if (!mOldMagnifiedBounds.equals(magnifiedBounds)) {
- Region bounds = Region.obtain();
- bounds.set(magnifiedBounds);
- mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED,
- bounds).sendToTarget();
+ final boolean magnifiedChanged = !mOldMagnifiedBounds.equals(magnifiedBounds);
+ final boolean availableChanged = !mOldAvailableBounds.equals(availableBounds);
+ if (magnifiedChanged || availableChanged) {
+ if (magnifiedChanged) {
+ mWindow.setBounds(magnifiedBounds);
+ Rect dirtyRect = mTempRect1;
+ if (mFullRedrawNeeded) {
+ mFullRedrawNeeded = false;
+ dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
+ screenWidth - mDrawBorderInset,
+ screenHeight - mDrawBorderInset);
+ mWindow.invalidate(dirtyRect);
+ } else {
+ Region dirtyRegion = mTempRegion3;
+ dirtyRegion.set(magnifiedBounds);
+ dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION);
+ dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
+ dirtyRegion.getBounds(dirtyRect);
+ mWindow.invalidate(dirtyRect);
+ }
- mWindow.setBounds(magnifiedBounds);
- Rect dirtyRect = mTempRect1;
- if (mFullRedrawNeeded) {
- mFullRedrawNeeded = false;
- dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
- screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset);
- mWindow.invalidate(dirtyRect);
- } else {
- Region dirtyRegion = mTempRegion3;
- dirtyRegion.set(magnifiedBounds);
- dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION);
- dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
- dirtyRegion.getBounds(dirtyRect);
- mWindow.invalidate(dirtyRect);
+ mOldMagnifiedBounds.set(magnifiedBounds);
}
- mOldMagnifiedBounds.set(magnifiedBounds);
+ if (availableChanged) {
+ mOldAvailableBounds.set(availableBounds);
+ }
+
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = Region.obtain(magnifiedBounds);
+ args.arg2 = Region.obtain(availableBounds);
+ mHandler.obtainMessage(
+ MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget();
}
}
@@ -867,9 +878,12 @@
public void handleMessage(Message message) {
switch (message.what) {
case MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED: {
- Region bounds = (Region) message.obj;
- mCallbacks.onMagnifedBoundsChanged(bounds);
- bounds.recycle();
+ final SomeArgs args = (SomeArgs) message.obj;
+ final Region magnifiedBounds = (Region) args.arg1;
+ final Region availableBounds = (Region) args.arg2;
+ mCallbacks.onMagnifiedBoundsChanged(magnifiedBounds, availableBounds);
+ magnifiedBounds.recycle();
+ availableBounds.recycle();
} break;
case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c6b6c77..c359c53 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -52,6 +52,7 @@
import android.Manifest;
import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.app.ActivityManagerNative;
import android.app.AppOpsManager;
import android.app.IActivityManager;
@@ -10178,7 +10179,7 @@
}
@Override
- public void setMagnificationCallbacks(MagnificationCallbacks callbacks) {
+ public void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks) {
synchronized (mWindowMap) {
if (mAccessibilityController == null) {
mAccessibilityController = new AccessibilityController(
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index e611ea4..6e42391 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -737,7 +737,9 @@
public String toString() {
StringBuffer sb = new StringBuffer();
for (String key : mFields.keySet()) {
- sb.append(key).append(" ").append(mFields.get(key)).append("\n");
+ // Don't display password in toString().
+ String value = (key == PASSWORD_KEY) ? "<removed>" : mFields.get(key);
+ sb.append(key).append(" ").append(value).append("\n");
}
return sb.toString();
}