summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author sallyyuen <sallyyuen@google.com> 2021-08-24 09:10:18 -0700
committer sallyyuen <sallyyuen@google.com> 2021-09-17 11:26:18 -0700
commitf163deb788a78f5346c45ceab322f314789f2193 (patch)
tree8e151fa3e71a1804b288d8b1a5951cfccc20b0fb
parentcd9481d29abd982eb21a9e681a140685a66394ab (diff)
Add accessibility support for Drag & Drop
go/a11yDragAndDrop Add AccessibilityAction#ACTION_DROP to potential drop targets. Add A11yAction#ACTION_CANCEL to the source of the drag. Developers will be required add the A11yAction#ACTION_DRAG action (androidX can do this) Send AccessibilityEvents when appropriate. These events will be sent when a drag is performed via a11y actions, or via touch events with a11y enabled. Drop and cancel events are mutuall exclusive. To avoid a scenario where a user starts a drag via an a11yAction but doesn't drop or cancel, the system will cancel the event after a minute timeout. Corner cases: 1) A user cannot start a drag on another view without first cancelling the current one 2) An ACTION_DRAG on a View won't be successful if the last touch point was in that View For a11y, we send window events directly from the View to avoid source merging in ViewRootImpl. (Specifically for the start events, where the event gets sent from the common ancestor of the source and target instead of the source) Bug: 26871588 Test: atest DragDropControllerTests CtsWindowManagerDeviceTestCases:CrossAppDragAndDropTests AccessibilityDragAndDropTest Use Android's official sample drag and drop apps. Drag within window and across split screens Use ReceiveContentDemo target app with sample app Change-Id: Ie8554d25ab6c43b08d2dacb8c8907b1636bd72b4
-rw-r--r--core/java/android/view/IWindowSession.aidl5
-rw-r--r--core/java/android/view/View.java137
-rw-r--r--core/java/android/view/ViewParent.java3
-rw-r--r--core/java/android/view/ViewRootImpl.java23
-rw-r--r--core/java/android/view/WindowlessWindowManager.java5
-rw-r--r--services/core/java/com/android/server/wm/DragDropController.java101
-rw-r--r--services/core/java/com/android/server/wm/DragState.java27
-rw-r--r--services/core/java/com/android/server/wm/Session.java11
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java40
9 files changed, 303 insertions, 49 deletions
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index a6abed020959..9da50889e43f 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -175,6 +175,11 @@ interface IWindowSession {
float touchX, float touchY, float thumbCenterX, float thumbCenterY, in ClipData data);
/**
+ * Drops the content of the current drag operation for accessibility
+ */
+ boolean dropForAccessibility(IWindow window, int x, int y);
+
+ /**
* Report the result of a drop action targeted to the given window.
* consumed is 'true' when the drop was accepted by a valid recipient,
* 'false' otherwise.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1ecdb900316d..afdb755179a6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3515,6 +3515,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* 1 PFLAG4_ALLOW_CLICK_WHEN_DISABLED
* 1 PFLAG4_DETACHED
* 1 PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE
+ * 1 PFLAG4_DRAG_A11Y_STARTED
* |-------|-------|-------|-------|
*/
@@ -3586,6 +3587,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
private static final int PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE = 0x000004000;
+ /**
+ * Indicates that the view has started a drag with {@link AccessibilityAction#ACTION_DRAG_START}
+ */
+ private static final int PFLAG4_DRAG_A11Y_STARTED = 0x000008000;
+
/* End of masks for mPrivateFlags4 */
/** @hide */
@@ -10384,8 +10390,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (mTouchDelegate != null) {
info.setTouchDelegateInfo(mTouchDelegate.getTouchDelegateInfo());
}
+
+ if (startedSystemDragForAccessibility()) {
+ info.addAction(AccessibilityAction.ACTION_DRAG_CANCEL);
+ }
+
+ if (canAcceptAccessibilityDrop()) {
+ info.addAction(AccessibilityAction.ACTION_DRAG_DROP);
+ }
}
+
/**
* Adds extra data to an {@link AccessibilityNodeInfo} based on an explicit request for the
* additional data.
@@ -14222,9 +14237,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return true;
}
}
+
+ if (action == R.id.accessibilityActionDragDrop) {
+ if (!canAcceptAccessibilityDrop()) {
+ return false;
+ }
+ try {
+ if (mAttachInfo != null && mAttachInfo.mSession != null) {
+ final int[] location = new int[2];
+ getLocationInWindow(location);
+ final int centerX = location[0] + getWidth() / 2;
+ final int centerY = location[1] + getHeight() / 2;
+ return mAttachInfo.mSession.dropForAccessibility(mAttachInfo.mWindow,
+ centerX, centerY);
+ }
+ } catch (RemoteException e) {
+ Log.e(VIEW_LOG_TAG, "Unable to drop for accessibility", e);
+ }
+ return false;
+ } else if (action == R.id.accessibilityActionDragCancel) {
+ if (!startedSystemDragForAccessibility()) {
+ return false;
+ }
+ if (mAttachInfo != null && mAttachInfo.mDragToken != null) {
+ cancelDragAndDrop();
+ return true;
+ }
+ return false;
+ }
return false;
}
+ private boolean canAcceptAccessibilityDrop() {
+ if (!canAcceptDrag()) {
+ return false;
+ }
+ ListenerInfo li = mListenerInfo;
+ return (li != null) && (li.mOnDragListener != null || li.mOnReceiveContentListener != null);
+ }
+
private boolean traverseAtGranularity(int granularity, boolean forward,
boolean extendSelection) {
CharSequence text = getIterableTextForAccessibility();
@@ -26664,6 +26715,37 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0);
}
+ Rect bounds = new Rect();
+ getBoundsOnScreen(bounds, true);
+
+ Point lastTouchPoint = new Point();
+ mAttachInfo.mViewRootImpl.getLastTouchPoint(lastTouchPoint);
+ final ViewRootImpl root = mAttachInfo.mViewRootImpl;
+
+ // Skip surface logic since shadows and animation are not required during the a11y drag
+ final boolean a11yEnabled = AccessibilityManager.getInstance(mContext).isEnabled();
+ if (a11yEnabled && (flags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) != 0) {
+ try {
+ IBinder token = mAttachInfo.mSession.performDrag(
+ mAttachInfo.mWindow, flags, null,
+ mAttachInfo.mViewRootImpl.getLastTouchSource(),
+ 0f, 0f, 0f, 0f, data);
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(VIEW_LOG_TAG, "startDragAndDrop via a11y action returned " + token);
+ }
+ if (token != null) {
+ root.setLocalDragState(myLocalState);
+ mAttachInfo.mDragToken = token;
+ mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
+ setAccessibilityDragStarted(true);
+ }
+ return token != null;
+ } catch (Exception e) {
+ Log.e(VIEW_LOG_TAG, "Unable to initiate a11y drag", e);
+ return false;
+ }
+ }
+
Point shadowSize = new Point();
Point shadowTouchPoint = new Point();
shadowBuilder.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
@@ -26688,7 +26770,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
+ " shadowX=" + shadowTouchPoint.x + " shadowY=" + shadowTouchPoint.y);
}
- final ViewRootImpl root = mAttachInfo.mViewRootImpl;
final SurfaceSession session = new SurfaceSession();
final SurfaceControl surfaceControl = new SurfaceControl.Builder(session)
.setName("drag surface")
@@ -26711,12 +26792,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
surface.unlockCanvasAndPost(canvas);
}
- // repurpose 'shadowSize' for the last touch point
- root.getLastTouchPoint(shadowSize);
-
- token = mAttachInfo.mSession.performDrag(
- mAttachInfo.mWindow, flags, surfaceControl, root.getLastTouchSource(),
- shadowSize.x, shadowSize.y, shadowTouchPoint.x, shadowTouchPoint.y, data);
+ token = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, flags, surfaceControl,
+ root.getLastTouchSource(), lastTouchPoint.x, lastTouchPoint.y,
+ shadowTouchPoint.x, shadowTouchPoint.y, data);
if (ViewDebug.DEBUG_DRAG) {
Log.d(VIEW_LOG_TAG, "performDrag returned " + token);
}
@@ -26728,6 +26806,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mAttachInfo.mDragToken = token;
// Cache the local state object for delivery with DragEvents
root.setLocalDragState(myLocalState);
+ if (a11yEnabled) {
+ // Set for AccessibilityEvents
+ mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
+ }
}
return token != null;
} catch (Exception e) {
@@ -26741,6 +26823,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
+ void setAccessibilityDragStarted(boolean started) {
+ int pflags4 = mPrivateFlags4;
+ if (started) {
+ pflags4 |= PFLAG4_DRAG_A11Y_STARTED;
+ } else {
+ pflags4 &= ~PFLAG4_DRAG_A11Y_STARTED;
+ }
+
+ if (pflags4 != mPrivateFlags4) {
+ mPrivateFlags4 = pflags4;
+ sendWindowContentChangedAccessibilityEvent(CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+ }
+
+ private boolean startedSystemDragForAccessibility() {
+ return (mPrivateFlags4 & PFLAG4_DRAG_A11Y_STARTED) != 0;
+ }
+
/**
* Cancels an ongoing drag and drop operation.
* <p>
@@ -26958,6 +27058,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
switch (event.mAction) {
+ case DragEvent.ACTION_DRAG_STARTED: {
+ if (result && li.mOnDragListener != null) {
+ sendWindowContentChangedAccessibilityEvent(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+ } break;
case DragEvent.ACTION_DRAG_ENTERED: {
mPrivateFlags2 |= View.PFLAG2_DRAG_HOVERED;
refreshDrawableState();
@@ -26966,7 +27072,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED;
refreshDrawableState();
} break;
+ case DragEvent.ACTION_DROP: {
+ if (result && (li.mOnDragListener != null | li.mOnReceiveContentListener != null)) {
+ sendWindowContentChangedAccessibilityEvent(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_DROPPED);
+ }
+ } break;
case DragEvent.ACTION_DRAG_ENDED: {
+ sendWindowContentChangedAccessibilityEvent(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
mPrivateFlags2 &= ~View.DRAG_MASK;
refreshDrawableState();
} break;
@@ -26979,6 +27093,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return (mPrivateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0;
}
+ void sendWindowContentChangedAccessibilityEvent(int changeType) {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ event.setContentChangeTypes(changeType);
+ sendAccessibilityEventUnchecked(event);
+ }
+ }
+
/**
* This needs to be a better API (NOT ON VIEW) before it is exposed. If
* it is ever exposed at all.
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 775c15e77d5d..49f5229d3c09 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -412,6 +412,9 @@ public interface ViewParent {
* <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_SUBTREE}
* <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT}
* <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED}
+ * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_STARTED}
+ * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_CANCELLED}
+ * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_DRAG_DROPPED}
* </ul>
*/
public void notifySubtreeAccessibilityStateChanged(
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 4c36fc939c34..d53286885f85 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -648,6 +648,7 @@ public final class ViewRootImpl implements ViewParent,
/* Drag/drop */
ClipDescription mDragDescription;
View mCurrentDragView;
+ View mStartedDragViewForA11y;
volatile Object mLocalDragState;
final PointF mDragPoint = new PointF();
final PointF mLastTouchPoint = new PointF();
@@ -7573,6 +7574,11 @@ public final class ViewRootImpl implements ViewParent,
if (what == DragEvent.ACTION_DRAG_STARTED) {
mCurrentDragView = null; // Start the current-recipient tracking
mDragDescription = event.mClipDescription;
+ if (mStartedDragViewForA11y != null) {
+ // Send a drag started a11y event
+ mStartedDragViewForA11y.sendWindowContentChangedAccessibilityEvent(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_STARTED);
+ }
} else {
if (what == DragEvent.ACTION_DRAG_ENDED) {
mDragDescription = null;
@@ -7647,6 +7653,16 @@ public final class ViewRootImpl implements ViewParent,
// When the drag operation ends, reset drag-related state
if (what == DragEvent.ACTION_DRAG_ENDED) {
+ if (mStartedDragViewForA11y != null) {
+ // If the drag failed, send a cancelled event from the source. Otherwise,
+ // the View that accepted the drop sends CONTENT_CHANGE_TYPE_DRAG_DROPPED
+ if (!event.getResult()) {
+ mStartedDragViewForA11y.sendWindowContentChangedAccessibilityEvent(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_CANCELLED);
+ }
+ mStartedDragViewForA11y.setAccessibilityDragStarted(false);
+ }
+ mStartedDragViewForA11y = null;
mCurrentDragView = null;
setLocalDragState(null);
mAttachInfo.mDragToken = null;
@@ -7726,6 +7742,13 @@ public final class ViewRootImpl implements ViewParent,
mCurrentDragView = newDragTarget;
}
+ /** Sets the view that started drag and drop for the purpose of sending AccessibilityEvents */
+ void setDragStartedViewForAccessibility(View view) {
+ if (mStartedDragViewForA11y == null) {
+ mStartedDragViewForA11y = view;
+ }
+ }
+
private AudioManager getAudioManager() {
if (mView == null) {
throw new IllegalStateException("getAudioManager called when there is no mView");
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index c413a9ba7a27..ffb7efa67243 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -498,4 +498,9 @@ public class WindowlessWindowManager implements IWindowSession {
public void generateDisplayHash(IWindow window, Rect boundsInWindow, String hashAlgorithm,
RemoteCallback callback) {
}
+
+ @Override
+ public boolean dropForAccessibility(IWindow window, int x, int y) {
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index d12d07ab7448..cc6a8807bb9d 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -22,6 +22,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
import android.content.ClipData;
+import android.content.Context;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -32,6 +33,7 @@ import android.view.Display;
import android.view.IWindow;
import android.view.SurfaceControl;
import android.view.View;
+import android.view.accessibility.AccessibilityManager;
import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
@@ -43,7 +45,8 @@ import java.util.concurrent.atomic.AtomicReference;
*/
class DragDropController {
private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
- private static final long DRAG_TIMEOUT_MS = 5000;
+ static final long DRAG_TIMEOUT_MS = 5000;
+ private static final int A11Y_DRAG_TIMEOUT_DEFAULT_MS = 60000;
// Messages for Handler.
static final int MSG_DRAG_END_TIMEOUT = 0;
@@ -151,36 +154,48 @@ class DragDropController {
mDragState.mOriginalAlpha = alpha;
mDragState.mToken = dragToken;
mDragState.mDisplayContent = displayContent;
+ mDragState.mData = data;
- final Display display = displayContent.getDisplay();
- if (!mCallback.get().registerInputChannel(
- mDragState, display, mService.mInputManager,
- callingWin.mInputChannel)) {
- Slog.e(TAG_WM, "Unable to transfer touch focus");
- return null;
- }
+ if ((flags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) == 0) {
+ final Display display = displayContent.getDisplay();
+ if (!mCallback.get().registerInputChannel(
+ mDragState, display, mService.mInputManager,
+ callingWin.mInputChannel)) {
+ Slog.e(TAG_WM, "Unable to transfer touch focus");
+ return null;
+ }
- final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
- mDragState.mData = data;
- mDragState.broadcastDragStartedLocked(touchX, touchY);
- mDragState.overridePointerIconLocked(touchSource);
- // remember the thumb offsets for later
- mDragState.mThumbOffsetX = thumbCenterX;
- mDragState.mThumbOffsetY = thumbCenterY;
-
- // Make the surface visible at the proper location
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag");
-
- final SurfaceControl.Transaction transaction = mDragState.mTransaction;
- transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha);
- transaction.setPosition(
- surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY);
- transaction.show(surfaceControl);
- displayContent.reparentToOverlay(transaction, surfaceControl);
- callingWin.scheduleAnimation();
-
- if (SHOW_LIGHT_TRANSACTIONS) {
- Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
+ final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
+ mDragState.broadcastDragStartedLocked(touchX, touchY);
+ mDragState.overridePointerIconLocked(touchSource);
+ // remember the thumb offsets for later
+ mDragState.mThumbOffsetX = thumbCenterX;
+ mDragState.mThumbOffsetY = thumbCenterY;
+
+ // Make the surface visible at the proper location
+ if (SHOW_LIGHT_TRANSACTIONS) {
+ Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag");
+ }
+
+ final SurfaceControl.Transaction transaction = mDragState.mTransaction;
+ transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha);
+ transaction.setPosition(
+ surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY);
+ transaction.show(surfaceControl);
+ displayContent.reparentToOverlay(transaction, surfaceControl);
+ callingWin.scheduleAnimation();
+ if (SHOW_LIGHT_TRANSACTIONS) {
+ Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
+ }
+ } else {
+ // Skip surface logic for a drag triggered by an AccessibilityAction
+ mDragState.broadcastDragStartedLocked(touchX, touchY);
+
+ // Timeout for the user to drop the content
+ sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, callingWin.mClient.asBinder(),
+ getAccessibilityManager().getRecommendedTimeoutMillis(
+ A11Y_DRAG_TIMEOUT_DEFAULT_MS,
+ AccessibilityManager.FLAG_CONTENT_CONTROLS));
}
} finally {
if (surface != null) {
@@ -309,10 +324,10 @@ class DragDropController {
/**
* Sends a timeout message to the Handler managed by DragDropController.
*/
- void sendTimeoutMessage(int what, Object arg) {
+ void sendTimeoutMessage(int what, Object arg, long timeoutMs) {
mHandler.removeMessages(what, arg);
final Message msg = mHandler.obtainMessage(what, arg);
- mHandler.sendMessageDelayed(msg, DRAG_TIMEOUT_MS);
+ mHandler.sendMessageDelayed(msg, timeoutMs);
}
/**
@@ -332,6 +347,30 @@ class DragDropController {
}
}
+ boolean dropForAccessibility(IWindow window, float x, float y) {
+ synchronized (mService.mGlobalLock) {
+ final boolean isA11yEnabled = getAccessibilityManager().isEnabled();
+ if (!dragDropActiveLocked()) {
+ return false;
+ }
+ if (mDragState.isAccessibilityDragDrop() && isA11yEnabled) {
+ final WindowState winState = mService.windowForClientLocked(
+ null, window, false);
+ if (!mDragState.isWindowNotified(winState)) {
+ return false;
+ }
+ IBinder token = winState.mInputChannelToken;
+ return mDragState.reportDropWindowLock(token, x, y);
+ }
+ return false;
+ }
+ }
+
+ AccessibilityManager getAccessibilityManager() {
+ return (AccessibilityManager) mService.mContext.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ }
+
private class DragHandler extends Handler {
/**
* Lock for window manager.
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 18ea738b08ce..e92de67377cd 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -253,7 +253,7 @@ class DragState {
mTransaction.reparent(mSurfaceControl, null).apply();
} else {
mDragDropController.sendTimeoutMessage(MSG_REMOVE_DRAG_SURFACE_TIMEOUT,
- mSurfaceControl);
+ mSurfaceControl, DragDropController.DRAG_TIMEOUT_MS);
}
mSurfaceControl = null;
}
@@ -276,9 +276,9 @@ class DragState {
* Notify the drop target and tells it about the data. If the drop event is not sent to the
* target, invokes {@code endDragLocked} immediately.
*/
- void reportDropWindowLock(IBinder token, float x, float y) {
+ boolean reportDropWindowLock(IBinder token, float x, float y) {
if (mAnimator != null) {
- return;
+ return false;
}
final WindowState touchedWin = mService.mInputToWindowMap.get(token);
@@ -288,7 +288,7 @@ class DragState {
mDragResult = false;
endDragLocked();
if (DEBUG_DRAG) Slog.d(TAG_WM, "Drop outside a valid window " + touchedWin);
- return;
+ return false;
}
if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin);
@@ -322,16 +322,19 @@ class DragState {
touchedWin.mClient.dispatchDragEvent(event);
// 5 second timeout for this window to respond to the drop
- mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, clientToken);
+ mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, clientToken,
+ DragDropController.DRAG_TIMEOUT_MS);
} catch (RemoteException e) {
Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
endDragLocked();
+ return false;
} finally {
if (myPid != touchedWin.mSession.mPid) {
event.recycle();
}
}
mToken = clientToken;
+ return true;
}
class InputInterceptor {
@@ -552,7 +555,7 @@ class DragState {
}
}
- private boolean isWindowNotified(WindowState newWin) {
+ boolean isWindowNotified(WindowState newWin) {
for (WindowState ws : mNotifiedWindows) {
if (ws == newWin) {
return true;
@@ -566,8 +569,10 @@ class DragState {
return;
}
if (!mDragResult) {
- mAnimator = createReturnAnimationLocked();
- return; // Will call closeLocked() when the animation is done.
+ if (!isAccessibilityDragDrop()) {
+ mAnimator = createReturnAnimationLocked();
+ return; // Will call closeLocked() when the animation is done.
+ }
}
closeLocked();
}
@@ -576,7 +581,7 @@ class DragState {
if (mAnimator != null) {
return;
}
- if (!mDragInProgress || skipAnimation) {
+ if (!mDragInProgress || skipAnimation || isAccessibilityDragDrop()) {
// mDragInProgress is false if an app invokes Session#cancelDragAndDrop before
// Session#performDrag. Reset the drag state without playing the cancel animation
// because H.DRAG_START_TIMEOUT may be sent to WindowManagerService, which will cause
@@ -721,4 +726,8 @@ class DragState {
mDragDropController.sendHandlerMessage(MSG_ANIMATION_END, null);
}
}
+
+ boolean isAccessibilityDragDrop() {
+ return (mFlags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) != 0;
+ }
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 0b5677724fc3..8c056b2a43b3 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -299,6 +299,17 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
}
+
+ @Override
+ public boolean dropForAccessibility(IWindow window, int x, int y) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mDragDropController.dropForAccessibility(window, x, y);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
/**
* Validates the given drag data.
*/
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 223dc3121cd6..a1e8ca4aa84b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -58,6 +58,7 @@ import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
@@ -70,6 +71,7 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
@@ -97,14 +99,20 @@ public class DragDropControllerTests extends WindowTestsBase {
static class TestDragDropController extends DragDropController {
private Runnable mCloseCallback;
boolean mDeferDragStateClosed;
+ boolean mIsAccessibilityDrag;
TestDragDropController(WindowManagerService service, Looper looper) {
super(service, looper);
}
void setOnClosedCallbackLocked(Runnable runnable) {
- assertTrue(dragDropActiveLocked());
- mCloseCallback = runnable;
+ if (mIsAccessibilityDrag) {
+ // Accessibility does not use animation
+ assertTrue(!dragDropActiveLocked());
+ } else {
+ assertTrue(dragDropActiveLocked());
+ mCloseCallback = runnable;
+ }
}
@Override
@@ -171,6 +179,10 @@ public class DragDropControllerTests extends WindowTestsBase {
}
latch = new CountDownLatch(1);
mTarget.setOnClosedCallbackLocked(latch::countDown);
+ if (mTarget.mIsAccessibilityDrag) {
+ mTarget.mIsAccessibilityDrag = false;
+ return;
+ }
assertTrue(awaitInWmLock(() -> latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)));
}
@@ -180,6 +192,12 @@ public class DragDropControllerTests extends WindowTestsBase {
}
@Test
+ public void testA11yDragFlow() {
+ mTarget.mIsAccessibilityDrag = true;
+ doA11yDragAndDrop(0, ClipData.newPlainText("label", "Test"), 0, 0);
+ }
+
+ @Test
public void testPerformDrag_NullDataWithGrantUri() {
doDragAndDrop(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0);
}
@@ -436,4 +454,22 @@ public class DragDropControllerTests extends WindowTestsBase {
appSession.kill();
}
}
+
+ private void doA11yDragAndDrop(int flags, ClipData data, float dropX, float dropY) {
+ spyOn(mTarget);
+ AccessibilityManager accessibilityManager = Mockito.mock(AccessibilityManager.class);
+ when(accessibilityManager.isEnabled()).thenReturn(true);
+ doReturn(accessibilityManager).when(mTarget).getAccessibilityManager();
+ startA11yDrag(flags, data, () -> {
+ boolean dropped = mTarget.dropForAccessibility(mWindow.mClient, dropX, dropY);
+ mToken = mWindow.mClient.asBinder();
+ });
+ }
+
+ private void startA11yDrag(int flags, ClipData data, Runnable r) {
+ mToken = mTarget.performDrag(0, 0, mWindow.mClient,
+ flags | View.DRAG_FLAG_ACCESSIBILITY_ACTION, null, 0, 0, 0, 0, 0, data);
+ assertNotNull(mToken);
+ r.run();
+ }
}