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:
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user