Display magnification via the nav bar Accessibility Button
Adds support for invoking display magnification by first selecting
the Accessibility Button, then touching an area of the screen to
magnify.
Bug: 30960346
Test: Manual
Change-Id: Ifd8a355562f204182e34bd37f71a3637d85cf0e1
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8a2a14c..391ee83 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5605,10 +5605,10 @@
"accessibility_web_content_key_bindings";
/**
- * Setting that specifies whether the display magnification is enabled.
- * Display magnifications allows the user to zoom in the display content
- * and is targeted to low vision users. The current magnification scale
- * is controlled by {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE}.
+ * Setting that specifies whether the display magnification is enabled via a system-wide
+ * triple tap gesture. Display magnifications allows the user to zoom in the display content
+ * and is targeted to low vision users. The current magnification scale is controlled by
+ * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE}.
*
* @hide
*/
@@ -5616,11 +5616,23 @@
"accessibility_display_magnification_enabled";
/**
+ * Setting that specifies whether the display magnification is enabled via a shortcut
+ * affordance within the system's navigation area. Display magnifications allows the user to
+ * zoom in the display content and is targeted to low vision users. The current
+ * magnification scale is controlled by {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE}.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED =
+ "accessibility_display_magnification_navbar_enabled";
+
+ /**
* Setting that specifies what the display magnification scale is.
* Display magnifications allows the user to zoom in the display
* content and is targeted to low vision users. Whether a display
* magnification is performed is controlled by
- * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED}
+ * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED} and
+ * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED}
*
* @hide
*/
@@ -6950,6 +6962,7 @@
ACCESSIBILITY_DISPLAY_DALTONIZER,
ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+ ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
ACCESSIBILITY_SCRIPT_INJECTION,
ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS,
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index a541a4c..6dbc09c 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -168,6 +168,14 @@
public abstract void setMagnificationSpec(MagnificationSpec spec);
/**
+ * Set by the accessibility framework to indicate whether the magnifiable regions of the display
+ * should be shown.
+ *
+ * @param show {@code true} to show magnifiable region bounds, {@code false} to hide
+ */
+ public abstract void setForceShowMagnifiableBounds(boolean show);
+
+ /**
* Obtains the magnification regions.
*
* @param magnificationRegion the current magnification region
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 8a3c4e3..2b52b48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -35,6 +35,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
+import android.database.ContentObserver;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.inputmethodservice.InputMethodService;
@@ -46,6 +47,7 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.Settings;
import android.support.annotation.VisibleForTesting;
import android.telecom.TelecomManager;
import android.text.TextUtils;
@@ -104,6 +106,7 @@
private int mNavigationIconHints = 0;
private int mNavigationBarMode;
private AccessibilityManager mAccessibilityManager;
+ private MagnificationContentObserver mMagnificationObserver;
private int mDisabledFlags1;
private StatusBar mStatusBar;
@@ -135,6 +138,12 @@
mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
mAccessibilityManager.addAccessibilityServicesStateChangeListener(
this::updateAccessibilityServicesState);
+ mMagnificationObserver = new MagnificationContentObserver(
+ getContext().getMainThreadHandler());
+ getContext().getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false,
+ mMagnificationObserver);
+
if (savedInstanceState != null) {
mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0);
}
@@ -154,6 +163,7 @@
mCommandQueue.removeCallbacks(this);
mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
this::updateAccessibilityServicesState);
+ getContext().getContentResolver().unregisterContentObserver(mMagnificationObserver);
try {
WindowManagerGlobal.getWindowManagerService()
.removeRotationWatcher(mRotationWatcher);
@@ -387,6 +397,7 @@
ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
accessibilityButton.setOnClickListener(this::onAccessibilityClick);
accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
+ updateAccessibilityServicesState();
}
private boolean onHomeTouch(View v, MotionEvent event) {
@@ -550,10 +561,18 @@
}
private void updateAccessibilityServicesState() {
+ int requestingServices = 0;
+ try {
+ if (Settings.Secure.getInt(getContext().getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED) == 1) {
+ requestingServices++;
+ }
+ } catch (Settings.SettingNotFoundException e) {
+ }
+
final List<AccessibilityServiceInfo> services =
mAccessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
- int requestingServices = 0;
for (int i = services.size() - 1; i >= 0; --i) {
AccessibilityServiceInfo info = services.get(i);
if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
@@ -600,6 +619,18 @@
mNavigationBarView.getBarTransitions().finishAnimations();
}
+ private class MagnificationContentObserver extends ContentObserver {
+
+ public MagnificationContentObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ NavigationBarFragment.this.updateAccessibilityServicesState();
+ }
+ }
+
private final Stub mRotationWatcher = new Stub() {
@Override
public void onRotationChanged(int rotation) throws RemoteException {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index fd93865..9e4d89c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -77,9 +77,6 @@
*/
static final int FLAG_FEATURE_INJECT_MOTION_EVENTS = 0x00000010;
- static final int FEATURES_AFFECTING_MOTION_EVENTS = FLAG_FEATURE_INJECT_MOTION_EVENTS
- | FLAG_FEATURE_AUTOCLICK | FLAG_FEATURE_TOUCH_EXPLORATION
- | FLAG_FEATURE_SCREEN_MAGNIFIER;
/**
* Flag for enabling the feature to control the screen magnifier. If
* {@link #FLAG_FEATURE_SCREEN_MAGNIFIER} is set this flag is ignored
@@ -90,6 +87,16 @@
*/
static final int FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER = 0x00000020;
+ /**
+ * Flag for enabling the feature to trigger the screen magnifier
+ * from another on-device interaction.
+ */
+ static final int FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER = 0x00000040;
+
+ static final int FEATURES_AFFECTING_MOTION_EVENTS = FLAG_FEATURE_INJECT_MOTION_EVENTS
+ | FLAG_FEATURE_AUTOCLICK | FLAG_FEATURE_TOUCH_EXPLORATION
+ | FLAG_FEATURE_SCREEN_MAGNIFIER | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
+
private final Runnable mProcessBatchedEventsRunnable = new Runnable() {
@Override
public void run() {
@@ -379,6 +386,12 @@
}
}
+ void notifyAccessibilityButtonClicked() {
+ if (mMagnificationGestureHandler != null) {
+ mMagnificationGestureHandler.notifyShortcutTriggered();
+ }
+ }
+
private void enableFeatures() {
resetStreamState();
@@ -393,11 +406,14 @@
}
if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
- || (mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) {
+ || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
+ || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
final boolean detectControlGestures = (mEnabledFeatures
& FLAG_FEATURE_SCREEN_MAGNIFIER) != 0;
+ final boolean triggerable = (mEnabledFeatures
+ & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0;
mMagnificationGestureHandler = new MagnificationGestureHandler(
- mContext, mAms, detectControlGestures);
+ mContext, mAms, detectControlGestures, triggerable);
addFirstEventHandler(mMagnificationGestureHandler);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 98ce00e..397938a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -781,6 +781,7 @@
userState.mIsTouchExplorationEnabled = false;
userState.mIsEnhancedWebAccessibilityEnabled = false;
userState.mIsDisplayMagnificationEnabled = false;
+ userState.mIsNavBarMagnificationEnabled = false;
userState.mIsAutoclickEnabled = false;
userState.mEnabledServices.clear();
}
@@ -831,6 +832,7 @@
userState.mIsTouchExplorationEnabled = touchExplorationEnabled;
userState.mIsEnhancedWebAccessibilityEnabled = false;
userState.mIsDisplayMagnificationEnabled = false;
+ userState.mIsNavBarMagnificationEnabled = false;
userState.mIsAutoclickEnabled = false;
userState.mEnabledServices.clear();
userState.mEnabledServices.add(service);
@@ -1152,11 +1154,16 @@
private void notifyAccessibilityButtonClickedLocked() {
final UserState state = getCurrentUserStateLocked();
- for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final Service service = state.mBoundServices.get(i);
- // TODO(b/34720082): Only notify a single user-defined service
- if (service.mRequestAccessibilityButton) {
- service.notifyAccessibilityButtonClickedLocked();
+ if (state.mIsNavBarMagnificationEnabled) {
+ mMainHandler.obtainMessage(
+ MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget();
+ } else {
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ final Service service = state.mBoundServices.get(i);
+ // TODO(b/34720082): Only notify a single user-defined service
+ if (service.mRequestAccessibilityButton) {
+ service.notifyAccessibilityButtonClickedLocked();
+ }
}
}
}
@@ -1548,6 +1555,9 @@
if (userState.mIsDisplayMagnificationEnabled) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
}
+ if (userState.mIsNavBarMagnificationEnabled) {
+ flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
+ }
if (userHasMagnificationServicesLocked(userState)) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER;
}
@@ -1781,7 +1791,7 @@
somethingChanged |= readTouchExplorationEnabledSettingLocked(userState);
somethingChanged |= readHighTextContrastEnabledSettingLocked(userState);
somethingChanged |= readEnhancedWebAccessibilityEnabledChangedLocked(userState);
- somethingChanged |= readDisplayMagnificationEnabledSettingLocked(userState);
+ somethingChanged |= readMagnificationEnabledSettingsLocked(userState);
somethingChanged |= readAutoclickEnabledSettingLocked(userState);
somethingChanged |= readAccessibilityShortcutSettingLocked(userState);
return somethingChanged;
@@ -1810,13 +1820,19 @@
return false;
}
- private boolean readDisplayMagnificationEnabledSettingLocked(UserState userState) {
+ private boolean readMagnificationEnabledSettingsLocked(UserState userState) {
final boolean displayMagnificationEnabled = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
0, userState.mUserId) == 1;
- if (displayMagnificationEnabled != userState.mIsDisplayMagnificationEnabled) {
+ final boolean navBarMagnificationEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
+ 0, userState.mUserId) == 1;
+ if ((displayMagnificationEnabled != userState.mIsDisplayMagnificationEnabled)
+ || (navBarMagnificationEnabled != userState.mIsNavBarMagnificationEnabled)) {
userState.mIsDisplayMagnificationEnabled = displayMagnificationEnabled;
+ userState.mIsNavBarMagnificationEnabled = navBarMagnificationEnabled;
return true;
}
return false;
@@ -2018,8 +2034,8 @@
return;
}
- if (userState.mIsDisplayMagnificationEnabled ||
- userHasListeningMagnificationServicesLocked(userState)) {
+ if (userState.mIsDisplayMagnificationEnabled || userState.mIsNavBarMagnificationEnabled
+ || userHasListeningMagnificationServicesLocked(userState)) {
// Initialize the magnification controller if necessary
getMagnificationController();
mMagnificationController.register();
@@ -2241,6 +2257,8 @@
pw.append(", touchExplorationEnabled=" + userState.mIsTouchExplorationEnabled);
pw.append(", displayMagnificationEnabled="
+ userState.mIsDisplayMagnificationEnabled);
+ pw.append(", navBarMagnificationEnabled="
+ + userState.mIsNavBarMagnificationEnabled);
pw.append(", autoclickEnabled=" + userState.mIsAutoclickEnabled);
if (userState.mUiAutomationService != null) {
pw.append(", ");
@@ -2320,6 +2338,7 @@
public static final int MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS = 10;
public static final int MSG_UPDATE_FINGERPRINT = 11;
public static final int MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS = 12;
+ public static final int MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER = 13;
public MainHandler(Looper looper) {
super(looper);
@@ -2406,6 +2425,14 @@
}
});
} break;
+
+ case MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER: {
+ synchronized (mLock) {
+ if (mHasInputFilter && mInputFilter != null) {
+ mInputFilter.notifyAccessibilityButtonClicked();
+ }
+ }
+ }
}
}
@@ -4788,6 +4815,7 @@
public boolean mIsTextHighContrastEnabled;
public boolean mIsEnhancedWebAccessibilityEnabled;
public boolean mIsDisplayMagnificationEnabled;
+ public boolean mIsNavBarMagnificationEnabled;
public boolean mIsAutoclickEnabled;
public boolean mIsPerformGesturesEnabled;
public boolean mIsFilterKeyEventsEnabled;
@@ -4856,6 +4884,7 @@
mIsTouchExplorationEnabled = false;
mIsEnhancedWebAccessibilityEnabled = false;
mIsDisplayMagnificationEnabled = false;
+ mIsNavBarMagnificationEnabled = false;
mIsAutoclickEnabled = false;
mSoftKeyboardShowMode = 0;
@@ -4888,6 +4917,9 @@
private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
+ private final Uri mNavBarMagnificationEnabledUri = Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+
private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED);
@@ -4927,6 +4959,8 @@
false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri,
false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(mNavBarMagnificationEnabledUri,
+ false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(mAutoclickEnabledUri,
false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri,
@@ -4966,8 +5000,9 @@
if (readTouchExplorationEnabledSettingLocked(userState)) {
onUserStateChangedLocked(userState);
}
- } else if (mDisplayMagnificationEnabledUri.equals(uri)) {
- if (readDisplayMagnificationEnabledSettingLocked(userState)) {
+ } else if (mDisplayMagnificationEnabledUri.equals(uri)
+ || mNavBarMagnificationEnabledUri.equals(uri)) {
+ if (readMagnificationEnabledSettingsLocked(userState)) {
onUserStateChangedLocked(userState);
}
} else if (mAutoclickEnabledUri.equals(uri)) {
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index f65046c..caa74b9 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -685,6 +685,12 @@
}
}
+ void setForceShowMagnifiableBounds(boolean show) {
+ if (mRegistered) {
+ mWindowManager.setForceShowMagnifiableBounds(show);
+ }
+ }
+
private void getMagnifiedFrameInContentCoordsLocked(Rect outFrame) {
final float scale = getSentScale();
final float offsetX = getSentOffsetX();
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index f6e5340..7e82eda 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -16,7 +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.os.Handler;
import android.os.Message;
import android.util.MathUtils;
@@ -57,22 +60,30 @@
* be the same but when the finger goes up the screen will stay magnified.
* In other words, the initial magnified state is sticky.
*
- * 3. Pinching with any number of additional fingers when viewport dragging
+ * 3. Magnification can optionally be "triggered" by some external shortcut
+ * affordance. When this occurs via {@link #notifyShortcutTriggered()} a
+ * subsequent tap in a magnifiable region will engage permanent screen
+ * magnification as described in #1. Alternatively, a subsequent long-press
+ * or drag will engage magnification with viewport dragging as described in
+ * #2. Once magnified, all following behaviors apply whether magnification
+ * was engaged via a triple-tap or by a triggered shortcut.
+ *
+ * 4. Pinching with any number of additional fingers when viewport dragging
* is enabled, i.e. the user triple tapped and holds, would adjust the
* magnification scale which will become the current default magnification
* scale. The next time the user magnifies the same magnification scale
* would be used.
*
- * 4. When in a permanent magnified state the user can use two or more fingers
+ * 5. When in a permanent magnified state the user can use two or more fingers
* to pan the viewport. Note that in this mode the content is panned as
* opposed to the viewport dragging mode in which the viewport is moved.
*
- * 5. When in a permanent magnified state the user can use two or more
+ * 6. When in a permanent magnified state the user can use two or more
* fingers to change the magnification scale which will become the current
* default magnification scale. The next time the user magnifies the same
* magnification scale would be used.
*
- * 6. The magnification scale will be persisted in settings and in the cloud.
+ * 7. The magnification scale will be persisted in settings and in the cloud.
*/
class MagnificationGestureHandler implements EventStreamTransformation {
private static final String LOG_TAG = "MagnificationEventHandler";
@@ -94,8 +105,10 @@
private final MagnifiedContentInteractionStateHandler mMagnifiedContentInteractionStateHandler;
private final StateViewportDraggingHandler mStateViewportDraggingHandler;
+ private final ScreenStateReceiver mScreenStateReceiver;
- private final boolean mDetectControlGestures;
+ private final boolean mDetectTripleTap;
+ private final boolean mTriggerable;
private EventStreamTransformation mNext;
@@ -104,19 +117,39 @@
private boolean mTranslationEnabledBeforePan;
+ private boolean mShortcutTriggered;
+
private PointerCoords[] mTempPointerCoords;
private PointerProperties[] mTempPointerProperties;
private long mDelegatingStateDownTime;
+ /**
+ * @param context Context for resolving various magnification-related resources
+ * @param ams AccessibilityManagerService used to obtain a {@link MagnificationController}
+ * @param detectTripleTap {@code true} if this detector should detect and respond to triple-tap
+ * gestures for engaging and disengaging magnification,
+ * {@code false} if it should ignore such gestures
+ * @param triggerable {@code true} if this detector should be "triggerable" by some external
+ * shortcut invoking {@link #notifyShortcutTriggered}, {@code
+ * false} if it should ignore such triggers.
+ */
public MagnificationGestureHandler(Context context, AccessibilityManagerService ams,
- boolean detectControlGestures) {
+ boolean detectTripleTap, boolean triggerable) {
mMagnificationController = ams.getMagnificationController();
mDetectingStateHandler = new DetectingStateHandler(context);
mStateViewportDraggingHandler = new StateViewportDraggingHandler();
mMagnifiedContentInteractionStateHandler =
new MagnifiedContentInteractionStateHandler(context);
- mDetectControlGestures = detectControlGestures;
+ mDetectTripleTap = detectTripleTap;
+ mTriggerable = triggerable;
+
+ if (triggerable) {
+ mScreenStateReceiver = new ScreenStateReceiver(context, this);
+ mScreenStateReceiver.register();
+ } else {
+ mScreenStateReceiver = null;
+ }
transitionToState(STATE_DETECTING);
}
@@ -129,7 +162,7 @@
}
return;
}
- if (!mDetectControlGestures) {
+ if (!mDetectTripleTap && !mTriggerable) {
if (mNext != null) {
dispatchTransformedEvent(event, rawEvent, policyFlags);
}
@@ -151,7 +184,7 @@
break;
case STATE_MAGNIFIED_INTERACTION: {
// mMagnifiedContentInteractionStateHandler handles events only
- // if this is the current state since it uses ScaleGestureDetecotr
+ // if this is the current state since it uses ScaleGestureDetector
// and a GestureDetector which need well formed event stream.
}
break;
@@ -193,11 +226,34 @@
@Override
public void onDestroy() {
+ if (mScreenStateReceiver != null) {
+ mScreenStateReceiver.unregister();
+ }
clear();
}
+ void notifyShortcutTriggered() {
+ if (mTriggerable) {
+ if (mMagnificationController.resetIfNeeded(true)) {
+ clear();
+ } else {
+ setMagnificationShortcutTriggered(!mShortcutTriggered);
+ }
+ }
+ }
+
+ private void setMagnificationShortcutTriggered(boolean state) {
+ if (mShortcutTriggered == state) {
+ return;
+ }
+
+ mShortcutTriggered = state;
+ mMagnificationController.setForceShowMagnifiableBounds(state);
+ }
+
private void clear() {
mCurrentState = STATE_DETECTING;
+ setMagnificationShortcutTriggered(false);
mDetectingStateHandler.clear();
mStateViewportDraggingHandler.clear();
mMagnifiedContentInteractionStateHandler.clear();
@@ -575,31 +631,51 @@
mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
if (!mMagnificationController.magnificationRegionContains(
event.getX(), event.getY())) {
- transitionToDelegatingStateAndClear();
+ transitionToDelegatingState(!mShortcutTriggered);
return;
}
- if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null
- && GestureUtils.isMultiTap(mLastDownEvent, event,
- mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
+ if (mShortcutTriggered) {
Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD,
policyFlags, 0, event);
mHandler.sendMessageDelayed(message,
ViewConfiguration.getLongPressTimeout());
- } else if (mTapCount < ACTION_TAP_COUNT) {
+ return;
+ }
+ if (mDetectTripleTap) {
+ if ((mTapCount == ACTION_TAP_COUNT - 1) && (mLastDownEvent != null)
+ && GestureUtils.isMultiTap(mLastDownEvent, event, mMultiTapTimeSlop,
+ mMultiTapDistanceSlop, 0)) {
+ Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD,
+ policyFlags, 0, event);
+ mHandler.sendMessageDelayed(message,
+ ViewConfiguration.getLongPressTimeout());
+ } else if (mTapCount < ACTION_TAP_COUNT) {
+ Message message = mHandler.obtainMessage(
+ MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+ mHandler.sendMessageDelayed(message, mMultiTapTimeSlop);
+ }
+ clearLastDownEvent();
+ mLastDownEvent = MotionEvent.obtain(event);
+ } else if (mMagnificationController.isMagnifying()) {
+ // If magnified, consume an ACTION_DOWN until mMultiTapTimeSlop or
+ // mTapDistanceSlop is reached to ensure MAGNIFIED_INTERACTION is reachable.
Message message = mHandler.obtainMessage(
MESSAGE_TRANSITION_TO_DELEGATING_STATE);
mHandler.sendMessageDelayed(message, mMultiTapTimeSlop);
+ return;
+ } else {
+ transitionToDelegatingState(true);
+ return;
}
- clearLastDownEvent();
- mLastDownEvent = MotionEvent.obtain(event);
}
break;
case MotionEvent.ACTION_POINTER_DOWN: {
if (mMagnificationController.isMagnifying()) {
+ mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
transitionToState(STATE_MAGNIFIED_INTERACTION);
clear();
} else {
- transitionToDelegatingStateAndClear();
+ transitionToDelegatingState(true);
}
}
break;
@@ -608,29 +684,34 @@
final double distance = GestureUtils.computeDistance(mLastDownEvent,
event, 0);
if (Math.abs(distance) > mTapDistanceSlop) {
- transitionToDelegatingStateAndClear();
+ transitionToDelegatingState(true);
}
}
}
break;
case MotionEvent.ACTION_UP: {
+ if (!mMagnificationController.magnificationRegionContains(
+ event.getX(), event.getY())) {
+ transitionToDelegatingState(!mShortcutTriggered);
+ return;
+ }
+ if (mShortcutTriggered) {
+ clear();
+ onActionTap(event, policyFlags);
+ return;
+ }
if (mLastDownEvent == null) {
return;
}
mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
- if (!mMagnificationController.magnificationRegionContains(
- event.getX(), event.getY())) {
- transitionToDelegatingStateAndClear();
- return;
- }
if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop,
mTapDistanceSlop, 0)) {
- transitionToDelegatingStateAndClear();
+ transitionToDelegatingState(true);
return;
}
- if (mLastTapUpEvent != null && !GestureUtils.isMultiTap(mLastTapUpEvent,
- event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
- transitionToDelegatingStateAndClear();
+ if (mLastTapUpEvent != null && !GestureUtils.isMultiTap(
+ mLastTapUpEvent, event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
+ transitionToDelegatingState(true);
return;
}
mTapCount++;
@@ -655,6 +736,7 @@
@Override
public void clear() {
+ setMagnificationShortcutTriggered(false);
mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
clearTapDetectionState();
@@ -714,10 +796,12 @@
}
}
- private void transitionToDelegatingStateAndClear() {
+ private void transitionToDelegatingState(boolean andClear) {
transitionToState(STATE_DELEGATING);
sendDelayedMotionEvents();
- clear();
+ if (andClear) {
+ clear();
+ }
}
private void onActionTap(MotionEvent up, int policyFlags) {
@@ -820,4 +904,30 @@
mPolicyFlags = 0;
}
}
+
+ /**
+ * BroadcastReceiver used to cancel the magnification shortcut when the screen turns off
+ */
+ private static class ScreenStateReceiver extends BroadcastReceiver {
+ private final Context mContext;
+ private final MagnificationGestureHandler mGestureHandler;
+
+ public ScreenStateReceiver(Context context, MagnificationGestureHandler gestureHandler) {
+ mContext = context;
+ mGestureHandler = gestureHandler;
+ }
+
+ 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) {
+ mGestureHandler.setMagnificationShortcutTriggered(false);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 1510dd1..5abc4e4 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -222,6 +222,14 @@
|| mWindowsForAccessibilityObserver != null);
}
+ /** NOTE: This has to be called within a surface transaction. */
+ public void setForceShowMagnifiableBoundsLocked(boolean show) {
+ if (mDisplayMagnifier != null) {
+ mDisplayMagnifier.setForceShowMagnifiableBoundsLocked(show);
+ mDisplayMagnifier.drawMagnifiedRegionBorderIfNeededLocked();
+ }
+ }
+
private static void populateTransformationMatrixLocked(WindowState windowState,
Matrix outMatrix) {
sTempFloats[Matrix.MSCALE_X] = windowState.mWinAnimator.mDsDx;
@@ -266,6 +274,8 @@
private final long mLongAnimationDuration;
+ private boolean mForceShowMagnifiableBounds = false;
+
public DisplayMagnifier(WindowManagerService windowManagerService,
MagnificationCallbacks callbacks) {
mContext = windowManagerService.mContext;
@@ -283,6 +293,15 @@
mWindowManagerService.scheduleAnimationLocked();
}
+ public void setForceShowMagnifiableBoundsLocked(boolean show) {
+ mForceShowMagnifiableBounds = show;
+ mMagnifedViewport.setMagnifiedRegionBorderShownLocked(show, true);
+ }
+
+ public boolean isForceShowingMagnifiableBoundsLocked() {
+ return mForceShowMagnifiableBounds;
+ }
+
public void onRectangleOnScreenRequestedLocked(Rect rectangle) {
if (DEBUG_RECTANGLE_REQUESTED) {
Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle);
@@ -488,7 +507,8 @@
// to show the border. We will do so when the pending message is handled.
if (!mHandler.hasMessages(
MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
- setMagnifiedRegionBorderShownLocked(isMagnifyingLocked(), true);
+ setMagnifiedRegionBorderShownLocked(
+ isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked(), true);
}
}
@@ -600,11 +620,11 @@
}
public void onRotationChangedLocked() {
- // If we are magnifying, hide the magnified border window immediately so
+ // If we are showing the magnification border, hide it immediately so
// the user does not see strange artifacts during rotation. The screenshot
- // used for rotation has already the border. After the rotation is complete
+ // used for rotation already has the border. After the rotation is complete
// we will show the border.
- if (isMagnifyingLocked()) {
+ if (isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked()) {
setMagnifiedRegionBorderShownLocked(false, false);
final long delay = (long) (mLongAnimationDuration
* mWindowManagerService.getWindowAnimationScaleLocked());
@@ -926,7 +946,8 @@
case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
synchronized (mWindowManagerService.mWindowMap) {
- if (mMagnifedViewport.isMagnifyingLocked()) {
+ if (mMagnifedViewport.isMagnifyingLocked()
+ || isForceShowingMagnifiableBoundsLocked()) {
mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true);
mWindowManagerService.scheduleAnimationLocked();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b063e01..014a89d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7158,6 +7158,17 @@
}
@Override
+ public void setForceShowMagnifiableBounds(boolean show) {
+ synchronized (mWindowMap) {
+ if (mAccessibilityController != null) {
+ mAccessibilityController.setForceShowMagnifiableBoundsLocked(show);
+ } else {
+ throw new IllegalStateException("Magnification callbacks not set!");
+ }
+ }
+ }
+
+ @Override
public void getMagnificationRegion(@NonNull Region magnificationRegion) {
synchronized (mWindowMap) {
if (mAccessibilityController != null) {