Merge "Add accessibility support for Drag & Drop" into sc-v2-dev

This commit is contained in:
Sally Yuen
2021-09-27 04:44:18 +00:00
committed by Android (Google) Code Review
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, IBinder performDrag(IWindow window, int flags, in SurfaceControl surface, int touchSource,
float touchX, float touchY, float thumbCenterX, float thumbCenterY, in ClipData data); 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. * Report the result of a drop action targeted to the given window.
* consumed is 'true' when the drop was accepted by a valid recipient, * 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_ALLOW_CLICK_WHEN_DISABLED
* 1 PFLAG4_DETACHED * 1 PFLAG4_DETACHED
* 1 PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE * 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; 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 */ /* End of masks for mPrivateFlags4 */
/** @hide */ /** @hide */
@@ -10384,8 +10390,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (mTouchDelegate != null) { if (mTouchDelegate != null) {
info.setTouchDelegateInfo(mTouchDelegate.getTouchDelegateInfo()); 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 * Adds extra data to an {@link AccessibilityNodeInfo} based on an explicit request for the
* additional data. * additional data.
@@ -14222,9 +14237,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return true; 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; 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, private boolean traverseAtGranularity(int granularity, boolean forward,
boolean extendSelection) { boolean extendSelection) {
CharSequence text = getIterableTextForAccessibility(); CharSequence text = getIterableTextForAccessibility();
@@ -26664,6 +26715,37 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0); 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 shadowSize = new Point();
Point shadowTouchPoint = new Point(); Point shadowTouchPoint = new Point();
shadowBuilder.onProvideShadowMetrics(shadowSize, shadowTouchPoint); shadowBuilder.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
@@ -26688,7 +26770,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
+ " shadowX=" + shadowTouchPoint.x + " shadowY=" + shadowTouchPoint.y); + " shadowX=" + shadowTouchPoint.x + " shadowY=" + shadowTouchPoint.y);
} }
final ViewRootImpl root = mAttachInfo.mViewRootImpl;
final SurfaceSession session = new SurfaceSession(); final SurfaceSession session = new SurfaceSession();
final SurfaceControl surfaceControl = new SurfaceControl.Builder(session) final SurfaceControl surfaceControl = new SurfaceControl.Builder(session)
.setName("drag surface") .setName("drag surface")
@@ -26711,12 +26792,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
surface.unlockCanvasAndPost(canvas); surface.unlockCanvasAndPost(canvas);
} }
// repurpose 'shadowSize' for the last touch point token = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, flags, surfaceControl,
root.getLastTouchPoint(shadowSize); root.getLastTouchSource(), lastTouchPoint.x, lastTouchPoint.y,
shadowTouchPoint.x, shadowTouchPoint.y, data);
token = mAttachInfo.mSession.performDrag(
mAttachInfo.mWindow, flags, surfaceControl, root.getLastTouchSource(),
shadowSize.x, shadowSize.y, shadowTouchPoint.x, shadowTouchPoint.y, data);
if (ViewDebug.DEBUG_DRAG) { if (ViewDebug.DEBUG_DRAG) {
Log.d(VIEW_LOG_TAG, "performDrag returned " + token); Log.d(VIEW_LOG_TAG, "performDrag returned " + token);
} }
@@ -26728,6 +26806,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mAttachInfo.mDragToken = token; mAttachInfo.mDragToken = token;
// Cache the local state object for delivery with DragEvents // Cache the local state object for delivery with DragEvents
root.setLocalDragState(myLocalState); root.setLocalDragState(myLocalState);
if (a11yEnabled) {
// Set for AccessibilityEvents
mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
}
} }
return token != null; return token != null;
} catch (Exception e) { } 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. * Cancels an ongoing drag and drop operation.
* <p> * <p>
@@ -26958,6 +27058,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
} }
switch (event.mAction) { 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: { case DragEvent.ACTION_DRAG_ENTERED: {
mPrivateFlags2 |= View.PFLAG2_DRAG_HOVERED; mPrivateFlags2 |= View.PFLAG2_DRAG_HOVERED;
refreshDrawableState(); refreshDrawableState();
@@ -26966,7 +27072,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED; mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED;
refreshDrawableState(); refreshDrawableState();
} break; } 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: { case DragEvent.ACTION_DRAG_ENDED: {
sendWindowContentChangedAccessibilityEvent(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
mPrivateFlags2 &= ~View.DRAG_MASK; mPrivateFlags2 &= ~View.DRAG_MASK;
refreshDrawableState(); refreshDrawableState();
} break; } break;
@@ -26979,6 +27093,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return (mPrivateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0; 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 * This needs to be a better API (NOT ON VIEW) before it is exposed. If
* it is ever exposed at all. * 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_SUBTREE}
* <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT} * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT}
* <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED} * <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> * </ul>
*/ */
public void notifySubtreeAccessibilityStateChanged( public void notifySubtreeAccessibilityStateChanged(

View File

@@ -651,6 +651,7 @@ public final class ViewRootImpl implements ViewParent,
/* Drag/drop */ /* Drag/drop */
ClipDescription mDragDescription; ClipDescription mDragDescription;
View mCurrentDragView; View mCurrentDragView;
View mStartedDragViewForA11y;
volatile Object mLocalDragState; volatile Object mLocalDragState;
final PointF mDragPoint = new PointF(); final PointF mDragPoint = new PointF();
final PointF mLastTouchPoint = new PointF(); final PointF mLastTouchPoint = new PointF();
@@ -7584,6 +7585,11 @@ public final class ViewRootImpl implements ViewParent,
if (what == DragEvent.ACTION_DRAG_STARTED) { if (what == DragEvent.ACTION_DRAG_STARTED) {
mCurrentDragView = null; // Start the current-recipient tracking mCurrentDragView = null; // Start the current-recipient tracking
mDragDescription = event.mClipDescription; mDragDescription = event.mClipDescription;
if (mStartedDragViewForA11y != null) {
// Send a drag started a11y event
mStartedDragViewForA11y.sendWindowContentChangedAccessibilityEvent(
AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_STARTED);
}
} else { } else {
if (what == DragEvent.ACTION_DRAG_ENDED) { if (what == DragEvent.ACTION_DRAG_ENDED) {
mDragDescription = null; mDragDescription = null;
@@ -7658,6 +7664,16 @@ public final class ViewRootImpl implements ViewParent,
// When the drag operation ends, reset drag-related state // When the drag operation ends, reset drag-related state
if (what == DragEvent.ACTION_DRAG_ENDED) { 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; mCurrentDragView = null;
setLocalDragState(null); setLocalDragState(null);
mAttachInfo.mDragToken = null; mAttachInfo.mDragToken = null;
@@ -7737,6 +7753,13 @@ public final class ViewRootImpl implements ViewParent,
mCurrentDragView = newDragTarget; 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() { private AudioManager getAudioManager() {
if (mView == null) { if (mView == null) {
throw new IllegalStateException("getAudioManager called when there is no mView"); 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, public void generateDisplayHash(IWindow window, Rect boundsInWindow, String hashAlgorithm,
RemoteCallback callback) { 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.annotation.NonNull;
import android.content.ClipData; import android.content.ClipData;
import android.content.Context;
import android.os.Binder; import android.os.Binder;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
@@ -32,6 +33,7 @@ import android.view.Display;
import android.view.IWindow; import android.view.IWindow;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.view.View; import android.view.View;
import android.view.accessibility.AccessibilityManager;
import com.android.server.wm.WindowManagerInternal.IDragDropCallback; import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
@@ -43,7 +45,8 @@ import java.util.concurrent.atomic.AtomicReference;
*/ */
class DragDropController { class DragDropController {
private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f; 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. // Messages for Handler.
static final int MSG_DRAG_END_TIMEOUT = 0; static final int MSG_DRAG_END_TIMEOUT = 0;
@@ -151,36 +154,48 @@ class DragDropController {
mDragState.mOriginalAlpha = alpha; mDragState.mOriginalAlpha = alpha;
mDragState.mToken = dragToken; mDragState.mToken = dragToken;
mDragState.mDisplayContent = displayContent; 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.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 ((flags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) == 0) {
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag"); 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; final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha); mDragState.broadcastDragStartedLocked(touchX, touchY);
transaction.setPosition( mDragState.overridePointerIconLocked(touchSource);
surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY); // remember the thumb offsets for later
transaction.show(surfaceControl); mDragState.mThumbOffsetX = thumbCenterX;
displayContent.reparentToOverlay(transaction, surfaceControl); mDragState.mThumbOffsetY = thumbCenterY;
callingWin.scheduleAnimation();
if (SHOW_LIGHT_TRANSACTIONS) { // Make the surface visible at the proper location
Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag"); 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 { } finally {
if (surface != null) { if (surface != null) {
@@ -309,10 +324,10 @@ class DragDropController {
/** /**
* Sends a timeout message to the Handler managed by 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); mHandler.removeMessages(what, arg);
final Message msg = mHandler.obtainMessage(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 { private class DragHandler extends Handler {
/** /**
* Lock for window manager. * Lock for window manager.

View File

@@ -253,7 +253,7 @@ class DragState {
mTransaction.reparent(mSurfaceControl, null).apply(); mTransaction.reparent(mSurfaceControl, null).apply();
} else { } else {
mDragDropController.sendTimeoutMessage(MSG_REMOVE_DRAG_SURFACE_TIMEOUT, mDragDropController.sendTimeoutMessage(MSG_REMOVE_DRAG_SURFACE_TIMEOUT,
mSurfaceControl); mSurfaceControl, DragDropController.DRAG_TIMEOUT_MS);
} }
mSurfaceControl = null; 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 * Notify the drop target and tells it about the data. If the drop event is not sent to the
* target, invokes {@code endDragLocked} immediately. * target, invokes {@code endDragLocked} immediately.
*/ */
void reportDropWindowLock(IBinder token, float x, float y) { boolean reportDropWindowLock(IBinder token, float x, float y) {
if (mAnimator != null) { if (mAnimator != null) {
return; return false;
} }
final WindowState touchedWin = mService.mInputToWindowMap.get(token); final WindowState touchedWin = mService.mInputToWindowMap.get(token);
@@ -288,7 +288,7 @@ class DragState {
mDragResult = false; mDragResult = false;
endDragLocked(); endDragLocked();
if (DEBUG_DRAG) Slog.d(TAG_WM, "Drop outside a valid window " + touchedWin); 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); if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin);
@@ -322,16 +322,19 @@ class DragState {
touchedWin.mClient.dispatchDragEvent(event); touchedWin.mClient.dispatchDragEvent(event);
// 5 second timeout for this window to respond to the drop // 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) { } catch (RemoteException e) {
Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin); Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
endDragLocked(); endDragLocked();
return false;
} finally { } finally {
if (myPid != touchedWin.mSession.mPid) { if (myPid != touchedWin.mSession.mPid) {
event.recycle(); event.recycle();
} }
} }
mToken = clientToken; mToken = clientToken;
return true;
} }
class InputInterceptor { class InputInterceptor {
@@ -553,7 +556,7 @@ class DragState {
} }
} }
private boolean isWindowNotified(WindowState newWin) { boolean isWindowNotified(WindowState newWin) {
for (WindowState ws : mNotifiedWindows) { for (WindowState ws : mNotifiedWindows) {
if (ws == newWin) { if (ws == newWin) {
return true; return true;
@@ -567,8 +570,10 @@ class DragState {
return; return;
} }
if (!mDragResult) { if (!mDragResult) {
mAnimator = createReturnAnimationLocked(); if (!isAccessibilityDragDrop()) {
return; // Will call closeLocked() when the animation is done. mAnimator = createReturnAnimationLocked();
return; // Will call closeLocked() when the animation is done.
}
} }
closeLocked(); closeLocked();
} }
@@ -577,7 +582,7 @@ class DragState {
if (mAnimator != null) { if (mAnimator != null) {
return; return;
} }
if (!mDragInProgress || skipAnimation) { if (!mDragInProgress || skipAnimation || isAccessibilityDragDrop()) {
// mDragInProgress is false if an app invokes Session#cancelDragAndDrop before // mDragInProgress is false if an app invokes Session#cancelDragAndDrop before
// Session#performDrag. Reset the drag state without playing the cancel animation // Session#performDrag. Reset the drag state without playing the cancel animation
// because H.DRAG_START_TIMEOUT may be sent to WindowManagerService, which will cause // 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); 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. * Validates the given drag data.
*/ */

View File

@@ -58,6 +58,7 @@ import android.view.SurfaceControl;
import android.view.SurfaceSession; import android.view.SurfaceSession;
import android.view.View; import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
@@ -70,6 +71,7 @@ import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mockito;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@@ -97,14 +99,20 @@ public class DragDropControllerTests extends WindowTestsBase {
static class TestDragDropController extends DragDropController { static class TestDragDropController extends DragDropController {
private Runnable mCloseCallback; private Runnable mCloseCallback;
boolean mDeferDragStateClosed; boolean mDeferDragStateClosed;
boolean mIsAccessibilityDrag;
TestDragDropController(WindowManagerService service, Looper looper) { TestDragDropController(WindowManagerService service, Looper looper) {
super(service, looper); super(service, looper);
} }
void setOnClosedCallbackLocked(Runnable runnable) { void setOnClosedCallbackLocked(Runnable runnable) {
assertTrue(dragDropActiveLocked()); if (mIsAccessibilityDrag) {
mCloseCallback = runnable; // Accessibility does not use animation
assertTrue(!dragDropActiveLocked());
} else {
assertTrue(dragDropActiveLocked());
mCloseCallback = runnable;
}
} }
@Override @Override
@@ -171,6 +179,10 @@ public class DragDropControllerTests extends WindowTestsBase {
} }
latch = new CountDownLatch(1); latch = new CountDownLatch(1);
mTarget.setOnClosedCallbackLocked(latch::countDown); mTarget.setOnClosedCallbackLocked(latch::countDown);
if (mTarget.mIsAccessibilityDrag) {
mTarget.mIsAccessibilityDrag = false;
return;
}
assertTrue(awaitInWmLock(() -> latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS))); 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); doDragAndDrop(0, ClipData.newPlainText("label", "Test"), 0, 0);
} }
@Test
public void testA11yDragFlow() {
mTarget.mIsAccessibilityDrag = true;
doA11yDragAndDrop(0, ClipData.newPlainText("label", "Test"), 0, 0);
}
@Test @Test
public void testPerformDrag_NullDataWithGrantUri() { public void testPerformDrag_NullDataWithGrantUri() {
doDragAndDrop(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0); 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(); 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();
}
} }