Merge "Add accessibility support for Drag & Drop" into sc-v2-dev am: 69b9a3fb5e

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15667830

Change-Id: I16ee567270b03f69a761590b3b38ab369b874768
This commit is contained in:
Sally Yuen
2021-09-27 05:00:08 +00:00
committed by Automerger Merge Worker
9 changed files with 301 additions and 47 deletions

View File

@@ -174,6 +174,11 @@ interface IWindowSession {
IBinder performDrag(IWindow window, int flags, in SurfaceControl surface, int touchSource,
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,

View File

@@ -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.

View File

@@ -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(

View File

@@ -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");

View File

@@ -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;
}
}

View File

@@ -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;
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");
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.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();
final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
mDragState.broadcastDragStartedLocked(touchX, touchY);
mDragState.overridePointerIconLocked(touchSource);
// remember the thumb offsets for later
mDragState.mThumbOffsetX = thumbCenterX;
mDragState.mThumbOffsetY = thumbCenterY;
if (SHOW_LIGHT_TRANSACTIONS) {
Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
// 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.

View File

@@ -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;
}
}

View File

@@ -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.
*/

View File

@@ -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)));
}
@@ -179,6 +191,12 @@ public class DragDropControllerTests extends WindowTestsBase {
doDragAndDrop(0, ClipData.newPlainText("label", "Test"), 0, 0);
}
@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();
}
}