diff options
22 files changed, 1833 insertions, 524 deletions
diff --git a/api/current.txt b/api/current.txt index de9abd0b2932..cca8293486e3 100644 --- a/api/current.txt +++ b/api/current.txt @@ -332,6 +332,7 @@ package android { 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 @@ package android.accessibilityservice { 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 @@ package android.accessibilityservice { 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 @@ package android.accessibilityservice { 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 65ecdc139777..cd6c58e78454 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -424,6 +424,7 @@ package android { 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 @@ package android.accessibilityservice { 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 @@ package android.accessibilityservice { 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 @@ package android.accessibilityservice { 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 9d6aa132759c..273483a6db8a 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 android.view.accessibility.AccessibilityWindowInfo; 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 abstract class AccessibilityService extends Service { 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 @@ public abstract class AccessibilityService extends Service { private WindowManager mWindowManager; + private MagnificationController mMagnificationController; + /** * Callback for {@link android.view.accessibility.AccessibilityEvent}s. * @@ -396,6 +408,20 @@ public abstract class AccessibilityService extends Service { 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 @@ public abstract class AccessibilityService extends Service { } /** + * 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 @@ public abstract class AccessibilityService extends Service { return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() { @Override public void onServiceConnected() { - AccessibilityService.this.onServiceConnected(); + AccessibilityService.this.dispatchServiceConnected(); } @Override @@ -678,6 +1083,12 @@ public abstract class AccessibilityService extends Service { 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 @@ public abstract class AccessibilityService extends Service { 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 @@ public abstract class AccessibilityService extends Service { 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 @@ public abstract class AccessibilityService extends Service { } } 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 4edb0c6d1b07..2c98fef20cdd 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -104,6 +104,12 @@ public class AccessibilityServiceInfo implements Parcelable { */ 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 @@ public class AccessibilityServiceInfo implements Parcelable { 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 @@ public class AccessibilityServiceInfo implements Parcelable { .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 @@ public class AccessibilityServiceInfo implements Parcelable { 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 8b503ddad947..15666bf03ae8 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 @@ import android.view.KeyEvent; 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 5f7a17d33bd1..6ac50bd6efbc 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -18,6 +18,7 @@ package android.accessibilityservice; 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 @@ interface IAccessibilityServiceConnection { 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 efed2e09733e..f7848f98b006 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -21,9 +21,11 @@ import android.accessibilityservice.AccessibilityService.IAccessibilityServiceCl 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 final class UiAutomation { 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 25d9aa9bae67..09a15de8778e 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -685,6 +685,48 @@ public final class BluetoothHeadset implements BluetoothProfile { } /** + * 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 0e23fada8317..0bb4088f62c9 100755 --- a/core/java/android/bluetooth/IBluetoothHeadset.aidl +++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl @@ -50,6 +50,8 @@ interface IBluetoothHeadset { 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 0ee6714f7b5f..49242bb5aa65 100644 --- a/core/java/android/view/MagnificationSpec.java +++ b/core/java/android/view/MagnificationSpec.java @@ -28,10 +28,21 @@ import android.util.Pools.SynchronizedPool; 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 @@ public class MagnificationSpec implements Parcelable { 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 @@ public class MagnificationSpec implements Parcelable { } @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 3882bca043e8..89b1eb933025 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 @@ public abstract class WindowManagerInternal { * 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 @@ public abstract class WindowManagerInternal { * * @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 50cf30262f69..90fc22b8b3b8 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3242,6 +3242,14 @@ i </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 54e43c8e3e7c..b6b2e204b9cf 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 faa76f2fe9ce..00c0fe8b26e1 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 6820c259a51a..e8f6b462d447 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 b52687a78298..5f6cbf9f0025 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.os.PowerManager; 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 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private TouchExplorer mTouchExplorer; - private ScreenMagnifier mScreenMagnifier; + private MagnificationGestureHandler mMagnificationGestureHandler; private AutoclickController mAutoclickController; @@ -363,14 +362,13 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } 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 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo 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 535a8efc496b..9f1dc0a96559 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.AccessibilityService; 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 android.view.accessibility.IAccessibilityManagerClient; 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private MagnificationController mMagnificationController; + private boolean mUnregisterMagnificationOnReset; + private InteractionBridge mInteractionBridge; private AlertDialog mEnableTouchExplorationDialog; @@ -762,6 +766,28 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } /** + * 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + /** + * 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { updateTouchExplorationLocked(userState); updateEnhancedWebAccessibilityLocked(userState); updateDisplayColorAdjustmentSettingsLocked(userState); + updateMagnificationLocked(userState); scheduleUpdateInputFilter(userState); scheduleUpdateClientsIfNeededLocked(userState); } @@ -1663,6 +1704,44 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } @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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { 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 class AccessibilityManagerService extends IAccessibilityManager.Stub { 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { & 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 781d13453be4..a093d9225deb 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 @@ import android.view.animation.DecelerateInterpolator; * 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 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 MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain(); + 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 @@ class MagnificationController { } /** - * 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); - - 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(); + public void setMagnifiedRegion(Region magnified, Region available, boolean updateSpec) { + synchronized (mLock) { + mMagnifiedRegion.set(magnified); + mMagnifiedRegion.getBounds(mMagnifiedBounds); + mAvailableRegion.set(available); + + 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 @@ class MagnificationController { * 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 @@ class MagnificationController { 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 @@ class MagnificationController { } /** + * 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 @@ class MagnificationController { * @return the scale currently used by the window manager */ public float getSentScale() { - return mSentMagnificationSpec.scale; + return mSpecAnimationBridge.mSentMagnificationSpec.scale; } /** @@ -176,7 +291,7 @@ class MagnificationController { * @return the X offset currently used by the window manager */ public float getSentOffsetX() { - return mSentMagnificationSpec.offsetX; + return mSpecAnimationBridge.mSentMagnificationSpec.offsetX; } /** @@ -187,7 +302,7 @@ class MagnificationController { * @return the Y offset currently used by the window manager */ public float getSentOffsetY() { - return mSentMagnificationSpec.offsetY; + return mSpecAnimationBridge.mSentMagnificationSpec.offsetY; } /** @@ -196,21 +311,24 @@ class MagnificationController { * * @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 @@ class MagnificationController { * 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 @@ class MagnificationController { * 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 @@ class MagnificationController { * 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; - } - if (mTransformationAnimator.isRunning()) { - mTransformationAnimator.cancel(); + public boolean setScaleAndCenter(float scale, float centerX, float centerY, boolean animate) { + synchronized (mLock) { + return setScaleAndCenterLocked(scale, centerX, centerY, animate); } - 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 @@ class MagnificationController { * @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); - } - - 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); - } - - private float getMinOffsetX() { - final Rect magnifiedFrame = mTempRect; - mMagnifiedBounds.getBounds(magnifiedFrame); - final float viewportWidth = magnifiedFrame.width(); + 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); + } + } + + /** + * 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(); + } + + /** + * 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; + + synchronized (mLock) { + if (isMagnifying()) { + reset(false); + } + } + } + } + + private boolean isScreenMagnificationAutoUpdateEnabled() { + return (Settings.Secure.getInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE, + DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1); } - public void setMagnificationSpec(MagnificationSpec spec) { - if (DEBUG_SET_MAGNIFICATION_SPEC) { - Slog.i(LOG_TAG, "Sending: " + spec); + /** + * 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; + } } - mSentMagnificationSpec.scale = spec.scale; - mSentMagnificationSpec.offsetX = spec.offsetX; - mSentMagnificationSpec.offsetY = spec.offsetY; - mWindowManager.setMagnificationSpec(MagnificationSpec.obtain(spec)); } - public MagnificationSpec getMagnificationSpec() { - return mSentMagnificationSpec; + 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 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; + } + } + } } - private static class MagnificationSpecEvaluator implements TypeEvaluator<MagnificationSpec> { - private final MagnificationSpec mTempTransformationSpec = MagnificationSpec.obtain(); + /** + * 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 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 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 index 8feb167f4c99..51c8ab572350 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.PointerCoords; 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 @@ import java.util.Locale; * * 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 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 float MIN_SCALE = 2.0f; + private static final float MAX_SCALE = 5.0f; - 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio @Override public void onKeyEvent(KeyEvent event, int policyFlags) { if (mNext != null) { - mNext.onKeyEvent(event, policyFlags); + mNext.onKeyEvent(event, policyFlags); } } @@ -366,15 +184,13 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio @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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } }; + @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { cacheDelayedMotionEvent(event, rawEvent, policyFlags); final int action = event.getActionMasked(); @@ -678,7 +563,7 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } clearLastDownEvent(); mLastDownEvent = MotionEvent.obtain(event); - } break; + } + break; case MotionEvent.ACTION_POINTER_DOWN: { if (mMagnificationController.isMagnifying()) { transitionToState(STATE_MAGNIFIED_INTERACTION); @@ -698,7 +584,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio transitionToDelegatingStateAndClear(); } } - } break; + } + break; case MotionEvent.ACTION_UP: { if (mLastDownEvent == null) { return; @@ -715,8 +603,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio } 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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,35 +721,16 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio if (DEBUG_DETECTING) { Slog.i(LOG_TAG, "onActionTapAndHold()"); } + clear(); mTranslationEnabledBeforePan = mMagnificationController.isMagnifying(); - mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(), - 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(); - } + final float targetScale = mMagnificationController.getPersistedScale(); + final float scale = MathUtils.constrain(targetScale, MIN_SCALE, MAX_SCALE); + mMagnificationController.setScaleAndCenter(scale, down.getX(), down.getY(), true); - 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); + transitionToState(STATE_VIEWPORT_DRAGGING); + } } private static final class MotionEventInfo { @@ -864,14 +738,19 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio 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 d713751d3786..c2466091bb91 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 @@ final class AccessibilityController { 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 @@ final class AccessibilityController { 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(); - - 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); + 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); + } + + mOldMagnifiedBounds.set(magnifiedBounds); + } + + if (availableChanged) { + mOldAvailableBounds.set(availableBounds); } - mOldMagnifiedBounds.set(magnifiedBounds); + 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 @@ final class AccessibilityController { 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 c6b6c7742343..c359c5306b4c 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 static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; 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 @@ public class WindowManagerService extends IWindowManager.Stub } @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 e611ea448497..6e423911f09c 100644 --- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java +++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java @@ -737,7 +737,9 @@ public class WifiEnterpriseConfig implements Parcelable { 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(); } |