summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 e38b8838586a..20888f75b55f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -651,6 +651,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();
@@ -7584,6 +7585,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;
@@ -7658,6 +7664,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;
@@ -7737,6 +7753,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 aa257f847e25..4fc123d3f68a 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 {
@@ -553,7 +556,7 @@ class DragState {
}
}
- private boolean isWindowNotified(WindowState newWin) {
+ boolean isWindowNotified(WindowState newWin) {
for (WindowState ws : mNotifiedWindows) {
if (ws == newWin) {
return true;
@@ -567,8 +570,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();
}
@@ -577,7 +582,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
@@ -722,4 +727,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();
+ }
}