calabash-android 0.5.1 → 0.5.2.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/bin/calabash-android-console.rb +24 -9
- data/bin/calabash-android-run.rb +4 -1
- data/bin/calabash-android-setup.rb +1 -1
- data/lib/calabash-android/deprecated_actions.map +8 -0
- data/lib/calabash-android/env.rb +30 -2
- data/lib/calabash-android/environment_helpers.rb +12 -0
- data/lib/calabash-android/gestures.rb +308 -0
- data/lib/calabash-android/helpers.rb +55 -6
- data/lib/calabash-android/java_keystore.rb +2 -1
- data/lib/calabash-android/lib/TestServer.apk +0 -0
- data/lib/calabash-android/operations.rb +901 -707
- data/lib/calabash-android/removed_actions.txt +4 -0
- data/lib/calabash-android/steps/date_picker_steps.rb +4 -4
- data/lib/calabash-android/steps/time_picker_steps.rb +3 -3
- data/lib/calabash-android/touch_helpers.rb +114 -18
- data/lib/calabash-android/version.rb +1 -1
- data/test-server/calabash-js/src/calabash.js +42 -1
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/HttpServer.java +67 -2
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/MultiTouchGesture.java +749 -0
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/softkey/PressKey.java +85 -0
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/text/HideSoftKeyboard.java +24 -2
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/text/PressUserActionButton.java +128 -0
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/webview/QueryHelper.java +8 -1
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/query/InvocationOperation.java +56 -9
- metadata +10 -8
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/time/SetDateByContentDescription.java +0 -33
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/time/SetDateByIndex.java +0 -24
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/time/SetTimeByContentDescription.java +0 -34
- data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/time/SetTimeByIndex.java +0 -26
@@ -1,8 +1,8 @@
|
|
1
1
|
|
2
|
-
Given /^I set the date to "(\d\d-\d\d-\d\d\d\d)" on DatePicker with index
|
3
|
-
|
2
|
+
Given /^I set the date to "(\d\d-\d\d-\d\d\d\d)" on DatePicker with index ([^\"]*)$/ do |date, index|
|
3
|
+
set_date("android.widget.DatePicker index:#{index.to_i-1}", date)
|
4
4
|
end
|
5
5
|
|
6
6
|
Given /^I set the "([^\"]*)" date to "(\d\d-\d\d-\d\d\d\d)"$/ do |content_description, date|
|
7
|
-
|
8
|
-
end
|
7
|
+
set_date("android.widget.DatePicker {contentDescription LIKE[c] '#{content_description}'}", date)
|
8
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
|
2
|
-
Given /^I set the time to "(\d\d:\d\d)" on TimePicker with index
|
3
|
-
|
2
|
+
Given /^I set the time to "(\d\d:\d\d)" on TimePicker with index ([^\"]*)$/ do |time, index|
|
3
|
+
set_time("android.widget.TimePicker index:#{index.to_i-1}", time)
|
4
4
|
end
|
5
5
|
|
6
6
|
Given /^I set the "([^\"]*)" time to "(\d\d:\d\d)"$/ do |content_description, time|
|
7
|
-
|
7
|
+
set_time("android.widget.TimePicker {contentDescription LIKE[c] '#{content_description}'}", time)
|
8
8
|
end
|
@@ -1,34 +1,120 @@
|
|
1
1
|
module Calabash
|
2
2
|
module Android
|
3
3
|
module TouchHelpers
|
4
|
+
include ::Calabash::Android::Gestures
|
5
|
+
|
6
|
+
def execute_gesture(multi_touch_gesture)
|
7
|
+
result = JSON.parse(http("/gesture", JSON.parse(multi_touch_gesture.to_json), read_timeout: multi_touch_gesture.timeout+10))
|
8
|
+
|
9
|
+
if result['outcome'] != 'SUCCESS'
|
10
|
+
raise "Failed to perform gesture. #{result['reason']}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
4
14
|
def tap(mark, *args)
|
15
|
+
puts "Warning: The method tap is deprecated. Use tap_mark instead. In later Calabash versions we will change the semantics of `tap` to take a general query."
|
16
|
+
|
17
|
+
tap_mark(mark, *args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def tap_mark(mark, *args)
|
5
21
|
touch("* marked:'#{mark}'", *args)
|
6
22
|
end
|
7
23
|
|
8
|
-
def
|
9
|
-
|
24
|
+
def touch(query_string, options={})
|
25
|
+
if query_result?(query_string)
|
26
|
+
center_x, center_y = find_coordinate(query_string, options)
|
27
|
+
|
28
|
+
perform_action("touch_coordinate", center_x, center_y)
|
29
|
+
else
|
30
|
+
execute_gesture(Gesture.with_parameters(Gesture.tap(options), {query_string: query_string}.merge(options)))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def double_tap(query_string, options={})
|
35
|
+
if query_result?(query_string)
|
36
|
+
center_x, center_y = find_coordinate(query_string, options)
|
10
37
|
|
11
|
-
|
38
|
+
perform_action("double_tap_coordinate", center_x, center_y)
|
39
|
+
else
|
40
|
+
execute_gesture(Gesture.with_parameters(Gesture.double_tap(options), {query_string: query_string}.merge(options)))
|
41
|
+
end
|
12
42
|
end
|
13
43
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
44
|
+
def long_press(query_string, options={})
|
45
|
+
if query_result?(query_string)
|
46
|
+
center_x, center_y = find_coordinate(query_string, options)
|
47
|
+
length = options[:length]
|
48
|
+
|
49
|
+
perform_action("long_press_coordinate", center_x, center_y, *(length unless length.nil?))
|
50
|
+
else
|
51
|
+
length = options[:length]
|
52
|
+
|
53
|
+
if length
|
54
|
+
puts "Using the length key is deprecated. Use 'time' (in seconds) instead."
|
55
|
+
options[:time] = length/1000.0
|
56
|
+
end
|
57
|
+
|
58
|
+
options[:time] ||= 1
|
59
|
+
|
60
|
+
touch(query_string, options)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def drag(*args)
|
65
|
+
pan(*args)
|
26
66
|
end
|
27
67
|
|
28
|
-
def
|
29
|
-
|
68
|
+
def pan_left(options={})
|
69
|
+
pan("DecorView", :left, options)
|
70
|
+
end
|
71
|
+
|
72
|
+
def pan_right(options={})
|
73
|
+
pan("DecorView", :right, options)
|
74
|
+
end
|
30
75
|
|
31
|
-
|
76
|
+
def pan_up(options={})
|
77
|
+
pan("* id:'content'", :up, options)
|
78
|
+
end
|
79
|
+
|
80
|
+
def pan_down(options={})
|
81
|
+
pan("* id:'content'", :down, options)
|
82
|
+
end
|
83
|
+
|
84
|
+
def pan(query_string, direction, options={})
|
85
|
+
execute_gesture(Gesture.with_parameters(Gesture.swipe(direction, options), {query_string: query_string}.merge(options)))
|
86
|
+
end
|
87
|
+
|
88
|
+
def flick_left(options={})
|
89
|
+
flick("DecorView", :left, options)
|
90
|
+
end
|
91
|
+
|
92
|
+
def flick_right(options={})
|
93
|
+
flick("DecorView", :right, options)
|
94
|
+
end
|
95
|
+
|
96
|
+
def flick_up(options={})
|
97
|
+
flick("* id:'content'", :up, options)
|
98
|
+
end
|
99
|
+
|
100
|
+
def flick_down(options={})
|
101
|
+
flick("* id:'content'", :down, options)
|
102
|
+
end
|
103
|
+
|
104
|
+
def flick(query_string, direction, options={})
|
105
|
+
execute_gesture(Gesture.with_parameters(Gesture.swipe(direction, {flick: true}.merge(options)), {query_string: query_string}.merge(options)))
|
106
|
+
end
|
107
|
+
|
108
|
+
def pinch_out(options={})
|
109
|
+
pinch("* id:'content'", :out, options)
|
110
|
+
end
|
111
|
+
|
112
|
+
def pinch_in(options={})
|
113
|
+
pinch("* id:'content'", :in, options)
|
114
|
+
end
|
115
|
+
|
116
|
+
def pinch(query_string, direction, options={})
|
117
|
+
execute_gesture(Gesture.with_parameters(Gesture.pinch(direction, options), {query_string: query_string}.merge(options)))
|
32
118
|
end
|
33
119
|
|
34
120
|
def find_coordinate(uiquery, options={})
|
@@ -68,6 +154,16 @@ module Calabash
|
|
68
154
|
when_element_exists(query_string, options)
|
69
155
|
end
|
70
156
|
end
|
157
|
+
|
158
|
+
def query_result?(uiquery)
|
159
|
+
element = if uiquery.is_a?(Array)
|
160
|
+
uiquery.first
|
161
|
+
else
|
162
|
+
uiquery
|
163
|
+
end
|
164
|
+
|
165
|
+
element.is_a?(Hash) && element.has_key?('rect') && element['rect'].has_key?('center_x') && element['rect'].has_key?('center_y')
|
166
|
+
end
|
71
167
|
end
|
72
168
|
end
|
73
169
|
end
|
@@ -98,8 +98,40 @@
|
|
98
98
|
return res;
|
99
99
|
}
|
100
100
|
|
101
|
+
function applyMethods(object, arguments) {
|
102
|
+
var length = arguments.length;
|
103
|
+
|
104
|
+
for(var i = 0; i < length; i++) {
|
105
|
+
var argument = arguments[i];
|
106
|
+
|
107
|
+
if (typeof argument === 'string') {
|
108
|
+
argument = {method_name: argument, arguments: []}
|
109
|
+
}
|
110
|
+
|
111
|
+
var methodName = argument.method_name;
|
112
|
+
var methodArguments = argument.arguments;
|
113
|
+
|
114
|
+
if (typeof object[methodName] === 'undefined') {
|
115
|
+
var type = Object.prototype.toString.call(object);
|
116
|
+
|
117
|
+
object =
|
118
|
+
{
|
119
|
+
error: "No such method '" + methodName + "'",
|
120
|
+
methodName: methodName,
|
121
|
+
receiverString: object.constructor.name,
|
122
|
+
receiverClass: type
|
123
|
+
};
|
124
|
+
|
125
|
+
break;
|
126
|
+
} else {
|
127
|
+
object = object[methodName].apply(object, methodArguments);
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
101
132
|
var exp = '%@'/* dynamic */,
|
102
|
-
queryType = '%@'
|
133
|
+
queryType = '%@' /* dynamic */,
|
134
|
+
arguments = '%@' /* dynamic */,
|
103
135
|
nodes = null,
|
104
136
|
res = [],
|
105
137
|
i,N;
|
@@ -122,5 +154,14 @@
|
|
122
154
|
{
|
123
155
|
return JSON.stringify({error:'Exception while running query: '+exp, details:e.toString()})
|
124
156
|
}
|
157
|
+
|
158
|
+
if (arguments !== '%@') {
|
159
|
+
var length = res.length;
|
160
|
+
|
161
|
+
for (var i = 0; i < length; i++) {
|
162
|
+
res[i] = applyMethods(res[i], arguments);
|
163
|
+
}
|
164
|
+
}
|
165
|
+
|
125
166
|
return JSON.stringify(toJSON(res));
|
126
167
|
})();
|
@@ -9,6 +9,7 @@ import java.io.StringWriter;
|
|
9
9
|
import java.lang.InterruptedException;
|
10
10
|
import java.lang.Override;
|
11
11
|
import java.lang.Runnable;
|
12
|
+
import java.util.Collections;
|
12
13
|
import java.util.Enumeration;
|
13
14
|
import java.util.List;
|
14
15
|
import java.util.Map;
|
@@ -23,10 +24,14 @@ import sh.calaba.instrumentationbackend.FranklyResult;
|
|
23
24
|
import sh.calaba.instrumentationbackend.InstrumentationBackend;
|
24
25
|
import sh.calaba.instrumentationbackend.Result;
|
25
26
|
import sh.calaba.instrumentationbackend.json.JSONUtils;
|
27
|
+
import sh.calaba.instrumentationbackend.query.InvocationOperation;
|
28
|
+
import sh.calaba.instrumentationbackend.query.Operation;
|
26
29
|
import sh.calaba.instrumentationbackend.query.Query;
|
27
30
|
import sh.calaba.instrumentationbackend.query.QueryResult;
|
28
31
|
import sh.calaba.org.codehaus.jackson.map.ObjectMapper;
|
29
32
|
|
33
|
+
import android.app.Application;
|
34
|
+
import android.content.Context;
|
30
35
|
import android.graphics.Bitmap;
|
31
36
|
import android.util.Log;
|
32
37
|
import android.view.View;
|
@@ -123,7 +128,48 @@ public class HttpServer extends NanoHTTPD {
|
|
123
128
|
errorResult = FranklyResult.fromThrowable(e);
|
124
129
|
}
|
125
130
|
return new NanoHTTPD.Response(HTTP_INTERNALERROR, "application/json;charset=utf-8", errorResult.asJson());
|
126
|
-
}
|
131
|
+
}
|
132
|
+
else if (uri.endsWith("/backdoor")) {
|
133
|
+
try {
|
134
|
+
String json = params.getProperty("json");
|
135
|
+
ObjectMapper mapper = new ObjectMapper();
|
136
|
+
Map backdoorMethod = mapper.readValue(json, Map.class);
|
137
|
+
|
138
|
+
String methodName = (String) backdoorMethod.get("method_name");
|
139
|
+
List arguments = (List) backdoorMethod.get("arguments");
|
140
|
+
Operation operation = new InvocationOperation(methodName, arguments);
|
141
|
+
|
142
|
+
Application application = InstrumentationBackend.solo.getCurrentActivity().getApplication();
|
143
|
+
Object invocationResult;
|
144
|
+
|
145
|
+
invocationResult = operation.apply(application);
|
146
|
+
|
147
|
+
if (invocationResult instanceof Map && ((Map) invocationResult).containsKey("error")) {
|
148
|
+
Context context = getRootView().getContext();
|
149
|
+
invocationResult = operation.apply(context);
|
150
|
+
}
|
151
|
+
|
152
|
+
Map<String, String> result = new HashMap<String, String>();
|
153
|
+
|
154
|
+
if (invocationResult instanceof Map && ((Map) invocationResult).containsKey("error")) {
|
155
|
+
result.put("outcome", "ERROR");
|
156
|
+
result.put("result", (String) ((Map) invocationResult).get("error"));
|
157
|
+
result.put("details", invocationResult.toString());
|
158
|
+
} else {
|
159
|
+
result.put("outcome", "SUCCESS");
|
160
|
+
result.put("result", String.valueOf(invocationResult));
|
161
|
+
}
|
162
|
+
|
163
|
+
ObjectMapper resultMapper = new ObjectMapper();
|
164
|
+
|
165
|
+
return new NanoHTTPD.Response(HTTP_OK, "application/json;charset=utf-8", resultMapper.writeValueAsString(result));
|
166
|
+
} catch (Exception e) {
|
167
|
+
e.printStackTrace();
|
168
|
+
Exception ex = new Exception("Could not invoke method", e);
|
169
|
+
|
170
|
+
return new NanoHTTPD.Response(HTTP_OK, "application/json;charset=utf-8", FranklyResult.fromThrowable(ex).asJson());
|
171
|
+
}
|
172
|
+
}
|
127
173
|
else if (uri.endsWith("/map")) {
|
128
174
|
FranklyResult errorResult = null;
|
129
175
|
try {
|
@@ -189,7 +235,26 @@ public class HttpServer extends NanoHTTPD {
|
|
189
235
|
} else if (uri.endsWith("/query")) {
|
190
236
|
return new Response(HTTP_BADREQUEST, MIME_PLAINTEXT,
|
191
237
|
"/query endpoint is discontinued - use /map with operation query");
|
192
|
-
|
238
|
+
} else if (uri.endsWith("/gesture")) {
|
239
|
+
FranklyResult errorResult;
|
240
|
+
|
241
|
+
try {
|
242
|
+
String json = params.getProperty("json");
|
243
|
+
ObjectMapper mapper = new ObjectMapper();
|
244
|
+
Map gesture = mapper.readValue(json, Map.class);
|
245
|
+
|
246
|
+
(new MultiTouchGesture(gesture)).perform();
|
247
|
+
|
248
|
+
return new NanoHTTPD.Response(HTTP_OK, "application/json;charset=utf-8",
|
249
|
+
FranklyResult.successResult(new QueryResult(Collections.emptyList())).asJson());
|
250
|
+
|
251
|
+
} catch (Exception e ) {
|
252
|
+
e.printStackTrace();
|
253
|
+
errorResult = FranklyResult.fromThrowable(e);
|
254
|
+
}
|
255
|
+
|
256
|
+
return new NanoHTTPD.Response(HTTP_OK, "application/json;charset=utf-8", errorResult.asJson());
|
257
|
+
} else if (uri.endsWith("/kill")) {
|
193
258
|
lock.lock();
|
194
259
|
try {
|
195
260
|
running = false;
|
@@ -0,0 +1,749 @@
|
|
1
|
+
package sh.calaba.instrumentationbackend.actions;
|
2
|
+
|
3
|
+
import android.app.Activity;
|
4
|
+
import android.app.Instrumentation;
|
5
|
+
import android.app.KeyguardManager;
|
6
|
+
import android.content.Context;
|
7
|
+
import android.os.Build;
|
8
|
+
import android.os.SystemClock;
|
9
|
+
import android.util.Pair;
|
10
|
+
import android.view.MotionEvent;
|
11
|
+
|
12
|
+
import java.lang.reflect.Method;
|
13
|
+
|
14
|
+
import java.util.ArrayList;
|
15
|
+
import java.util.HashMap;
|
16
|
+
import java.util.List;
|
17
|
+
import java.util.Map;
|
18
|
+
|
19
|
+
import sh.calaba.instrumentationbackend.InstrumentationBackend;
|
20
|
+
import sh.calaba.instrumentationbackend.query.Query;
|
21
|
+
import sh.calaba.instrumentationbackend.query.QueryResult;
|
22
|
+
|
23
|
+
public class MultiTouchGesture {
|
24
|
+
Map<String, Object> multiTouchGestureMap;
|
25
|
+
Instrumentation instrumentation;
|
26
|
+
List<Gesture> pressedGestures;
|
27
|
+
List<Gesture> gesturesToPerform;
|
28
|
+
boolean hasPressedFirstGesture;
|
29
|
+
|
30
|
+
public MultiTouchGesture(Map<String, Object> multiTouchGesture) {
|
31
|
+
this.multiTouchGestureMap = multiTouchGesture;
|
32
|
+
instrumentation = InstrumentationBackend.instrumentation;
|
33
|
+
gesturesToPerform = new ArrayList<Gesture>();
|
34
|
+
hasPressedFirstGesture = false;
|
35
|
+
}
|
36
|
+
|
37
|
+
public void parseGesture() {
|
38
|
+
List<Map<String, Object>> sentGestures = (ArrayList<Map<String, Object>>) multiTouchGestureMap.get("gestures");
|
39
|
+
List<String> queryStrings = new ArrayList<String>();
|
40
|
+
|
41
|
+
// We query before generating the gestures. TODO: Implement new class with temporary information
|
42
|
+
for (Map<String, Object> gestureMap : sentGestures) {
|
43
|
+
String queryString = (String) gestureMap.get("query_string");
|
44
|
+
|
45
|
+
if (queryString != null) {
|
46
|
+
queryStrings.add(queryString);
|
47
|
+
}
|
48
|
+
|
49
|
+
ArrayList<Map<String, Object>> sentTouches = (ArrayList<Map<String, Object>>) gestureMap.get("touches");
|
50
|
+
|
51
|
+
for (Map<String,Object> sentTouch : sentTouches) {
|
52
|
+
if (sentTouch.containsKey("query_string")) {
|
53
|
+
queryString = (String) sentTouch.get("query_string");
|
54
|
+
|
55
|
+
if (queryString != null) {
|
56
|
+
queryStrings.add(queryString);
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
long timeout = (long) ((Double) multiTouchGestureMap.get("query_timeout") * 1000);
|
63
|
+
Map<String, Map<String, Integer>> evaluatedQueries = evaluateQueries(queryStrings, timeout);
|
64
|
+
|
65
|
+
for (Map<String, Object> gestureMap : sentGestures) {
|
66
|
+
String queryString = (String) gestureMap.get("query_string");
|
67
|
+
Map<String,Integer> rect = evaluatedQueries.get(queryString);
|
68
|
+
|
69
|
+
ArrayList<Map<String, Object>> sentTouches = (ArrayList<Map<String, Object>>) gestureMap.get("touches");
|
70
|
+
|
71
|
+
Gesture gesture = new Gesture();
|
72
|
+
int length = sentTouches.size();
|
73
|
+
|
74
|
+
for (int i = 0; i < length; i++) {
|
75
|
+
Map<String, Object> touch = sentTouches.get(i);
|
76
|
+
Map<String,Integer> specificRect = rect;
|
77
|
+
|
78
|
+
if (touch.containsKey("query_string")) {
|
79
|
+
String specificQueryString = (String) touch.get("query_string");
|
80
|
+
|
81
|
+
if (specificQueryString != null) {
|
82
|
+
specificRect = evaluatedQueries.get(specificQueryString);
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
int resultX = 0, resultY = 0, resultWidth = 0, resultHeight = 0;
|
87
|
+
|
88
|
+
if (specificRect != null) {
|
89
|
+
resultX = specificRect.get("x");
|
90
|
+
resultY = specificRect.get("y");
|
91
|
+
resultWidth = specificRect.get("width");
|
92
|
+
resultHeight = specificRect.get("height");
|
93
|
+
}
|
94
|
+
|
95
|
+
int offsetX = (Integer) touch.get("offset_x");
|
96
|
+
int offsetY = (Integer) touch.get("offset_y");
|
97
|
+
int x, y;
|
98
|
+
|
99
|
+
if (specificRect == null) {
|
100
|
+
x = offsetX;
|
101
|
+
y = offsetY;
|
102
|
+
} else {
|
103
|
+
x = ((((Integer) touch.get("x")) * resultWidth)/100 + offsetX + resultX);
|
104
|
+
y = ((((Integer) touch.get("y")) * resultHeight)/100 + offsetY + resultY);
|
105
|
+
}
|
106
|
+
|
107
|
+
long time = (long) ((Double) touch.get("time") * 1000);
|
108
|
+
long wait = (long) ((Double) touch.get("wait") * 1000);
|
109
|
+
boolean release = (Boolean) touch.get("release");
|
110
|
+
|
111
|
+
if (i == length - 1) {
|
112
|
+
release = true;
|
113
|
+
}
|
114
|
+
|
115
|
+
if (!gesture.hasEvents()) {
|
116
|
+
gesture.addEvent(new Event(new Coordinate(x, y), wait));
|
117
|
+
} else {
|
118
|
+
gesture.addEvent(new Event(new Coordinate(x, y), 0l));
|
119
|
+
|
120
|
+
if (wait != 0l) {
|
121
|
+
gesture.addEvent(new Event(new Coordinate(x, y), wait), false);
|
122
|
+
} else {
|
123
|
+
gesture.setFling(true);
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
127
|
+
gesture.addTimeOffset(time);
|
128
|
+
|
129
|
+
if (release) {
|
130
|
+
gesture.addEvent(new Event(gesture.upEvent().getCoordinate(), 0l), true);
|
131
|
+
long endTime = gesture.upEvent().getTime();
|
132
|
+
gesturesToPerform.add(gesture);
|
133
|
+
gesture = new Gesture(endTime);
|
134
|
+
}
|
135
|
+
}
|
136
|
+
}
|
137
|
+
}
|
138
|
+
|
139
|
+
public void perform() {
|
140
|
+
parseGesture();
|
141
|
+
|
142
|
+
// Sometimes the keyguard window or a service pops up very briefly.
|
143
|
+
// We handle it by waiting a fixed time
|
144
|
+
tryWaitForKeyguard(2);
|
145
|
+
|
146
|
+
long time;
|
147
|
+
long startTime = SystemClock.uptimeMillis();
|
148
|
+
long endTime = findEndTime();
|
149
|
+
pressedGestures = new ArrayList<Gesture>();
|
150
|
+
|
151
|
+
while ((time = SystemClock.uptimeMillis() - startTime) <= endTime) {
|
152
|
+
releaseGestures(time);
|
153
|
+
|
154
|
+
if (hasPressedFirstGesture) {
|
155
|
+
pressGestures(time);
|
156
|
+
|
157
|
+
// Instead of reassigning the time variable we only move gestures when we no longer offset the time
|
158
|
+
moveGestures(time);
|
159
|
+
} else {
|
160
|
+
// We catch the security exception thrown when the keyguard is visible briefly and retry if it's the first press
|
161
|
+
// Offset the startTime by the needed extra time if needed
|
162
|
+
long currentTime = SystemClock.uptimeMillis();
|
163
|
+
pressGestures(time);
|
164
|
+
long timeTaken = SystemClock.uptimeMillis() - currentTime;
|
165
|
+
|
166
|
+
// Offset the start time (this changing the future values of time). This is done to keep relative timings consistent
|
167
|
+
if (hasPressedFirstGesture) { // We just pressed our first gesture
|
168
|
+
startTime += timeTaken;
|
169
|
+
}
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
releaseGestures(endTime);
|
174
|
+
}
|
175
|
+
|
176
|
+
private Map<String, Map<String, Integer>> evaluateQueries(List<String> queryStrings, long timeout) {
|
177
|
+
List<String> distinctQueryStrings = new ArrayList<String>();
|
178
|
+
|
179
|
+
for (String queryString : queryStrings) {
|
180
|
+
if (!distinctQueryStrings.contains(queryString)) {
|
181
|
+
distinctQueryStrings.add(queryString);
|
182
|
+
}
|
183
|
+
}
|
184
|
+
|
185
|
+
Map<String, Map<String, Integer>> evaluatedQueries = new HashMap<String, Map<String, Integer>>();
|
186
|
+
|
187
|
+
long endTime = SystemClock.uptimeMillis() + timeout;
|
188
|
+
|
189
|
+
do {
|
190
|
+
evaluatedQueries.clear();
|
191
|
+
|
192
|
+
for (String queryString : distinctQueryStrings) {
|
193
|
+
QueryResult queryResult = new Query(queryString, java.util.Collections.emptyList()).executeQuery();
|
194
|
+
// For now we calculate the views location and save it. In an implementation in the future, we should save the actual views and use their coordinates later on.
|
195
|
+
List<Object> results = queryResult.asList();
|
196
|
+
|
197
|
+
if (results.size() == 0) {
|
198
|
+
break;
|
199
|
+
} else {
|
200
|
+
Map<Object,Object> firstItem = (Map<Object, Object>) results.get(0);
|
201
|
+
Map<String,Integer> rect = (Map<String, Integer>) firstItem.get("rect");
|
202
|
+
evaluatedQueries.put(queryString, rect);
|
203
|
+
}
|
204
|
+
}
|
205
|
+
|
206
|
+
if (evaluatedQueries.size() == distinctQueryStrings.size()) {
|
207
|
+
// All the queries have been evaluated
|
208
|
+
return evaluatedQueries;
|
209
|
+
}
|
210
|
+
|
211
|
+
// Avoid affecting the UI Thread and device performance too much
|
212
|
+
try {
|
213
|
+
Thread.sleep(500);
|
214
|
+
} catch (InterruptedException e) {
|
215
|
+
throw new RuntimeException(e);
|
216
|
+
}
|
217
|
+
} while (SystemClock.uptimeMillis() <= endTime);
|
218
|
+
|
219
|
+
throw new RuntimeException("Could not find views '" + distinctQueryStrings.toString() + "'");
|
220
|
+
}
|
221
|
+
|
222
|
+
private void sendPointerSync(MotionEvent motionEvent) {
|
223
|
+
instrumentation.sendPointerSync(motionEvent);
|
224
|
+
}
|
225
|
+
|
226
|
+
private void pressGestures(long currentTime) {
|
227
|
+
int i = 0;
|
228
|
+
|
229
|
+
while (i < gesturesToPerform.size()) {
|
230
|
+
Gesture gesture = gesturesToPerform.get(i);
|
231
|
+
|
232
|
+
// Sometimes the keyguard window or a service pops up very briefly.
|
233
|
+
// We handle it by retrying - but only if no gestures are currently down to avoid mistiming gestures.
|
234
|
+
int retries = hasPressedFirstGesture ? 1 : 10;
|
235
|
+
SecurityException ex = null;
|
236
|
+
|
237
|
+
for (int j = 0; j < retries; j++) {
|
238
|
+
try {
|
239
|
+
if (gesture.shouldPress(currentTime)) {
|
240
|
+
sendPointerSync(gesture.generateDownEvent(pressedGestures));
|
241
|
+
hasPressedFirstGesture = true;
|
242
|
+
pressedGestures.add(gesture);
|
243
|
+
gesturesToPerform.remove(i);
|
244
|
+
} else {
|
245
|
+
i++;
|
246
|
+
}
|
247
|
+
|
248
|
+
ex = null;
|
249
|
+
break;
|
250
|
+
} catch (SecurityException e) {
|
251
|
+
ex = e;
|
252
|
+
|
253
|
+
try {
|
254
|
+
Thread.sleep(100);
|
255
|
+
} catch (InterruptedException interruptedException) {
|
256
|
+
throw new RuntimeException(interruptedException);
|
257
|
+
}
|
258
|
+
}
|
259
|
+
}
|
260
|
+
|
261
|
+
if (ex != null) {
|
262
|
+
throw new SecurityException(ex);
|
263
|
+
}
|
264
|
+
}
|
265
|
+
}
|
266
|
+
|
267
|
+
private void releaseGestures(long currentTime) {
|
268
|
+
int i = 0;
|
269
|
+
|
270
|
+
while (i < pressedGestures.size()) {
|
271
|
+
Gesture gesture = pressedGestures.get(i);
|
272
|
+
|
273
|
+
if (gesture.shouldRelease(currentTime)) {
|
274
|
+
// Ensure that the gesture will always move to its end-coordinate
|
275
|
+
long uptimeMillis = SystemClock.uptimeMillis();
|
276
|
+
moveGestures(currentTime, uptimeMillis);
|
277
|
+
|
278
|
+
sendPointerSync(gesture.generateUpEvent(pressedGestures, uptimeMillis+1));
|
279
|
+
pressedGestures.remove(i);
|
280
|
+
} else {
|
281
|
+
i++;
|
282
|
+
}
|
283
|
+
}
|
284
|
+
}
|
285
|
+
|
286
|
+
private void moveGestures(long currentTime) {
|
287
|
+
moveGestures(currentTime, null);
|
288
|
+
}
|
289
|
+
|
290
|
+
private void moveGestures(long currentTime, Long systemClockUptimeMillis) {
|
291
|
+
List<Gesture> gesturesToMove = new ArrayList<Gesture>();
|
292
|
+
|
293
|
+
for (Gesture gesture : pressedGestures) {
|
294
|
+
// Flinging gestures should never move to their last coordinate before releasing the touch.
|
295
|
+
if (gesture.shouldMove(currentTime)) {
|
296
|
+
gesturesToMove.add(gesture);
|
297
|
+
}
|
298
|
+
}
|
299
|
+
|
300
|
+
int gestureLength = gesturesToMove.size();
|
301
|
+
|
302
|
+
if (gestureLength > 0) {
|
303
|
+
MotionEvent motionEvent = obtainMotionEvent(gesturesToMove, MotionEvent.ACTION_MOVE, currentTime, findAbsoluteStartTime(), systemClockUptimeMillis);
|
304
|
+
sendPointerSync(motionEvent);
|
305
|
+
}
|
306
|
+
}
|
307
|
+
|
308
|
+
private long findAbsoluteStartTime() {
|
309
|
+
long startTime = SystemClock.uptimeMillis();
|
310
|
+
|
311
|
+
for (Gesture gesture : pressedGestures) {
|
312
|
+
if (gesture.getAbsoluteDownTime() != null) {
|
313
|
+
startTime = Math.min(gesture.getAbsoluteDownTime(), startTime);
|
314
|
+
}
|
315
|
+
}
|
316
|
+
|
317
|
+
return startTime;
|
318
|
+
}
|
319
|
+
|
320
|
+
private long findEndTime() {
|
321
|
+
long endTime = 0;
|
322
|
+
|
323
|
+
for (Gesture gesture : gesturesToPerform) {
|
324
|
+
endTime = Math.max(gesture.upEvent().getTime(), endTime);
|
325
|
+
}
|
326
|
+
|
327
|
+
return endTime;
|
328
|
+
}
|
329
|
+
|
330
|
+
public static void tryWaitForKeyguard(int timeoutSeconds) {
|
331
|
+
Activity activity = InstrumentationBackend.solo.getCurrentActivity();
|
332
|
+
KeyguardManager keyguardManager = (KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE);
|
333
|
+
|
334
|
+
if (!keyguardManager.inKeyguardRestrictedInputMode()) {
|
335
|
+
return;
|
336
|
+
}
|
337
|
+
|
338
|
+
long startTime = SystemClock.uptimeMillis();
|
339
|
+
|
340
|
+
while (SystemClock.uptimeMillis() - startTime <= timeoutSeconds * 1000) {
|
341
|
+
if (!keyguardManager.inKeyguardRestrictedInputMode()) {
|
342
|
+
break;
|
343
|
+
}
|
344
|
+
}
|
345
|
+
|
346
|
+
// For now, if the keyguard has shown up once, we sleep a bit more.
|
347
|
+
// TODO: Improve the implementation to detect focus of the activity to replace stopping when the keyguard is gone
|
348
|
+
|
349
|
+
try {
|
350
|
+
Thread.sleep(100);
|
351
|
+
} catch (InterruptedException e) {
|
352
|
+
e.printStackTrace();
|
353
|
+
}
|
354
|
+
}
|
355
|
+
|
356
|
+
public static MotionEvent obtainMotionEvent(List<Gesture> pressedGestures, int motionEventAction, long currentTime, Long absoluteDownTime) {
|
357
|
+
return obtainMotionEvent(pressedGestures, motionEventAction, currentTime, absoluteDownTime, null);
|
358
|
+
}
|
359
|
+
|
360
|
+
public static MotionEvent obtainMotionEvent(List<Gesture> pressedGestures, int motionEventAction, long currentTime, Long absoluteDownTime, Long systemClockUptimeMillis) {
|
361
|
+
int gestureLength = pressedGestures.size();
|
362
|
+
|
363
|
+
Coordinate[] coordinates = new Coordinate[gestureLength];
|
364
|
+
int[] pointerIds = new int[gestureLength];
|
365
|
+
|
366
|
+
for (int i = 0; i < gestureLength; i++) {
|
367
|
+
Gesture gesture = pressedGestures.get(i);
|
368
|
+
coordinates[i] = gesture.getPosition(currentTime);
|
369
|
+
pointerIds[i] = gesture.getId();
|
370
|
+
}
|
371
|
+
|
372
|
+
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.FROYO) {
|
373
|
+
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[gestureLength];
|
374
|
+
|
375
|
+
for (int i = 0; i < gestureLength; i++) {
|
376
|
+
MotionEvent.PointerCoords pointerCoord = new MotionEvent.PointerCoords();
|
377
|
+
pointerCoord.x = coordinates[i].getX();
|
378
|
+
pointerCoord.y = coordinates[i].getY();
|
379
|
+
pointerCoord.pressure = 1;
|
380
|
+
pointerCoord.size = 1;
|
381
|
+
pointerCoords[i] = pointerCoord;
|
382
|
+
}
|
383
|
+
|
384
|
+
if (systemClockUptimeMillis == null) {
|
385
|
+
systemClockUptimeMillis = SystemClock.uptimeMillis();
|
386
|
+
}
|
387
|
+
|
388
|
+
if (absoluteDownTime == null) {
|
389
|
+
absoluteDownTime = systemClockUptimeMillis;
|
390
|
+
}
|
391
|
+
|
392
|
+
return MotionEvent.obtain(absoluteDownTime, systemClockUptimeMillis, motionEventAction, gestureLength, pointerIds, pointerCoords, 0, 1, 1, 0, 0, 0, 0);
|
393
|
+
} else {
|
394
|
+
try {
|
395
|
+
Method method = MotionEvent.class.getMethod("obtainNano", long.class, long.class, long.class, int.class, int.class, int[].class, float[].class, int.class, float.class, float.class, int.class, int.class);
|
396
|
+
float[] inData = new float[4*(gestureLength)];
|
397
|
+
|
398
|
+
for (int i = 0; i < gestureLength; i++) {
|
399
|
+
inData[i*4] = coordinates[i].getX();
|
400
|
+
inData[i*4+1] = coordinates[i].getY();
|
401
|
+
inData[i*4+2] = 1.0f;
|
402
|
+
inData[i*4+3] = 1.0f;
|
403
|
+
}
|
404
|
+
|
405
|
+
if (systemClockUptimeMillis == null) {
|
406
|
+
systemClockUptimeMillis = SystemClock.uptimeMillis();
|
407
|
+
}
|
408
|
+
|
409
|
+
if (absoluteDownTime == null) {
|
410
|
+
absoluteDownTime = systemClockUptimeMillis;
|
411
|
+
}
|
412
|
+
|
413
|
+
return (MotionEvent)method.invoke(null, absoluteDownTime, systemClockUptimeMillis, systemClockUptimeMillis * 1000000, motionEventAction, gestureLength, pointerIds, inData, 0, 1, 1, 0, 0);
|
414
|
+
} catch (Exception e) {
|
415
|
+
e.printStackTrace();
|
416
|
+
throw new RuntimeException(e);
|
417
|
+
}
|
418
|
+
}
|
419
|
+
}
|
420
|
+
|
421
|
+
private class Gesture {
|
422
|
+
List<Event> events;
|
423
|
+
Integer id;
|
424
|
+
Long absoluteDownTime;
|
425
|
+
long timeOffset;
|
426
|
+
boolean fling;
|
427
|
+
|
428
|
+
public Gesture() {
|
429
|
+
this(0);
|
430
|
+
}
|
431
|
+
|
432
|
+
public Gesture(long timeOffset) {
|
433
|
+
events = new ArrayList<Event>();
|
434
|
+
id = null;
|
435
|
+
absoluteDownTime = null;
|
436
|
+
this.timeOffset = timeOffset;
|
437
|
+
setFling(false);
|
438
|
+
}
|
439
|
+
|
440
|
+
public void addTimeOffset(long timeOffset) {
|
441
|
+
this.timeOffset += timeOffset;
|
442
|
+
}
|
443
|
+
|
444
|
+
public long getTimeOffset() {
|
445
|
+
return timeOffset;
|
446
|
+
}
|
447
|
+
|
448
|
+
public Integer getId() {
|
449
|
+
return id;
|
450
|
+
}
|
451
|
+
|
452
|
+
public boolean getFling() {
|
453
|
+
return fling;
|
454
|
+
}
|
455
|
+
|
456
|
+
public Long getAbsoluteDownTime() {
|
457
|
+
return absoluteDownTime;
|
458
|
+
}
|
459
|
+
|
460
|
+
public void setFling(boolean fling) {
|
461
|
+
this.fling = fling;
|
462
|
+
}
|
463
|
+
|
464
|
+
public void addEvent(Event event) {
|
465
|
+
addEvent(event, true);
|
466
|
+
}
|
467
|
+
|
468
|
+
public void addEvent(Event event, boolean useOffset) {
|
469
|
+
if (upEvent() != null) {
|
470
|
+
event.setTime(event.getTime() + upEvent().getTime());
|
471
|
+
}
|
472
|
+
|
473
|
+
if (useOffset) {
|
474
|
+
event.setTime(event.getTime() + timeOffset);
|
475
|
+
}
|
476
|
+
|
477
|
+
events.add(event);
|
478
|
+
timeOffset = 0;
|
479
|
+
}
|
480
|
+
|
481
|
+
public boolean hasEvents() {
|
482
|
+
return (events != null && events.size() != 0);
|
483
|
+
}
|
484
|
+
|
485
|
+
public Event downEvent() {
|
486
|
+
if (!hasEvents()) {
|
487
|
+
return null;
|
488
|
+
} else {
|
489
|
+
return events.get(0);
|
490
|
+
}
|
491
|
+
}
|
492
|
+
|
493
|
+
public Event upEvent() {
|
494
|
+
if (!hasEvents()) {
|
495
|
+
return null;
|
496
|
+
} else {
|
497
|
+
return events.get(events.size() - 1);
|
498
|
+
}
|
499
|
+
}
|
500
|
+
|
501
|
+
public Pair<Event,Event> getEventPair(long time) {
|
502
|
+
if (downEvent().getTime() == time) {
|
503
|
+
return new Pair<Event,Event>(downEvent(), events.get(1));
|
504
|
+
}
|
505
|
+
|
506
|
+
int i;
|
507
|
+
|
508
|
+
for (i = 0; i < events.size(); i++) {
|
509
|
+
if (events.get(i).getTime() > time) {
|
510
|
+
break;
|
511
|
+
}
|
512
|
+
}
|
513
|
+
|
514
|
+
if (i == 0) {
|
515
|
+
return null;
|
516
|
+
}
|
517
|
+
|
518
|
+
return new Pair<Event,Event>(events.get(i-1), events.get(i));
|
519
|
+
}
|
520
|
+
|
521
|
+
public boolean shouldPress(long time) {
|
522
|
+
return (getEventPair(time) != null);
|
523
|
+
}
|
524
|
+
|
525
|
+
public boolean shouldRelease(long time) {
|
526
|
+
return (time >= upEvent().getTime());
|
527
|
+
}
|
528
|
+
|
529
|
+
public boolean shouldMove(long time) {
|
530
|
+
if (!getFling() || time >= upEvent().getTime()) {
|
531
|
+
return true;
|
532
|
+
}
|
533
|
+
|
534
|
+
Coordinate position = getPosition(time);
|
535
|
+
Coordinate upCoordinate = upEvent().getCoordinate();
|
536
|
+
double fullDistance = getEventPair(time).first.getCoordinate().distance(upCoordinate);
|
537
|
+
double distance = position.distance(upCoordinate);
|
538
|
+
|
539
|
+
return (distance / fullDistance > 0.05);
|
540
|
+
}
|
541
|
+
|
542
|
+
public Coordinate getPosition(long time) {
|
543
|
+
if (time >= upEvent().getTime()) {
|
544
|
+
return upEvent().getCoordinate();
|
545
|
+
}
|
546
|
+
|
547
|
+
Pair<Event,Event> eventPair = getEventPair(time);
|
548
|
+
|
549
|
+
long startTime = eventPair.first.getTime();
|
550
|
+
long endTime = eventPair.second.getTime();
|
551
|
+
Coordinate from = eventPair.first.getCoordinate();
|
552
|
+
Coordinate to = eventPair.second.getCoordinate();
|
553
|
+
|
554
|
+
if (endTime == startTime) {
|
555
|
+
return to;
|
556
|
+
}
|
557
|
+
|
558
|
+
float fraction = (float) (time - startTime)/(endTime - startTime);
|
559
|
+
|
560
|
+
if (fraction == 0.0f) {
|
561
|
+
return from;
|
562
|
+
} else {
|
563
|
+
return from.between(to, fraction);
|
564
|
+
}
|
565
|
+
}
|
566
|
+
|
567
|
+
public MotionEvent.PointerProperties getPointerProperty() {
|
568
|
+
MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
|
569
|
+
pointerProperties.id = id;
|
570
|
+
pointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
|
571
|
+
|
572
|
+
return pointerProperties;
|
573
|
+
}
|
574
|
+
|
575
|
+
public MotionEvent.PointerCoords getPointerCoord(long time) {
|
576
|
+
Coordinate position = getPosition(time);
|
577
|
+
MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
|
578
|
+
pointerCoords.x = position.getX();
|
579
|
+
pointerCoords.y = position.getY();
|
580
|
+
pointerCoords.pressure = 1.0f;
|
581
|
+
pointerCoords.size = 1.0f;
|
582
|
+
|
583
|
+
return pointerCoords;
|
584
|
+
}
|
585
|
+
|
586
|
+
public void setId(List<Gesture> pressedGestures) {
|
587
|
+
// The id of the pointer is 0-based. The id should always be as low as possible
|
588
|
+
int id = 0;
|
589
|
+
boolean idSet = false;
|
590
|
+
|
591
|
+
while (!idSet) {
|
592
|
+
idSet = true;
|
593
|
+
|
594
|
+
for (Gesture gesture : pressedGestures) {
|
595
|
+
if (gesture.getId() == id) {
|
596
|
+
idSet = false;
|
597
|
+
id++;
|
598
|
+
}
|
599
|
+
}
|
600
|
+
}
|
601
|
+
|
602
|
+
this.id = id;
|
603
|
+
}
|
604
|
+
|
605
|
+
public MotionEvent generateDownEvent(List<Gesture> pressedGestures) {
|
606
|
+
int gestureLength = pressedGestures.size();
|
607
|
+
setId(pressedGestures);
|
608
|
+
int motionEventAction;
|
609
|
+
|
610
|
+
// The down action is based on the amount of gestures currently down, not its own id/timing.
|
611
|
+
if (gestureLength > 0) {
|
612
|
+
motionEventAction = (gestureLength << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + MotionEvent.ACTION_POINTER_DOWN;
|
613
|
+
} else {
|
614
|
+
motionEventAction = MotionEvent.ACTION_DOWN;
|
615
|
+
}
|
616
|
+
|
617
|
+
long time = downEvent().getTime();
|
618
|
+
|
619
|
+
// Because this gesture is not yet down, it is not included in the list of gestures passed as the first parameter
|
620
|
+
List<Gesture> allPressedGestures = new ArrayList<Gesture>(pressedGestures);
|
621
|
+
allPressedGestures.add(this);
|
622
|
+
|
623
|
+
absoluteDownTime = SystemClock.uptimeMillis();
|
624
|
+
|
625
|
+
return obtainMotionEvent(allPressedGestures, motionEventAction, time, absoluteDownTime);
|
626
|
+
}
|
627
|
+
|
628
|
+
public MotionEvent generateUpEvent(List<Gesture> pressedGestures) {
|
629
|
+
return generateUpEvent(pressedGestures, null);
|
630
|
+
}
|
631
|
+
|
632
|
+
public MotionEvent generateUpEvent(List<Gesture> pressedGestures, Long systemClockUptimeMillis) {
|
633
|
+
int gestureLength = pressedGestures.size();
|
634
|
+
int motionEventAction;
|
635
|
+
int indexInPressedGestures = pressedGestures.indexOf(this);
|
636
|
+
|
637
|
+
// The up action is based on the amount of gestures currently down, not its own id/timing.
|
638
|
+
if (gestureLength > 1) {
|
639
|
+
motionEventAction = (indexInPressedGestures << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + MotionEvent.ACTION_POINTER_UP;
|
640
|
+
} else {
|
641
|
+
motionEventAction = MotionEvent.ACTION_UP;
|
642
|
+
}
|
643
|
+
|
644
|
+
long time = upEvent().getTime();
|
645
|
+
|
646
|
+
return obtainMotionEvent(pressedGestures, motionEventAction, time, getAbsoluteDownTime(), systemClockUptimeMillis);
|
647
|
+
}
|
648
|
+
|
649
|
+
public String toString() {
|
650
|
+
return "Gesture {Events: "+events.toString()+"}";
|
651
|
+
}
|
652
|
+
}
|
653
|
+
|
654
|
+
private class Event {
|
655
|
+
private Coordinate coordinate;
|
656
|
+
private Long time;
|
657
|
+
|
658
|
+
public Event() {
|
659
|
+
this(null, null);
|
660
|
+
}
|
661
|
+
|
662
|
+
public Event(Coordinate coordinate, Long time) {
|
663
|
+
setCoordinate(coordinate);
|
664
|
+
setTime(time);
|
665
|
+
}
|
666
|
+
|
667
|
+
public Event copy() {
|
668
|
+
return new Event(coordinate.copy(), getTime());
|
669
|
+
}
|
670
|
+
|
671
|
+
public Coordinate getCoordinate() {
|
672
|
+
return coordinate;
|
673
|
+
}
|
674
|
+
|
675
|
+
public void setCoordinate(Coordinate coordinate) {
|
676
|
+
this.coordinate = coordinate;
|
677
|
+
}
|
678
|
+
|
679
|
+
public long getTime() {
|
680
|
+
return time;
|
681
|
+
}
|
682
|
+
|
683
|
+
public void setTime(Long time) {
|
684
|
+
this.time = time;
|
685
|
+
}
|
686
|
+
|
687
|
+
public String toString() {
|
688
|
+
return "Event {time: "+getTime()+", coordinate: "+getCoordinate()+"}";
|
689
|
+
}
|
690
|
+
}
|
691
|
+
|
692
|
+
private class Coordinate {
|
693
|
+
private int x;
|
694
|
+
private int y;
|
695
|
+
|
696
|
+
public Coordinate(int x, int y) {
|
697
|
+
setX(x);
|
698
|
+
setY(y);
|
699
|
+
}
|
700
|
+
|
701
|
+
public Coordinate copy() {
|
702
|
+
return new Coordinate(getX(), getY());
|
703
|
+
}
|
704
|
+
|
705
|
+
public int getX() {
|
706
|
+
return x;
|
707
|
+
}
|
708
|
+
|
709
|
+
public int getY() {
|
710
|
+
return y;
|
711
|
+
}
|
712
|
+
|
713
|
+
public void setX(int x) {
|
714
|
+
this.x = x;
|
715
|
+
}
|
716
|
+
|
717
|
+
public void setY(int y) {
|
718
|
+
this.y = y;
|
719
|
+
}
|
720
|
+
|
721
|
+
public Coordinate between(Coordinate to, float fraction) {
|
722
|
+
int x = (int) ((to.getX() - getX()) * fraction + getX());
|
723
|
+
int y = (int) ((to.getY() - getY()) * fraction + getY());
|
724
|
+
|
725
|
+
return new Coordinate(x, y);
|
726
|
+
}
|
727
|
+
|
728
|
+
public double distance(Coordinate to) {
|
729
|
+
float xDistance = getX() - to.getX();
|
730
|
+
float yDistance = getY() - to.getY();
|
731
|
+
|
732
|
+
return Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
|
733
|
+
}
|
734
|
+
|
735
|
+
public String toString() {
|
736
|
+
return "Coordinate {x: "+getX()+", y: "+getY()+"}";
|
737
|
+
}
|
738
|
+
|
739
|
+
public boolean equals(Object object, double precision) {
|
740
|
+
if (!(object instanceof Coordinate)) {
|
741
|
+
return false;
|
742
|
+
}
|
743
|
+
|
744
|
+
Coordinate coordinate = (Coordinate)object;
|
745
|
+
|
746
|
+
return (distance(coordinate) <= precision);
|
747
|
+
}
|
748
|
+
}
|
749
|
+
}
|