├── .gitignore
├── LICENSE
├── NOTICE
├── README.md
├── logo.png
├── pom.xml
└── robotium-solo
├── pom.xml
└── src
└── main
└── java
└── com
└── robotium
└── solo
├── ActivityUtils.java
├── Asserter.java
├── By.java
├── Checker.java
├── Clicker.java
├── Condition.java
├── DialogUtils.java
├── GLRenderWrapper.java
├── Getter.java
├── Illustration.java
├── Illustrator.java
├── Presser.java
├── PressurePoint.java
├── Reflect.java
├── RobotiumTextView.java
├── RobotiumUtils.java
├── RobotiumWeb.js
├── RobotiumWebClient.java
├── Rotator.java
├── ScreenshotTaker.java
├── Scroller.java
├── Searcher.java
├── Sender.java
├── Setter.java
├── Sleeper.java
├── Solo.java
├── Swiper.java
├── SystemUtils.java
├── Tapper.java
├── TextEnterer.java
├── Timeout.java
├── ViewFetcher.java
├── ViewLocationComparator.java
├── Waiter.java
├── WebElement.java
├── WebElementCreator.java
├── WebUtils.java
└── Zoomer.java
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | target
3 | .idea
4 | *.iws
5 | *.ipr
6 | *.iml
7 | .classpath
8 | .project
9 | .settings
10 | .springBeans
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [2012] [Robotium Developers]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | =========================================================================
2 | == NOTICE file corresponding to the section 4 d of ==
3 | == the Apache License, Version 2.0, ==
4 | == in this case for the Robotium-specific code. ==
5 | =========================================================================
6 |
7 | Robotium
8 | Copyright 2010, 2011 Jayway AB
9 |
10 | This product includes software developed at
11 | Jayway AB (http://www.jayway.com/).
12 |
13 |
14 | =========================================================================
15 | == NOTICE file corresponding to the section 4 d of ==
16 | == the Apache License, Version 2.0, ==
17 | == in this case for the Android-specific code. ==
18 | =========================================================================
19 |
20 | Android Code
21 | Copyright 2005-2008 The Android Open Source Project
22 |
23 | This product includes software developed as part of
24 | The Android Open Source Project (http://source.android.com).
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # User scenario testing for Android
4 | Robotium is an Android test automation framework that has full support for native and hybrid applications. Robotium makes it easy to write powerful and robust automatic black-box UI tests for Android applications. With the support of Robotium, test case developers can write function, system and user acceptance test scenarios, spanning multiple Android activities.
5 |
6 |
7 | See [Questions & Answers](https://github.com/RobotiumTech/robotium/wiki/Questions-&-Answers) for common Robotium questions and answers.
8 |
9 |
10 | See [Getting Started](https://github.com/RobotiumTech/robotium/wiki/Getting-Started) for instructions and examples on how to create your first Robotium tests.
11 |
12 |
13 | Join the discussions in the [Robotium Developers Group](http://groups.google.com/group/robotium-developers).
14 |
15 | ----
16 | ### NEWS: Robotium 5.6.3 is released!
17 | [Robotium 5.6.3](https://github.com/RobotiumTech/robotium/wiki/Downloads) is the fastest, most accurate and stable version of Robotium yet.
18 |
19 | New functionality includes: clickInRecyclerView(int itemIndex, int recyclerViewIndex, int id) and Config.sleepDuration, Config.sleepMiniDuration (to adjust the execution speed, lower means faster) and more. See the [Javadoc](http://recorder.robotium.com/javadoc/) for more information.
20 |
21 | ----
22 |
23 | #### Robotium provides the following benefits:
24 | * Test Android apps, both native and hybrid.
25 | * Requires minimal knowledge of the application under test.
26 | * The framework handles multiple Android activities automatically.
27 | * Minimal time needed to write solid test cases.
28 | * Readability of test cases is greatly improved, compared to standard instrumentation tests.
29 | * Test cases are more robust due to the run-time binding to UI components.
30 | * Fast test case execution.
31 | * Integrates smoothly with Maven, Gradle or Ant to run tests as part of continuous integration.
32 |
33 |
34 | #### Robotium Recorder ####
35 | * Check out [Robotium Recorder](http://Robotium.com) that will allow you to record Robotium test cases in minutes. Now also available for Android Studio!
36 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobotiumTech/robotium/75e567c38f26a6a87dc8bef90b3886a20e28d291/logo.png
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 | com.jayway.android.robotium
4 | robotium
5 | 5.6.4-SNAPSHOT
6 | pom
7 | Robotium
8 | User scenario testing for Android
9 | http://www.robotium.org/
10 | 2009
11 |
12 | robotium-solo
13 |
14 |
15 | master
16 | UTF-8
17 | 3.2.5
18 | 4.0.1.2
19 |
20 |
21 | http://github.com/jayway/robotium/tree/${scm.branch}
22 | scm:git:git://github.com/jayway/robotium.git
23 | scm:git:ssh://git@github.com/jayway/robotium.git
24 | HEAD
25 |
26 |
27 | Google Code Issue Tracking
28 | http://code.google.com/p/robotium/issues/list
29 |
30 |
31 | Hudson
32 | http://hudson.josefson.org/view/Robotium/
33 |
34 |
35 |
36 | Apache 2.0
37 | LICENSE
38 |
39 |
40 |
41 |
42 | See homepage and mailinglist for contributors.
43 | robotium-project
44 | http://www.robotium.org/
45 |
46 | Developer
47 | Contributor
48 |
49 |
50 |
51 |
52 |
53 | Robotium Developers
54 | http://groups.google.com/group/robotium-developers/topics
55 |
56 |
57 |
58 | ${maven.version}
59 |
60 |
61 |
62 |
63 | com.google.android
64 | android
65 | ${android.jar.version}
66 | provided
67 |
68 |
69 | com.google.android
70 | android-test
71 | ${android.jar.version}
72 | provided
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | maven-clean-plugin
81 | 2.6.1
82 |
83 |
84 | maven-compiler-plugin
85 | 3.2
86 |
87 |
88 | maven-deploy-plugin
89 | 2.8.2
90 |
91 |
92 | maven-install-plugin
93 | 2.5.2
94 |
95 |
96 | maven-gpg-plugin
97 | 1.6
98 |
99 |
100 | maven-jar-plugin
101 | 2.5
102 |
103 |
104 | maven-resources-plugin
105 | 2.7
106 |
107 |
108 | maven-surefire-plugin
109 | 2.18.1
110 |
111 |
112 | maven-javadoc-plugin
113 | 2.10.1
114 |
115 |
116 | maven-release-plugin
117 | 2.5.1
118 |
119 |
120 | maven-scm-plugin
121 | 1.9.2
122 |
123 |
124 | maven-site-plugin
125 | 3.4
126 |
127 |
128 | maven-source-plugin
129 | 2.4
130 |
131 |
132 | maven-enforcer-plugin
133 | 1.4
134 |
135 |
136 |
140 | org.codehaus.mojo
141 | versions-maven-plugin
142 | 2.1
143 |
144 |
145 | maven-assembly-plugin
146 | 2.5.3
147 |
148 |
149 |
150 |
151 |
152 | maven-scm-plugin
153 |
154 | branch
155 | ${scm.branch}
156 |
157 |
158 |
159 | maven-release-plugin
160 |
161 | true
162 | false
163 | -Pofficial-release
164 |
165 |
166 |
167 | maven-compiler-plugin
168 |
169 | 1.5
170 | 1.5
171 |
172 |
173 |
174 | maven-jar-plugin
175 |
176 |
177 |
178 | 1.5
179 | 1.5
180 |
181 |
182 |
183 |
184 |
185 | maven-enforcer-plugin
186 |
187 |
188 | enforce-maven
189 |
190 | enforce
191 |
192 | initialize
193 |
194 |
195 |
196 |
197 |
198 | [${maven.version},)
199 | Check for Maven version >=${maven.version} failed. Update your Maven install.
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 | src/main/resources
208 |
209 |
210 | src/main/java
211 |
212 | **/*.js
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 | oss.sonatype.org-jayway-staging
221 | OpenSource Release Staging on Sonatype.org
222 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
223 |
224 |
225 | oss.sonatype.org-jayway-snapshots
226 | Jayway OpenSource SNAPSHOTs on Sonatype.org
227 | https://oss.sonatype.org/content/repositories/jayway-snapshots/
228 |
229 |
230 |
231 |
232 |
233 | official-release
234 |
235 |
236 |
237 |
238 | maven-gpg-plugin
239 |
240 | true
241 |
242 |
243 |
244 | sign-artifacts
245 | verify
246 |
247 | sign
248 |
249 |
250 |
251 |
252 |
253 |
254 | true
255 | maven-deploy-plugin
256 |
257 | true
258 |
259 |
260 |
261 | maven-source-plugin
262 |
263 |
264 | attach-sources
265 |
266 | jar-no-fork
267 |
268 |
269 |
270 |
271 |
272 | maven-javadoc-plugin
273 |
274 | ${project.build.sourceEncoding}
275 |
276 |
277 |
278 | attach-javadocs
279 |
280 | jar
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
--------------------------------------------------------------------------------
/robotium-solo/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 | com.jayway.android.robotium
4 | robotium-solo
5 | 5.6.4-SNAPSHOT
6 | jar
7 | Robotium :: Solo
8 |
9 | com.jayway.android.robotium
10 | robotium
11 | 5.6.4-SNAPSHOT
12 |
13 |
14 |
15 | com.google.android
16 | android
17 |
18 |
19 | com.google.android
20 | android-test
21 |
22 |
23 | com.google.android
24 | support-v4
25 | r7
26 | provided
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/ActivityUtils.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import java.lang.ref.WeakReference;
4 | import java.util.ArrayList;
5 | import java.util.Iterator;
6 | import java.util.Stack;
7 | import java.util.Timer;
8 | import com.robotium.solo.Solo.Config;
9 | import junit.framework.Assert;
10 | import android.app.Activity;
11 | import android.app.Instrumentation;
12 | import android.app.Instrumentation.ActivityMonitor;
13 | import android.content.IntentFilter;
14 | import android.os.SystemClock;
15 | import android.util.Log;
16 | import android.view.KeyEvent;
17 |
18 | /**
19 | * Contains activity related methods. Examples are:
20 | * getCurrentActivity(), getActivityMonitor(), setActivityOrientation(int orientation).
21 | *
22 | * @author Renas Reda, renas.reda@robotium.com
23 | *
24 | */
25 |
26 | class ActivityUtils {
27 |
28 | private final Config config;
29 | private final Instrumentation inst;
30 | private ActivityMonitor activityMonitor;
31 | private Activity activity;
32 | private final Sleeper sleeper;
33 | private final String LOG_TAG = "Robotium";
34 | private final int MINISLEEP = 100;
35 | private Stack> activityStack;
36 | private WeakReference weakActivityReference;
37 | private Stack activitiesStoredInActivityStack;
38 | private Timer activitySyncTimer;
39 | private volatile boolean registerActivities;
40 | Thread activityThread;
41 |
42 | /**
43 | * Constructs this object.
44 | *
45 | * @param config the {@code Config} instance
46 | * @param inst the {@code Instrumentation} instance.
47 | * @param activity the start {@code Activity}
48 | * @param sleeper the {@code Sleeper} instance
49 | */
50 |
51 | public ActivityUtils(Config config, Instrumentation inst, Activity activity, Sleeper sleeper) {
52 | this.config = config;
53 | this.inst = inst;
54 | this.activity = activity;
55 | this.sleeper = sleeper;
56 | createStackAndPushStartActivity();
57 | activitySyncTimer = new Timer();
58 | activitiesStoredInActivityStack = new Stack();
59 | setupActivityMonitor();
60 | setupActivityStackListener();
61 | }
62 |
63 |
64 |
65 | /**
66 | * Creates a new activity stack and pushes the start activity.
67 | */
68 |
69 | private void createStackAndPushStartActivity(){
70 | activityStack = new Stack>();
71 | if (activity != null && config.trackActivities){
72 | WeakReference weakReference = new WeakReference(activity);
73 | activity = null;
74 | activityStack.push(weakReference);
75 | }
76 | }
77 |
78 |
79 | /**
80 | * Returns a {@code List} of all the opened/active activities.
81 | *
82 | * @return a {@code List} of all the opened/active activities
83 | */
84 |
85 | public ArrayList getAllOpenedActivities()
86 | {
87 | ArrayList activities = new ArrayList();
88 | Iterator> activityStackIterator = activityStack.iterator();
89 |
90 | while(activityStackIterator.hasNext()){
91 | Activity activity = activityStackIterator.next().get();
92 | if(activity!=null)
93 | activities.add(activity);
94 | }
95 | return activities;
96 | }
97 |
98 | /**
99 | * This is were the activityMonitor is set up. The monitor will keep check
100 | * for the currently active activity.
101 | */
102 |
103 | private void setupActivityMonitor() {
104 | if(config.trackActivities){
105 | try {
106 | IntentFilter filter = null;
107 | activityMonitor = inst.addMonitor(filter, null, false);
108 | } catch (Exception e) {
109 | e.printStackTrace();
110 | }
111 | }
112 | }
113 |
114 |
115 | /**
116 | * Returns true if registration of Activites should be performed
117 | *
118 | * @return true if registration of Activities should be performed
119 | */
120 |
121 | public boolean shouldRegisterActivities() {
122 | return registerActivities;
123 | }
124 |
125 |
126 | /**
127 | * Set true if registration of Activities should be performed
128 | * @param registerActivities true if registration of Activities should be performed
129 | *
130 | */
131 |
132 | public void setRegisterActivities(boolean registerActivities) {
133 | this.registerActivities = registerActivities;
134 | }
135 |
136 | /**
137 | * This is were the activityStack listener is set up. The listener will keep track of the
138 | * opened activities and their positions.
139 | */
140 |
141 | private void setupActivityStackListener() {
142 | if(activityMonitor == null){
143 | return;
144 | }
145 |
146 | setRegisterActivities(true);
147 |
148 | activityThread = new RegisterActivitiesThread(this);
149 | activityThread.start();
150 | }
151 |
152 |
153 | void monitorActivities() {
154 | if(activityMonitor != null){
155 | Activity activity = activityMonitor.waitForActivityWithTimeout(2000L);
156 |
157 | if(activity != null){
158 | if (activitiesStoredInActivityStack.remove(activity.toString())){
159 | removeActivityFromStack(activity);
160 | }
161 | if(!activity.isFinishing()){
162 | addActivityToStack(activity);
163 | }
164 | }
165 | }
166 | }
167 |
168 |
169 |
170 | /**
171 | * Removes a given activity from the activity stack
172 | *
173 | * @param activity the activity to remove
174 | */
175 |
176 | private void removeActivityFromStack(Activity activity){
177 |
178 | Iterator> activityStackIterator = activityStack.iterator();
179 | while(activityStackIterator.hasNext()){
180 | Activity activityFromWeakReference = activityStackIterator.next().get();
181 |
182 | if(activityFromWeakReference == null){
183 | activityStackIterator.remove();
184 | }
185 |
186 | if(activity != null && activityFromWeakReference != null && activityFromWeakReference.equals(activity)){
187 | activityStackIterator.remove();
188 | }
189 | }
190 | }
191 |
192 | /**
193 | * Returns the ActivityMonitor used by Robotium.
194 | *
195 | * @return the ActivityMonitor used by Robotium
196 | */
197 |
198 | public ActivityMonitor getActivityMonitor(){
199 | return activityMonitor;
200 | }
201 |
202 | /**
203 | * Sets the Orientation (Landscape/Portrait) for the current activity.
204 | *
205 | * @param orientation An orientation constant such as {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE} or {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_PORTRAIT}
206 | */
207 |
208 | public void setActivityOrientation(int orientation)
209 | {
210 | Activity activity = getCurrentActivity();
211 | if(activity != null){
212 | activity.setRequestedOrientation(orientation);
213 | }
214 | }
215 |
216 | /**
217 | * Returns the current {@code Activity}, after sleeping a default pause length.
218 | *
219 | * @param shouldSleepFirst whether to sleep a default pause first
220 | * @return the current {@code Activity}
221 | */
222 |
223 | public Activity getCurrentActivity(boolean shouldSleepFirst) {
224 | return getCurrentActivity(shouldSleepFirst, true);
225 | }
226 |
227 | /**
228 | * Returns the current {@code Activity}, after sleeping a default pause length.
229 | *
230 | * @return the current {@code Activity}
231 | */
232 |
233 | public Activity getCurrentActivity() {
234 | return getCurrentActivity(true, true);
235 | }
236 |
237 | /**
238 | * Adds an activity to the stack
239 | *
240 | * @param activity the activity to add
241 | */
242 |
243 | private void addActivityToStack(Activity activity){
244 | activitiesStoredInActivityStack.push(activity.toString());
245 | weakActivityReference = new WeakReference(activity);
246 | activity = null;
247 | activityStack.push(weakActivityReference);
248 | }
249 |
250 | /**
251 | * Waits for an activity to be started if one is not provided
252 | * by the constructor.
253 | */
254 |
255 | private final void waitForActivityIfNotAvailable(){
256 | if(activityStack.isEmpty() || activityStack.peek().get() == null){
257 |
258 | if (activityMonitor != null) {
259 | Activity activity = activityMonitor.getLastActivity();
260 | while (activity == null){
261 | sleeper.sleepMini();
262 | activity = activityMonitor.getLastActivity();
263 | }
264 | addActivityToStack(activity);
265 | }
266 | else if(config.trackActivities){
267 | sleeper.sleepMini();
268 | setupActivityMonitor();
269 | waitForActivityIfNotAvailable();
270 | }
271 | }
272 | }
273 |
274 | /**
275 | * Returns the name of the most recent Activity
276 | *
277 | * @return the name of the current {@code Activity}
278 | */
279 |
280 | public String getCurrentActivityName(){
281 | if(!activitiesStoredInActivityStack.isEmpty()){
282 | return activitiesStoredInActivityStack.peek();
283 | }
284 | return "";
285 | }
286 |
287 | /**
288 | * Returns the current {@code Activity}.
289 | *
290 | * @param shouldSleepFirst whether to sleep a default pause first
291 | * @param waitForActivity whether to wait for the activity
292 | * @return the current {@code Activity}
293 | */
294 |
295 | public Activity getCurrentActivity(boolean shouldSleepFirst, boolean waitForActivity) {
296 | if(shouldSleepFirst){
297 | sleeper.sleep();
298 | }
299 | if(!config.trackActivities){
300 | return activity;
301 | }
302 |
303 | if(waitForActivity){
304 | waitForActivityIfNotAvailable();
305 | }
306 | if(!activityStack.isEmpty()){
307 | activity=activityStack.peek().get();
308 | }
309 | return activity;
310 | }
311 |
312 | /**
313 | * Check if activity stack is empty.
314 | *
315 | * @return true if activity stack is empty
316 | */
317 |
318 | public boolean isActivityStackEmpty() {
319 | return activityStack.isEmpty();
320 | }
321 |
322 | /**
323 | * Returns to the given {@link Activity}.
324 | *
325 | * @param name the name of the {@code Activity} to return to, e.g. {@code "MyActivity"}
326 | */
327 |
328 | public void goBackToActivity(String name)
329 | {
330 | ArrayList activitiesOpened = getAllOpenedActivities();
331 | boolean found = false;
332 | for(int i = 0; i < activitiesOpened.size(); i++){
333 | if(activitiesOpened.get(i).getClass().getSimpleName().equals(name)){
334 | found = true;
335 | break;
336 | }
337 | }
338 | if(found){
339 | while(!getCurrentActivity().getClass().getSimpleName().equals(name))
340 | {
341 | try{
342 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
343 | }catch(SecurityException ignored){}
344 | }
345 | }
346 | else{
347 | for (int i = 0; i < activitiesOpened.size(); i++){
348 | Log.d(LOG_TAG, "Activity priorly opened: "+ activitiesOpened.get(i).getClass().getSimpleName());
349 | }
350 | Assert.fail("No Activity named: '" + name + "' has been priorly opened");
351 | }
352 | }
353 |
354 | /**
355 | * Returns a localized string.
356 | *
357 | * @param resId the resource ID for the string
358 | * @return the localized string
359 | */
360 |
361 | public String getString(int resId)
362 | {
363 | Activity activity = getCurrentActivity(false);
364 | if(activity == null){
365 | return "";
366 | }
367 | return activity.getString(resId);
368 | }
369 |
370 | /**
371 | * Finalizes the solo object.
372 | */
373 |
374 | @Override
375 | public void finalize() throws Throwable {
376 | activitySyncTimer.cancel();
377 | stopActivityMonitor();
378 | super.finalize();
379 | }
380 |
381 | /**
382 | * Removes the ActivityMonitor
383 | */
384 | private void stopActivityMonitor(){
385 | try {
386 | // Remove the monitor added during startup
387 | if (activityMonitor != null) {
388 | inst.removeMonitor(activityMonitor);
389 | activityMonitor = null;
390 | }
391 | } catch (Exception ignored) {}
392 |
393 | }
394 |
395 | /**
396 | * All activites that have been opened are finished.
397 | */
398 |
399 | public void finishOpenedActivities(){
400 | // Stops the activityStack listener
401 | activitySyncTimer.cancel();
402 | if(!config.trackActivities){
403 | useGoBack(3);
404 | return;
405 | }
406 | ArrayList activitiesOpened = getAllOpenedActivities();
407 | // Finish all opened activities
408 | for (int i = activitiesOpened.size()-1; i >= 0; i--) {
409 | sleeper.sleep(MINISLEEP);
410 | finishActivity(activitiesOpened.get(i));
411 | }
412 | activitiesOpened = null;
413 | sleeper.sleep(MINISLEEP);
414 | // Finish the initial activity, pressing Back for good measure
415 | finishActivity(getCurrentActivity(true, false));
416 | stopActivityMonitor();
417 | setRegisterActivities(false);
418 | this.activity = null;
419 | sleeper.sleepMini();
420 | useGoBack(1);
421 | clearActivityStack();
422 | }
423 |
424 | /**
425 | * Sends the back button command a given number of times
426 | *
427 | * @param numberOfTimes the number of times to press "back"
428 | */
429 |
430 | private void useGoBack(int numberOfTimes){
431 | for(int i = 0; i < numberOfTimes; i++){
432 | try {
433 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
434 | sleeper.sleep(MINISLEEP);
435 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
436 | } catch (Throwable ignored) {
437 | // Guard against lack of INJECT_EVENT permission
438 | }
439 | }
440 | }
441 |
442 | /**
443 | * Clears the activity stack.
444 | */
445 |
446 | private void clearActivityStack(){
447 |
448 | activityStack.clear();
449 | activitiesStoredInActivityStack.clear();
450 | }
451 |
452 | /**
453 | * Finishes an activity.
454 | *
455 | * @param activity the activity to finish
456 | */
457 |
458 | private void finishActivity(Activity activity){
459 | if(activity != null) {
460 | try{
461 | activity.finish();
462 | }catch(Throwable e){
463 | e.printStackTrace();
464 | }
465 | }
466 | }
467 |
468 | private static final class RegisterActivitiesThread extends Thread {
469 |
470 | public static final long REGISTER_ACTIVITY_THREAD_SLEEP_MS = 16L;
471 | private final WeakReference activityUtilsWR;
472 |
473 | RegisterActivitiesThread(ActivityUtils activityUtils) {
474 | super("activityMonitorThread");
475 | activityUtilsWR = new WeakReference(activityUtils);
476 | setPriority(Thread.MIN_PRIORITY);
477 | }
478 |
479 | @Override
480 | public void run() {
481 | while (shouldMonitor()) {
482 | monitorActivities();
483 | SystemClock.sleep(REGISTER_ACTIVITY_THREAD_SLEEP_MS);
484 | }
485 | }
486 |
487 | private boolean shouldMonitor() {
488 | ActivityUtils activityUtils = activityUtilsWR.get();
489 |
490 | return activityUtils != null && activityUtils.shouldRegisterActivities();
491 | }
492 |
493 | private void monitorActivities() {
494 | ActivityUtils activityUtils = activityUtilsWR.get();
495 | if (activityUtils != null) {
496 | activityUtils.monitorActivities();
497 | }
498 | }
499 | }
500 |
501 | }
502 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Asserter.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import junit.framework.Assert;
4 | import android.app.Activity;
5 | import android.app.ActivityManager;
6 |
7 | /**
8 | * Contains assert methods examples are assertActivity() and assertLowMemory().
9 | *
10 | * @author Renas Reda, renas.reda@robotium.com
11 | *
12 | */
13 |
14 | class Asserter {
15 | private final ActivityUtils activityUtils;
16 | private final Waiter waiter;
17 |
18 | /**
19 | * Constructs this object.
20 | *
21 | * @param activityUtils the {@code ActivityUtils} instance.
22 | * @param waiter the {@code Waiter} instance.
23 | */
24 |
25 | public Asserter(ActivityUtils activityUtils, Waiter waiter) {
26 | this.activityUtils = activityUtils;
27 | this.waiter = waiter;
28 | }
29 |
30 | /**
31 | * Asserts that an expected {@link Activity} is currently active one.
32 | *
33 | * @param message the message that should be displayed if the assert fails
34 | * @param name the name of the {@code Activity} that is expected to be active e.g. {@code "MyActivity"}
35 | */
36 |
37 | public void assertCurrentActivity(String message, String name) {
38 | boolean foundActivity = waiter.waitForActivity(name);
39 |
40 | if(!foundActivity){
41 | Activity activity = activityUtils.getCurrentActivity();
42 | if(activity != null){
43 | Assert.assertEquals(message, name, activity.getClass().getSimpleName());
44 | }
45 | else{
46 | Assert.assertEquals(message, name, "No actvity found");
47 | }
48 | }
49 | }
50 |
51 | /**
52 | * Asserts that an expected {@link Activity} is currently active one.
53 | *
54 | * @param message the message that should be displayed if the assert fails
55 | * @param expectedClass the {@code Class} object that is expected to be active e.g. {@code MyActivity.class}
56 | */
57 |
58 | public void assertCurrentActivity(String message, Class extends Activity> expectedClass) {
59 | if(expectedClass == null){
60 | Assert.fail("The specified Activity is null!");
61 | }
62 |
63 | boolean foundActivity = waiter.waitForActivity(expectedClass);
64 |
65 | if(!foundActivity) {
66 | Activity activity = activityUtils.getCurrentActivity();
67 | if(activity != null){
68 | Assert.assertEquals(message, expectedClass.getName(), activity.getClass().getName());
69 | }
70 | else{
71 | Assert.assertEquals(message, expectedClass.getName(), "No activity found");
72 | }
73 | }
74 | }
75 |
76 | /**
77 | * Asserts that an expected {@link Activity} is currently active one, with the possibility to
78 | * verify that the expected {@code Activity} is a new instance of the {@code Activity}.
79 | *
80 | * @param message the message that should be displayed if the assert fails
81 | * @param name the name of the {@code Activity} that is expected to be active e.g. {@code "MyActivity"}
82 | * @param isNewInstance {@code true} if the expected {@code Activity} is a new instance of the {@code Activity}
83 | */
84 |
85 | public void assertCurrentActivity(String message, String name, boolean isNewInstance) {
86 | assertCurrentActivity(message, name);
87 | Activity activity = activityUtils.getCurrentActivity();
88 | if(activity != null){
89 | assertCurrentActivity(message, activity.getClass(),
90 | isNewInstance);
91 | }
92 | }
93 |
94 | /**
95 | * Asserts that an expected {@link Activity} is currently active one, with the possibility to
96 | * verify that the expected {@code Activity} is a new instance of the {@code Activity}.
97 | *
98 | * @param message the message that should be displayed if the assert fails
99 | * @param expectedClass the {@code Class} object that is expected to be active e.g. {@code MyActivity.class}
100 | * @param isNewInstance {@code true} if the expected {@code Activity} is a new instance of the {@code Activity}
101 | */
102 |
103 | public void assertCurrentActivity(String message, Class extends Activity> expectedClass,
104 | boolean isNewInstance) {
105 | boolean found = false;
106 | assertCurrentActivity(message, expectedClass);
107 | Activity activity = activityUtils.getCurrentActivity(false);
108 | if(activity == null){
109 | Assert.assertNotSame(message, isNewInstance, false);
110 | return;
111 | }
112 |
113 | for (int i = 0; i < activityUtils.getAllOpenedActivities().size() - 1; i++) {
114 | String instanceString = activityUtils.getAllOpenedActivities().get(i).toString();
115 | if (instanceString.equals(activity.toString()))
116 | found = true;
117 | }
118 | Assert.assertNotSame(message, isNewInstance, found);
119 | }
120 |
121 | /**
122 | * Asserts that the available memory is not considered low by the system.
123 | */
124 |
125 | public void assertMemoryNotLow() {
126 | ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
127 | ((ActivityManager)activityUtils.getCurrentActivity().getSystemService("activity")).getMemoryInfo(mi);
128 | Assert.assertFalse("Low memory available: " + mi.availMem + " bytes!", mi.lowMemory);
129 | }
130 |
131 | }
132 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/By.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | /**
4 | * Used in conjunction with the web methods. Examples are By.id(String id) and By.cssSelector(String selector).
5 | *
6 | * @author Renas Reda, renas.reda@robotium.com
7 | *
8 | */
9 |
10 | public abstract class By {
11 |
12 | /**
13 | * Select a WebElement by its id.
14 | *
15 | * @param id the id of the web element
16 | * @return the Id object
17 | */
18 |
19 | public static By id(final String id) {
20 | return new Id(id);
21 |
22 | }
23 |
24 | /**
25 | * Select a WebElement by its xpath.
26 | *
27 | * @param xpath the xpath of the web element
28 | * @return the Xpath object
29 | */
30 |
31 | public static By xpath(final String xpath) {
32 | return new Xpath(xpath);
33 |
34 | }
35 |
36 | /**
37 | * Select a WebElement by its css selector.
38 | *
39 | * @param selectors the css selector of the web element
40 | * @return the CssSelector object
41 | */
42 |
43 | public static By cssSelector(final String selectors) {
44 | return new CssSelector(selectors);
45 |
46 | }
47 |
48 | /**
49 | * Select a WebElement by its name.
50 | *
51 | * @param name the name of the web element
52 | * @return the Name object
53 | */
54 |
55 | public static By name(final String name) {
56 | return new Name(name);
57 |
58 | }
59 |
60 | /**
61 | * Select a WebElement by its class name.
62 | *
63 | * @param className the class name of the web element
64 | * @return the ClassName object
65 | */
66 |
67 | public static By className(final String className) {
68 | return new ClassName(className);
69 |
70 | }
71 |
72 | /**
73 | * Select a WebElement by its text content.
74 | *
75 | * @param textContent the text content of the web element
76 | * @return the TextContent object
77 | */
78 |
79 | public static By textContent(final String textContent) {
80 | return new Text(textContent);
81 |
82 | }
83 |
84 | /**
85 | * Select a WebElement by its tag name.
86 | *
87 | * @param tagName the tag name of the web element
88 | * @return the TagName object
89 | */
90 |
91 | public static By tagName(final String tagName) {
92 | return new TagName(tagName);
93 |
94 | }
95 |
96 | /**
97 | * Returns the value.
98 | *
99 | * @return the value
100 | */
101 |
102 | public String getValue(){
103 | return "";
104 | }
105 |
106 |
107 | static class Id extends By {
108 | private final String id;
109 |
110 | public Id(String id) {
111 | this.id = id;
112 | }
113 |
114 | @Override
115 | public String getValue(){
116 | return id;
117 | }
118 | }
119 |
120 | static class Xpath extends By {
121 | private final String xpath;
122 |
123 | public Xpath(String xpath) {
124 | this.xpath = xpath;
125 | }
126 |
127 | @Override
128 | public String getValue(){
129 | return xpath;
130 | }
131 | }
132 |
133 | static class CssSelector extends By {
134 | private final String selector;
135 |
136 | public CssSelector(String selector) {
137 | this.selector = selector;
138 | }
139 |
140 | @Override
141 | public String getValue(){
142 | return selector;
143 | }
144 | }
145 |
146 | static class Name extends By {
147 | private final String name;
148 |
149 | public Name(String name) {
150 | this.name = name;
151 | }
152 |
153 | @Override
154 | public String getValue(){
155 | return name;
156 | }
157 | }
158 |
159 | static class ClassName extends By {
160 | private final String className;
161 |
162 | public ClassName(String className) {
163 | this.className = className;
164 | }
165 |
166 | @Override
167 | public String getValue(){
168 | return className;
169 | }
170 | }
171 |
172 | static class Text extends By {
173 | private final String textContent;
174 |
175 | public Text(String textContent) {
176 | this.textContent = textContent;
177 | }
178 |
179 | @Override
180 | public String getValue(){
181 | return textContent;
182 | }
183 | }
184 |
185 | static class TagName extends By {
186 | private final String tagName;
187 |
188 | public TagName(String tagName){
189 | this.tagName = tagName;
190 | }
191 |
192 | @Override
193 | public String getValue(){
194 | return tagName;
195 | }
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Checker.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import java.util.ArrayList;
4 | import android.widget.CheckedTextView;
5 | import android.widget.CompoundButton;
6 | import android.widget.Spinner;
7 | import android.widget.TextView;
8 |
9 |
10 | /**
11 | * Contains various check methods. Examples are: isButtonChecked(),
12 | * isSpinnerTextSelected.
13 | *
14 | * @author Renas Reda, renas.reda@robotium.com
15 | *
16 | */
17 |
18 | class Checker {
19 |
20 | private final ViewFetcher viewFetcher;
21 | private final Waiter waiter;
22 |
23 | /**
24 | * Constructs this object.
25 | *
26 | * @param viewFetcher the {@code ViewFetcher} instance
27 | * @param waiter the {@code Waiter} instance
28 | */
29 |
30 | public Checker(ViewFetcher viewFetcher, Waiter waiter){
31 | this.viewFetcher = viewFetcher;
32 | this.waiter = waiter;
33 | }
34 |
35 |
36 | /**
37 | * Checks if a {@link CompoundButton} with a given index is checked.
38 | *
39 | * @param expectedClass the expected class, e.g. {@code CheckBox.class} or {@code RadioButton.class}
40 | * @param index of the {@code CompoundButton} to check. {@code 0} if only one is available
41 | * @return {@code true} if {@code CompoundButton} is checked and {@code false} if it is not checked
42 | */
43 |
44 | public boolean isButtonChecked(Class expectedClass, int index)
45 | {
46 | return (waiter.waitForAndGetView(index, expectedClass).isChecked());
47 | }
48 |
49 | /**
50 | * Checks if a {@link CompoundButton} with a given text is checked.
51 | *
52 | * @param expectedClass the expected class, e.g. {@code CheckBox.class} or {@code RadioButton.class}
53 | * @param text the text that is expected to be checked
54 | * @return {@code true} if {@code CompoundButton} is checked and {@code false} if it is not checked
55 | */
56 |
57 | public boolean isButtonChecked(Class expectedClass, String text)
58 | {
59 | T button = waiter.waitForText(expectedClass, text, 0, Timeout.getSmallTimeout(), true);
60 |
61 | if(button != null && button.isChecked()){
62 | return true;
63 | }
64 | return false;
65 | }
66 |
67 | /**
68 | * Checks if a {@link CheckedTextView} with a given text is checked.
69 | *
70 | * @param checkedTextView the {@code CheckedTextView} object
71 | * @param text the text that is expected to be checked
72 | * @return {@code true} if {@code CheckedTextView} is checked and {@code false} if it is not checked
73 | */
74 |
75 | public boolean isCheckedTextChecked(String text)
76 | {
77 | CheckedTextView checkedTextView = waiter.waitForText(CheckedTextView.class, text, 0, Timeout.getSmallTimeout(), true);
78 |
79 | if(checkedTextView != null && checkedTextView.isChecked()) {
80 | return true;
81 | }
82 | return false;
83 | }
84 |
85 |
86 | /**
87 | * Checks if a given text is selected in any {@link Spinner} located on the current screen.
88 | *
89 | * @param text the text that is expected to be selected
90 | * @return {@code true} if the given text is selected in any {@code Spinner} and false if it is not
91 | */
92 |
93 | public boolean isSpinnerTextSelected(String text)
94 | {
95 | waiter.waitForAndGetView(0, Spinner.class);
96 |
97 | ArrayList spinnerList = viewFetcher.getCurrentViews(Spinner.class, true);
98 | for(int i = 0; i < spinnerList.size(); i++){
99 | if(isSpinnerTextSelected(i, text))
100 | return true;
101 | }
102 | return false;
103 | }
104 |
105 | /**
106 | * Checks if a given text is selected in a given {@link Spinner}
107 | * @param spinnerIndex the index of the spinner to check. 0 if only one spinner is available
108 | * @param text the text that is expected to be selected
109 | * @return true if the given text is selected in the given {@code Spinner} and false if it is not
110 | */
111 |
112 | public boolean isSpinnerTextSelected(int spinnerIndex, String text)
113 | {
114 | Spinner spinner = waiter.waitForAndGetView(spinnerIndex, Spinner.class);
115 |
116 | TextView textView = (TextView) spinner.getChildAt(0);
117 | if(textView.getText().equals(text))
118 | return true;
119 | else
120 | return false;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Condition.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | /**
4 | * Represents a conditional statement.
5 | * Implementations may be used with {@link Solo#waitForCondition(Condition, int)}.
6 | */
7 | public interface Condition {
8 |
9 | /**
10 | * Should do the necessary work needed to check a condition and then return whether this condition is satisfied or not.
11 | * @return {@code true} if condition is satisfied and {@code false} if it is not satisfied
12 | */
13 | public boolean isSatisfied();
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/DialogUtils.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 |
4 | import android.app.Activity;
5 | import android.app.Instrumentation;
6 | import android.content.Context;
7 | import android.os.SystemClock;
8 | import android.view.ContextThemeWrapper;
9 | import android.view.View;
10 | import android.view.inputmethod.InputMethodManager;
11 | import android.widget.EditText;
12 |
13 |
14 | /**
15 | * Contains the waitForDialogToClose() method.
16 | *
17 | * @author Renas Reda, renas.reda@robotium.com
18 | *
19 | */
20 |
21 | class DialogUtils {
22 |
23 | private final Instrumentation instrumentation;
24 | private final ActivityUtils activityUtils;
25 | private final ViewFetcher viewFetcher;
26 | private final Sleeper sleeper;
27 | private final static int TIMEOUT_DIALOG_TO_CLOSE = 1000;
28 | private final int MINISLEEP = 200;
29 |
30 | /**
31 | * Constructs this object.
32 | *
33 | * @param activityUtils the {@code ActivityUtils} instance
34 | * @param viewFetcher the {@code ViewFetcher} instance
35 | * @param sleeper the {@code Sleeper} instance
36 | */
37 |
38 | public DialogUtils(Instrumentation instrumentation, ActivityUtils activityUtils, ViewFetcher viewFetcher, Sleeper sleeper) {
39 | this.instrumentation = instrumentation;
40 | this.activityUtils = activityUtils;
41 | this.viewFetcher = viewFetcher;
42 | this.sleeper = sleeper;
43 | }
44 |
45 |
46 | /**
47 | * Waits for a {@link android.app.Dialog} to close.
48 | *
49 | * @param timeout the amount of time in milliseconds to wait
50 | * @return {@code true} if the {@code Dialog} is closed before the timeout and {@code false} if it is not closed
51 | */
52 |
53 | public boolean waitForDialogToClose(long timeout) {
54 | waitForDialogToOpen(TIMEOUT_DIALOG_TO_CLOSE, false);
55 | final long endTime = SystemClock.uptimeMillis() + timeout;
56 |
57 | while (SystemClock.uptimeMillis() < endTime) {
58 |
59 | if(!isDialogOpen()){
60 | return true;
61 | }
62 | sleeper.sleep(MINISLEEP);
63 | }
64 | return false;
65 | }
66 |
67 |
68 |
69 | /**
70 | * Waits for a {@link android.app.Dialog} to open.
71 | *
72 | * @param timeout the amount of time in milliseconds to wait
73 | * @return {@code true} if the {@code Dialog} is opened before the timeout and {@code false} if it is not opened
74 | */
75 |
76 | public boolean waitForDialogToOpen(long timeout, boolean sleepFirst) {
77 | final long endTime = SystemClock.uptimeMillis() + timeout;
78 | boolean dialogIsOpen = isDialogOpen();
79 |
80 | if(sleepFirst)
81 | sleeper.sleep();
82 |
83 | if(dialogIsOpen){
84 | return true;
85 | }
86 |
87 | while (SystemClock.uptimeMillis() < endTime) {
88 |
89 | if(isDialogOpen()){
90 | return true;
91 | }
92 | sleeper.sleepMini();
93 | }
94 | return false;
95 | }
96 |
97 | /**
98 | * Checks if a dialog is open.
99 | *
100 | * @return true if dialog is open
101 | */
102 |
103 | private boolean isDialogOpen(){
104 | final Activity activity = activityUtils.getCurrentActivity(false);
105 | final View[] views = viewFetcher.getWindowDecorViews();
106 | View view = viewFetcher.getRecentDecorView(views);
107 |
108 | if(!isDialog(activity, view)){
109 | for(View v : views){
110 | if(isDialog(activity, v)){
111 | return true;
112 | }
113 | }
114 | }
115 | else {
116 | return true;
117 | }
118 | return false;
119 | }
120 |
121 | /**
122 | * Checks that the specified DecorView and the Activity DecorView are not equal.
123 | *
124 | * @param activity the activity which DecorView is to be compared
125 | * @param decorView the DecorView to compare
126 | * @return true if not equal
127 | */
128 |
129 | private boolean isDialog(Activity activity, View decorView){
130 | if(decorView == null || !decorView.isShown() || activity == null){
131 | return false;
132 | }
133 | Context viewContext = null;
134 | if(decorView != null){
135 | viewContext = decorView.getContext();
136 | }
137 |
138 | if (viewContext instanceof ContextThemeWrapper) {
139 | ContextThemeWrapper ctw = (ContextThemeWrapper) viewContext;
140 | viewContext = ctw.getBaseContext();
141 | }
142 | Context activityContext = activity;
143 | Context activityBaseContext = activity.getBaseContext();
144 | return (activityContext.equals(viewContext) || activityBaseContext.equals(viewContext)) && (decorView != activity.getWindow().getDecorView());
145 | }
146 |
147 | /**
148 | * Hides the soft keyboard
149 | *
150 | * @param shouldSleepFirst whether to sleep a default pause first
151 | * @param shouldSleepAfter whether to sleep a default pause after
152 | */
153 |
154 | public void hideSoftKeyboard(EditText editText, boolean shouldSleepFirst, boolean shouldSleepAfter) {
155 | InputMethodManager inputMethodManager;
156 |
157 | Activity activity = activityUtils.getCurrentActivity(shouldSleepFirst);
158 | if(activity == null){
159 | inputMethodManager = (InputMethodManager) instrumentation.getTargetContext().getSystemService(Context.INPUT_METHOD_SERVICE);
160 | }
161 | else {
162 | inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
163 | }
164 |
165 | if(editText != null) {
166 | inputMethodManager.hideSoftInputFromWindow(editText.getWindowToken(), 0);
167 | return;
168 | }
169 | View focusedView = activity.getCurrentFocus();
170 |
171 | if(!(focusedView instanceof EditText)) {
172 | EditText freshestEditText = viewFetcher.getFreshestView(viewFetcher.getCurrentViews(EditText.class, true));
173 | if(freshestEditText != null){
174 | focusedView = freshestEditText;
175 | }
176 | }
177 | if(focusedView != null) {
178 | inputMethodManager.hideSoftInputFromWindow(focusedView.getWindowToken(), 0);
179 | }
180 | if(shouldSleepAfter){
181 | sleeper.sleep();
182 | }
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/GLRenderWrapper.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import java.nio.IntBuffer;
4 | import java.util.concurrent.CountDownLatch;
5 | import javax.microedition.khronos.egl.EGLConfig;
6 | import javax.microedition.khronos.opengles.GL10;
7 | import android.graphics.Bitmap;
8 | import android.opengl.GLES20;
9 | import android.opengl.GLSurfaceView;
10 | import android.opengl.GLSurfaceView.Renderer;
11 | import android.view.View;
12 |
13 | /**
14 | * Used to wrap and replace the renderer to gain access to the gl context.
15 | *
16 | * @author Per-Erik Bergman, bergman@uncle.se
17 | *
18 | */
19 |
20 | class GLRenderWrapper implements Renderer {
21 |
22 | private Renderer renderer;
23 | private int width;
24 | private int height;
25 | private final GLSurfaceView view;
26 | private CountDownLatch latch;
27 | private boolean takeScreenshot = true;
28 | private int glVersion;
29 |
30 | /**
31 | * Constructs this object.
32 | *
33 | * @param view the current glSurfaceView
34 | * @param renderer the renderer to wrap
35 | * @param latch the count down latch
36 | */
37 |
38 | public GLRenderWrapper(GLSurfaceView view,
39 | Renderer renderer, CountDownLatch latch) {
40 | this.view = view;
41 | this.renderer = renderer;
42 | this.latch = latch;
43 |
44 | this.width = view.getWidth();
45 | this.height = view.getHeight();
46 |
47 | Integer out = new Reflect(view).field("mEGLContextClientVersion")
48 | .out(Integer.class);
49 | if ( out != null ) {
50 | this.glVersion = out.intValue();
51 | } else {
52 | this.glVersion = -1;
53 | this.takeScreenshot = false;
54 | }
55 | }
56 |
57 | @Override
58 | /*
59 | * (non-Javadoc)
60 | * @see android.opengl.GLSurfaceView.Renderer#onSurfaceCreated(javax.microedition.khronos.opengles.GL10, javax.microedition.khronos.egl.EGLConfig)
61 | */
62 |
63 | public void onSurfaceCreated(GL10 gl, EGLConfig config) {
64 | renderer.onSurfaceCreated(gl, config);
65 | }
66 |
67 | @Override
68 | /*
69 | * (non-Javadoc)
70 | * @see android.opengl.GLSurfaceView.Renderer#onSurfaceChanged(javax.microedition.khronos.opengles.GL10, int, int)
71 | */
72 |
73 | public void onSurfaceChanged(GL10 gl, int width, int height) {
74 | this.width = width;
75 | this.height = height;
76 | renderer.onSurfaceChanged(gl, width, height);
77 | }
78 |
79 | @Override
80 | /*
81 | * (non-Javadoc)
82 | * @see android.opengl.GLSurfaceView.Renderer#onDrawFrame(javax.microedition.khronos.opengles.GL10)
83 | */
84 |
85 | public void onDrawFrame(GL10 gl) {
86 | renderer.onDrawFrame(gl);
87 | if (takeScreenshot) {
88 | Bitmap screenshot = null;
89 |
90 | if (glVersion >= 2) {
91 | screenshot = savePixels(0, 0, width, height);
92 | } else {
93 | screenshot = savePixels(0, 0, width, height, gl);
94 | }
95 |
96 | new Reflect(view).field("mDrawingCache").type(View.class)
97 | .in(screenshot);
98 | latch.countDown();
99 | takeScreenshot = false;
100 | }
101 | }
102 |
103 | /**
104 | * Tell the wrapper to take a screen shot
105 | */
106 |
107 | public void setTakeScreenshot() {
108 | takeScreenshot = true;
109 | }
110 |
111 | /**
112 | * Set the count down latch
113 | */
114 |
115 | public void setLatch(CountDownLatch latch) {
116 | this.latch = latch;
117 | }
118 |
119 | /**
120 | * Extract the bitmap from OpenGL
121 | *
122 | * @param x the start column
123 | * @param y the start line
124 | * @param w the width of the bitmap
125 | * @param h the height of the bitmap
126 | */
127 |
128 | private Bitmap savePixels(int x, int y, int w, int h) {
129 | int b[] = new int[w * (y + h)];
130 | int bt[] = new int[w * h];
131 | IntBuffer ib = IntBuffer.wrap(b);
132 | ib.position(0);
133 | GLES20.glReadPixels(x, 0, w, y + h, GLES20.GL_RGBA,
134 | GLES20.GL_UNSIGNED_BYTE, ib);
135 |
136 | for (int i = 0, k = 0; i < h; i++, k++) {
137 | // remember, that OpenGL bitmap is incompatible with Android bitmap
138 | // and so, some correction need.
139 | for (int j = 0; j < w; j++) {
140 | int pix = b[i * w + j];
141 | int pb = (pix >> 16) & 0xff;
142 | int pr = (pix << 16) & 0x00ff0000;
143 | int pix1 = (pix & 0xff00ff00) | pr | pb;
144 | bt[(h - k - 1) * w + j] = pix1;
145 | }
146 | }
147 |
148 | Bitmap sb = Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);
149 | return sb;
150 | }
151 |
152 | /**
153 | * Extract the bitmap from OpenGL
154 | *
155 | * @param x the start column
156 | * @param y the start line
157 | * @param w the width of the bitmap
158 | * @param h the height of the bitmap
159 | * @param gl the current GL reference
160 | */
161 |
162 | private static Bitmap savePixels(int x, int y, int w, int h, GL10 gl) {
163 | int b[] = new int[w * (y + h)];
164 | int bt[] = new int[w * h];
165 | IntBuffer ib = IntBuffer.wrap(b);
166 | ib.position(0);
167 | gl.glReadPixels(x, 0, w, y + h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, ib);
168 |
169 | for (int i = 0, k = 0; i < h; i++, k++) {
170 | // remember, that OpenGL bitmap is incompatible with Android bitmap
171 | // and so, some correction need.
172 | for (int j = 0; j < w; j++) {
173 | int pix = b[i * w + j];
174 | int pb = (pix >> 16) & 0xff;
175 | int pr = (pix << 16) & 0x00ff0000;
176 | int pix1 = (pix & 0xff00ff00) | pr | pb;
177 | bt[(h - k - 1) * w + j] = pix1;
178 | }
179 | }
180 |
181 | Bitmap sb = Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);
182 | return sb;
183 | }
184 |
185 | }
186 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Getter.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import junit.framework.Assert;
4 | import android.app.Activity;
5 | import android.app.Instrumentation;
6 | import android.content.Context;
7 | import android.view.View;
8 | import android.view.Window;
9 | import android.widget.TextView;
10 |
11 |
12 | /**
13 | * Contains various get methods. Examples are: getView(int id),
14 | * getView(Class classToFilterBy, int index).
15 | *
16 | * @author Renas Reda, renas.reda@robotium.com
17 | *
18 | */
19 |
20 | class Getter {
21 |
22 | private final Instrumentation instrumentation;
23 | private final ActivityUtils activityUtils;
24 | private final Waiter waiter;
25 | private final int TIMEOUT = 1000;
26 |
27 | /**
28 | * Constructs this object.
29 | *
30 | * @param inst the {@code Instrumentation} instance
31 | * @param viewFetcher the {@code ViewFetcher} instance
32 | * @param waiter the {@code Waiter} instance
33 | */
34 |
35 | public Getter(Instrumentation instrumentation, ActivityUtils activityUtils, Waiter waiter){
36 | this.instrumentation = instrumentation;
37 | this.activityUtils = activityUtils;
38 | this.waiter = waiter;
39 | }
40 |
41 |
42 | /**
43 | * Returns a {@code View} with a certain index, from the list of current {@code View}s of the specified type.
44 | *
45 | * @param classToFilterBy which {@code View}s to choose from
46 | * @param index choose among all instances of this type, e.g. {@code Button.class} or {@code EditText.class}
47 | * @return a {@code View} with a certain index, from the list of current {@code View}s of the specified type
48 | */
49 |
50 | public T getView(Class classToFilterBy, int index) {
51 | return waiter.waitForAndGetView(index, classToFilterBy);
52 | }
53 |
54 | /**
55 | * Returns a {@code View} that shows a given text, from the list of current {@code View}s of the specified type.
56 | *
57 | * @param classToFilterBy which {@code View}s to choose from
58 | * @param text the text that the view shows
59 | * @param onlyVisible {@code true} if only visible texts on the screen should be returned
60 | * @return a {@code View} showing a given text, from the list of current {@code View}s of the specified type
61 | */
62 |
63 | public T getView(Class classToFilterBy, String text, boolean onlyVisible) {
64 |
65 | T viewToReturn = (T) waiter.waitForText(classToFilterBy, text, 0, Timeout.getSmallTimeout(), false, onlyVisible, false);
66 |
67 | if(viewToReturn == null)
68 | Assert.fail(classToFilterBy.getSimpleName() + " with text: '" + text + "' is not found!");
69 |
70 | return viewToReturn;
71 | }
72 |
73 | /**
74 | * Returns a localized string
75 | *
76 | * @param id the resource ID for the string
77 | * @return the localized string
78 | */
79 |
80 | public String getString(int id)
81 | {
82 | Activity activity = activityUtils.getCurrentActivity(false);
83 | if(activity == null){
84 | return "";
85 | }
86 | return activity.getString(id);
87 | }
88 |
89 | /**
90 | * Returns a localized string
91 | *
92 | * @param id the resource ID for the string
93 | * @return the localized string
94 | */
95 |
96 | public String getString(String id)
97 | {
98 | Context targetContext = instrumentation.getTargetContext();
99 | String packageName = targetContext.getPackageName();
100 | int viewId = targetContext.getResources().getIdentifier(id, "string", packageName);
101 | if(viewId == 0){
102 | viewId = targetContext.getResources().getIdentifier(id, "string", "android");
103 | }
104 | return getString(viewId);
105 | }
106 |
107 | /**
108 | * Returns a {@code View} with a given id.
109 | *
110 | * @param id the R.id of the {@code View} to be returned
111 | * @param index the index of the {@link View}. {@code 0} if only one is available
112 | * @param timeout the timeout in milliseconds
113 | * @return a {@code View} with a given id
114 | */
115 |
116 | public View getView(int id, int index, int timeout){
117 | return waiter.waitForView(id, index, timeout);
118 | }
119 |
120 | /**
121 | * Returns a {@code View} with a given id.
122 | *
123 | * @param id the R.id of the {@code View} to be returned
124 | * @param index the index of the {@link View}. {@code 0} if only one is available
125 | * @return a {@code View} with a given id
126 | */
127 |
128 | public View getView(int id, int index){
129 | return getView(id, index, 0);
130 | }
131 |
132 | /**
133 | * Returns a {@code View} with a given id.
134 | *
135 | * @param id the id of the {@link View} to return
136 | * @param index the index of the {@link View}. {@code 0} if only one is available
137 | * @return a {@code View} with a given id
138 | */
139 |
140 | public View getView(String id, int index){
141 | View viewToReturn = null;
142 | Context targetContext = instrumentation.getTargetContext();
143 | String packageName = targetContext.getPackageName();
144 | int viewId = targetContext.getResources().getIdentifier(id, "id", packageName);
145 |
146 | if(viewId != 0){
147 | viewToReturn = getView(viewId, index, TIMEOUT);
148 | }
149 |
150 | if(viewToReturn == null){
151 | int androidViewId = targetContext.getResources().getIdentifier(id, "id", "android");
152 | if(androidViewId != 0){
153 | viewToReturn = getView(androidViewId, index, TIMEOUT);
154 | }
155 | }
156 |
157 | if(viewToReturn != null){
158 | return viewToReturn;
159 | }
160 | return getView(viewId, index);
161 | }
162 |
163 | /**
164 | * Returns a {@code View} with a given tag.
165 | *
166 | * @param tag the tag of the {@code View} to be returned
167 | * @param index the index of the {@link View}. {@code 0} if only one is available
168 | * @param timeout the timeout in milliseconds
169 | * @return a {@code View} with a given tag if available, null otherwise
170 | */
171 |
172 | public View getView(Object tag, int index, int timeout){
173 | //Because https://github.com/android/platform_frameworks_base/blob/master/core/java/android/view/View.java#L17005-L17007
174 | if(tag == null) {
175 | return null;
176 | }
177 |
178 | final Activity activity = activityUtils.getCurrentActivity(false);
179 | View viewToReturn = null;
180 |
181 | if(index < 1){
182 | index = 0;
183 | if(activity != null){
184 | //Using https://github.com/android/platform_frameworks_base/blob/master/core/java/android/app/Activity.java#L2070-L2072
185 | Window window = activity.getWindow();
186 | if(window != null) {
187 | View decorView = window.getDecorView();
188 | if(decorView != null) {
189 | viewToReturn = decorView.findViewWithTag(tag);
190 | }
191 | }
192 | }
193 | }
194 |
195 | if (viewToReturn != null) {
196 | return viewToReturn;
197 | }
198 |
199 | return waiter.waitForView(tag, index, timeout);
200 | }
201 |
202 | /**
203 | * Returns a {@code View} with a given tag.
204 | *
205 | * @param tag the tag of the {@code View} to be returned
206 | * @param index the index of the {@link View}. {@code 0} if only one is available
207 | * @return a {@code View} with a given tag if available, null otherwise
208 | */
209 |
210 | public View getView(Object tag, int index){
211 | return getView(tag, index, 0);
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Illustration.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import java.util.ArrayList;
4 | import android.view.MotionEvent;
5 |
6 | /**
7 | * A class used to pass Illustrations to an Illustrator.
8 | * Compatible with specific MotionEvent.TOOL_TYPEs
9 | *
10 | * @author Jake Kuli, 3kajjak3@gmail.com
11 | */
12 | public class Illustration {
13 |
14 | private final int toolType;
15 | private final ArrayList points;
16 |
17 | private Illustration(Builder builder) {
18 | this.toolType = builder.builderToolType;
19 | this.points = builder.builderPoints;
20 | }
21 |
22 | /**
23 | * Builder class to build illustrations
24 | */
25 | public static class Builder {
26 |
27 | private int builderToolType = MotionEvent.TOOL_TYPE_FINGER;
28 | private ArrayList builderPoints = new ArrayList();
29 |
30 | /**
31 | * Sets the tool type to use when illustrating.
32 | * By default this is set to MotionEvent.TOOL_TYPE_FINGER
33 | * @param toolType an int from MotionEvent's static int TOOL_TYPEs
34 | */
35 | public Builder setToolType(int toolType) {
36 | builderToolType = toolType;
37 | return this;
38 | }
39 |
40 | public Builder addPoint(float x, float y, float pressure) {
41 | builderPoints.add(new PressurePoint(x, y, pressure));
42 | return this;
43 | }
44 |
45 | public Illustration build() {
46 | return new Illustration(this);
47 | }
48 | }
49 |
50 | public ArrayList getPoints() {
51 | return points;
52 | }
53 |
54 | public int getToolType() {
55 | return toolType;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Illustrator.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import java.util.ArrayList;
4 | import android.view.MotionEvent;
5 | import android.view.MotionEvent.PointerProperties;
6 | import android.view.MotionEvent.PointerCoords;
7 | import android.view.InputDevice;
8 | import android.app.Instrumentation;
9 | import android.os.SystemClock;
10 |
11 | /**
12 | * A class that draws Illustrations to the screen
13 | *
14 | * @author Jake Kuli, 3kajjak3@gmail.com
15 | */
16 | class Illustrator {
17 |
18 | private Instrumentation inst;
19 |
20 | public Illustrator(Instrumentation inst) {
21 | this.inst = inst;
22 | }
23 |
24 | public void illustrate(Illustration illustration) {
25 | if (illustration == null || illustration.getPoints().isEmpty()) {
26 | throw new IllegalArgumentException("Illustration must not be null and requires at least one point.");
27 | }
28 | MotionEvent event;
29 | int currentAction;
30 | long downTime = SystemClock.uptimeMillis();
31 | long eventTime = SystemClock.uptimeMillis();
32 | PointerCoords[] coords = new PointerCoords[1];
33 | PointerCoords coord = new PointerCoords();
34 | PointerProperties[] properties = new PointerProperties[1];
35 | PointerProperties prop = new PointerProperties();
36 | prop.id = 0;
37 | prop.toolType = illustration.getToolType();
38 | properties[0] = prop;
39 | coords[0] = coord;
40 | ArrayList points = illustration.getPoints();
41 | for (int i = 0; i < points.size(); i++) {
42 | PressurePoint currentPoint = points.get(i);
43 | coord.x = currentPoint.x;
44 | coord.y = currentPoint.y;
45 | coord.pressure = currentPoint.pressure;
46 | coord.size = 1;
47 | if (i == 0) {
48 | currentAction = MotionEvent.ACTION_DOWN;
49 | }
50 | else {
51 | currentAction = MotionEvent.ACTION_MOVE;
52 | }
53 | eventTime = SystemClock.uptimeMillis();
54 | event = MotionEvent.obtain(downTime,
55 | eventTime,
56 | currentAction,
57 | 1,
58 | properties,
59 | coords,
60 | 0, 0, 1, 1, 0, 0,
61 | InputDevice.SOURCE_TOUCHSCREEN,
62 | 0);
63 | try {
64 | inst.sendPointerSync(event);
65 | }
66 | catch (SecurityException ignored) {}
67 | }
68 | currentAction = MotionEvent.ACTION_UP;
69 | coords[0] = coord;
70 | PressurePoint currentPoint = points.get(points.size() - 1);
71 | coord.x = currentPoint.x;
72 | coord.y = currentPoint.y;
73 | coord.pressure = currentPoint.pressure;
74 | coord.size = 1;
75 | eventTime = SystemClock.uptimeMillis();
76 | event = MotionEvent.obtain(downTime,
77 | eventTime,
78 | currentAction,
79 | 1,
80 | properties,
81 | coords,
82 | 0, 0, 1, 1, 0, 0,
83 | InputDevice.SOURCE_TOUCHSCREEN,
84 | 0);
85 | try {
86 | inst.sendPointerSync(event);
87 | }
88 | catch (SecurityException ignored) {}
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Presser.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import android.widget.EditText;
4 | import android.widget.Spinner;
5 | import junit.framework.Assert;
6 | import android.app.Instrumentation;
7 | import android.view.KeyEvent;
8 |
9 | /**
10 | * Contains press methods. Examples are pressMenuItem(),
11 | * pressSpinnerItem().
12 | *
13 | * @author Renas Reda, renas.reda@robotium.com
14 | *
15 | */
16 |
17 | class Presser{
18 |
19 | private final Clicker clicker;
20 | private final Instrumentation inst;
21 | private final Sleeper sleeper;
22 | private final Waiter waiter;
23 | private final DialogUtils dialogUtils;
24 | private final ViewFetcher viewFetcher;
25 |
26 |
27 | /**
28 | * Constructs this object.
29 | *
30 | * @param viewFetcher the {@code ViewFetcher} instance
31 | * @param clicker the {@code Clicker} instance
32 | * @param inst the {@code Instrumentation} instance
33 | * @param sleeper the {@code Sleeper} instance
34 | * @param waiter the {@code Waiter} instance
35 | * @param dialogUtils the {@code DialogUtils} instance
36 | */
37 |
38 | public Presser(ViewFetcher viewFetcher, Clicker clicker, Instrumentation inst, Sleeper sleeper, Waiter waiter, DialogUtils dialogUtils) {
39 | this.viewFetcher = viewFetcher;
40 | this.clicker = clicker;
41 | this.inst = inst;
42 | this.sleeper = sleeper;
43 | this.waiter = waiter;
44 | this.dialogUtils = dialogUtils;
45 | }
46 |
47 |
48 | /**
49 | * Presses a {@link android.view.MenuItem} with a given index. Index {@code 0} is the first item in the
50 | * first row, Index {@code 3} is the first item in the second row and
51 | * index {@code 5} is the first item in the third row.
52 | *
53 | * @param index the index of the {@code MenuItem} to be pressed
54 | */
55 |
56 | public void pressMenuItem(int index){
57 | pressMenuItem(index, 3);
58 | }
59 |
60 | /**
61 | * Presses a {@link android.view.MenuItem} with a given index. Supports three rows with a given amount
62 | * of items. If itemsPerRow equals 5 then index 0 is the first item in the first row,
63 | * index 5 is the first item in the second row and index 10 is the first item in the third row.
64 | *
65 | * @param index the index of the {@code MenuItem} to be pressed
66 | * @param itemsPerRow the amount of menu items there are per row.
67 | */
68 |
69 | public void pressMenuItem(int index, int itemsPerRow) {
70 | int[] row = new int[4];
71 | for(int i = 1; i <=3; i++)
72 | row[i] = itemsPerRow*i;
73 |
74 | sleeper.sleep();
75 | try{
76 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
77 | dialogUtils.waitForDialogToOpen(Timeout.getSmallTimeout(), true);
78 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
79 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
80 | }catch(SecurityException e){
81 | Assert.fail("Can not press the menu!");
82 | }
83 | if (index < row[1]) {
84 | for (int i = 0; i < index; i++) {
85 | sleeper.sleepMini();
86 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_RIGHT);
87 | }
88 | } else if (index >= row[1] && index < row[2]) {
89 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
90 |
91 | for (int i = row[1]; i < index; i++) {
92 | sleeper.sleepMini();
93 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_RIGHT);
94 | }
95 | } else if (index >= row[2]) {
96 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
97 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
98 |
99 | for (int i = row[2]; i < index; i++) {
100 | sleeper.sleepMini();
101 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_RIGHT);
102 | }
103 | }
104 |
105 | try{
106 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_ENTER);
107 | }catch (SecurityException ignored) {}
108 | }
109 |
110 | /**
111 | * Presses the soft keyboard search/next button.
112 | *
113 | * @param imeAction the action to be performed
114 | *
115 | */
116 |
117 | public void pressSoftKeyboard(final int imeAction){
118 | final EditText freshestEditText = viewFetcher.getFreshestView(viewFetcher.getCurrentViews(EditText.class, true));
119 | if(freshestEditText != null){
120 | inst.runOnMainSync(new Runnable()
121 | {
122 | public void run()
123 | {
124 | freshestEditText.onEditorAction(imeAction);
125 |
126 | }
127 | });
128 | }
129 | }
130 |
131 | /**
132 | * Presses on a {@link android.widget.Spinner} (drop-down menu) item.
133 | *
134 | * @param spinnerIndex the index of the {@code Spinner} menu to be used
135 | * @param itemIndex the index of the {@code Spinner} item to be pressed relative to the currently selected item.
136 | * A Negative number moves up on the {@code Spinner}, positive moves down
137 | */
138 |
139 | public void pressSpinnerItem(int spinnerIndex, int itemIndex)
140 | {
141 | clicker.clickOnScreen(waiter.waitForAndGetView(spinnerIndex, Spinner.class));
142 | dialogUtils.waitForDialogToOpen(Timeout.getSmallTimeout(), true);
143 |
144 | try{
145 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
146 | }catch(SecurityException ignored){}
147 |
148 | boolean countingUp = true;
149 | if(itemIndex < 0){
150 | countingUp = false;
151 | itemIndex *= -1;
152 | }
153 | for(int i = 0; i < itemIndex; i++)
154 | {
155 | sleeper.sleepMini();
156 | if(countingUp){
157 | try{
158 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
159 | }catch(SecurityException ignored){}
160 | }else{
161 | try{
162 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
163 | }catch(SecurityException ignored){}
164 | }
165 | }
166 | try{
167 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_ENTER);
168 | }catch(SecurityException ignored){}
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/PressurePoint.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | /**
4 | * A class to describe points and respective pressures in an Illustration
5 | *
6 | * @author Jake Kuli, 3kajjak3@gmail.com
7 | */
8 | class PressurePoint {
9 | public final float x;
10 | public final float y;
11 | public final float pressure;
12 |
13 | public PressurePoint(float x, float y, float pressure) {
14 | this.x = x;
15 | this.y = y;
16 | this.pressure = pressure;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Reflect.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import java.lang.reflect.Field;
4 |
5 | /**
6 | * A reflection utility class.
7 | *
8 | * @author Per-Erik Bergman, bergman@uncle.se
9 | *
10 | */
11 |
12 | class Reflect {
13 | private Object object;
14 |
15 | /**
16 | * Constructs this object
17 | *
18 | * @param object the object to reflect on
19 | */
20 |
21 | public Reflect(Object object) {
22 | if (object == null)
23 | throw new IllegalArgumentException("Object can not be null.");
24 | this.object = object;
25 | }
26 |
27 | /**
28 | * Get a field from the object
29 | *
30 | * @param name the name of the field
31 | *
32 | * @return a field reference
33 | */
34 |
35 | public FieldRf field(String name) {
36 | return new FieldRf(object, name);
37 | }
38 |
39 | /**
40 | * A field reference.
41 | */
42 | public class FieldRf {
43 | private Class> clazz;
44 | private Object object;
45 | private String name;
46 |
47 | /**
48 | * Constructs this object
49 | *
50 | * @param object the object to reflect on
51 | * @param name the name of the field
52 | */
53 |
54 | public FieldRf(Object object, String name) {
55 | this.object = object;
56 | this.name = name;
57 | }
58 |
59 | /**
60 | * Constructs this object
61 | *
62 | * @param outclazz the output type
63 | *
64 | * @return T
65 | */
66 |
67 | public T out(Class outclazz) {
68 | Field field = getField();
69 | Object obj = getValue(field);
70 | return outclazz.cast(obj);
71 | }
72 |
73 | /**
74 | * Set a value to a field
75 | *
76 | * @param value the value to set
77 | */
78 |
79 | public void in(Object value) {
80 | Field field = getField();
81 | try {
82 | field.set(object, value);
83 | } catch (IllegalArgumentException e) {
84 | e.printStackTrace();
85 | } catch (IllegalAccessException e) {
86 | e.printStackTrace();
87 | }
88 | }
89 |
90 | /**
91 | * Set the class type
92 | *
93 | * @param clazz the type
94 | *
95 | * @return a field reference
96 | */
97 |
98 | public FieldRf type(Class> clazz) {
99 | this.clazz = clazz;
100 | return this;
101 | }
102 |
103 | private Field getField() {
104 | if (clazz == null) {
105 | clazz = object.getClass();
106 | }
107 |
108 | Field field = null;
109 | try {
110 | field = clazz.getDeclaredField(name);
111 | field.setAccessible(true);
112 | } catch (NoSuchFieldException ignored) {}
113 | return field;
114 | }
115 |
116 | private Object getValue(Field field) {
117 | if (field == null) {
118 | return null;
119 | }
120 | Object obj = null;
121 | try {
122 | obj = field.get(object);
123 | } catch (IllegalArgumentException e) {
124 | e.printStackTrace();
125 | } catch (IllegalAccessException e) {
126 | e.printStackTrace();
127 | }
128 | return obj;
129 | }
130 | }
131 |
132 | }
133 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/RobotiumTextView.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import android.content.Context;
4 | import android.widget.TextView;
5 |
6 | /**
7 | * Used to create a TextView object that is based on a web element. Contains the web element text and location.
8 | *
9 | * @author Renas Reda, renas.reda@robotium.com
10 | *
11 | */
12 |
13 | class RobotiumTextView extends TextView {
14 | private int locationX = 0;
15 | private int locationY = 0;
16 |
17 | /**
18 | * Constructs this object
19 | *
20 | * @param context the given context
21 | */
22 |
23 | public RobotiumTextView(Context context){
24 | super(context);
25 | }
26 |
27 | /**
28 | * Constructs this object
29 | *
30 | * @param context the given context
31 | * @param text the given text to be set
32 | */
33 |
34 | public RobotiumTextView(Context context, String text, int locationX, int locationY) {
35 | super(context);
36 | this.setText(text);
37 | setLocationX(locationX);
38 | setLocationY(locationY);
39 | }
40 |
41 | /**
42 | * Returns the location on screen of the {@code TextView} that is based on a web element
43 | */
44 |
45 | @Override
46 | public void getLocationOnScreen(int[] location) {
47 |
48 | location[0] = locationX;
49 | location[1] = locationY;
50 | }
51 |
52 | /**
53 | * Sets the X location of the TextView
54 | *
55 | * @param locationX the X location of the {@code TextView}
56 | */
57 |
58 | public void setLocationX(int locationX){
59 | this.locationX = locationX;
60 | }
61 |
62 |
63 | /**
64 | * Sets the Y location
65 | *
66 | * @param locationY the Y location of the {@code TextView}
67 | */
68 |
69 | public void setLocationY(int locationY){
70 | this.locationY = locationY;
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/RobotiumUtils.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collections;
5 | import java.util.List;
6 | import java.util.Set;
7 | import java.util.regex.Matcher;
8 | import java.util.regex.Pattern;
9 | import java.util.regex.PatternSyntaxException;
10 |
11 | import android.view.View;
12 | import android.widget.TextView;
13 |
14 | /**
15 | * Contains utility methods. Examples are: removeInvisibleViews(Iterable viewList),
16 | * filterViews(Class classToFilterBy, Iterable> viewList), sortViewsByLocationOnScreen(List extends View> views).
17 | *
18 | * @author Renas Reda, renas.reda@robotium.com
19 | *
20 | */
21 |
22 | public class RobotiumUtils {
23 |
24 |
25 | /**
26 | * Removes invisible Views.
27 | *
28 | * @param viewList an Iterable with Views that is being checked for invisible Views
29 | * @return a filtered Iterable with no invisible Views
30 | */
31 |
32 | public static ArrayList removeInvisibleViews(Iterable viewList) {
33 | ArrayList tmpViewList = new ArrayList();
34 | for (T view : viewList) {
35 | if (view != null && view.isShown()) {
36 | tmpViewList.add(view);
37 | }
38 | }
39 | return tmpViewList;
40 | }
41 |
42 | /**
43 | * Filters Views based on the given class type.
44 | *
45 | * @param classToFilterBy the class to filter
46 | * @param viewList the Iterable to filter from
47 | * @return an ArrayList with filtered views
48 | */
49 |
50 | public static ArrayList filterViews(Class classToFilterBy, Iterable> viewList) {
51 | ArrayList filteredViews = new ArrayList();
52 | for (Object view : viewList) {
53 | if (view != null && classToFilterBy.isAssignableFrom(view.getClass())) {
54 | filteredViews.add(classToFilterBy.cast(view));
55 | }
56 | }
57 | viewList = null;
58 | return filteredViews;
59 | }
60 |
61 | /**
62 | * Filters all Views not within the given set.
63 | *
64 | * @param classSet contains all classes that are ok to pass the filter
65 | * @param viewList the Iterable to filter form
66 | * @return an ArrayList with filtered views
67 | */
68 |
69 | public static ArrayList filterViewsToSet(Class classSet[], Iterable viewList) {
70 | ArrayList filteredViews = new ArrayList();
71 | for (View view : viewList) {
72 | if (view == null)
73 | continue;
74 | for (Class filter : classSet) {
75 | if (filter.isAssignableFrom(view.getClass())) {
76 | filteredViews.add(view);
77 | break;
78 | }
79 | }
80 | }
81 | return filteredViews;
82 | }
83 |
84 | /**
85 | * Orders Views by their location on-screen.
86 | *
87 | * @param views The views to sort
88 | * @see ViewLocationComparator
89 | */
90 |
91 | public static void sortViewsByLocationOnScreen(List extends View> views) {
92 | Collections.sort(views, new ViewLocationComparator());
93 | }
94 |
95 | /**
96 | * Orders Views by their location on-screen.
97 | *
98 | * @param views The views to sort
99 | * @param yAxisFirst Whether the y-axis should be compared before the x-axis
100 | * @see ViewLocationComparator
101 | */
102 |
103 | public static void sortViewsByLocationOnScreen(List extends View> views, boolean yAxisFirst) {
104 | Collections.sort(views, new ViewLocationComparator(yAxisFirst));
105 | }
106 |
107 | /**
108 | * Checks if a View matches a certain string and returns the amount of total matches.
109 | *
110 | * @param regex the regex to match
111 | * @param view the view to check
112 | * @param uniqueTextViews set of views that have matched
113 | * @return number of total matches
114 | */
115 |
116 | public static int getNumberOfMatches(String regex, TextView view, Set uniqueTextViews){
117 | if(view == null) {
118 | return uniqueTextViews.size();
119 | }
120 |
121 | Pattern pattern = null;
122 | try{
123 | pattern = Pattern.compile(regex);
124 | }catch(PatternSyntaxException e){
125 | pattern = Pattern.compile(regex, Pattern.LITERAL);
126 | }
127 |
128 | Matcher matcher = pattern.matcher(view.getText().toString());
129 |
130 | if (matcher.find()){
131 | uniqueTextViews.add(view);
132 | }
133 |
134 | if (view.getError() != null){
135 | matcher = pattern.matcher(view.getError().toString());
136 | if (matcher.find()){
137 | uniqueTextViews.add(view);
138 | }
139 | }
140 | if (view.getText().toString().equals("") && view.getHint() != null){
141 | matcher = pattern.matcher(view.getHint().toString());
142 | if (matcher.find()){
143 | uniqueTextViews.add(view);
144 | }
145 | }
146 | return uniqueTextViews.size();
147 | }
148 |
149 | /**
150 | * Filters a collection of Views and returns a list that contains only Views
151 | * with text that matches a specified regular expression.
152 | *
153 | * @param views The collection of views to scan
154 | * @param regex The text pattern to search for
155 | * @return A list of views whose text matches the given regex
156 | */
157 |
158 | public static List filterViewsByText(Iterable views, String regex) {
159 | return filterViewsByText(views, Pattern.compile(regex));
160 | }
161 |
162 | /**
163 | * Filters a collection of Views and returns a list that contains only Views
164 | * with text that matches a specified regular expression.
165 | *
166 | * @param views The collection of views to scan
167 | * @param regex The text pattern to search for
168 | * @return A list of views whose text matches the given regex
169 | */
170 |
171 | public static List filterViewsByText(Iterable views, Pattern regex) {
172 | final ArrayList filteredViews = new ArrayList();
173 | for (T view : views) {
174 | if (view != null && regex.matcher(view.getText()).matches()) {
175 | filteredViews.add(view);
176 | }
177 | }
178 | return filteredViews;
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/RobotiumWeb.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Used by the web methods.
3 | *
4 | * @author Renas Reda, renas.reda@robotium.com
5 | *
6 | */
7 |
8 | function allWebElements() {
9 | for (var key in document.all){
10 | try{
11 | promptElement(document.all[key]);
12 | }catch(ignored){}
13 | }
14 | finished();
15 | }
16 |
17 | function allTexts() {
18 | var range = document.createRange();
19 | var walk=document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
20 | while(n=walk.nextNode()){
21 | try{
22 | promptText(n, range);
23 | }catch(ignored){}
24 | }
25 | finished();
26 | }
27 |
28 | function clickElement(element){
29 | var e = document.createEvent('MouseEvents');
30 | e.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
31 | element.dispatchEvent(e);
32 | }
33 |
34 | function id(id, click) {
35 | var element = document.getElementById(id);
36 | if(element != null){
37 |
38 | if(click == 'true'){
39 | clickElement(element);
40 | }
41 | else{
42 | promptElement(element);
43 | }
44 | }
45 | else {
46 | for (var key in document.all){
47 | try{
48 | element = document.all[key];
49 | if(element.id == id) {
50 | if(click == 'true'){
51 | clickElement(element);
52 | return;
53 | }
54 | else{
55 | promptElement(element);
56 | }
57 | }
58 | } catch(ignored){}
59 | }
60 | }
61 | finished();
62 | }
63 |
64 | function xpath(xpath, click) {
65 | var elements = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
66 |
67 | if (elements){
68 | var element = elements.iterateNext();
69 | while(element) {
70 | if(click == 'true'){
71 | clickElement(element);
72 | return;
73 | }
74 | else{
75 | promptElement(element);
76 | element = elements.iterateNext();
77 | }
78 | }
79 | finished();
80 | }
81 | }
82 |
83 | function cssSelector(cssSelector, click) {
84 | var elements = document.querySelectorAll(cssSelector);
85 | for (var key in elements) {
86 | if(elements != null){
87 | try{
88 | if(click == 'true'){
89 | clickElement(elements[key]);
90 | return;
91 | }
92 | else{
93 | promptElement(elements[key]);
94 | }
95 | }catch(ignored){}
96 | }
97 | }
98 | finished();
99 | }
100 |
101 | function name(name, click) {
102 | var walk=document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, false);
103 | while(n=walk.nextNode()){
104 | try{
105 | var attributeName = n.getAttribute('name');
106 | if(attributeName != null && attributeName.trim().length>0 && attributeName == name){
107 | if(click == 'true'){
108 | clickElement(n);
109 | return;
110 | }
111 | else{
112 | promptElement(n);
113 | }
114 | }
115 | }catch(ignored){}
116 | }
117 | finished();
118 | }
119 |
120 | function className(nameOfClass, click) {
121 | var walk=document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, false);
122 | while(n=walk.nextNode()){
123 | try{
124 | var className = n.className;
125 | if(className != null && className.trim().length>0 && className == nameOfClass) {
126 | if(click == 'true'){
127 | clickElement(n);
128 | return;
129 | }
130 | else{
131 | promptElement(n);
132 | }
133 | }
134 | }catch(ignored){}
135 | }
136 | finished();
137 | }
138 |
139 | function textContent(text, click) {
140 | var range = document.createRange();
141 | var walk=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT,null,false);
142 | while(n=walk.nextNode()){
143 | try{
144 | var textContent = n.textContent;
145 | if(textContent.trim() == text.trim()){
146 | if(click == 'true'){
147 | clickElement(n);
148 | return;
149 | }
150 | else{
151 | promptText(n, range);
152 | }
153 | }
154 | }catch(ignored){}
155 | }
156 | finished();
157 | }
158 |
159 | function tagName(tagName, click) {
160 | var elements = document.getElementsByTagName(tagName);
161 | for (var key in elements) {
162 | if(elements != null){
163 | try{
164 | if(click == 'true'){
165 | clickElement(elements[key]);
166 | return;
167 | }
168 | else{
169 | promptElement(elements[key]);
170 | }
171 | }catch(ignored){}
172 | }
173 | }
174 | finished();
175 | }
176 |
177 | function enterTextById(id, text) {
178 | var element = document.getElementById(id);
179 | if(element != null)
180 | element.value = text;
181 |
182 | finished();
183 | }
184 |
185 | function enterTextByXpath(xpath, text) {
186 | var element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue;
187 | if(element != null)
188 | element.value = text;
189 |
190 | finished();
191 | }
192 |
193 | function enterTextByCssSelector(cssSelector, text) {
194 | var element = document.querySelector(cssSelector);
195 | if(element != null)
196 | element.value = text;
197 |
198 | finished();
199 | }
200 |
201 | function enterTextByName(name, text) {
202 | var walk=document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, false);
203 | while(n=walk.nextNode()){
204 | var attributeName = n.getAttribute('name');
205 | if(attributeName != null && attributeName.trim().length>0 && attributeName == name)
206 | n.value=text;
207 | }
208 | finished();
209 | }
210 |
211 | function enterTextByClassName(name, text) {
212 | var walk=document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, false);
213 | while(n=walk.nextNode()){
214 | var className = n.className;
215 | if(className != null && className.trim().length>0 && className == name)
216 | n.value=text;
217 | }
218 | finished();
219 | }
220 |
221 | function enterTextByTextContent(textContent, text) {
222 | var walk=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT, null, false);
223 | while(n=walk.nextNode()){
224 | var textValue = n.textContent;
225 | if(textValue == textContent)
226 | n.parentNode.value = text;
227 | }
228 | finished();
229 | }
230 |
231 | function enterTextByTagName(tagName, text) {
232 | var elements = document.getElementsByTagName(tagName);
233 | if(elements != null){
234 | elements[0].value = text;
235 | }
236 | finished();
237 | }
238 |
239 | function promptElement(element) {
240 | var id = element.id;
241 | var text = element.innerText;
242 | if(text.trim().length == 0){
243 | text = element.value;
244 | }
245 | var name = element.getAttribute('name');
246 | var className = element.className;
247 | var tagName = element.tagName;
248 | var attributes = "";
249 | var htmlAttributes = element.attributes;
250 | for (var i = 0, htmlAttribute; htmlAttribute = htmlAttributes[i]; i++){
251 | attributes += htmlAttribute.name + "::" + htmlAttribute.value;
252 | if (i + 1 < htmlAttributes.length) {
253 | attributes += "#$";
254 | }
255 | }
256 |
257 | var rect = element.getBoundingClientRect();
258 | if(rect.width > 0 && rect.height > 0 && rect.left >= 0 && rect.top >= 0){
259 | prompt(id + ';,' + text + ';,' + name + ";," + className + ";," + tagName + ";," + rect.left + ';,' + rect.top + ';,' + rect.width + ';,' + rect.height + ';,' + attributes);
260 | }
261 | }
262 |
263 | function promptText(element, range) {
264 | var text = element.textContent;
265 | if(text.trim().length>0) {
266 | range.selectNodeContents(element);
267 | var rect = range.getBoundingClientRect();
268 | if(rect.width > 0 && rect.height > 0 && rect.left >= 0 && rect.top >= 0){
269 | var id = element.parentNode.id;
270 | var name = element.parentNode.getAttribute('name');
271 | var className = element.parentNode.className;
272 | var tagName = element.parentNode.tagName;
273 | prompt(id + ';,' + text + ';,' + name + ";," + className + ";," + tagName + ";," + rect.left + ';,' + rect.top + ';,' + rect.width + ';,' + rect.height);
274 | }
275 | }
276 | }
277 |
278 | function finished(){
279 | prompt('robotium-finished');
280 | }
281 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/RobotiumWebClient.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import java.util.List;
4 | import android.app.Instrumentation;
5 | import android.graphics.Bitmap;
6 | import android.os.Message;
7 | import android.view.View;
8 | import android.webkit.ConsoleMessage;
9 | import android.webkit.GeolocationPermissions;
10 | import android.webkit.JsPromptResult;
11 | import android.webkit.JsResult;
12 | import android.webkit.ValueCallback;
13 | import android.webkit.WebChromeClient;
14 | import android.webkit.WebStorage;
15 | import android.webkit.WebView;
16 |
17 | /**
18 | * WebChromeClient used to get information on web elements by injections of JavaScript.
19 | *
20 | * @author Renas Reda, renas.reda@robotium.com
21 | *
22 | */
23 |
24 | class RobotiumWebClient extends WebChromeClient{
25 | WebElementCreator webElementCreator;
26 | private Instrumentation inst;
27 | private WebChromeClient robotiumWebClient;
28 | private WebChromeClient originalWebChromeClient = null;
29 |
30 |
31 | /**
32 | * Constructs this object.
33 | *
34 | * @param instrumentation the {@code Instrumentation} instance
35 | * @param webElementCreator the {@code WebElementCreator} instance
36 | */
37 |
38 | public RobotiumWebClient(Instrumentation inst, WebElementCreator webElementCreator){
39 | this.inst = inst;
40 | this.webElementCreator = webElementCreator;
41 | robotiumWebClient = this;
42 | }
43 |
44 | /**
45 | * Enables JavaScript in the given {@code WebViews} objects.
46 | *
47 | * @param webViews the {@code WebView} objects to enable JavaScript in
48 | */
49 |
50 | public void enableJavascriptAndSetRobotiumWebClient(List webViews, WebChromeClient originalWebChromeClient){
51 | this.originalWebChromeClient = originalWebChromeClient;
52 | for(final WebView webView : webViews){
53 | if(webView != null){
54 | inst.runOnMainSync(new Runnable() {
55 | public void run() {
56 | webView.getSettings().setJavaScriptEnabled(true);
57 | webView.setWebChromeClient(robotiumWebClient);
58 | }
59 | });
60 | }
61 | }
62 | }
63 |
64 | /**
65 | * Overrides onJsPrompt in order to create {@code WebElement} objects based on the web elements attributes prompted by the injections of JavaScript
66 | */
67 |
68 | @Override
69 | public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult r) {
70 |
71 | if(message != null && (message.contains(";,") || message.contains("robotium-finished"))){
72 |
73 | if(message.equals("robotium-finished")){
74 | webElementCreator.setFinished(true);
75 | }
76 | else{
77 | webElementCreator.createWebElementAndAddInList(message, view);
78 | }
79 | r.confirm();
80 | return true;
81 | }
82 | else {
83 | if(originalWebChromeClient != null) {
84 | return originalWebChromeClient.onJsPrompt(view, url, message, defaultValue, r);
85 | }
86 | return true;
87 | }
88 |
89 | }
90 |
91 | @Override
92 | public Bitmap getDefaultVideoPoster() {
93 | if (originalWebChromeClient != null) {
94 | return originalWebChromeClient.getDefaultVideoPoster();
95 | }
96 | return null;
97 | }
98 |
99 | @Override
100 | public View getVideoLoadingProgressView() {
101 | if (originalWebChromeClient != null) {
102 | return originalWebChromeClient.getVideoLoadingProgressView();
103 | }
104 | return null;
105 | }
106 |
107 | @Override
108 | public void getVisitedHistory(ValueCallback callback) {
109 | if (originalWebChromeClient != null) {
110 | originalWebChromeClient.getVisitedHistory(callback);
111 | }
112 | }
113 |
114 | @Override
115 | public void onCloseWindow(WebView window) {
116 | if (originalWebChromeClient != null) {
117 | originalWebChromeClient.onCloseWindow(window);
118 | }
119 | }
120 |
121 | @Override
122 | public void onConsoleMessage(String message, int lineNumber, String sourceID) {
123 | if (originalWebChromeClient != null) {
124 | originalWebChromeClient.onConsoleMessage(message, lineNumber, sourceID);
125 | }
126 | }
127 |
128 | @Override
129 | public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
130 | if (originalWebChromeClient != null) {
131 | return originalWebChromeClient.onConsoleMessage(consoleMessage);
132 | }
133 | return true;
134 | }
135 |
136 | @Override
137 | public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
138 | if (originalWebChromeClient != null) {
139 | return originalWebChromeClient.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
140 | }
141 | return true;
142 | }
143 |
144 | @Override
145 | public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota,
146 | long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater) {
147 | if (originalWebChromeClient != null) {
148 | originalWebChromeClient.onExceededDatabaseQuota(url, databaseIdentifier, quota, estimatedDatabaseSize, totalQuota, quotaUpdater);
149 | }
150 | }
151 |
152 | @Override
153 | public void onGeolocationPermissionsHidePrompt() {
154 | if (originalWebChromeClient != null) {
155 | originalWebChromeClient.onGeolocationPermissionsHidePrompt();
156 | }
157 | }
158 |
159 | @Override
160 | public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
161 | if (originalWebChromeClient != null) {
162 | originalWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback);
163 | }
164 | }
165 |
166 | @Override
167 | public void onHideCustomView() {
168 | if (originalWebChromeClient != null) {
169 | originalWebChromeClient.onHideCustomView();
170 | }
171 | }
172 |
173 | @Override
174 | public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
175 | if (originalWebChromeClient != null) {
176 | return originalWebChromeClient.onJsAlert(view, url, message, result);
177 | }
178 | return true;
179 | }
180 |
181 | @Override
182 | public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
183 | if (originalWebChromeClient.onJsBeforeUnload(view, url, message, result)) {
184 | return originalWebChromeClient.onJsBeforeUnload(view, url, message, result);
185 | }
186 | return true;
187 | }
188 |
189 | @Override
190 | public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
191 | if (originalWebChromeClient != null) {
192 | return originalWebChromeClient.onJsConfirm(view, url, message, result);
193 | }
194 | return true;
195 | }
196 |
197 | @Override
198 | public boolean onJsTimeout() {
199 | if (originalWebChromeClient != null) {
200 | return originalWebChromeClient.onJsTimeout();
201 | }
202 | return true;
203 | }
204 |
205 | @Override
206 | public void onProgressChanged(WebView view, int newProgress) {
207 | if (originalWebChromeClient != null) {
208 | originalWebChromeClient.onProgressChanged(view, newProgress);
209 | }
210 | }
211 |
212 | @Override
213 | public void onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater) {
214 | if (originalWebChromeClient != null) {
215 | originalWebChromeClient.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater);
216 | }
217 | }
218 |
219 | @Override
220 | public void onReceivedIcon(WebView view, Bitmap icon) {
221 | if (originalWebChromeClient != null) {
222 | originalWebChromeClient.onReceivedIcon(view, icon);
223 | }
224 | }
225 |
226 | @Override
227 | public void onReceivedTitle(WebView view, String title) {
228 | if (originalWebChromeClient != null) {
229 | originalWebChromeClient.onReceivedTitle(view, title);
230 | }
231 | }
232 |
233 | @Override
234 | public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {
235 | if (originalWebChromeClient != null) {
236 | originalWebChromeClient.onReceivedTouchIconUrl(view, url, precomposed);
237 | }
238 | }
239 |
240 | @Override
241 | public void onRequestFocus(WebView view) {
242 | if (originalWebChromeClient != null) {
243 | originalWebChromeClient.onRequestFocus(view);
244 | }
245 | }
246 |
247 | @Override
248 | public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
249 | if (originalWebChromeClient != null) {
250 | originalWebChromeClient.onShowCustomView(view, callback);
251 | }
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Rotator.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import android.app.Instrumentation;
4 | import android.graphics.PointF;
5 | import android.os.SystemClock;
6 | import android.view.InputDevice;
7 | import android.view.MotionEvent;
8 | import android.view.MotionEvent.PointerCoords;
9 | import android.view.MotionEvent.PointerProperties;
10 |
11 | class Rotator
12 | {
13 | private final Instrumentation _instrument;
14 | private static final int EVENT_TIME_INTERVAL_MS = 10;
15 | public static final int LARGE = 0;
16 | public static final int SMALL = 1;
17 |
18 | public Rotator(Instrumentation inst)
19 | {
20 | this._instrument = inst;
21 | }
22 |
23 | public void generateRotateGesture(int size, PointF center1, PointF center2)
24 | {
25 | double incrementFactor = 0;
26 | float startX1 = center1.x;
27 | float startY1 = center1.y;
28 | float startX2 = center2.x;
29 | float startY2 = center2.y;
30 |
31 | long downTime = SystemClock.uptimeMillis();
32 | long eventTime = SystemClock.uptimeMillis();
33 |
34 | // pointer 1
35 | float x1 = startX1;
36 | float y1 = startY1;
37 |
38 | // pointer 2
39 | float x2 = startX2;
40 | float y2 = startY2;
41 |
42 | PointerCoords[] pointerCoords = new PointerCoords[2];
43 | PointerCoords pc1 = new PointerCoords();
44 | PointerCoords pc2 = new PointerCoords();
45 | pc1.x = x1;
46 | pc1.y = y1;
47 | pc1.pressure = 1;
48 | pc1.size = 1;
49 | pc2.x = x2;
50 | pc2.y = y2;
51 | pc2.pressure = 1;
52 | pc2.size = 1;
53 | pointerCoords[0] = pc1;
54 | pointerCoords[1] = pc2;
55 |
56 | PointerProperties[] pointerProperties = new PointerProperties[2];
57 | PointerProperties pp1 = new PointerProperties();
58 | PointerProperties pp2 = new PointerProperties();
59 | pp1.id = 0;
60 | pp1.toolType = MotionEvent.TOOL_TYPE_FINGER;
61 | pp2.id = 1;
62 | pp2.toolType = MotionEvent.TOOL_TYPE_FINGER;
63 | pointerProperties[0] = pp1;
64 | pointerProperties[1] = pp2;
65 |
66 | MotionEvent event;
67 | // send the initial touches
68 | event = MotionEvent.obtain(downTime, eventTime,
69 | MotionEvent.ACTION_DOWN, 1, pointerProperties, pointerCoords,
70 | 0, 0, // metaState, buttonState
71 | 1, // x precision
72 | 1, // y precision
73 | 0, 0, // deviceId, edgeFlags
74 | InputDevice.SOURCE_TOUCHSCREEN, 0); // source, flags
75 | _instrument.sendPointerSync(event);
76 |
77 | event = MotionEvent.obtain(downTime, eventTime,
78 | MotionEvent.ACTION_POINTER_DOWN
79 | + (pp2.id << MotionEvent.ACTION_POINTER_INDEX_SHIFT),
80 | 2, pointerProperties, pointerCoords, 0, 0, 1, 1, 0, 0,
81 | InputDevice.SOURCE_TOUCHSCREEN, 0);
82 | _instrument.sendPointerSync(event);
83 |
84 | switch(size)
85 | {
86 | case 0:
87 | {
88 | incrementFactor = 0.01;
89 | }
90 | break;
91 | case 1:
92 | {
93 | incrementFactor = 0.1;
94 | }
95 | break;
96 | }
97 | for (double i = 0; i < Math.PI; i += incrementFactor)
98 | {
99 | eventTime += EVENT_TIME_INTERVAL_MS;
100 | pointerCoords[0].x += Math.cos(i);
101 | pointerCoords[0].y += Math.sin(i);
102 | pointerCoords[1].x += Math.cos(i + Math.PI);
103 | pointerCoords[1].y += Math.sin(i + Math.PI);
104 |
105 | event = MotionEvent.obtain(downTime, eventTime,
106 | MotionEvent.ACTION_MOVE, 2, pointerProperties,
107 | pointerCoords, 0, 0, 1, 1, 0, 0,
108 | InputDevice.SOURCE_TOUCHSCREEN, 0);
109 | _instrument.sendPointerSync(event);
110 | }
111 |
112 | // and remove them fingers from the screen
113 | eventTime += EVENT_TIME_INTERVAL_MS;
114 | event = MotionEvent.obtain(downTime, eventTime,
115 | MotionEvent.ACTION_POINTER_UP
116 | + (pp2.id << MotionEvent.ACTION_POINTER_INDEX_SHIFT),
117 | 2, pointerProperties, pointerCoords, 0, 0, 1, 1, 0, 0,
118 | InputDevice.SOURCE_TOUCHSCREEN, 0);
119 | _instrument.sendPointerSync(event);
120 |
121 | eventTime += EVENT_TIME_INTERVAL_MS;
122 | event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP,
123 | 1, pointerProperties, pointerCoords, 0, 0, 1, 1, 0, 0,
124 | InputDevice.SOURCE_TOUCHSCREEN, 0);
125 | _instrument.sendPointerSync(event);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/ScreenshotTaker.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import java.io.File;
4 | import java.io.FileOutputStream;
5 | import java.text.SimpleDateFormat;
6 | import java.util.ArrayList;
7 | import java.util.Date;
8 | import java.util.concurrent.CountDownLatch;
9 | import java.util.concurrent.TimeUnit;
10 | import com.robotium.solo.Solo.Config;
11 | import com.robotium.solo.Solo.Config.ScreenshotFileType;
12 | import android.app.Activity;
13 | import android.app.Instrumentation;
14 | import android.graphics.Bitmap;
15 | import android.graphics.Canvas;
16 | import android.graphics.Picture;
17 | import android.opengl.GLSurfaceView;
18 | import android.opengl.GLSurfaceView.Renderer;
19 | import android.os.Handler;
20 | import android.os.HandlerThread;
21 | import android.os.Message;
22 | import android.os.SystemClock;
23 | import android.util.Log;
24 | import android.view.View;
25 | import android.webkit.WebView;
26 |
27 | /**
28 | * Contains screenshot methods like: takeScreenshot(final View, final String name), startScreenshotSequence(final String name, final int quality, final int frameDelay, final int maxFrames),
29 | * stopScreenshotSequence().
30 | *
31 | *
32 | * @author Renas Reda, renas.reda@robotium.com
33 | *
34 | */
35 |
36 | class ScreenshotTaker {
37 |
38 | private static final long TIMEOUT_SCREENSHOT_MUTEX = TimeUnit.SECONDS.toMillis(2);
39 | private final Object screenshotMutex = new Object();
40 | private final Config config;
41 | private final Instrumentation instrumentation;
42 | private final ActivityUtils activityUtils;
43 | private final String LOG_TAG = "Robotium";
44 | private ScreenshotSequenceThread screenshotSequenceThread = null;
45 | private HandlerThread screenShotSaverThread = null;
46 | private ScreenShotSaver screenShotSaver = null;
47 | private final ViewFetcher viewFetcher;
48 | private final Sleeper sleeper;
49 |
50 |
51 | /**
52 | * Constructs this object.
53 | *
54 | * @param config the {@code Config} instance
55 | * @param instrumentation the {@code Instrumentation} instance.
56 | * @param activityUtils the {@code ActivityUtils} instance
57 | * @param viewFetcher the {@code ViewFetcher} instance
58 | * @param sleeper the {@code Sleeper} instance
59 | *
60 | */
61 | ScreenshotTaker(Config config, Instrumentation instrumentation, ActivityUtils activityUtils, ViewFetcher viewFetcher, Sleeper sleeper) {
62 | this.config = config;
63 | this.instrumentation = instrumentation;
64 | this.activityUtils = activityUtils;
65 | this.viewFetcher = viewFetcher;
66 | this.sleeper = sleeper;
67 | }
68 |
69 | /**
70 | * Takes a screenshot and saves it in the {@link Config} objects save path.
71 | * Requires write permission (android.permission.WRITE_EXTERNAL_STORAGE) in AndroidManifest.xml of the application under test.
72 | *
73 | * @param name the name to give the screenshot image
74 | * @param quality the compression rate. From 0 (compress for lowest size) to 100 (compress for maximum quality).
75 | */
76 | public void takeScreenshot(final String name, final int quality) {
77 | View decorView = getScreenshotView();
78 | if(decorView == null)
79 | return;
80 |
81 | initScreenShotSaver();
82 | ScreenshotRunnable runnable = new ScreenshotRunnable(decorView, name, quality);
83 |
84 | synchronized (screenshotMutex) {
85 | Activity activity = activityUtils.getCurrentActivity(false);
86 | if(activity != null)
87 | activity.runOnUiThread(runnable);
88 | else
89 | instrumentation.runOnMainSync(runnable);
90 |
91 | try {
92 | screenshotMutex.wait(TIMEOUT_SCREENSHOT_MUTEX);
93 | } catch (InterruptedException ignored) {
94 | }
95 | }
96 | }
97 |
98 | /**
99 | * Takes a screenshot sequence and saves the images with the name prefix in the {@link Config} objects save path.
100 | *
101 | * The name prefix is appended with "_" + sequence_number for each image in the sequence,
102 | * where numbering starts at 0.
103 | *
104 | * Requires write permission (android.permission.WRITE_EXTERNAL_STORAGE) in the
105 | * AndroidManifest.xml of the application under test.
106 | *
107 | * Taking a screenshot will take on the order of 40-100 milliseconds of time on the
108 | * main UI thread. Therefore it is possible to mess up the timing of tests if
109 | * the frameDelay value is set too small.
110 | *
111 | * At present multiple simultaneous screenshot sequences are not supported.
112 | * This method will throw an exception if stopScreenshotSequence() has not been
113 | * called to finish any prior sequences.
114 | *
115 | * @param name the name prefix to give the screenshot
116 | * @param quality the compression rate. From 0 (compress for lowest size) to 100 (compress for maximum quality)
117 | * @param frameDelay the time in milliseconds to wait between each frame
118 | * @param maxFrames the maximum number of frames that will comprise this sequence
119 | *
120 | */
121 | public void startScreenshotSequence(final String name, final int quality, final int frameDelay, final int maxFrames) {
122 | initScreenShotSaver();
123 |
124 | if(screenshotSequenceThread != null) {
125 | throw new RuntimeException("only one screenshot sequence is supported at a time");
126 | }
127 |
128 | screenshotSequenceThread = new ScreenshotSequenceThread(name, quality, frameDelay, maxFrames);
129 |
130 | screenshotSequenceThread.start();
131 | }
132 |
133 | /**
134 | * Causes a screenshot sequence to end.
135 | *
136 | * If this method is not called to end a sequence and a prior sequence is still in
137 | * progress, startScreenshotSequence() will throw an exception.
138 | */
139 | public void stopScreenshotSequence() {
140 | if(screenshotSequenceThread != null) {
141 | screenshotSequenceThread.interrupt();
142 | screenshotSequenceThread = null;
143 | }
144 | }
145 |
146 | /**
147 | * Gets the proper view to use for a screenshot.
148 | */
149 | private View getScreenshotView() {
150 | View decorView = viewFetcher.getRecentDecorView(viewFetcher.getWindowDecorViews());
151 | final long endTime = SystemClock.uptimeMillis() + Timeout.getSmallTimeout();
152 |
153 | while (decorView == null) {
154 |
155 | final boolean timedOut = SystemClock.uptimeMillis() > endTime;
156 |
157 | if (timedOut){
158 | return null;
159 | }
160 | sleeper.sleepMini();
161 | decorView = viewFetcher.getRecentDecorView(viewFetcher.getWindowDecorViews());
162 | }
163 | wrapAllGLViews(decorView);
164 |
165 | return decorView;
166 | }
167 |
168 | /**
169 | * Extract and wrap the all OpenGL ES Renderer.
170 | */
171 | private void wrapAllGLViews(View decorView) {
172 | ArrayList currentViews = viewFetcher.getCurrentViews(GLSurfaceView.class, true, decorView);
173 | final CountDownLatch latch = new CountDownLatch(currentViews.size());
174 |
175 | for (GLSurfaceView glView : currentViews) {
176 | Object renderContainer = new Reflect(glView).field("mGLThread")
177 | .type(GLSurfaceView.class).out(Object.class);
178 |
179 | Renderer renderer = new Reflect(renderContainer).field("mRenderer").out(Renderer.class);
180 |
181 | if (renderer == null) {
182 | renderer = new Reflect(glView).field("mRenderer").out(Renderer.class);
183 | renderContainer = glView;
184 | }
185 | if (renderer == null) {
186 | latch.countDown();
187 | continue;
188 | }
189 | if (renderer instanceof GLRenderWrapper) {
190 | GLRenderWrapper wrapper = (GLRenderWrapper) renderer;
191 | wrapper.setTakeScreenshot();
192 | wrapper.setLatch(latch);
193 | } else {
194 | GLRenderWrapper wrapper = new GLRenderWrapper(glView, renderer, latch);
195 | new Reflect(renderContainer).field("mRenderer").in(wrapper);
196 | }
197 | }
198 |
199 | try {
200 | latch.await();
201 | } catch (InterruptedException ex) {
202 | ex.printStackTrace();
203 | }
204 | }
205 |
206 |
207 | /**
208 | * Returns a bitmap of a given WebView.
209 | *
210 | * @param webView the webView to save a bitmap from
211 | * @return a bitmap of the given web view
212 | *
213 | */
214 |
215 | private Bitmap getBitmapOfWebView(final WebView webView){
216 | Picture picture = webView.capturePicture();
217 | Bitmap b = Bitmap.createBitmap( picture.getWidth(), picture.getHeight(), Bitmap.Config.ARGB_8888);
218 | Canvas c = new Canvas(b);
219 | picture.draw(c);
220 | return b;
221 | }
222 |
223 | /**
224 | * Returns a bitmap of a given View.
225 | *
226 | * @param view the view to save a bitmap from
227 | * @return a bitmap of the given view
228 | *
229 | */
230 |
231 | private Bitmap getBitmapOfView(final View view){
232 | view.destroyDrawingCache();
233 | view.buildDrawingCache(false);
234 | Bitmap orig = view.getDrawingCache();
235 | Bitmap.Config config = null;
236 |
237 | if(orig == null) {
238 | return null;
239 | }
240 |
241 | config = orig.getConfig();
242 |
243 | if(config == null) {
244 | config = Bitmap.Config.ARGB_8888;
245 | }
246 | Bitmap b = orig.copy(config, false);
247 | orig.recycle();
248 | view.destroyDrawingCache();
249 | return b;
250 | }
251 |
252 | /**
253 | * Returns a proper filename depending on if name is given or not.
254 | *
255 | * @param name the given name
256 | * @return a proper filename depedning on if a name is given or not
257 | *
258 | */
259 |
260 | private String getFileName(final String name){
261 | SimpleDateFormat sdf = new SimpleDateFormat("ddMMyy-hhmmss");
262 | String fileName = null;
263 | if(name == null){
264 | if(config.screenshotFileType == ScreenshotFileType.JPEG){
265 | fileName = sdf.format( new Date()).toString()+ ".jpg";
266 | }
267 | else{
268 | fileName = sdf.format( new Date()).toString()+ ".png";
269 | }
270 | }
271 | else {
272 | if(config.screenshotFileType == ScreenshotFileType.JPEG){
273 | fileName = name + ".jpg";
274 | }
275 | else {
276 | fileName = name + ".png";
277 | }
278 | }
279 | return fileName;
280 | }
281 |
282 | /**
283 | * This method initializes the aysnc screenshot saving logic
284 | */
285 | private void initScreenShotSaver() {
286 | if(screenShotSaverThread == null || screenShotSaver == null) {
287 | screenShotSaverThread = new HandlerThread("ScreenShotSaver");
288 | screenShotSaverThread.start();
289 | screenShotSaver = new ScreenShotSaver(screenShotSaverThread);
290 | }
291 | }
292 |
293 | /**
294 | * This is the thread which causes a screenshot sequence to happen
295 | * in parallel with testing.
296 | */
297 | private class ScreenshotSequenceThread extends Thread {
298 | private int seqno = 0;
299 |
300 | private String name;
301 | private int quality;
302 | private int frameDelay;
303 | private int maxFrames;
304 |
305 | private boolean keepRunning = true;
306 |
307 | public ScreenshotSequenceThread(String _name, int _quality, int _frameDelay, int _maxFrames) {
308 | name = _name;
309 | quality = _quality;
310 | frameDelay = _frameDelay;
311 | maxFrames = _maxFrames;
312 | }
313 |
314 | public void run() {
315 | while(seqno < maxFrames) {
316 | if(!keepRunning || Thread.interrupted()) break;
317 | doScreenshot();
318 | seqno++;
319 | try {
320 | Thread.sleep(frameDelay);
321 | } catch (InterruptedException e) {
322 | }
323 | }
324 | screenshotSequenceThread = null;
325 | }
326 |
327 | public void doScreenshot() {
328 | View v = getScreenshotView();
329 | if(v == null) keepRunning = false;
330 | String final_name = name+"_"+seqno;
331 | ScreenshotRunnable r = new ScreenshotRunnable(v, final_name, quality);
332 | Log.d(LOG_TAG, "taking screenshot "+final_name);
333 | Activity activity = activityUtils.getCurrentActivity(false);
334 | if(activity != null){
335 | activity.runOnUiThread(r);
336 | }
337 | else {
338 | instrumentation.runOnMainSync(r);
339 | }
340 | }
341 |
342 | public void interrupt() {
343 | keepRunning = false;
344 | super.interrupt();
345 | }
346 | }
347 |
348 | /**
349 | * Here we have a Runnable which is responsible for taking the actual screenshot,
350 | * and then posting the bitmap to a Handler which will save it.
351 | *
352 | * This Runnable is run on the UI thread.
353 | */
354 | private class ScreenshotRunnable implements Runnable {
355 |
356 | private View view;
357 | private String name;
358 | private int quality;
359 |
360 | public ScreenshotRunnable(final View _view, final String _name, final int _quality) {
361 | view = _view;
362 | name = _name;
363 | quality = _quality;
364 | }
365 |
366 | public void run() {
367 | if(view !=null){
368 | Bitmap b;
369 |
370 | if(view instanceof WebView){
371 | b = getBitmapOfWebView((WebView) view);
372 | }
373 | else{
374 | b = getBitmapOfView(view);
375 | }
376 | if(b != null) {
377 | screenShotSaver.saveBitmap(b, name, quality);
378 | b = null;
379 | // Return here so that the screenshotMutex is not unlocked,
380 | // since this is handled by save bitmap
381 | return;
382 | }
383 | else
384 | Log.d(LOG_TAG, "NULL BITMAP!!");
385 | }
386 |
387 | // Make sure the screenshotMutex is unlocked
388 | synchronized (screenshotMutex) {
389 | screenshotMutex.notify();
390 | }
391 | }
392 | }
393 |
394 | /**
395 | * This class is a Handler which deals with saving the screenshots on a separate thread.
396 | *
397 | * The screenshot logic by necessity has to run on the ui thread. However, in practice
398 | * it seems that saving a screenshot (with quality 100) takes approx twice as long
399 | * as taking it in the first place.
400 | *
401 | * Saving the screenshots in a separate thread like this will thus make the screenshot
402 | * process approx 3x faster as far as the main thread is concerned.
403 | *
404 | */
405 | private class ScreenShotSaver extends Handler {
406 | public ScreenShotSaver(HandlerThread thread) {
407 | super(thread.getLooper());
408 | }
409 |
410 | /**
411 | * This method posts a Bitmap with meta-data to the Handler queue.
412 | *
413 | * @param bitmap the bitmap to save
414 | * @param name the name of the file
415 | * @param quality the compression rate. From 0 (compress for lowest size) to 100 (compress for maximum quality).
416 | */
417 | public void saveBitmap(Bitmap bitmap, String name, int quality) {
418 | Message message = this.obtainMessage();
419 | message.arg1 = quality;
420 | message.obj = bitmap;
421 | message.getData().putString("name", name);
422 | this.sendMessage(message);
423 | }
424 |
425 | /**
426 | * Here we process the Handler queue and save the bitmaps.
427 | *
428 | * @param message A Message containing the bitmap to save, and some metadata.
429 | */
430 | public void handleMessage(Message message) {
431 | synchronized (screenshotMutex) {
432 | String name = message.getData().getString("name");
433 | int quality = message.arg1;
434 | Bitmap b = (Bitmap)message.obj;
435 | if(b != null) {
436 | saveFile(name, b, quality);
437 | b.recycle();
438 | }
439 | else {
440 | Log.d(LOG_TAG, "NULL BITMAP!!");
441 | }
442 |
443 | screenshotMutex.notify();
444 | }
445 | }
446 |
447 | /**
448 | * Saves a file.
449 | *
450 | * @param name the name of the file
451 | * @param b the bitmap to save
452 | * @param quality the compression rate. From 0 (compress for lowest size) to 100 (compress for maximum quality).
453 | *
454 | */
455 | private void saveFile(String name, Bitmap b, int quality){
456 | FileOutputStream fos = null;
457 | String fileName = getFileName(name);
458 |
459 | File directory = new File(config.screenshotSavePath);
460 | directory.mkdir();
461 |
462 | File fileToSave = new File(directory,fileName);
463 | try {
464 | fos = new FileOutputStream(fileToSave);
465 | if(config.screenshotFileType == ScreenshotFileType.JPEG){
466 | if (b.compress(Bitmap.CompressFormat.JPEG, quality, fos) == false){
467 | Log.d(LOG_TAG, "Compress/Write failed");
468 | }
469 | }
470 | else{
471 | if (b.compress(Bitmap.CompressFormat.PNG, quality, fos) == false){
472 | Log.d(LOG_TAG, "Compress/Write failed");
473 | }
474 | }
475 | fos.flush();
476 | fos.close();
477 | } catch (Exception e) {
478 | Log.d(LOG_TAG, "Can't save the screenshot! Requires write permission (android.permission.WRITE_EXTERNAL_STORAGE) in AndroidManifest.xml of the application under test.");
479 | e.printStackTrace();
480 | }
481 | }
482 | }
483 | }
484 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Scroller.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.Set;
6 | import com.robotium.solo.Solo.Config;
7 | import junit.framework.Assert;
8 | import android.app.Instrumentation;
9 | import android.content.Context;
10 | import android.os.SystemClock;
11 | import android.view.MotionEvent;
12 | import android.view.View;
13 | import android.view.WindowManager;
14 | import android.webkit.WebView;
15 | import android.widget.AbsListView;
16 | import android.widget.GridView;
17 | import android.widget.ListView;
18 | import android.widget.ScrollView;
19 |
20 |
21 | /**
22 | * Contains scroll methods. Examples are scrollDown(), scrollUpList(),
23 | * scrollToSide().
24 | *
25 | * @author Renas Reda, renas.reda@robotium.com
26 | *
27 | */
28 |
29 | class Scroller {
30 |
31 | public static final int DOWN = 0;
32 | public static final int UP = 1;
33 | public enum Side {LEFT, RIGHT}
34 | private boolean canScroll = false;
35 | private final Instrumentation inst;
36 | private final ViewFetcher viewFetcher;
37 | private final Sleeper sleeper;
38 | private final Config config;
39 |
40 |
41 | /**
42 | * Constructs this object.
43 | *
44 | * @param inst the {@code Instrumentation} instance
45 | * @param viewFetcher the {@code ViewFetcher} instance
46 | * @param sleeper the {@code Sleeper} instance
47 | */
48 |
49 | public Scroller(Config config, Instrumentation inst, ViewFetcher viewFetcher, Sleeper sleeper) {
50 | this.config = config;
51 | this.inst = inst;
52 | this.viewFetcher = viewFetcher;
53 | this.sleeper = sleeper;
54 | }
55 |
56 |
57 | /**
58 | * Simulate touching a specific location and dragging to a new location.
59 | *
60 | * This method was copied from {@code TouchUtils.java} in the Android Open Source Project, and modified here.
61 | *
62 | * @param fromX X coordinate of the initial touch, in screen coordinates
63 | * @param toX Xcoordinate of the drag destination, in screen coordinates
64 | * @param fromY X coordinate of the initial touch, in screen coordinates
65 | * @param toY Y coordinate of the drag destination, in screen coordinates
66 | * @param stepCount How many move steps to include in the drag
67 | */
68 |
69 | public void drag(float fromX, float toX, float fromY, float toY,
70 | int stepCount) {
71 | long downTime = SystemClock.uptimeMillis();
72 | long eventTime = SystemClock.uptimeMillis();
73 | float y = fromY;
74 | float x = fromX;
75 | float yStep = (toY - fromY) / stepCount;
76 | float xStep = (toX - fromX) / stepCount;
77 | MotionEvent event = MotionEvent.obtain(downTime, eventTime,MotionEvent.ACTION_DOWN, fromX, fromY, 0);
78 | try {
79 | inst.sendPointerSync(event);
80 | } catch (SecurityException ignored) {}
81 | for (int i = 0; i < stepCount; ++i) {
82 | y += yStep;
83 | x += xStep;
84 | eventTime = SystemClock.uptimeMillis();
85 | event = MotionEvent.obtain(downTime, eventTime,MotionEvent.ACTION_MOVE, x, y, 0);
86 | try {
87 | inst.sendPointerSync(event);
88 | } catch (SecurityException ignored) {}
89 | }
90 | eventTime = SystemClock.uptimeMillis();
91 | event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP,toX, toY, 0);
92 | try {
93 | inst.sendPointerSync(event);
94 | } catch (SecurityException ignored) {}
95 | }
96 |
97 |
98 | /**
99 | * Scrolls a ScrollView.
100 | *
101 | * @param direction the direction to be scrolled
102 | * @return {@code true} if scrolling occurred, false if it did not
103 | */
104 |
105 | public boolean scrollView(final View view, int direction){
106 | if(view == null){
107 | return false;
108 | }
109 |
110 | int height = view.getHeight();
111 | height--;
112 | int scrollTo = -1;
113 |
114 | if (direction == DOWN) {
115 | scrollTo = height;
116 | }
117 |
118 | else if (direction == UP) {
119 | scrollTo = -height;
120 | }
121 |
122 | int originalY = view.getScrollY();
123 | final int scrollAmount = scrollTo;
124 | inst.runOnMainSync(new Runnable(){
125 | public void run(){
126 | view.scrollBy(0, scrollAmount);
127 | }
128 | });
129 |
130 | if (originalY == view.getScrollY()) {
131 | return false;
132 | }
133 | else{
134 | return true;
135 | }
136 | }
137 |
138 | /**
139 | * Scrolls a ScrollView to top or bottom.
140 | *
141 | * @param direction the direction to be scrolled
142 | */
143 |
144 | public void scrollViewAllTheWay(final View view, final int direction) {
145 | while(scrollView(view, direction));
146 | }
147 |
148 | /**
149 | * Scrolls up or down.
150 | *
151 | * @param direction the direction in which to scroll
152 | * @return {@code true} if more scrolling can be done
153 | */
154 |
155 | public boolean scroll(int direction) {
156 | return scroll(direction, false);
157 | }
158 |
159 | /**
160 | * Scrolls down.
161 | *
162 | * @return {@code true} if more scrolling can be done
163 | */
164 |
165 | public boolean scrollDown() {
166 | if(!config.shouldScroll) {
167 | return false;
168 | }
169 | return scroll(Scroller.DOWN);
170 | }
171 |
172 | /**
173 | * Scrolls up and down.
174 | *
175 | * @param direction the direction in which to scroll
176 | * @param allTheWay true if the view should be scrolled to the beginning or end,
177 | * false to scroll one page up or down.
178 | * @return {@code true} if more scrolling can be done
179 | */
180 |
181 | @SuppressWarnings("unchecked")
182 | public boolean scroll(int direction, boolean allTheWay) {
183 | ArrayList viewList = RobotiumUtils.removeInvisibleViews(viewFetcher.getAllViews(true));
184 |
185 | ArrayList filteredViews = RobotiumUtils.filterViewsToSet(new Class[] { ListView.class,
186 | ScrollView.class, GridView.class, WebView.class}, viewList);
187 |
188 | List scrollableSupportPackageViews = viewFetcher.getScrollableSupportPackageViews(true);
189 |
190 | for(View viewToScroll : scrollableSupportPackageViews){
191 | filteredViews.add(viewToScroll);
192 | }
193 |
194 | View view = viewFetcher.getFreshestView(filteredViews);
195 |
196 | if (view == null) {
197 | return false;
198 | }
199 |
200 | if (view instanceof AbsListView) {
201 | return scrollList((AbsListView)view, direction, allTheWay);
202 | }
203 |
204 | if(view instanceof WebView){
205 | return scrollWebView((WebView)view, direction, allTheWay);
206 | }
207 |
208 | if (allTheWay) {
209 | scrollViewAllTheWay(view, direction);
210 | return false;
211 | } else {
212 | return scrollView(view, direction);
213 | }
214 | }
215 |
216 | /**
217 | * Scrolls a WebView.
218 | *
219 | * @param webView the WebView to scroll
220 | * @param direction the direction to scroll
221 | * @param allTheWay {@code true} to scroll the view all the way up or down, {@code false} to scroll one page up or down or down.
222 | * @return {@code true} if more scrolling can be done
223 | */
224 |
225 | public boolean scrollWebView(final WebView webView, int direction, final boolean allTheWay){
226 |
227 | if (direction == DOWN) {
228 | inst.runOnMainSync(new Runnable(){
229 | public void run(){
230 | canScroll = webView.pageDown(allTheWay);
231 | }
232 | });
233 | }
234 | if(direction == UP){
235 | inst.runOnMainSync(new Runnable(){
236 | public void run(){
237 | canScroll = webView.pageUp(allTheWay);
238 | }
239 | });
240 | }
241 | return canScroll;
242 | }
243 |
244 | /**
245 | * Scrolls a list.
246 | *
247 | * @param absListView the list to be scrolled
248 | * @param direction the direction to be scrolled
249 | * @param allTheWay {@code true} to scroll the view all the way up or down, {@code false} to scroll one page up or down
250 | * @return {@code true} if more scrolling can be done
251 | */
252 |
253 | public boolean scrollList(T absListView, int direction, boolean allTheWay) {
254 |
255 | if(absListView == null){
256 | return false;
257 | }
258 |
259 | if (direction == DOWN) {
260 |
261 | int listCount = absListView.getCount();
262 | int lastVisiblePosition = absListView.getLastVisiblePosition();
263 |
264 | if (allTheWay) {
265 | scrollListToLine(absListView, listCount-1);
266 | return false;
267 | }
268 |
269 | if (lastVisiblePosition >= listCount - 1) {
270 | if(lastVisiblePosition > 0){
271 | scrollListToLine(absListView, lastVisiblePosition);
272 | }
273 | return false;
274 | }
275 |
276 | int firstVisiblePosition = absListView.getFirstVisiblePosition();
277 |
278 |
279 | if(firstVisiblePosition != lastVisiblePosition)
280 | scrollListToLine(absListView, lastVisiblePosition);
281 |
282 | else
283 | scrollListToLine(absListView, firstVisiblePosition + 1);
284 |
285 | } else if (direction == UP) {
286 | int firstVisiblePosition = absListView.getFirstVisiblePosition();
287 |
288 | if (allTheWay || firstVisiblePosition < 2) {
289 | scrollListToLine(absListView, 0);
290 | return false;
291 | }
292 | int lastVisiblePosition = absListView.getLastVisiblePosition();
293 |
294 | final int lines = lastVisiblePosition - firstVisiblePosition;
295 |
296 | int lineToScrollTo = firstVisiblePosition - lines;
297 |
298 | if(lineToScrollTo == lastVisiblePosition)
299 | lineToScrollTo--;
300 |
301 | if(lineToScrollTo < 0)
302 | lineToScrollTo = 0;
303 |
304 | scrollListToLine(absListView, lineToScrollTo);
305 | }
306 | sleeper.sleep();
307 | return true;
308 | }
309 |
310 |
311 | /**
312 | * Scroll the list to a given line
313 | *
314 | * @param view the {@link AbsListView} to scroll
315 | * @param line the line to scroll to
316 | */
317 |
318 | public void scrollListToLine(final T view, final int line){
319 | if(view == null)
320 | Assert.fail("AbsListView is null!");
321 |
322 | final int lineToMoveTo;
323 | if(view instanceof GridView) {
324 | lineToMoveTo = line+1;
325 | }
326 | else {
327 | lineToMoveTo = line;
328 | }
329 |
330 | inst.runOnMainSync(new Runnable(){
331 | public void run(){
332 | view.setSelection(lineToMoveTo);
333 | }
334 | });
335 | }
336 |
337 |
338 | /**
339 | * Scrolls horizontally.
340 | *
341 | * @param side the side to which to scroll; {@link Side#RIGHT} or {@link Side#LEFT}
342 | * @param scrollPosition the position to scroll to, from 0 to 1 where 1 is all the way. Example is: 0.55.
343 | * @param stepCount how many move steps to include in the scroll. Less steps results in a faster scroll
344 | */
345 |
346 | @SuppressWarnings("deprecation")
347 | public void scrollToSide(Side side, float scrollPosition, int stepCount) {
348 | WindowManager windowManager = (WindowManager)
349 | inst.getTargetContext().getSystemService(Context.WINDOW_SERVICE);
350 |
351 | int screenHeight = windowManager.getDefaultDisplay()
352 | .getHeight();
353 | int screenWidth = windowManager.getDefaultDisplay()
354 | .getWidth();
355 | float x = screenWidth * scrollPosition;
356 | float y = screenHeight / 2.0f;
357 | if (side == Side.LEFT)
358 | drag(70, x, y, y, stepCount);
359 | else if (side == Side.RIGHT)
360 | drag(x, 0, y, y, stepCount);
361 | }
362 |
363 | /**
364 | * Scrolls view horizontally.
365 | *
366 | * @param view the view to scroll
367 | * @param side the side to which to scroll; {@link Side#RIGHT} or {@link Side#LEFT}
368 | * @param scrollPosition the position to scroll to, from 0 to 1 where 1 is all the way. Example is: 0.55.
369 | * @param stepCount how many move steps to include in the scroll. Less steps results in a faster scroll
370 | */
371 |
372 | public void scrollViewToSide(View view, Side side, float scrollPosition, int stepCount) {
373 | int[] corners = new int[2];
374 | view.getLocationOnScreen(corners);
375 | int viewHeight = view.getHeight();
376 | int viewWidth = view.getWidth();
377 | float x = corners[0] + viewWidth * scrollPosition;
378 | float y = corners[1] + viewHeight / 2.0f;
379 | if (side == Side.LEFT)
380 | drag(corners[0], x, y, y, stepCount);
381 | else if (side == Side.RIGHT)
382 | drag(x, corners[0], y, y, stepCount);
383 | }
384 | }
385 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Searcher.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 | import java.util.HashSet;
6 | import java.util.List;
7 | import java.util.Set;
8 | import java.util.concurrent.Callable;
9 | import android.os.SystemClock;
10 | import android.util.Log;
11 | import android.view.View;
12 | import android.widget.TextView;
13 |
14 |
15 | /**
16 | * Contains various search methods. Examples are: searchForEditTextWithTimeout(),
17 | * searchForTextWithTimeout(), searchForButtonWithTimeout().
18 | *
19 | * @author Renas Reda, renas.reda@robotium.com
20 | *
21 | */
22 |
23 | class Searcher {
24 |
25 | private final ViewFetcher viewFetcher;
26 | private final WebUtils webUtils;
27 | private final Scroller scroller;
28 | private final Sleeper sleeper;
29 | private final String LOG_TAG = "Robotium";
30 | Set uniqueTextViews;
31 | List webElements;
32 | private int numberOfUniqueViews;
33 | private final int TIMEOUT = 5000;
34 |
35 |
36 | /**
37 | * Constructs this object.
38 | *
39 | * @param viewFetcher the {@code ViewFetcher} instance
40 | * @param webUtils the {@code WebUtils} instance
41 | * @param scroller the {@code Scroller} instance
42 | * @param sleeper the {@code Sleeper} instance.
43 | */
44 |
45 | public Searcher(ViewFetcher viewFetcher, WebUtils webUtils, Scroller scroller, Sleeper sleeper) {
46 | this.viewFetcher = viewFetcher;
47 | this.webUtils = webUtils;
48 | this.scroller = scroller;
49 | this.sleeper = sleeper;
50 | webElements = new ArrayList();
51 | uniqueTextViews = new HashSet();
52 | }
53 |
54 |
55 | /**
56 | * Searches for a {@code View} with the given regex string and returns {@code true} if the
57 | * searched {@code Button} is found a given number of times. Will automatically scroll when needed.
58 | *
59 | * @param viewClass what kind of {@code View} to search for, e.g. {@code Button.class} or {@code TextView.class}
60 | * @param regex the text to search for. The parameter will be interpreted as a regular expression.
61 | * @param expectedMinimumNumberOfMatches the minimum number of matches expected to be found. {@code 0} matches means that one or more
62 | * matches are expected to be found
63 | * @param scroll whether scrolling should be performed
64 | * @param onlyVisible {@code true} if only texts visible on the screen should be searched
65 | *
66 | * @return {@code true} if a {@code View} of the specified class with the given text is found a given number of
67 | * times, and {@code false} if it is not found
68 | */
69 |
70 | public boolean searchWithTimeoutFor(Class extends TextView> viewClass, String regex, int expectedMinimumNumberOfMatches, boolean scroll, boolean onlyVisible) {
71 | final long endTime = SystemClock.uptimeMillis() + TIMEOUT;
72 |
73 | TextView foundAnyMatchingView = null;
74 |
75 | while (SystemClock.uptimeMillis() < endTime) {
76 | sleeper.sleep();
77 | foundAnyMatchingView = searchFor(viewClass, regex, expectedMinimumNumberOfMatches, 0, scroll, onlyVisible);
78 | if (foundAnyMatchingView !=null){
79 | return true;
80 | }
81 | }
82 | return false;
83 | }
84 |
85 |
86 | /**
87 | * Searches for a {@code View} with the given regex string and returns {@code true} if the
88 | * searched {@code View} is found a given number of times.
89 | *
90 | * @param viewClass what kind of {@code View} to search for, e.g. {@code Button.class} or {@code TextView.class}
91 | * @param regex the text to search for. The parameter will be interpreted as a regular expression.
92 | * @param expectedMinimumNumberOfMatches the minimum number of matches expected to be found. {@code 0} matches means that one or more
93 | * matches are expected to be found.
94 | * @param timeout the amount of time in milliseconds to wait
95 | * @param scroll whether scrolling should be performed
96 | * @param onlyVisible {@code true} if only texts visible on the screen should be searched
97 | *
98 | * @return {@code true} if a view of the specified class with the given text is found a given number of times.
99 | * {@code false} if it is not found.
100 | */
101 |
102 | public T searchFor(final Class viewClass, final String regex, int expectedMinimumNumberOfMatches, final long timeout, final boolean scroll, final boolean onlyVisible) {
103 | if(expectedMinimumNumberOfMatches < 1) {
104 | expectedMinimumNumberOfMatches = 1;
105 | }
106 |
107 | final Callable> viewFetcherCallback = new Callable>() {
108 | @SuppressWarnings("unchecked")
109 | public Collection call() throws Exception {
110 | sleeper.sleep();
111 |
112 | ArrayList viewsToReturn = viewFetcher.getCurrentViews(viewClass, true);
113 |
114 | if(onlyVisible){
115 | viewsToReturn = RobotiumUtils.removeInvisibleViews(viewsToReturn);
116 | }
117 |
118 | if(viewClass.isAssignableFrom(TextView.class)) {
119 | viewsToReturn.addAll((Collection extends T>) webUtils.getTextViewsFromWebView());
120 | }
121 | return viewsToReturn;
122 | }
123 | };
124 |
125 | try {
126 | return searchFor(viewFetcherCallback, regex, expectedMinimumNumberOfMatches, timeout, scroll);
127 | } catch (Exception e) {
128 | throw new RuntimeException(e);
129 | }
130 | }
131 |
132 | /**
133 | * Searches for a view class.
134 | *
135 | * @param uniqueViews the set of unique views
136 | * @param viewClass the view class to search for
137 | * @param index the index of the view class
138 | * @return true if view class if found a given number of times
139 | */
140 |
141 | public boolean searchFor(Set uniqueViews, Class viewClass, final int index) {
142 | ArrayList allViews = RobotiumUtils.removeInvisibleViews(viewFetcher.getCurrentViews(viewClass, true));
143 |
144 | int uniqueViewsFound = (getNumberOfUniqueViews(uniqueViews, allViews));
145 |
146 | if(uniqueViewsFound > 0 && index < uniqueViewsFound) {
147 | return true;
148 | }
149 |
150 | if(uniqueViewsFound > 0 && index == 0) {
151 | return true;
152 | }
153 | return false;
154 | }
155 |
156 | /**
157 | * Searches for a given view.
158 | *
159 | * @param view the view to search
160 | * @param scroll true if scrolling should be performed
161 | * @return true if view is found
162 | */
163 |
164 | public boolean searchFor(View view) {
165 | ArrayList views = viewFetcher.getAllViews(true);
166 | for(View v : views){
167 | if(v.equals(view)){
168 | return true;
169 | }
170 | }
171 | return false;
172 | }
173 |
174 | /**
175 | * Searches for a {@code View} with the given regex string and returns {@code true} if the
176 | * searched {@code View} is found a given number of times. Will not scroll, because the caller needs to find new
177 | * {@code View}s to evaluate after scrolling, and call this method again.
178 | *
179 | * @param viewFetcherCallback callback which should return an updated collection of views to search
180 | * @param regex the text to search for. The parameter will be interpreted as a regular expression.
181 | * @param expectedMinimumNumberOfMatches the minimum number of matches expected to be found. {@code 0} matches means that one or more
182 | * matches are expected to be found.
183 | * @param timeout the amount of time in milliseconds to wait
184 | * @param scroll whether scrolling should be performed
185 | *
186 | * @return {@code true} if a view of the specified class with the given text is found a given number of times.
187 | * {@code false} if it is not found.
188 | *
189 | * @throws Exception not really, it's just the signature of {@code Callable}
190 | */
191 |
192 | public T searchFor(Callable> viewFetcherCallback, String regex, int expectedMinimumNumberOfMatches, long timeout, boolean scroll) throws Exception {
193 | final long endTime = SystemClock.uptimeMillis() + timeout;
194 | Collection views;
195 |
196 | while (true) {
197 | final boolean timedOut = timeout > 0 && SystemClock.uptimeMillis() > endTime;
198 |
199 | if(timedOut){
200 | logMatchesFound(regex);
201 | return null;
202 | }
203 |
204 | views = viewFetcherCallback.call();
205 |
206 | for(T view : views){
207 | if (RobotiumUtils.getNumberOfMatches(regex, view, uniqueTextViews) == expectedMinimumNumberOfMatches) {
208 | uniqueTextViews.clear();
209 | return view;
210 | }
211 | }
212 | if(scroll && !scroller.scrollDown()){
213 | logMatchesFound(regex);
214 | return null;
215 | }
216 | if(!scroll){
217 | logMatchesFound(regex);
218 | return null;
219 | }
220 | }
221 | }
222 |
223 | /**
224 | * Searches for a web element.
225 | *
226 | * @param by the By object e.g. By.id("id");
227 | * @param minimumNumberOfMatches the minimum number of matches that are expected to be shown. {@code 0} means any number of matches
228 | * @return the web element or null if not found
229 | */
230 |
231 | public WebElement searchForWebElement(final By by, int minimumNumberOfMatches){
232 |
233 | if(minimumNumberOfMatches < 1){
234 | minimumNumberOfMatches = 1;
235 | }
236 |
237 | List viewsFromScreen = webUtils.getWebElements(by, true);
238 | addViewsToList (webElements, viewsFromScreen);
239 |
240 | return getViewFromList(webElements, minimumNumberOfMatches);
241 | }
242 |
243 | /**
244 | * Adds views to a given list.
245 | *
246 | * @param allWebElements the list of all views
247 | * @param webTextViewsOnScreen the list of views shown on screen
248 | */
249 |
250 | private void addViewsToList(List allWebElements, List webElementsOnScreen){
251 |
252 | int[] xyViewFromSet = new int[2];
253 | int[] xyViewFromScreen = new int[2];
254 |
255 | for(WebElement textFromScreen : webElementsOnScreen){
256 | boolean foundView = false;
257 | textFromScreen.getLocationOnScreen(xyViewFromScreen);
258 |
259 | for(WebElement textFromList : allWebElements){
260 | textFromList.getLocationOnScreen(xyViewFromSet);
261 |
262 | if(textFromScreen.getText().equals(textFromList.getText()) && xyViewFromScreen[0] == xyViewFromSet[0] && xyViewFromScreen[1] == xyViewFromSet[1]) {
263 | foundView = true;
264 | }
265 | }
266 |
267 | if(!foundView){
268 | allWebElements.add(textFromScreen);
269 | }
270 | }
271 |
272 | }
273 |
274 | /**
275 | * Returns a text view with a given match.
276 | *
277 | * @param webElements the list of views
278 | * @param match the match of the view to return
279 | * @return the view with a given match
280 | */
281 |
282 | private WebElement getViewFromList(List webElements, int match){
283 |
284 | WebElement webElementToReturn = null;
285 |
286 | if(webElements.size() >= match){
287 |
288 | try{
289 | webElementToReturn = webElements.get(--match);
290 | }catch(Exception ignored){}
291 | }
292 | if(webElementToReturn != null)
293 | webElements.clear();
294 |
295 | return webElementToReturn;
296 | }
297 |
298 | /**
299 | * Returns the number of unique views.
300 | *
301 | * @param uniqueViews the set of unique views
302 | * @param views the list of all views
303 | * @return number of unique views
304 | */
305 |
306 | public int getNumberOfUniqueViews(SetuniqueViews, ArrayList views){
307 | for(int i = 0; i < views.size(); i++){
308 | uniqueViews.add(views.get(i));
309 | }
310 | numberOfUniqueViews = uniqueViews.size();
311 | return numberOfUniqueViews;
312 | }
313 |
314 | /**
315 | * Returns the number of unique views.
316 | *
317 | * @return the number of unique views
318 | */
319 |
320 | public int getNumberOfUniqueViews(){
321 | return numberOfUniqueViews;
322 | }
323 |
324 | /**
325 | * Logs a (searchFor failed) message.
326 | *
327 | * @param regex the search string to log
328 | */
329 |
330 | public void logMatchesFound(String regex){
331 | if (uniqueTextViews.size() > 0) {
332 | Log.d(LOG_TAG, " There are only " + uniqueTextViews.size() + " matches of '" + regex + "'");
333 | }
334 | else if(webElements.size() > 0){
335 | Log.d(LOG_TAG, " There are only " + webElements.size() + " matches of '" + regex + "'");
336 | }
337 | uniqueTextViews.clear();
338 | webElements.clear();
339 | }
340 | }
341 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Sender.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 |
4 | import junit.framework.Assert;
5 | import android.app.Instrumentation;
6 | import android.view.KeyEvent;
7 |
8 | /**
9 | * Contains send key event methods. Examples are:
10 | * sendKeyCode(), goBack()
11 | *
12 | * @author Renas Reda, renas.reda@robotium.com
13 | *
14 | */
15 |
16 | class Sender {
17 |
18 | private final Instrumentation inst;
19 | private final Sleeper sleeper;
20 |
21 | /**
22 | * Constructs this object.
23 | *
24 | * @param inst the {@code Instrumentation} instance
25 | * @param sleeper the {@code Sleeper} instance
26 | */
27 |
28 | Sender(Instrumentation inst, Sleeper sleeper) {
29 | this.inst = inst;
30 | this.sleeper = sleeper;
31 | }
32 |
33 | /**
34 | * Tells Robotium to send a key code: Right, Left, Up, Down, Enter or other.
35 | *
36 | * @param keycode the key code to be sent. Use {@link KeyEvent#KEYCODE_ENTER}, {@link KeyEvent#KEYCODE_MENU}, {@link KeyEvent#KEYCODE_DEL}, {@link KeyEvent#KEYCODE_DPAD_RIGHT} and so on
37 | */
38 |
39 | public void sendKeyCode(int keycode)
40 | {
41 | sleeper.sleep();
42 | try{
43 | inst.sendCharacterSync(keycode);
44 | }catch(SecurityException e){
45 | Assert.fail("Can not complete action! ("+(e != null ? e.getClass().getName()+": "+e.getMessage() : "null")+")");
46 | }
47 | }
48 |
49 | /**
50 | * Simulates pressing the hardware back key.
51 | */
52 |
53 | public void goBack() {
54 | sleeper.sleep();
55 | try {
56 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
57 | sleeper.sleep();
58 | } catch (Throwable ignored) {}
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Setter.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import android.app.Activity;
4 | import android.view.View;
5 | import android.widget.DatePicker;
6 | import android.widget.ProgressBar;
7 | import android.widget.SlidingDrawer;
8 | import android.widget.TimePicker;
9 |
10 |
11 | /**
12 | * Contains set methods. Examples are setDatePicker(),
13 | * setTimePicker().
14 | *
15 | * @author Renas Reda, renas.reda@robotium.com
16 | *
17 | */
18 |
19 | class Setter{
20 |
21 | private final int CLOSED = 0;
22 | private final int OPENED = 1;
23 | private final ActivityUtils activityUtils;
24 | private final Getter getter;
25 | private final Clicker clicker;
26 | private final Waiter waiter;
27 |
28 | /**
29 | * Constructs this object.
30 | *
31 | * @param activityUtils the {@code ActivityUtils} instance
32 | * @param getter the {@code Getter} instance
33 | * @param clicker the {@code Clicker} instance
34 | * @param waiter the {@code Waiter} instance
35 | */
36 |
37 | public Setter(ActivityUtils activityUtils, Getter getter, Clicker clicker, Waiter waiter) {
38 | this.activityUtils = activityUtils;
39 | this.getter = getter;
40 | this.clicker = clicker;
41 | this.waiter = waiter;
42 | }
43 |
44 |
45 | /**
46 | * Sets the date in a given {@link DatePicker}.
47 | *
48 | * @param datePicker the {@code DatePicker} object.
49 | * @param year the year e.g. 2011
50 | * @param monthOfYear the month which is starting from zero e.g. 03
51 | * @param dayOfMonth the day e.g. 10
52 | */
53 |
54 | public void setDatePicker(final DatePicker datePicker, final int year, final int monthOfYear, final int dayOfMonth) {
55 | if(datePicker != null){
56 | Activity activity = activityUtils.getCurrentActivity(false);
57 | if(activity != null){
58 | activity.runOnUiThread(new Runnable()
59 | {
60 | public void run()
61 | {
62 | try{
63 | datePicker.updateDate(year, monthOfYear, dayOfMonth);
64 | }catch (Exception ignored){}
65 | }
66 | });
67 | }
68 | }
69 | }
70 |
71 |
72 | /**
73 | * Sets the time in a given {@link TimePicker}.
74 | *
75 | * @param timePicker the {@code TimePicker} object.
76 | * @param hour the hour e.g. 15
77 | * @param minute the minute e.g. 30
78 | */
79 |
80 | public void setTimePicker(final TimePicker timePicker, final int hour, final int minute) {
81 | if(timePicker != null){
82 | Activity activity = activityUtils.getCurrentActivity(false);
83 | if(activity != null){
84 | activity.runOnUiThread(new Runnable()
85 | {
86 | public void run()
87 | {
88 | try{
89 | timePicker.setCurrentHour(hour);
90 | timePicker.setCurrentMinute(minute);
91 | }catch (Exception ignored){}
92 | }
93 | });
94 | }
95 | }
96 | }
97 |
98 |
99 | /**
100 | * Sets the progress of a given {@link ProgressBar}. Examples are SeekBar and RatingBar.
101 | * @param progressBar the {@code ProgressBar}
102 | * @param progress the progress that the {@code ProgressBar} should be set to
103 | */
104 |
105 | public void setProgressBar(final ProgressBar progressBar,final int progress) {
106 | if(progressBar != null){
107 | Activity activity = activityUtils.getCurrentActivity(false);
108 | if(activity != null){
109 | activity.runOnUiThread(new Runnable()
110 | {
111 | public void run()
112 | {
113 | try{
114 | progressBar.setProgress(progress);
115 | }catch (Exception ignored){}
116 | }
117 | });
118 | }
119 | }
120 | }
121 |
122 |
123 | /**
124 | * Sets the status of a given SlidingDrawer. Examples are Solo.CLOSED and Solo.OPENED.
125 | *
126 | * @param slidingDrawer the {@link SlidingDrawer}
127 | * @param status the status that the {@link SlidingDrawer} should be set to
128 | */
129 |
130 | public void setSlidingDrawer(final SlidingDrawer slidingDrawer, final int status){
131 | if(slidingDrawer != null){
132 | Activity activity = activityUtils.getCurrentActivity(false);
133 | if(activity != null){
134 | activity.runOnUiThread(new Runnable()
135 | {
136 | public void run()
137 | {
138 | try{
139 | switch (status) {
140 | case CLOSED:
141 | slidingDrawer.close();
142 | break;
143 | case OPENED:
144 | slidingDrawer.open();
145 | break;
146 | }
147 | }catch (Exception ignored){}
148 | }
149 | });
150 | }
151 | }
152 | }
153 |
154 | /**
155 | * Sets the status of the NavigationDrawer. Examples are Solo.CLOSED and Solo.OPENED.
156 | *
157 | * @param status the status that the {@link NavigationDrawer} should be set to
158 | */
159 |
160 | public void setNavigationDrawer(final int status){
161 | final View homeView = getter.getView("home", 0);
162 | final View leftDrawer = getter.getView("left_drawer", 0);
163 |
164 | try{
165 | switch (status) {
166 |
167 | case CLOSED:
168 | if(leftDrawer != null && homeView != null && leftDrawer.isShown()){
169 | clicker.clickOnScreen(homeView);
170 | }
171 | break;
172 |
173 | case OPENED:
174 | if(leftDrawer != null && homeView != null && !leftDrawer.isShown()){
175 | clicker.clickOnScreen(homeView);
176 |
177 | Condition condition = new Condition() {
178 |
179 | @Override
180 | public boolean isSatisfied() {
181 | return leftDrawer.isShown();
182 | }
183 | };
184 | waiter.waitForCondition(condition, Timeout.getSmallTimeout());
185 | }
186 | break;
187 | }
188 | }catch (Exception ignored){}
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Sleeper.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | class Sleeper {
4 |
5 | private int pauseDuration;
6 | private int miniPauseDuration;
7 |
8 | private Sleeper() {
9 |
10 | }
11 |
12 | /**
13 | * Constructs this object.
14 | *
15 | * @param pauseDuration pause duration used in {@code sleep}
16 | * @param miniPauseDuration pause duration used in {@code sleepMini}
17 | */
18 |
19 | public Sleeper(int pauseDuration, int miniPauseDuration) {
20 | this.pauseDuration = pauseDuration;
21 | this.miniPauseDuration = miniPauseDuration;
22 | }
23 |
24 | /**
25 | * Sleeps the current thread for the pause length.
26 | */
27 |
28 | public void sleep() {
29 | sleep(pauseDuration);
30 | }
31 |
32 |
33 | /**
34 | * Sleeps the current thread for the mini pause length.
35 | */
36 |
37 | public void sleepMini() {
38 | sleep(miniPauseDuration);
39 | }
40 |
41 |
42 | /**
43 | * Sleeps the current thread for time milliseconds.
44 | *
45 | * @param time the length of the sleep in milliseconds
46 | */
47 |
48 | public void sleep(int time) {
49 | try {
50 | Thread.sleep(time);
51 | } catch (InterruptedException ignored) {}
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Swiper.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import android.app.Instrumentation;
4 | import android.graphics.PointF;
5 | import android.os.SystemClock;
6 | import android.view.MotionEvent;
7 | import android.view.MotionEvent.PointerCoords;
8 | import android.view.MotionEvent.PointerProperties;
9 |
10 | class Swiper
11 | {
12 | private final Instrumentation _instrument;
13 | public static final int GESTURE_DURATION_MS = 1000;
14 | public static final int EVENT_TIME_INTERVAL_MS = 10;
15 |
16 | public Swiper(Instrumentation inst)
17 | {
18 | this._instrument = inst;
19 | }
20 |
21 | public void generateSwipeGesture(PointF startPoint1, PointF startPoint2,
22 | PointF endPoint1, PointF endPoint2)
23 | {
24 |
25 | long downTime = SystemClock.uptimeMillis();
26 | long eventTime = SystemClock.uptimeMillis();
27 |
28 | float startX1 = startPoint1.x;
29 | float startY1 = startPoint1.y;
30 | float startX2 = startPoint2.x;
31 | float startY2 = startPoint2.y;
32 |
33 | float endX1 = endPoint1.x;
34 | float endY1 = endPoint1.y;
35 | float endX2 = endPoint2.x;
36 | float endY2 = endPoint2.y;
37 |
38 | // pointer 1
39 | float x1 = startX1;
40 | float y1 = startY1;
41 |
42 | // pointer 2
43 | float x2 = startX2;
44 | float y2 = startY2;
45 |
46 | PointerCoords[] pointerCoords = new PointerCoords[2];
47 | PointerCoords pc1 = new PointerCoords();
48 | PointerCoords pc2 = new PointerCoords();
49 | pc1.x = x1;
50 | pc1.y = y1;
51 | pc1.pressure = 1;
52 | pc1.size = 1;
53 | pc2.x = x2;
54 | pc2.y = y2;
55 | pc2.pressure = 1;
56 | pc2.size = 1;
57 | pointerCoords[0] = pc1;
58 | pointerCoords[1] = pc2;
59 |
60 | PointerProperties[] pointerProperties = new PointerProperties[2];
61 | PointerProperties pp1 = new PointerProperties();
62 | PointerProperties pp2 = new PointerProperties();
63 | pp1.id = 0;
64 | pp1.toolType = MotionEvent.TOOL_TYPE_FINGER;
65 | pp2.id = 1;
66 | pp2.toolType = MotionEvent.TOOL_TYPE_FINGER;
67 | pointerProperties[0] = pp1;
68 | pointerProperties[1] = pp2;
69 |
70 | MotionEvent event;
71 | // send the initial touches
72 | event = MotionEvent.obtain(downTime, eventTime,
73 | MotionEvent.ACTION_DOWN, 1, pointerProperties, pointerCoords,
74 | 0, 0, // metaState, buttonState
75 | 1, // x precision
76 | 1, // y precision
77 | 0, 0, 0, 0); // deviceId, edgeFlags, source, flags
78 | _instrument.sendPointerSync(event);
79 |
80 | event = MotionEvent.obtain(downTime, eventTime,
81 | MotionEvent.ACTION_POINTER_DOWN
82 | + (pp2.id << MotionEvent.ACTION_POINTER_INDEX_SHIFT),
83 | 2, pointerProperties, pointerCoords, 0, 0, 1, 1, 0, 0, 0, 0);
84 | _instrument.sendPointerSync(event);
85 |
86 | int numMoves = GESTURE_DURATION_MS / EVENT_TIME_INTERVAL_MS;
87 |
88 | float stepX1 = (endX1 - startX1) / numMoves;
89 | float stepY1 = (endY1 - startY1) / numMoves;
90 | float stepX2 = (endX2 - startX2) / numMoves;
91 | float stepY2 = (endY2 - startY2) / numMoves;
92 |
93 | // send the zoom
94 | for (int i = 0; i < numMoves; i++)
95 | {
96 | eventTime += EVENT_TIME_INTERVAL_MS;
97 | pointerCoords[0].x += stepX1;
98 | pointerCoords[0].y += stepY1;
99 | pointerCoords[1].x += stepX2;
100 | pointerCoords[1].y += stepY2;
101 |
102 | event = MotionEvent.obtain(downTime, eventTime,
103 | MotionEvent.ACTION_MOVE, 2, pointerProperties,
104 | pointerCoords, 0, 0, 1, 1, 0, 0, 0, 0);
105 | _instrument.sendPointerSync(event);
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/SystemUtils.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 |
4 | import java.lang.reflect.Method;
5 | import android.app.Instrumentation;
6 | import android.content.Context;
7 | import android.net.ConnectivityManager;
8 | import android.net.wifi.WifiManager;
9 |
10 |
11 | /**
12 | * Contains System methods. Examples are: setDeviceLocale(String language, String country),
13 | * setMobileData(Boolean turnedOn).
14 | *
15 | * @author Renas Reda, renas.reda@robotium.com
16 | *
17 | */
18 |
19 | public class SystemUtils {
20 | private Instrumentation instrumentation;
21 |
22 | public SystemUtils(Instrumentation instrumentation){
23 | this.instrumentation = instrumentation;
24 | }
25 |
26 |
27 | /**
28 | * Sets if mobile data should be turned on or off. Requires android.permission.CHANGE_NETWORK_STATE in the AndroidManifest.xml of the application under test.
29 | *
30 | * @param turnedOn true if mobile data is to be turned on and false if not
31 | */
32 |
33 | public void setMobileData(Boolean turnedOn){
34 | ConnectivityManager dataManager=(ConnectivityManager)instrumentation.getTargetContext().getSystemService(Context.CONNECTIVITY_SERVICE);
35 |
36 | Method dataClass = null;
37 | try {
38 | dataClass = ConnectivityManager.class.getDeclaredMethod("setMobileDataEnabled", boolean.class);
39 | dataClass.setAccessible(true);
40 | dataClass.invoke(dataManager, turnedOn);
41 | } catch (Exception e) {
42 | e.printStackTrace();
43 | }
44 | }
45 |
46 | /**
47 | * Sets if wifi data should be turned on or off. Requires android.permission.CHANGE_WIFI_STATE in the AndroidManifest.xml of the application under test.
48 | *
49 | *
50 | * @param turnedOn true if mobile wifi is to be turned on and false if not
51 | */
52 |
53 | public void setWiFiData(Boolean turnedOn){
54 | WifiManager wifiManager = (WifiManager)instrumentation.getTargetContext().getSystemService(Context.WIFI_SERVICE);
55 | try{
56 | wifiManager.setWifiEnabled(turnedOn);
57 | }catch(Exception e){
58 | e.printStackTrace();
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Tapper.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import android.app.Instrumentation;
4 | import android.graphics.PointF;
5 | import android.os.SystemClock;
6 | import android.view.InputDevice;
7 | import android.view.MotionEvent;
8 | import android.view.MotionEvent.PointerCoords;
9 | import android.view.MotionEvent.PointerProperties;
10 |
11 | class Tapper
12 | {
13 | private final Instrumentation _instrument;
14 | public static final int GESTURE_DURATION_MS = 1000;
15 | public static final int EVENT_TIME_INTERVAL_MS = 10;
16 |
17 | public Tapper(Instrumentation inst)
18 | {
19 | this._instrument = inst;
20 | }
21 |
22 | public void generateTapGesture(int numTaps, PointF... points)
23 | {
24 | MotionEvent event;
25 |
26 | long downTime = SystemClock.uptimeMillis();
27 | long eventTime = SystemClock.uptimeMillis();
28 |
29 | // pointer 1
30 | float x1 = points[0].x;
31 | float y1 = points[0].y;
32 |
33 | float x2 = 0;
34 | float y2 = 0;
35 | if (points.length == 2)
36 | {
37 | // pointer 2
38 | x2 = points[1].x;
39 | y2 = points[1].y;
40 | }
41 |
42 | PointerCoords[] pointerCoords = new PointerCoords[points.length];
43 | PointerCoords pc1 = new PointerCoords();
44 | pc1.x = x1;
45 | pc1.y = y1;
46 | pc1.pressure = 1;
47 | pc1.size = 1;
48 | pointerCoords[0] = pc1;
49 | PointerCoords pc2 = new PointerCoords();
50 | if (points.length == 2)
51 | {
52 | pc2.x = x2;
53 | pc2.y = y2;
54 | pc2.pressure = 1;
55 | pc2.size = 1;
56 | pointerCoords[1] = pc2;
57 | }
58 |
59 | PointerProperties[] pointerProperties = new PointerProperties[points.length];
60 | PointerProperties pp1 = new PointerProperties();
61 | pp1.id = 0;
62 | pp1.toolType = MotionEvent.TOOL_TYPE_FINGER;
63 | pointerProperties[0] = pp1;
64 | PointerProperties pp2 = new PointerProperties();
65 | if (points.length == 2)
66 | {
67 | pp2.id = 1;
68 | pp2.toolType = MotionEvent.TOOL_TYPE_FINGER;
69 | pointerProperties[1] = pp2;
70 | }
71 |
72 | int i = 0;
73 | while (i != numTaps)
74 | {
75 | event = MotionEvent.obtain(downTime, eventTime,
76 | MotionEvent.ACTION_DOWN, points.length, pointerProperties,
77 | pointerCoords, 0, 0, 1, 1, 0, 0,
78 | InputDevice.SOURCE_TOUCHSCREEN, 0);
79 | _instrument.sendPointerSync(event);
80 |
81 | if (points.length == 2)
82 | {
83 | event = MotionEvent
84 | .obtain(downTime,
85 | eventTime,
86 | MotionEvent.ACTION_POINTER_DOWN
87 | + (pp2.id << MotionEvent.ACTION_POINTER_INDEX_SHIFT),
88 | points.length, pointerProperties,
89 | pointerCoords, 0, 0, 1, 1, 0, 0,
90 | InputDevice.SOURCE_TOUCHSCREEN, 0);
91 | _instrument.sendPointerSync(event);
92 |
93 | eventTime += EVENT_TIME_INTERVAL_MS;
94 | event = MotionEvent
95 | .obtain(downTime,
96 | eventTime,
97 | MotionEvent.ACTION_POINTER_UP
98 | + (pp2.id << MotionEvent.ACTION_POINTER_INDEX_SHIFT),
99 | points.length, pointerProperties,
100 | pointerCoords, 0, 0, 1, 1, 0, 0,
101 | InputDevice.SOURCE_TOUCHSCREEN, 0);
102 | _instrument.sendPointerSync(event);
103 | }
104 |
105 | eventTime += EVENT_TIME_INTERVAL_MS;
106 | event = MotionEvent.obtain(downTime, eventTime,
107 | MotionEvent.ACTION_UP, points.length, pointerProperties,
108 | pointerCoords, 0, 0, 1, 1, 0, 0,
109 | InputDevice.SOURCE_TOUCHSCREEN, 0);
110 | _instrument.sendPointerSync(event);
111 |
112 | i++;
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/TextEnterer.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import junit.framework.Assert;
4 | import android.app.Instrumentation;
5 | import android.text.InputType;
6 | import android.widget.EditText;
7 |
8 |
9 | /**
10 | * Contains setEditText() to enter text into text fields.
11 | *
12 | * @author Renas Reda, renas.reda@robotium.com
13 | *
14 | */
15 |
16 | class TextEnterer{
17 |
18 | private final Instrumentation inst;
19 | private final Clicker clicker;
20 | private final DialogUtils dialogUtils;
21 |
22 | /**
23 | * Constructs this object.
24 | *
25 | * @param inst the {@code Instrumentation} instance
26 | * @param clicker the {@code Clicker} instance
27 | * @param dialogUtils the {@code DialogUtils} instance
28 | *
29 | */
30 |
31 | public TextEnterer(Instrumentation inst, Clicker clicker, DialogUtils dialogUtils) {
32 | this.inst = inst;
33 | this.clicker = clicker;
34 | this.dialogUtils = dialogUtils;
35 | }
36 |
37 | /**
38 | * Sets an {@code EditText} text
39 | *
40 | * @param index the index of the {@code EditText}
41 | * @param text the text that should be set
42 | */
43 |
44 | public void setEditText(final EditText editText, final String text) {
45 | if(editText != null){
46 | final String previousText = editText.getText().toString();
47 |
48 | inst.runOnMainSync(new Runnable()
49 | {
50 | public void run()
51 | {
52 | editText.setInputType(InputType.TYPE_NULL);
53 | editText.performClick();
54 | dialogUtils.hideSoftKeyboard(editText, false, false);
55 | if(text.equals(""))
56 | editText.setText(text);
57 | else{
58 | editText.setText(previousText + text);
59 | editText.setCursorVisible(false);
60 | }
61 | }
62 | });
63 | }
64 | }
65 |
66 | /**
67 | * Types text in an {@code EditText}
68 | *
69 | * @param index the index of the {@code EditText}
70 | * @param text the text that should be typed
71 | */
72 |
73 | public void typeText(final EditText editText, final String text){
74 | if(editText != null){
75 | inst.runOnMainSync(new Runnable()
76 | {
77 | public void run()
78 | {
79 | editText.setInputType(InputType.TYPE_NULL);
80 | }
81 | });
82 | clicker.clickOnScreen(editText, false, 0);
83 | dialogUtils.hideSoftKeyboard(editText, true, true);
84 |
85 | boolean successfull = false;
86 | int retry = 0;
87 |
88 | while(!successfull && retry < 10) {
89 |
90 | try{
91 | inst.sendStringSync(text);
92 | successfull = true;
93 | }catch(SecurityException e){
94 | dialogUtils.hideSoftKeyboard(editText, true, true);
95 | retry++;
96 | }
97 | }
98 | if(!successfull) {
99 | Assert.fail("Text can not be typed!");
100 | }
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Timeout.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import com.robotium.solo.Solo.Config;
4 |
5 |
6 |
7 |
8 | /**
9 | * Used to get and set the default timeout lengths of the various Solo methods.
10 | *
11 | * @author Renas Reda, renas.reda@robotium.com
12 | *
13 | */
14 |
15 | public class Timeout{
16 |
17 | private static int largeTimeout;
18 | private static int smallTimeout;
19 |
20 |
21 | /**
22 | * Sets the default timeout length of the waitFor methods. Will fall back to the default values set by {@link Config}.
23 | *
24 | * Timeout can also be set through adb shell (requires root access):
25 | *
26 | * 'adb shell setprop solo_large_timeout milliseconds'
27 | *
28 | * @param milliseconds the default timeout length of the waitFor methods
29 | *
30 | */
31 | public static void setLargeTimeout(int milliseconds){
32 | largeTimeout = milliseconds;
33 | }
34 |
35 | /**
36 | * Sets the default timeout length of the get, is, set, assert, enter, type and click methods. Will fall back to the default values set by {@link Config}.
37 | *
38 | * Timeout can also be set through adb shell (requires root access):
39 | *
40 | * 'adb shell setprop solo_small_timeout milliseconds'
41 | *
42 | * @param milliseconds the default timeout length of the get, is, set, assert, enter and click methods
43 | *
44 | */
45 | public static void setSmallTimeout(int milliseconds){
46 | smallTimeout = milliseconds;
47 | }
48 |
49 | /**
50 | * Gets the default timeout length of the waitFor methods.
51 | *
52 | * @return the timeout length in milliseconds
53 | *
54 | */
55 | public static int getLargeTimeout(){
56 | return largeTimeout;
57 | }
58 |
59 | /**
60 | * Gets the default timeout length of the get, is, set, assert, enter, type and click methods.
61 | *
62 | * @return the timeout length in milliseconds
63 | *
64 | */
65 | public static int getSmallTimeout(){
66 | return smallTimeout;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/ViewLocationComparator.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import android.view.View;
4 | import java.util.Comparator;
5 |
6 | /**
7 | * Orders {@link View}s by their location on-screen.
8 | *
9 | */
10 |
11 | class ViewLocationComparator implements Comparator {
12 |
13 | private final int a[] = new int[2];
14 | private final int b[] = new int[2];
15 | private final int axis1, axis2;
16 |
17 | public ViewLocationComparator() {
18 | this(true);
19 | }
20 |
21 | /**
22 | * @param yAxisFirst Whether the y-axis should be compared before the x-axis.
23 | */
24 |
25 | public ViewLocationComparator(boolean yAxisFirst) {
26 | this.axis1 = yAxisFirst ? 1 : 0;
27 | this.axis2 = yAxisFirst ? 0 : 1;
28 | }
29 |
30 | public int compare(View lhs, View rhs) {
31 | lhs.getLocationOnScreen(a);
32 | rhs.getLocationOnScreen(b);
33 |
34 | if (a[axis1] != b[axis1]) {
35 | return a[axis1] < b[axis1] ? -1 : 1;
36 | }
37 | if (a[axis2] < b[axis2]) {
38 | return -1;
39 | }
40 | return a[axis2] == b[axis2] ? 0 : 1;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/WebElement.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import java.util.Hashtable;
4 |
5 | /**
6 | * Represents an element shown in a WebView.
7 | *
8 | * @author Renas Reda, renas.reda@robotium.com
9 | *
10 | */
11 |
12 | public class WebElement {
13 |
14 | private int locationX = 0;
15 | private int locationY = 0;
16 | private String id;
17 | private String text;
18 | private String name;
19 | private String className;
20 | private String tagName;
21 | private Hashtable attributes;
22 |
23 |
24 | /**
25 | * Constructs this object.
26 | *
27 | * @param webId the given web id
28 | * @param textContent the given text to be set
29 | * @param name the given name to be set
30 | * @param className the given class name to set
31 | * @param tagName the given tag name to be set
32 | * @param attributes the attributes to set
33 | */
34 |
35 | public WebElement(String webId, String textContent, String name, String className, String tagName, Hashtable attributes) {
36 |
37 | this.setId(webId);
38 | this.setTextContent(textContent);
39 | this.setName(name);
40 | this.setClassName(className);
41 | this.setTagName(tagName);
42 | this.setAttributes(attributes);
43 | }
44 |
45 | /**
46 | * Returns the WebElements location on screen.
47 | */
48 |
49 | public void getLocationOnScreen(int[] location) {
50 |
51 | location[0] = locationX;
52 | location[1] = locationY;
53 | }
54 |
55 | /**
56 | * Sets the X location.
57 | *
58 | * @param locationX the X location of the {@code WebElement}
59 | */
60 |
61 | public void setLocationX(int locationX){
62 | this.locationX = locationX;
63 | }
64 |
65 | /**
66 | * Sets the Y location.
67 | *
68 | * @param locationY the Y location of the {@code WebElement}
69 | */
70 |
71 | public void setLocationY(int locationY){
72 | this.locationY = locationY;
73 | }
74 |
75 | /**
76 | * Returns the X location.
77 | *
78 | * @return the X location
79 | */
80 |
81 | public int getLocationX(){
82 | return this.locationX;
83 | }
84 |
85 | /**
86 | * Returns the Y location.
87 | *
88 | * @return the Y location
89 | */
90 |
91 | public int getLocationY(){
92 | return this.locationY;
93 | }
94 |
95 | /**
96 | * Returns the id.
97 | *
98 | * @return the id
99 | */
100 |
101 | public String getId() {
102 | return id;
103 | }
104 |
105 | /**
106 | * Sets the id.
107 | *
108 | * @param id the id to set
109 | */
110 |
111 | public void setId(String id) {
112 | this.id = id;
113 | }
114 |
115 | /**
116 | * Returns the name.
117 | *
118 | * @return the name
119 | */
120 |
121 | public String getName() {
122 | return name;
123 | }
124 |
125 | /**
126 | * Sets the name.
127 | *
128 | * @param name the name to set
129 | */
130 |
131 | public void setName(String name) {
132 | this.name = name;
133 | }
134 |
135 | /**
136 | * Returns the class name.
137 | *
138 | * @return the class name
139 | */
140 |
141 | public String getClassName() {
142 | return className;
143 | }
144 |
145 | /**
146 | * Sets the class name.
147 | *
148 | * @param className the class name to set
149 | */
150 |
151 | public void setClassName(String className) {
152 | this.className = className;
153 | }
154 |
155 | /**
156 | * Returns the tag name.
157 | *
158 | * @return the tag name
159 | */
160 |
161 | public String getTagName() {
162 | return tagName;
163 | }
164 |
165 | /**
166 | * Sets the tag name.
167 | *
168 | * @param tagName the tag name to set
169 | */
170 |
171 | public void setTagName(String tagName) {
172 | this.tagName = tagName;
173 | }
174 |
175 | /**
176 | * Returns the text content.
177 | *
178 | * @return the text content
179 | */
180 |
181 | public String getText() {
182 | return text;
183 | }
184 |
185 | /**
186 | * Sets the text content.
187 | *
188 | * @param textContent the text content to set
189 | */
190 |
191 | public void setTextContent(String textContent) {
192 | this.text = textContent;
193 | }
194 |
195 | /**
196 | * Returns the value for the specified attribute.
197 | *
198 | * @return the value for the specified attribute
199 | */
200 |
201 | public String getAttribute(String attributeName) {
202 | if (attributeName != null){
203 | return this.attributes.get(attributeName);
204 | }
205 |
206 | return null;
207 | }
208 |
209 | /**
210 | * Sets the attributes.
211 | *
212 | * @param attributes the attributes to set
213 | */
214 |
215 | public void setAttributes(Hashtable attributes) {
216 | this.attributes = attributes;
217 | }
218 |
219 | }
220 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/WebElementCreator.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Hashtable;
5 | import java.util.List;
6 | import java.util.concurrent.CopyOnWriteArrayList;
7 | import android.os.SystemClock;
8 | import android.webkit.WebView;
9 |
10 | /**
11 | * Contains TextView related methods. Examples are:
12 | * getTextViewsFromWebViews(), createTextViewAndAddInList().
13 | *
14 | * @author Renas Reda, renas.reda@robotium.com
15 | *
16 | */
17 |
18 | class WebElementCreator {
19 |
20 | private List webElements;
21 | private Sleeper sleeper;
22 | private boolean isFinished = false;
23 |
24 | /**
25 | * Constructs this object.
26 | *
27 | * @param sleeper the {@code Sleeper} instance
28 | *
29 | */
30 |
31 | public WebElementCreator(Sleeper sleeper){
32 | this.sleeper = sleeper;
33 | webElements = new CopyOnWriteArrayList();
34 | }
35 |
36 | /**
37 | * Prepares for start of creating {@code TextView} objects based on web elements
38 | */
39 |
40 | public void prepareForStart(){
41 | setFinished(false);
42 | webElements.clear();
43 | }
44 |
45 | /**
46 | * Returns an {@code ArrayList} of {@code TextView} objects based on the web elements shown
47 | *
48 | * @return an {@code ArrayList} of {@code TextView} objects based on the web elements shown
49 | */
50 |
51 | public ArrayList getWebElementsFromWebViews(){
52 | waitForWebElementsToBeCreated();
53 | return new ArrayList(webElements);
54 | }
55 |
56 | /**
57 | * Returns true if all {@code TextView} objects based on web elements have been created
58 | *
59 | * @return true if all {@code TextView} objects based on web elements have been created
60 | */
61 |
62 | public boolean isFinished(){
63 | return isFinished;
64 | }
65 |
66 |
67 | /**
68 | * Set to true if all {@code TextView} objects have been created
69 | *
70 | * @param isFinished true if all {@code TextView} objects have been created
71 | */
72 |
73 | public void setFinished(boolean isFinished){
74 | this.isFinished = isFinished;
75 | }
76 |
77 | /**
78 | * Creates a {@ WebElement} object from the given text and {@code WebView}
79 | *
80 | * @param webData the data of the web element
81 | * @param webView the {@code WebView} the text is shown in
82 | */
83 |
84 | public void createWebElementAndAddInList(String webData, WebView webView){
85 |
86 | WebElement webElement = createWebElementAndSetLocation(webData, webView);
87 |
88 | if((webElement!=null))
89 | webElements.add(webElement);
90 | }
91 |
92 | /**
93 | * Sets the location of a {@code WebElement}
94 | *
95 | * @param webElement the {@code TextView} object to set location
96 | * @param webView the {@code WebView} the text is shown in
97 | * @param x the x location to set
98 | * @param y the y location to set
99 | * @param width the width to set
100 | * @param height the height to set
101 | */
102 |
103 | private void setLocation(WebElement webElement, WebView webView, int x, int y, int width, int height ){
104 | float scale = webView.getScale();
105 | int[] locationOfWebViewXY = new int[2];
106 | webView.getLocationOnScreen(locationOfWebViewXY);
107 |
108 | int locationX = (int) (locationOfWebViewXY[0] + (x + (Math.floor(width / 2))) * scale);
109 | int locationY = (int) (locationOfWebViewXY[1] + (y + (Math.floor(height / 2))) * scale);
110 |
111 | webElement.setLocationX(locationX);
112 | webElement.setLocationY(locationY);
113 | }
114 |
115 | /**
116 | * Creates a {@code WebView} object
117 | *
118 | * @param information the data of the web element
119 | * @param webView the web view the text is shown in
120 | *
121 | * @return a {@code WebElement} object with a given text and location
122 | */
123 |
124 | private WebElement createWebElementAndSetLocation(String information, WebView webView){
125 | String[] data = information.split(";,");
126 | String[] elements = null;
127 | int x = 0;
128 | int y = 0;
129 | int width = 0;
130 | int height = 0;
131 | Hashtable attributes = new Hashtable();
132 | try{
133 | x = Math.round(Float.valueOf(data[5]));
134 | y = Math.round(Float.valueOf(data[6]));
135 | width = Math.round(Float.valueOf(data[7]));
136 | height = Math.round(Float.valueOf(data[8]));
137 | elements = data[9].split("\\#\\$");
138 | }catch(Exception ignored){}
139 |
140 | if(elements != null) {
141 | for (int index = 0; index < elements.length; index++){
142 | String[] element = elements[index].split("::");
143 | if (element.length > 1) {
144 | attributes.put(element[0], element[1]);
145 | } else {
146 | attributes.put(element[0], element[0]);
147 | }
148 | }
149 | }
150 |
151 | WebElement webElement = null;
152 |
153 | try{
154 | webElement = new WebElement(data[0], data[1], data[2], data[3], data[4], attributes);
155 | setLocation(webElement, webView, x, y, width, height);
156 | }catch(Exception ignored) {}
157 |
158 | return webElement;
159 | }
160 |
161 | /**
162 | * Waits for {@code WebElement} objects to be created
163 | *
164 | * @return true if successfully created before timout
165 | */
166 |
167 | private boolean waitForWebElementsToBeCreated(){
168 | final long endTime = SystemClock.uptimeMillis() + 5000;
169 |
170 | while(SystemClock.uptimeMillis() < endTime){
171 |
172 | if(isFinished){
173 | return true;
174 | }
175 |
176 | sleeper.sleepMini();
177 | }
178 | return false;
179 | }
180 |
181 | }
182 |
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/WebUtils.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.io.InputStreamReader;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import java.util.regex.Pattern;
10 | import com.robotium.solo.Solo.Config;
11 | import android.app.Instrumentation;
12 | import android.webkit.WebChromeClient;
13 | import android.webkit.WebView;
14 | import android.widget.TextView;
15 |
16 |
17 | /**
18 | * Contains web related methods. Examples are:
19 | * enterTextIntoWebElement(), getWebTexts(), getWebElements().
20 | *
21 | * @author Renas Reda, renas.reda@robotium.com
22 | *
23 | */
24 |
25 | class WebUtils {
26 |
27 | private ViewFetcher viewFetcher;
28 | private Instrumentation inst;
29 | RobotiumWebClient robotiumWebCLient;
30 | WebElementCreator webElementCreator;
31 | WebChromeClient originalWebChromeClient = null;
32 | private Config config;
33 |
34 |
35 | /**
36 | * Constructs this object.
37 | *
38 | * @param config the {@code Config} instance
39 | * @param instrumentation the {@code Instrumentation} instance
40 | * @param viewFetcher the {@code ViewFetcher}
41 | * @param sleeper the {@code Sleeper} instance
42 | */
43 |
44 | public WebUtils(Config config, Instrumentation instrumentation, ViewFetcher viewFetcher, Sleeper sleeper){
45 | this.config = config;
46 | this.inst = instrumentation;
47 | this.viewFetcher = viewFetcher;
48 | webElementCreator = new WebElementCreator(sleeper);
49 | robotiumWebCLient = new RobotiumWebClient(instrumentation, webElementCreator);
50 | }
51 |
52 | /**
53 | * Returns {@code TextView} objects based on web elements shown in the present WebViews
54 | *
55 | * @param onlyFromVisibleWebViews true if only from visible WebViews
56 | * @return an {@code ArrayList} of {@code TextViews}s created from the present {@code WebView}s
57 | */
58 |
59 | public ArrayList getTextViewsFromWebView(){
60 | boolean javaScriptWasExecuted = executeJavaScriptFunction("allTexts();");
61 |
62 | return createAndReturnTextViewsFromWebElements(javaScriptWasExecuted);
63 | }
64 |
65 | /**
66 | * Creates and returns TextView objects based on WebElements
67 | *
68 | * @return an ArrayList with TextViews
69 | */
70 |
71 | private ArrayList createAndReturnTextViewsFromWebElements(boolean javaScriptWasExecuted){
72 | ArrayList webElementsAsTextViews = new ArrayList();
73 |
74 | if(javaScriptWasExecuted){
75 | for(WebElement webElement : webElementCreator.getWebElementsFromWebViews()){
76 | if(isWebElementSufficientlyShown(webElement)){
77 | RobotiumTextView textView = new RobotiumTextView(inst.getContext(), webElement.getText(), webElement.getLocationX(), webElement.getLocationY());
78 | webElementsAsTextViews.add(textView);
79 | }
80 | }
81 | }
82 | return webElementsAsTextViews;
83 | }
84 |
85 | /**
86 | * Returns an ArrayList of WebElements currently shown in the active WebView.
87 | *
88 | * @param onlySufficientlyVisible true if only sufficiently visible {@link WebElement} objects should be returned
89 | * @return an {@code ArrayList} of the {@link WebElement} objects shown in the active WebView
90 | */
91 |
92 | public ArrayList getWebElements(boolean onlySufficientlyVisible){
93 | boolean javaScriptWasExecuted = executeJavaScriptFunction("allWebElements();");
94 |
95 | return getWebElements(javaScriptWasExecuted, onlySufficientlyVisible);
96 | }
97 |
98 | /**
99 | * Returns an ArrayList of WebElements of the specified By object currently shown in the active WebView.
100 | *
101 | * @param by the By object. Examples are By.id("id") and By.name("name")
102 | * @param onlySufficientlyVisible true if only sufficiently visible {@link WebElement} objects should be returned
103 | * @return an {@code ArrayList} of the {@link WebElement} objects currently shown in the active WebView
104 | */
105 |
106 | public ArrayList getWebElements(final By by, boolean onlySufficientlyVisbile){
107 | boolean javaScriptWasExecuted = executeJavaScript(by, false);
108 |
109 | if(config.useJavaScriptToClickWebElements){
110 | if(!javaScriptWasExecuted){
111 | return new ArrayList();
112 | }
113 | return webElementCreator.getWebElementsFromWebViews();
114 | }
115 |
116 | return getWebElements(javaScriptWasExecuted, onlySufficientlyVisbile);
117 | }
118 |
119 | /**
120 | * Returns the sufficiently shown WebElements
121 | *
122 | * @param javaScriptWasExecuted true if JavaScript was executed
123 | * @param onlySufficientlyVisible true if only sufficiently visible {@link WebElement} objects should be returned
124 | * @return the sufficiently shown WebElements
125 | */
126 |
127 | private ArrayList getWebElements(boolean javaScriptWasExecuted, boolean onlySufficientlyVisbile){
128 | ArrayList webElements = new ArrayList();
129 |
130 | if(javaScriptWasExecuted){
131 | for(WebElement webElement : webElementCreator.getWebElementsFromWebViews()){
132 | if(!onlySufficientlyVisbile){
133 | webElements.add(webElement);
134 | }
135 | else if(isWebElementSufficientlyShown(webElement)){
136 | webElements.add(webElement);
137 | }
138 | }
139 | }
140 | return webElements;
141 | }
142 |
143 | /**
144 | * Prepares for start of JavaScript execution
145 | *
146 | * @return the JavaScript as a String
147 | */
148 |
149 | private String prepareForStartOfJavascriptExecution(List webViews) {
150 | webElementCreator.prepareForStart();
151 |
152 | WebChromeClient currentWebChromeClient = getCurrentWebChromeClient();
153 |
154 | if(currentWebChromeClient != null && !currentWebChromeClient.getClass().isAssignableFrom(RobotiumWebClient.class)){
155 | originalWebChromeClient = currentWebChromeClient;
156 | }
157 | robotiumWebCLient.enableJavascriptAndSetRobotiumWebClient(webViews, originalWebChromeClient);
158 | return getJavaScriptAsString();
159 | }
160 |
161 | /**
162 | * Returns the current WebChromeClient through reflection
163 | *
164 | * @return the current WebChromeClient
165 | *
166 | */
167 |
168 | private WebChromeClient getCurrentWebChromeClient(){
169 | WebChromeClient currentWebChromeClient = null;
170 |
171 | Object currentWebView = viewFetcher.getFreshestView(viewFetcher.getCurrentViews(WebView.class, true));
172 |
173 | if (android.os.Build.VERSION.SDK_INT >= 16) {
174 | try{
175 | currentWebView = new Reflect(currentWebView).field("mProvider").out(Object.class);
176 | }catch(IllegalArgumentException ignored) {}
177 | }
178 |
179 | try{
180 | if (android.os.Build.VERSION.SDK_INT >= 19) {
181 | Object mClientAdapter = new Reflect(currentWebView).field("mContentsClientAdapter").out(Object.class);
182 | currentWebChromeClient = new Reflect(mClientAdapter).field("mWebChromeClient").out(WebChromeClient.class);
183 | }
184 | else {
185 | Object mCallbackProxy = new Reflect(currentWebView).field("mCallbackProxy").out(Object.class);
186 | currentWebChromeClient = new Reflect(mCallbackProxy).field("mWebChromeClient").out(WebChromeClient.class);
187 | }
188 | }catch(Exception ignored){}
189 |
190 | return currentWebChromeClient;
191 | }
192 |
193 | /**
194 | * Enters text into a web element using the given By method
195 | *
196 | * @param by the By object e.g. By.id("id");
197 | * @param text the text to enter
198 | */
199 |
200 | public void enterTextIntoWebElement(final By by, final String text){
201 | if(by instanceof By.Id){
202 | executeJavaScriptFunction("enterTextById(\""+by.getValue()+"\", \""+text+"\");");
203 | }
204 | else if(by instanceof By.Xpath){
205 | executeJavaScriptFunction("enterTextByXpath(\""+by.getValue()+"\", \""+text+"\");");
206 | }
207 | else if(by instanceof By.CssSelector){
208 | executeJavaScriptFunction("enterTextByCssSelector(\""+by.getValue()+"\", \""+text+"\");");
209 | }
210 | else if(by instanceof By.Name){
211 | executeJavaScriptFunction("enterTextByName(\""+by.getValue()+"\", \""+text+"\");");
212 | }
213 | else if(by instanceof By.ClassName){
214 | executeJavaScriptFunction("enterTextByClassName(\""+by.getValue()+"\", \""+text+"\");");
215 | }
216 | else if(by instanceof By.Text){
217 | executeJavaScriptFunction("enterTextByTextContent(\""+by.getValue()+"\", \""+text+"\");");
218 | }
219 | else if(by instanceof By.TagName){
220 | executeJavaScriptFunction("enterTextByTagName(\""+by.getValue()+"\", \""+text+"\");");
221 | }
222 | }
223 |
224 | /**
225 | * Executes JavaScript determined by the given By object
226 | *
227 | * @param by the By object e.g. By.id("id");
228 | * @param shouldClick true if click should be performed
229 | * @return true if JavaScript function was executed
230 | */
231 |
232 | public boolean executeJavaScript(final By by, boolean shouldClick){
233 | if(by instanceof By.Id){
234 | return executeJavaScriptFunction("id(\""+by.getValue()+"\", \"" + String.valueOf(shouldClick) + "\");");
235 | }
236 | else if(by instanceof By.Xpath){
237 | return executeJavaScriptFunction("xpath(\""+by.getValue()+"\", \"" + String.valueOf(shouldClick) + "\");");
238 | }
239 | else if(by instanceof By.CssSelector){
240 | return executeJavaScriptFunction("cssSelector(\""+by.getValue()+"\", \"" + String.valueOf(shouldClick) + "\");");
241 | }
242 | else if(by instanceof By.Name){
243 | return executeJavaScriptFunction("name(\""+by.getValue()+"\", \"" + String.valueOf(shouldClick) + "\");");
244 | }
245 | else if(by instanceof By.ClassName){
246 | return executeJavaScriptFunction("className(\""+by.getValue()+"\", \"" + String.valueOf(shouldClick) + "\");");
247 | }
248 | else if(by instanceof By.Text){
249 | return executeJavaScriptFunction("textContent(\""+by.getValue()+"\", \"" + String.valueOf(shouldClick) + "\");");
250 | }
251 | else if(by instanceof By.TagName){
252 | return executeJavaScriptFunction("tagName(\""+by.getValue()+"\", \"" + String.valueOf(shouldClick) + "\");");
253 | }
254 | return false;
255 | }
256 |
257 | /**
258 | * Executes the given JavaScript function
259 | *
260 | * @param function the function as a String
261 | * @return true if JavaScript function was executed
262 | */
263 |
264 | private boolean executeJavaScriptFunction(final String function) {
265 | List webViews = viewFetcher.getCurrentViews(WebView.class, true);
266 | final WebView webView = viewFetcher.getFreshestView((ArrayList) webViews);
267 |
268 | if(webView == null) {
269 | return false;
270 | }
271 |
272 | final String javaScript = setWebFrame(prepareForStartOfJavascriptExecution(webViews));
273 |
274 | inst.runOnMainSync(new Runnable() {
275 | public void run() {
276 | if(webView != null){
277 | webView.loadUrl("javascript:" + javaScript + function);
278 | }
279 | }
280 | });
281 | return true;
282 | }
283 |
284 | private String setWebFrame(String javascript){
285 | String frame = config.webFrame;
286 |
287 | if(frame.isEmpty() || frame.equals("document")){
288 | return javascript;
289 | }
290 | javascript = javascript.replaceAll(Pattern.quote("document, "), "document.getElementById(\""+frame+"\").contentDocument, ");
291 | javascript = javascript.replaceAll(Pattern.quote("document.body, "), "document.getElementById(\""+frame+"\").contentDocument, ");
292 | return javascript;
293 | }
294 |
295 | /**
296 | * Returns true if the view is sufficiently shown
297 | *
298 | * @param view the view to check
299 | * @return true if the view is sufficiently shown
300 | */
301 |
302 | public final boolean isWebElementSufficientlyShown(WebElement webElement){
303 | final WebView webView = viewFetcher.getFreshestView(viewFetcher.getCurrentViews(WebView.class, true));
304 | final int[] xyWebView = new int[2];
305 |
306 | if(webView != null && webElement != null){
307 | webView.getLocationOnScreen(xyWebView);
308 |
309 | if(xyWebView[1] + webView.getHeight() > webElement.getLocationY())
310 | return true;
311 | }
312 | return false;
313 | }
314 |
315 | /**
316 | * Splits a name by upper case.
317 | *
318 | * @param name the name to split
319 | * @return a String with the split name
320 | *
321 | */
322 |
323 | public String splitNameByUpperCase(String name) {
324 | String [] texts = name.split("(?=\\p{Upper})");
325 | StringBuilder stringToReturn = new StringBuilder();
326 |
327 | for(String string : texts){
328 |
329 | if(stringToReturn.length() > 0) {
330 | stringToReturn.append(" " + string.toLowerCase());
331 | }
332 | else {
333 | stringToReturn.append(string.toLowerCase());
334 | }
335 | }
336 | return stringToReturn.toString();
337 | }
338 |
339 | /**
340 | * Returns the JavaScript file RobotiumWeb.js as a String
341 | *
342 | * @return the JavaScript file RobotiumWeb.js as a {@code String}
343 | */
344 |
345 | private String getJavaScriptAsString() {
346 | InputStream fis = getClass().getResourceAsStream("RobotiumWeb.js");
347 | StringBuffer javaScript = new StringBuffer();
348 |
349 | try {
350 | BufferedReader input = new BufferedReader(new InputStreamReader(fis));
351 | String line = null;
352 | while (( line = input.readLine()) != null){
353 | javaScript.append(line);
354 | javaScript.append("\n");
355 | }
356 | input.close();
357 | } catch (IOException e) {
358 | throw new RuntimeException(e);
359 | }
360 | return javaScript.toString();
361 | }
362 | }
--------------------------------------------------------------------------------
/robotium-solo/src/main/java/com/robotium/solo/Zoomer.java:
--------------------------------------------------------------------------------
1 | package com.robotium.solo;
2 |
3 | import android.app.Instrumentation;
4 | import android.os.SystemClock;
5 | import android.view.MotionEvent;
6 | import android.view.MotionEvent.PointerProperties;
7 | import android.view.MotionEvent.PointerCoords;
8 | import android.graphics.PointF;
9 |
10 |
11 | class Zoomer {
12 |
13 | private final Instrumentation _instrument;
14 | public static final int GESTURE_DURATION_MS = 1000;
15 | public static final int EVENT_TIME_INTERVAL_MS = 10;
16 |
17 | public Zoomer(Instrumentation inst)
18 | {
19 | this._instrument = inst;
20 | }
21 |
22 | public void generateZoomGesture(PointF startPoint1, PointF startPoint2, PointF endPoint1, PointF endPoint2)
23 | {
24 |
25 | long downTime = SystemClock.uptimeMillis();
26 | long eventTime = SystemClock.uptimeMillis();
27 |
28 | float startX1 = startPoint1.x;
29 | float startY1 = startPoint1.y;
30 | float startX2 = startPoint2.x;
31 | float startY2 = startPoint2.y;
32 |
33 | float endX1 = endPoint1.x;
34 | float endY1 = endPoint1.y;
35 | float endX2 = endPoint2.x;
36 | float endY2 = endPoint2.y;
37 |
38 | //pointer 1
39 | float x1 = startX1;
40 | float y1 = startY1;
41 |
42 | //pointer 2
43 | float x2 = startX2;
44 | float y2 = startY2;
45 |
46 | PointerCoords[] pointerCoords = new PointerCoords[2];
47 | PointerCoords pc1 = new PointerCoords();
48 | PointerCoords pc2 = new PointerCoords();
49 | pc1.x = x1;
50 | pc1.y = y1;
51 | pc1.pressure = 1;
52 | pc1.size = 1;
53 | pc2.x = x2;
54 | pc2.y = y2;
55 | pc2.pressure = 1;
56 | pc2.size = 1;
57 | pointerCoords[0] = pc1;
58 | pointerCoords[1] = pc2;
59 |
60 | PointerProperties[] pointerProperties = new PointerProperties[2];
61 | PointerProperties pp1 = new PointerProperties();
62 | PointerProperties pp2 = new PointerProperties();
63 | pp1.id = 0;
64 | pp1.toolType = MotionEvent.TOOL_TYPE_FINGER;
65 | pp2.id = 1;
66 | pp2.toolType = MotionEvent.TOOL_TYPE_FINGER;
67 | pointerProperties[0] = pp1;
68 | pointerProperties[1] = pp2;
69 |
70 | MotionEvent event;
71 | // send the initial touches
72 | event = MotionEvent.obtain( downTime,
73 | eventTime,
74 | MotionEvent.ACTION_DOWN,
75 | 1,
76 | pointerProperties,
77 | pointerCoords,
78 | 0, 0, // metaState, buttonState
79 | 1, // x precision
80 | 1, // y precision
81 | 0, 0, 0, 0 ); // deviceId, edgeFlags, source, flags
82 | _instrument.sendPointerSync(event);
83 |
84 | event = MotionEvent.obtain( downTime,
85 | eventTime,
86 | MotionEvent.ACTION_POINTER_DOWN + (pp2.id << MotionEvent.ACTION_POINTER_INDEX_SHIFT),
87 | 2,
88 | pointerProperties,
89 | pointerCoords,
90 | 0, 0,
91 | 1,
92 | 1,
93 | 0, 0, 0, 0 );
94 | _instrument.sendPointerSync(event);
95 |
96 | int numMoves = GESTURE_DURATION_MS / EVENT_TIME_INTERVAL_MS;
97 |
98 | float stepX1 = (endX1 - startX1) / numMoves;
99 | float stepY1 = (endY1 - startY1) / numMoves;
100 | float stepX2 = (endX2 - startX2) / numMoves;
101 | float stepY2 = (endY2 - startY2) / numMoves;
102 |
103 | // send the zoom
104 | for (int i = 0; i < numMoves; i++)
105 | {
106 | eventTime += EVENT_TIME_INTERVAL_MS;
107 | pointerCoords[0].x += stepX1;
108 | pointerCoords[0].y += stepY1;
109 | pointerCoords[1].x += stepX2;
110 | pointerCoords[1].y += stepY2;
111 |
112 | event = MotionEvent.obtain( downTime,
113 | eventTime,
114 | MotionEvent.ACTION_MOVE,
115 | 2,
116 | pointerProperties,
117 | pointerCoords,
118 | 0, 0,
119 | 1,
120 | 1,
121 | 0, 0, 0, 0 );
122 | _instrument.sendPointerSync(event);
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------