diff options
| -rw-r--r-- | core/java/android/view/IWindowSession.aidl | 5 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 137 | ||||
| -rw-r--r-- | core/java/android/view/ViewParent.java | 3 | ||||
| -rw-r--r-- | core/java/android/view/ViewRootImpl.java | 23 | ||||
| -rw-r--r-- | core/java/android/view/WindowlessWindowManager.java | 5 | ||||
| -rw-r--r-- | services/core/java/com/android/server/wm/DragDropController.java | 101 | ||||
| -rw-r--r-- | services/core/java/com/android/server/wm/DragState.java | 27 | ||||
| -rw-r--r-- | services/core/java/com/android/server/wm/Session.java | 11 | ||||
| -rw-r--r-- | services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java | 40 |
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(); + } } |