Skip to content

Commit b4f0f19

Browse files
Mugurellmergify[bot]
authored andcommitted
Fix mozilla-mobile#9614 - Add a new InputResultDetail
This will serve the following purposes: - wrapper for all the new data from GeckoView's onTouchEventForDetailResult - filters out values not in range (eg: GV's INPUT_RESULT_IGNORED) - controls how the data can be updated and offers clear APIs for querying this data without needing to know about the implementation specifics.
1 parent 666f3c4 commit b4f0f19

File tree

2 files changed

+795
-0
lines changed

2 files changed

+795
-0
lines changed
Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
package mozilla.components.concept.engine
6+
7+
import android.view.MotionEvent
8+
import androidx.annotation.VisibleForTesting
9+
10+
// The below top-level values are following the same from [org.mozilla.geckoview.PanZoomController]
11+
12+
/**
13+
* The content has no scrollable element.
14+
*
15+
* @see [InputResultDetail.isTouchUnhandled]
16+
*/
17+
@VisibleForTesting
18+
internal const val INPUT_UNHANDLED = 0
19+
20+
/**
21+
* The touch event is consumed by the [EngineView]
22+
*
23+
* @see [InputResultDetail.isTouchHandledByBrowser]
24+
*/
25+
@VisibleForTesting
26+
internal const val INPUT_HANDLED = 1
27+
28+
/**
29+
* The touch event is consumed by the website through it's own touch listeners.
30+
*
31+
* @see [InputResultDetail.isTouchHandledByWebsite]
32+
*/
33+
@VisibleForTesting
34+
internal const val INPUT_HANDLED_CONTENT = 2
35+
36+
/**
37+
* The website content is not scrollable.
38+
*/
39+
@VisibleForTesting
40+
internal const val SCROLL_DIRECTIONS_NONE = 0
41+
42+
/**
43+
* The website content can be scrolled to the top.
44+
*
45+
* @see [InputResultDetail.canScrollToTop]
46+
*/
47+
@VisibleForTesting
48+
internal const val SCROLL_DIRECTIONS_TOP = 1 shl 0
49+
50+
/**
51+
* The website content can be scrolled to the right.
52+
*
53+
* @see [InputResultDetail.canScrollToRight]
54+
*/
55+
@VisibleForTesting
56+
internal const val SCROLL_DIRECTIONS_RIGHT = 1 shl 1
57+
58+
/**
59+
* The website content can be scrolled to the bottom.
60+
*
61+
* @see [InputResultDetail.canScrollToBottom]
62+
*/
63+
@VisibleForTesting
64+
internal const val SCROLL_DIRECTIONS_BOTTOM = 1 shl 2
65+
66+
/**
67+
* The website content can be scrolled to the left.
68+
*
69+
* @see [InputResultDetail.canScrollToLeft]
70+
*/
71+
@VisibleForTesting
72+
internal const val SCROLL_DIRECTIONS_LEFT = 1 shl 3
73+
74+
/**
75+
* The website content cannot be overscrolled.
76+
*/
77+
@VisibleForTesting
78+
internal const val OVERSCROLL_DIRECTIONS_NONE = 0
79+
80+
/**
81+
* The website content can be overscrolled horizontally.
82+
*
83+
* @see [InputResultDetail.canOverscrollRight]
84+
* @see [InputResultDetail.canOverscrollLeft]
85+
*/
86+
@VisibleForTesting
87+
internal const val OVERSCROLL_DIRECTIONS_HORIZONTAL = 1 shl 0
88+
89+
/**
90+
* The website content can be overscrolled vertically.
91+
*
92+
* @see [InputResultDetail.canOverscrollTop]
93+
* @see [InputResultDetail.canOverscrollBottom]
94+
*/
95+
@VisibleForTesting
96+
internal const val OVERSCROLL_DIRECTIONS_VERTICAL = 1 shl 1
97+
98+
/**
99+
* All data about how a touch will be handled by the browser.
100+
* - whether the event is used for panning/zooming by the browser / by the website or will be ignored.
101+
* - whether the event can scroll the page and in what direction.
102+
* - whether the event can overscroll the page and in what direction.
103+
*
104+
* @param inputResult Indicates who will use the current [MotionEvent].
105+
* Possible values: [[INPUT_UNHANDLED], [INPUT_HANDLED], [INPUT_HANDLED_CONTENT]]<br>.
106+
*
107+
* @param scrollDirections Bitwise ORed value of the directions the page can be scrolled to.
108+
* This is the same as GeckoView's [org.mozilla.geckoview.PanZoomController.ScrollableDirections].
109+
*
110+
* @param overscrollDirections Bitwise ORed value of the directions the page can be overscrolled to.
111+
* This is the same as GeckoView's [org.mozilla.geckoview.PanZoomController.OverscrollDirections].
112+
*/
113+
@Suppress("TooManyFunctions")
114+
class InputResultDetail private constructor(
115+
val inputResult: Int = INPUT_UNHANDLED,
116+
val scrollDirections: Int = SCROLL_DIRECTIONS_NONE,
117+
val overscrollDirections: Int = OVERSCROLL_DIRECTIONS_NONE
118+
) {
119+
120+
override fun equals(other: Any?): Boolean {
121+
return if (this !== other) {
122+
if (other is InputResultDetail) {
123+
return inputResult == other.inputResult &&
124+
scrollDirections == other.scrollDirections &&
125+
overscrollDirections == other.overscrollDirections
126+
} else {
127+
false
128+
}
129+
} else {
130+
true
131+
}
132+
}
133+
134+
@Suppress("MagicNumber")
135+
override fun hashCode(): Int {
136+
var hash = inputResult.hashCode() * 31
137+
hash += (scrollDirections.hashCode()) * 31
138+
hash += (overscrollDirections.hashCode()) * 31
139+
140+
return hash
141+
}
142+
143+
override fun toString(): String {
144+
return StringBuilder("InputResultDetail \$${hashCode()} (")
145+
.append("Input ${getInputResultHandledDescription()}. ")
146+
.append("Content ${getScrollDirectionsDescription()} and ${getOverscrollDirectionsDescription()}")
147+
.append(')')
148+
.toString()
149+
}
150+
151+
/**
152+
* Create a new instance of [InputResultDetail] with the option of keep some of the current values.
153+
*
154+
* The provided new values will be filtered out if not recognized and could corrupt the current state.
155+
*/
156+
fun copy(
157+
inputResult: Int? = this.inputResult,
158+
scrollDirections: Int? = this.scrollDirections,
159+
overscrollDirections: Int? = this.overscrollDirections
160+
): InputResultDetail {
161+
// Ensure this data will not get corrupted by users sending unknown arguments
162+
163+
val newValidInputResult = if (inputResult in INPUT_UNHANDLED..INPUT_HANDLED_CONTENT) {
164+
inputResult
165+
} else {
166+
this.inputResult
167+
}
168+
val newValidScrollDirections = if (scrollDirections in
169+
SCROLL_DIRECTIONS_NONE..(SCROLL_DIRECTIONS_LEFT or (SCROLL_DIRECTIONS_LEFT - 1))
170+
) {
171+
scrollDirections
172+
} else {
173+
this.scrollDirections
174+
}
175+
val newValidOverscrollDirections = if (overscrollDirections in
176+
OVERSCROLL_DIRECTIONS_NONE..(OVERSCROLL_DIRECTIONS_VERTICAL or (OVERSCROLL_DIRECTIONS_VERTICAL - 1))
177+
) {
178+
overscrollDirections
179+
} else {
180+
this.overscrollDirections
181+
}
182+
183+
// The range check automatically checks for null but doesn't yet have a contract to say so.
184+
// As such it it safe to use the not-null assertion operator.
185+
return InputResultDetail(newValidInputResult!!, newValidScrollDirections!!, newValidOverscrollDirections!!)
186+
}
187+
188+
/**
189+
* The [EngineView] handled the last [MotionEvent] to pan or zoom the content.
190+
*/
191+
fun isTouchHandledByBrowser() = inputResult == INPUT_HANDLED
192+
193+
/**
194+
* The website handled the last [MotionEvent] through it's own touch listeners
195+
* and consumed it without the [EngineView] panning or zooming the website
196+
*/
197+
fun isTouchHandledByWebsite() = inputResult == INPUT_HANDLED_CONTENT
198+
199+
/**
200+
* Neither the [EngineView], nor the website will handle this [MotionEvent].
201+
*
202+
* This might happen on a website without touch listeners that is not bigger than the screen
203+
* or when the content has no scrollable element.
204+
*/
205+
fun isTouchUnhandled() = inputResult == INPUT_UNHANDLED
206+
207+
/**
208+
* Whether the width of the webpage exceeds the display and the webpage can be scrolled to left.
209+
*/
210+
fun canScrollToLeft(): Boolean =
211+
inputResult == INPUT_HANDLED &&
212+
scrollDirections and SCROLL_DIRECTIONS_LEFT != 0
213+
214+
/**
215+
* Whether the height of the webpage exceeds the display and the webpage can be scrolled to top.
216+
*/
217+
fun canScrollToTop(): Boolean =
218+
inputResult == INPUT_HANDLED &&
219+
scrollDirections and SCROLL_DIRECTIONS_TOP != 0
220+
221+
/**
222+
* Whether the width of the webpage exceeds the display and the webpage can be scrolled to right.
223+
*/
224+
fun canScrollToRight(): Boolean =
225+
inputResult == INPUT_HANDLED &&
226+
scrollDirections and SCROLL_DIRECTIONS_RIGHT != 0
227+
228+
/**
229+
* Whether the height of the webpage exceeds the display and the webpage can be scrolled to bottom.
230+
*/
231+
fun canScrollToBottom(): Boolean =
232+
inputResult == INPUT_HANDLED &&
233+
scrollDirections and SCROLL_DIRECTIONS_BOTTOM != 0
234+
235+
/**
236+
* Whether the webpage can be overscrolled to the left.
237+
*
238+
* @return `true` if the page is already scrolled to the left most part
239+
* and the touch event is not handled by the webpage.
240+
*/
241+
fun canOverscrollLeft(): Boolean =
242+
inputResult != INPUT_HANDLED_CONTENT &&
243+
(scrollDirections and SCROLL_DIRECTIONS_LEFT == 0) &&
244+
(overscrollDirections and OVERSCROLL_DIRECTIONS_HORIZONTAL != 0)
245+
246+
/**
247+
* Whether the webpage can be overscrolled to the top.
248+
*
249+
* @return `true` if the page is already scrolled to the top most part
250+
* and the touch event is not handled by the webpage.
251+
*/
252+
fun canOverscrollTop(): Boolean =
253+
inputResult != INPUT_HANDLED_CONTENT &&
254+
(scrollDirections and SCROLL_DIRECTIONS_TOP == 0) &&
255+
(overscrollDirections and OVERSCROLL_DIRECTIONS_VERTICAL != 0)
256+
257+
/**
258+
* Whether the webpage can be overscrolled to the right.
259+
*
260+
* @return `true` if the page is already scrolled to the right most part
261+
* and the touch event is not handled by the webpage.
262+
*/
263+
fun canOverscrollRight(): Boolean =
264+
inputResult != INPUT_HANDLED_CONTENT &&
265+
(scrollDirections and SCROLL_DIRECTIONS_RIGHT == 0) &&
266+
(overscrollDirections and OVERSCROLL_DIRECTIONS_HORIZONTAL != 0)
267+
268+
/**
269+
* Whether the webpage can be overscrolled to the bottom.
270+
*
271+
* @return `true` if the page is already scrolled to the bottom most part
272+
* and the touch event is not handled by the webpage.
273+
*/
274+
fun canOverscrollBottom(): Boolean =
275+
inputResult != INPUT_HANDLED_CONTENT &&
276+
(scrollDirections and SCROLL_DIRECTIONS_BOTTOM == 0) &&
277+
(overscrollDirections and OVERSCROLL_DIRECTIONS_VERTICAL != 0)
278+
279+
@VisibleForTesting
280+
internal fun getInputResultHandledDescription() = when (inputResult) {
281+
INPUT_HANDLED -> INPUT_HANDLED_TOSTRING_DESCRIPTION
282+
INPUT_HANDLED_CONTENT -> INPUT_HANDLED_CONTENT_TOSTRING_DESCRIPTION
283+
else -> INPUT_UNHANDLED_TOSTRING_DESCRIPTION
284+
}
285+
286+
@VisibleForTesting
287+
internal fun getScrollDirectionsDescription(): String {
288+
if (scrollDirections == SCROLL_DIRECTIONS_NONE) {
289+
return SCROLL_IMPOSSIBLE_TOSTRING_DESCRIPTION
290+
}
291+
292+
val scrollDirections = StringBuilder()
293+
.append(if (canScrollToLeft()) "$SCROLL_LEFT_TOSTRING_DESCRIPTION$TOSTRING_SEPARATOR" else "")
294+
.append(if (canScrollToTop()) "$SCROLL_TOP_TOSTRING_DESCRIPTION$TOSTRING_SEPARATOR" else "")
295+
.append(if (canScrollToRight()) "$SCROLL_RIGHT_TOSTRING_DESCRIPTION$TOSTRING_SEPARATOR" else "")
296+
.append(if (canScrollToBottom()) SCROLL_BOTTOM_TOSTRING_DESCRIPTION else "")
297+
.removeSuffix(TOSTRING_SEPARATOR)
298+
.toString()
299+
300+
return if (scrollDirections.trim().isEmpty()) {
301+
SCROLL_IMPOSSIBLE_TOSTRING_DESCRIPTION
302+
} else {
303+
SCROLL_TOSTRING_DESCRIPTION + scrollDirections
304+
}
305+
}
306+
307+
@VisibleForTesting
308+
internal fun getOverscrollDirectionsDescription(): String {
309+
if (overscrollDirections == OVERSCROLL_DIRECTIONS_NONE) {
310+
return OVERSCROLL_IMPOSSIBLE_TOSTRING_DESCRIPTION
311+
}
312+
313+
val overscrollDirections = StringBuilder()
314+
.append(if (canOverscrollLeft()) "$SCROLL_LEFT_TOSTRING_DESCRIPTION$TOSTRING_SEPARATOR" else "")
315+
.append(if (canOverscrollTop()) "$SCROLL_TOP_TOSTRING_DESCRIPTION$TOSTRING_SEPARATOR" else "")
316+
.append(if (canOverscrollRight()) "$SCROLL_RIGHT_TOSTRING_DESCRIPTION$TOSTRING_SEPARATOR" else "")
317+
.append(if (canOverscrollBottom()) SCROLL_BOTTOM_TOSTRING_DESCRIPTION else "")
318+
.removeSuffix(TOSTRING_SEPARATOR)
319+
.toString()
320+
321+
return if (overscrollDirections.trim().isEmpty()) {
322+
OVERSCROLL_IMPOSSIBLE_TOSTRING_DESCRIPTION
323+
} else {
324+
OVERSCROLL_TOSTRING_DESCRIPTION + overscrollDirections
325+
}
326+
}
327+
328+
companion object {
329+
/**
330+
* Create a new instance of [InputResultDetail].
331+
*
332+
* @param verticalOverscrollInitiallyEnabled optional parameter for enabling pull to refresh
333+
* in the cases in which this class can be used before valid values being set and it helps more to have
334+
* overscroll vertically allowed and then stop depending on the values with which this class is updated
335+
* rather than start with a disabled overscroll functionality for the current gesture.
336+
*/
337+
fun newInstance(verticalOverscrollInitiallyEnabled: Boolean = false) = InputResultDetail(
338+
overscrollDirections = if (verticalOverscrollInitiallyEnabled) {
339+
OVERSCROLL_DIRECTIONS_VERTICAL
340+
} else {
341+
OVERSCROLL_DIRECTIONS_NONE
342+
}
343+
)
344+
345+
@VisibleForTesting internal const val TOSTRING_SEPARATOR = ", "
346+
@VisibleForTesting internal const val INPUT_HANDLED_TOSTRING_DESCRIPTION = "handled by the browser"
347+
@VisibleForTesting internal const val INPUT_HANDLED_CONTENT_TOSTRING_DESCRIPTION = "handled by the website"
348+
@VisibleForTesting internal const val INPUT_UNHANDLED_TOSTRING_DESCRIPTION = "unhandled"
349+
@VisibleForTesting internal const val SCROLL_IMPOSSIBLE_TOSTRING_DESCRIPTION = "cannot be scrolled"
350+
@VisibleForTesting internal const val OVERSCROLL_IMPOSSIBLE_TOSTRING_DESCRIPTION = "cannot be overscrolled"
351+
@VisibleForTesting internal const val SCROLL_TOSTRING_DESCRIPTION = "can be scrolled to "
352+
@VisibleForTesting internal const val OVERSCROLL_TOSTRING_DESCRIPTION = "can be overscrolled to "
353+
@VisibleForTesting internal const val SCROLL_LEFT_TOSTRING_DESCRIPTION = "left"
354+
@VisibleForTesting internal const val SCROLL_TOP_TOSTRING_DESCRIPTION = "top"
355+
@VisibleForTesting internal const val SCROLL_RIGHT_TOSTRING_DESCRIPTION = "right"
356+
@VisibleForTesting internal const val SCROLL_BOTTOM_TOSTRING_DESCRIPTION = "bottom"
357+
}
358+
}

0 commit comments

Comments
 (0)