Merge "Add accessibility support for Drag & Drop" into sc-v2-dev
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user