Merge "Detect when user wanted to drag vs. click" into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
f6ad8e392e
@@ -224,6 +224,20 @@ class DisplayTopologyPreference(context : Context)
|
|||||||
|
|
||||||
@VisibleForTesting var injector : Injector
|
@VisibleForTesting var injector : Injector
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many physical pixels to move in pane coordinates (Pythagorean distance) before a drag is
|
||||||
|
* considered non-trivial and intentional.
|
||||||
|
*
|
||||||
|
* This value is computed on-demand so that the injector can be changed at any time.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting val accidentalDragDistancePx
|
||||||
|
get() = DisplayTopology.dpToPx(4f, injector.densityDpi)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How long before until a tap is considered a drag regardless of distance moved.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting val accidentalDragTimeLimitMs = 800L
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is needed to prevent a repopulation of the pane causing another
|
* This is needed to prevent a repopulation of the pane causing another
|
||||||
* relayout and vice-versa ad infinitum.
|
* relayout and vice-versa ad infinitum.
|
||||||
@@ -295,10 +309,14 @@ class DisplayTopologyPreference(context : Context)
|
|||||||
open val wallpaper: Bitmap?
|
open val wallpaper: Bitmap?
|
||||||
get() = WallpaperManager.getInstance(context).bitmap
|
get() = WallpaperManager.getInstance(context).bitmap
|
||||||
|
|
||||||
open val densityDpi: Int
|
/**
|
||||||
get() {
|
* This density is the density of the current display (showing the topology pane). It is
|
||||||
|
* necessary to use this density here because the topology pane coordinates are in physical
|
||||||
|
* pixels, and the display coordinates are in density-independent pixels.
|
||||||
|
*/
|
||||||
|
open val densityDpi: Int by lazy {
|
||||||
val info = DisplayInfo()
|
val info = DisplayInfo()
|
||||||
return if (context.display.getDisplayInfo(info)) {
|
if (context.display.getDisplayInfo(info)) {
|
||||||
info.logicalDensityDpi
|
info.logicalDensityDpi
|
||||||
} else {
|
} else {
|
||||||
DisplayMetrics.DENSITY_DEFAULT
|
DisplayMetrics.DENSITY_DEFAULT
|
||||||
@@ -323,23 +341,29 @@ class DisplayTopologyPreference(context : Context)
|
|||||||
val positions: List<Pair<Int, RectF>>)
|
val positions: List<Pair<Int, RectF>>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds information about the current drag operation.
|
* Holds information about the current drag operation. The initial rawX, rawY values of the
|
||||||
|
* cursor are recorded in order to detect whether the drag was a substantial drag or likely
|
||||||
|
* accidental.
|
||||||
|
*
|
||||||
* @param stationaryDisps ID and position of displays that are not moving
|
* @param stationaryDisps ID and position of displays that are not moving
|
||||||
* @param display View that is currently being dragged
|
* @param display View that is currently being dragged
|
||||||
* @param displayId ID of display being dragged
|
* @param displayId ID of display being dragged
|
||||||
* @param displayWidth width of display being dragged in actual (not View) coordinates
|
* @param displayWidth width of display being dragged in actual (not View) coordinates
|
||||||
* @param displayHeight height of display being dragged in actual (not View) coordinates
|
* @param displayHeight height of display being dragged in actual (not View) coordinates
|
||||||
* @param dragOffsetX difference between event rawX coordinate and X of the display in the pane
|
* @param initialBlockX block's X coordinate upon touch down event
|
||||||
* @param dragOffsetY difference between event rawY coordinate and Y of the display in the pane
|
* @param initialBlockY block's Y coordinate upon touch down event
|
||||||
* @param didMove true if we have detected the user intentionally wanted to drag rather than
|
* @param initialTouchX rawX value of the touch down event
|
||||||
* just click
|
* @param initialTouchY rawY value of the touch down event
|
||||||
|
* @param startTimeMs time when tap down occurred, needed to detect the user intentionally
|
||||||
|
* wanted to drag rather than just click
|
||||||
*/
|
*/
|
||||||
private data class BlockDrag(
|
private data class BlockDrag(
|
||||||
val stationaryDisps : List<Pair<Int, RectF>>,
|
val stationaryDisps : List<Pair<Int, RectF>>,
|
||||||
val display: DisplayBlock, val displayId: Int,
|
val display: DisplayBlock, val displayId: Int,
|
||||||
val displayWidth: Float, val displayHeight: Float,
|
val displayWidth: Float, val displayHeight: Float,
|
||||||
val dragOffsetX: Float, val dragOffsetY: Float,
|
val initialBlockX: Float, val initialBlockY: Float,
|
||||||
var didMove: Boolean = false)
|
val initialTouchX: Float, val initialTouchY: Float,
|
||||||
|
val startTimeMs: Long)
|
||||||
|
|
||||||
private var mTopologyInfo : TopologyInfo? = null
|
private var mTopologyInfo : TopologyInfo? = null
|
||||||
private var mDrag : BlockDrag? = null
|
private var mDrag : BlockDrag? = null
|
||||||
@@ -394,14 +418,10 @@ class DisplayTopologyPreference(context : Context)
|
|||||||
recycleableBlocks.add(mPaneContent.getChildAt(i) as DisplayBlock)
|
recycleableBlocks.add(mPaneContent.getChildAt(i) as DisplayBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This density is the density of the current display (showing the topology pane). It is
|
|
||||||
// necessary to use this density here because the topology pane coordinates are in physical
|
|
||||||
// pixels, and the display coordinates are in density-independent pixels.
|
|
||||||
val dpi = injector.densityDpi
|
|
||||||
val scaling = TopologyScale(
|
val scaling = TopologyScale(
|
||||||
mPaneContent.width,
|
mPaneContent.width,
|
||||||
minEdgeLength = DisplayTopology.dpToPx(60f, dpi),
|
minEdgeLength = DisplayTopology.dpToPx(60f, injector.densityDpi),
|
||||||
maxEdgeLength = DisplayTopology.dpToPx(256f, dpi),
|
maxEdgeLength = DisplayTopology.dpToPx(256f, injector.densityDpi),
|
||||||
newBounds.map { it.second }.toList())
|
newBounds.map { it.second }.toList())
|
||||||
mPaneHolder.layoutParams.let {
|
mPaneHolder.layoutParams.let {
|
||||||
val newHeight = scaling.paneHeight.toInt()
|
val newHeight = scaling.paneHeight.toInt()
|
||||||
@@ -431,7 +451,7 @@ class DisplayTopologyPreference(context : Context)
|
|||||||
when (ev.actionMasked) {
|
when (ev.actionMasked) {
|
||||||
MotionEvent.ACTION_DOWN -> onBlockTouchDown(id, pos, block, ev)
|
MotionEvent.ACTION_DOWN -> onBlockTouchDown(id, pos, block, ev)
|
||||||
MotionEvent.ACTION_MOVE -> onBlockTouchMove(ev)
|
MotionEvent.ACTION_MOVE -> onBlockTouchMove(ev)
|
||||||
MotionEvent.ACTION_UP -> onBlockTouchUp()
|
MotionEvent.ACTION_UP -> onBlockTouchUp(ev)
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -462,7 +482,10 @@ class DisplayTopologyPreference(context : Context)
|
|||||||
// moving, and the raw coordinates are relative to the screen.
|
// moving, and the raw coordinates are relative to the screen.
|
||||||
mDrag = BlockDrag(
|
mDrag = BlockDrag(
|
||||||
stationaryDisps.toList(), block, displayId, displayPos.width(), displayPos.height(),
|
stationaryDisps.toList(), block, displayId, displayPos.width(), displayPos.height(),
|
||||||
ev.rawX - block.x, ev.rawY - block.y)
|
initialBlockX = block.x, initialBlockY = block.y,
|
||||||
|
initialTouchX = ev.rawX, initialTouchY = ev.rawY,
|
||||||
|
startTimeMs = ev.eventTime,
|
||||||
|
)
|
||||||
|
|
||||||
// Prevents a container of this view from intercepting the touch events in the case the
|
// Prevents a container of this view from intercepting the touch events in the case the
|
||||||
// pointer moves outside of the display block or the pane.
|
// pointer moves outside of the display block or the pane.
|
||||||
@@ -474,30 +497,31 @@ class DisplayTopologyPreference(context : Context)
|
|||||||
val drag = mDrag ?: return false
|
val drag = mDrag ?: return false
|
||||||
val topology = mTopologyInfo ?: return false
|
val topology = mTopologyInfo ?: return false
|
||||||
val dispDragCoor = topology.scaling.paneToDisplayCoor(
|
val dispDragCoor = topology.scaling.paneToDisplayCoor(
|
||||||
ev.rawX - drag.dragOffsetX, ev.rawY - drag.dragOffsetY)
|
ev.rawX - drag.initialTouchX + drag.initialBlockX,
|
||||||
|
ev.rawY - drag.initialTouchY + drag.initialBlockY)
|
||||||
val dispDragRect = RectF(
|
val dispDragRect = RectF(
|
||||||
dispDragCoor.x, dispDragCoor.y,
|
dispDragCoor.x, dispDragCoor.y,
|
||||||
dispDragCoor.x + drag.displayWidth, dispDragCoor.y + drag.displayHeight)
|
dispDragCoor.x + drag.displayWidth, dispDragCoor.y + drag.displayHeight)
|
||||||
val snapRect = clampPosition(drag.stationaryDisps.map { it.second }, dispDragRect)
|
val snapRect = clampPosition(drag.stationaryDisps.map { it.second }, dispDragRect)
|
||||||
|
|
||||||
drag.display.place(topology.scaling.displayToPaneCoor(snapRect.left, snapRect.top))
|
drag.display.place(topology.scaling.displayToPaneCoor(snapRect.left, snapRect.top))
|
||||||
drag.didMove = true
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onBlockTouchUp(): Boolean {
|
private fun onBlockTouchUp(ev: MotionEvent): Boolean {
|
||||||
val drag = mDrag ?: return false
|
val drag = mDrag ?: return false
|
||||||
val topology = mTopologyInfo ?: return false
|
val topology = mTopologyInfo ?: return false
|
||||||
mPaneContent.requestDisallowInterceptTouchEvent(false)
|
mPaneContent.requestDisallowInterceptTouchEvent(false)
|
||||||
drag.display.setHighlighted(false)
|
drag.display.setHighlighted(false)
|
||||||
|
|
||||||
mDrag = null
|
val netPxDragged = Math.hypot(
|
||||||
if (!drag.didMove) {
|
(drag.initialBlockX - drag.display.x).toDouble(),
|
||||||
// If no move event occurred, ignore the drag completely.
|
(drag.initialBlockY - drag.display.y).toDouble())
|
||||||
// TODO(b/352648432): Responding to a single move event no matter how small may be too
|
val timeDownMs = ev.eventTime - drag.startTimeMs
|
||||||
// sensitive. It is easy to slide by a small amount just by force of pressing down the
|
if (netPxDragged < accidentalDragDistancePx && timeDownMs < accidentalDragTimeLimitMs) {
|
||||||
// mouse button. Keep an eye on this.
|
drag.display.x = drag.initialBlockX
|
||||||
|
drag.display.y = drag.initialBlockY
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -419,4 +419,97 @@ class DisplayTopologyPreferenceTest {
|
|||||||
.build())
|
.build())
|
||||||
assertThat(leftBlock.background).isEqualTo(leftBlock.mUnselectedImage)
|
assertThat(leftBlock.background).isEqualTo(leftBlock.mUnselectedImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun dragBlockWithOneMoveEvent(
|
||||||
|
block: DisplayBlock, startTimeMs: Long, endTimeMs: Long, xDiff: Float, yDiff: Float) {
|
||||||
|
block.dispatchTouchEvent(MotionEventBuilder.newBuilder()
|
||||||
|
.setAction(MotionEvent.ACTION_DOWN)
|
||||||
|
.setPointer(0f, 0f)
|
||||||
|
.setEventTime(startTimeMs)
|
||||||
|
.build())
|
||||||
|
block.dispatchTouchEvent(MotionEventBuilder.newBuilder()
|
||||||
|
.setAction(MotionEvent.ACTION_MOVE)
|
||||||
|
.setPointer(xDiff, yDiff)
|
||||||
|
.setEventTime((startTimeMs + endTimeMs) / 2)
|
||||||
|
.build())
|
||||||
|
block.dispatchTouchEvent(MotionEventBuilder.newBuilder()
|
||||||
|
.setAction(MotionEvent.ACTION_UP)
|
||||||
|
.setPointer(xDiff, yDiff)
|
||||||
|
.setEventTime(endTimeMs)
|
||||||
|
.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun accidentalDrag_LittleAndBriefEnoughToBeAccidental() {
|
||||||
|
val (leftBlock, _) = setupTwoDisplays(POSITION_LEFT, childOffset = 42f)
|
||||||
|
val startTime = 424242L
|
||||||
|
val startX = leftBlock.x
|
||||||
|
val startY = leftBlock.y
|
||||||
|
|
||||||
|
preference.mTimesRefreshedBlocks = 0
|
||||||
|
dragBlockWithOneMoveEvent(
|
||||||
|
leftBlock, startTime,
|
||||||
|
endTimeMs = startTime + preference.accidentalDragTimeLimitMs - 10,
|
||||||
|
xDiff = preference.accidentalDragDistancePx - 1f, yDiff = 0f,
|
||||||
|
)
|
||||||
|
assertThat(leftBlock.x).isEqualTo(startX)
|
||||||
|
assertThat(preference.mTimesRefreshedBlocks).isEqualTo(0)
|
||||||
|
|
||||||
|
dragBlockWithOneMoveEvent(
|
||||||
|
leftBlock, startTime,
|
||||||
|
endTimeMs = startTime + preference.accidentalDragTimeLimitMs - 10,
|
||||||
|
xDiff = 0f, yDiff = preference.accidentalDragDistancePx - 1f,
|
||||||
|
)
|
||||||
|
assertThat(leftBlock.y).isEqualTo(startY)
|
||||||
|
assertThat(preference.mTimesRefreshedBlocks).isEqualTo(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun accidentalDrag_TooFarToBeAccidentalXAxis() {
|
||||||
|
val (topBlock, _) = setupTwoDisplays(POSITION_TOP, childOffset = -42f)
|
||||||
|
val startTime = 88888L
|
||||||
|
val startX = topBlock.x
|
||||||
|
|
||||||
|
preference.mTimesRefreshedBlocks = 0
|
||||||
|
dragBlockWithOneMoveEvent(
|
||||||
|
topBlock, startTime,
|
||||||
|
endTimeMs = startTime + preference.accidentalDragTimeLimitMs - 10,
|
||||||
|
xDiff = preference.accidentalDragDistancePx + 1f, yDiff = 0f,
|
||||||
|
)
|
||||||
|
assertThat(preference.mTimesRefreshedBlocks).isEqualTo(1)
|
||||||
|
assertThat(topBlock.x).isNotEqualTo(startX)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun accidentalDrag_TooFarToBeAccidentalYAxis() {
|
||||||
|
val (leftBlock, _) = setupTwoDisplays(POSITION_LEFT, childOffset = 42f)
|
||||||
|
val startTime = 88888L
|
||||||
|
val startY = leftBlock.y
|
||||||
|
|
||||||
|
preference.mTimesRefreshedBlocks = 0
|
||||||
|
dragBlockWithOneMoveEvent(
|
||||||
|
leftBlock, startTime,
|
||||||
|
endTimeMs = startTime + preference.accidentalDragTimeLimitMs - 10,
|
||||||
|
xDiff = 0f, yDiff = preference.accidentalDragDistancePx + 1f,
|
||||||
|
)
|
||||||
|
assertThat(leftBlock.y).isNotEqualTo(startY)
|
||||||
|
assertThat(preference.mTimesRefreshedBlocks).isEqualTo(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun accidentalDrag_TooSlowToBeAccidental() {
|
||||||
|
val (topBlock, _) = setupTwoDisplays(POSITION_TOP, childOffset = -42f)
|
||||||
|
val startTime = 88888L
|
||||||
|
val startX = topBlock.x
|
||||||
|
val startY = topBlock.y
|
||||||
|
|
||||||
|
preference.mTimesRefreshedBlocks = 0
|
||||||
|
dragBlockWithOneMoveEvent(
|
||||||
|
topBlock, startTime,
|
||||||
|
endTimeMs = startTime + preference.accidentalDragTimeLimitMs + 10,
|
||||||
|
xDiff = preference.accidentalDragDistancePx - 1f, yDiff = 0f,
|
||||||
|
)
|
||||||
|
assertThat(topBlock.x).isNotEqualTo(startX)
|
||||||
|
assertThat(preference.mTimesRefreshedBlocks).isEqualTo(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user