calabash-android 0.2.11 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. data/CHANGES.txt +6 -0
  2. data/features-skeleton/support/app_installation_hooks.rb +11 -3
  3. data/features-skeleton/support/app_life_cycle_hooks.rb +9 -0
  4. data/features-skeleton/support/hooks.rb +18 -2
  5. data/lib/calabash-android/operations.rb +1 -1
  6. data/lib/calabash-android/steps/assert_steps.rb +12 -0
  7. data/lib/calabash-android/steps/list_steps.rb +40 -0
  8. data/lib/calabash-android/steps/map_steps.rb +61 -0
  9. data/lib/calabash-android/steps/navigation_steps.rb +10 -1
  10. data/lib/calabash-android/steps/progress_steps.rb +5 -0
  11. data/lib/calabash-android/version.rb +2 -2
  12. data/test-server/AndroidManifest.xml +5 -5
  13. data/test-server/build.xml +4 -0
  14. data/test-server/instrumentation-backend/.gitignore +1 -0
  15. data/test-server/instrumentation-backend/AndroidManifest.xml +4 -3
  16. data/test-server/instrumentation-backend/project.properties +1 -1
  17. data/test-server/instrumentation-backend/src/com/jayway/android/robotium/solo/MapViewUtils.java +328 -0
  18. data/test-server/instrumentation-backend/src/com/jayway/android/robotium/solo/SoloEnhanced.java +97 -0
  19. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/InstrumentationBackend.java +3 -3
  20. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/TestHelpers.java +51 -1
  21. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/list/GetListItemProperties.java +195 -0
  22. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/list/GetListItemText.java +136 -0
  23. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/map/GetMapBounds.java +27 -0
  24. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/map/GetMapCenter.java +27 -0
  25. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/map/GetMapMarker.java +31 -0
  26. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/map/GetMapMarkers.java +48 -0
  27. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/map/GetMapZoom.java +19 -0
  28. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/map/PanMapTo.java +23 -0
  29. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/map/SetMapCenter.java +23 -0
  30. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/map/SetMapZoom.java +34 -0
  31. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/map/TapAwayFromMarkers.java +28 -0
  32. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/map/TapMapMarker.java +29 -0
  33. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/spinner/GetSelectedSpinnerItemText.java +36 -0
  34. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/text/AssertGridViewContainsNoDuplicates.java +72 -0
  35. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/text/GetTextById.java +42 -0
  36. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/view/AssertViewProperty.java +141 -0
  37. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/view/ClickOnViewById.java +1 -8
  38. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/view/GetActivityName.java +32 -0
  39. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/view/GetViewProperty.java +107 -0
  40. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/view/HasView.java +31 -0
  41. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/view/Press.java +1 -1
  42. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/view/SelectTab.java +110 -0
  43. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/wait/WaitForScreen.java +10 -6
  44. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/wait/WaitForTab.java +108 -0
  45. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/{view → wait}/WaitForView.java +1 -1
  46. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/{view → wait}/WaitForViewById.java +21 -11
  47. data/test-server/instrumentation-backend/src/sh/calaba/instrumentationbackend/actions/webview/QueryHelper.java +3 -3
  48. metadata +102 -88
@@ -1,3 +1,9 @@
1
+ 0.2.12:
2
+ Various small improvements and bug fixes.
3
+
4
+ Fixed support for scenario outline.
5
+ Added support for interacting with Google Maps.
6
+
1
7
  0.2.11:
2
8
  Added is_enabled action that determines if a view is enabled.
3
9
 
@@ -5,7 +5,12 @@ AfterConfiguration do |config|
5
5
  end
6
6
 
7
7
  Before do |scenario|
8
- feature_name = scenario.feature.name
8
+ @scenario_is_outline = (scenario.class == Cucumber::Ast::OutlineTable::ExampleRow)
9
+ if @scenario_is_outline
10
+ scenario = scenario.scenario_outline
11
+ end
12
+
13
+ feature_name = scenario.feature.title
9
14
  if FeatureNameMemory.feature_name != feature_name \
10
15
  or ENV["RESET_BETWEEN_SCENARIOS"] == "1"
11
16
  if ENV["RESET_BETWEEN_SCENARIOS"] == "1"
@@ -18,11 +23,14 @@ Before do |scenario|
18
23
  install_app(ENV["TEST_APP_PATH"])
19
24
  install_app(ENV["APP_PATH"])
20
25
  FeatureNameMemory.feature_name = feature_name
21
- end
26
+ FeatureNameMemory.invocation = 1
27
+ else
28
+ FeatureNameMemory.invocation += 1
29
+ end
22
30
  end
23
31
 
24
32
  FeatureNameMemory = Class.new
25
33
  class << FeatureNameMemory
26
34
  @feature_name = nil
27
- attr_accessor :feature_name
35
+ attr_accessor :feature_name, :invocation
28
36
  end
@@ -1,6 +1,15 @@
1
1
  require 'calabash-android/management/adb'
2
2
 
3
3
  Before do |scenario|
4
+ # John Gallagher provided the "scenario_is_outline" fix: https://groups.google.com/forum/?fromgroups#!topic/calabash-ios/ICA4f24eSsY
5
+ # ...there may be a better way of doing this...
6
+ @scenario_is_outline = (scenario.class == Cucumber::Ast::OutlineTable::ExampleRow)
7
+ if @scenario_is_outline
8
+ scenario = scenario.scenario_outline
9
+ # Still need to call connect_to_test_server...
10
+ elsif scenario.failed?
11
+ return #No need to start the server is anything before this has failed.
12
+ end
4
13
 
5
14
  return if scenario.failed? #No need to start the server is anything before this has failed.
6
15
  start_test_server_in_background
@@ -1,13 +1,29 @@
1
1
 
2
2
  Before do |scenario|
3
+ # https://groups.google.com/forum/?fromgroups#!topic/calabash-ios/ICA4f24eSsY
4
+ @scenario_is_outline = (scenario.class == Cucumber::Ast::OutlineTable::ExampleRow)
5
+ if @scenario_is_outline
6
+ scenario = scenario.scenario_outline
7
+ end
8
+
3
9
  StepCounter.step_index = 0
4
- StepCounter.step_line = scenario.raw_steps[StepCounter.step_index].line
10
+ # https://github.com/calabash/calabash-android/issues/58#issuecomment-6745642
11
+ if scenario.respond_to? :raw_steps
12
+ StepCounter.step_line = scenario.raw_steps[StepCounter.step_index].line
13
+ else
14
+ StepCounter.step_line = 0
15
+ end
5
16
  end
6
17
 
7
18
  AfterStep do |scenario|
8
19
  #Handle multiline steps
9
20
  StepCounter.step_index = StepCounter.step_index + 1
10
- StepCounter.step_line = scenario.raw_steps[StepCounter.step_index].line unless scenario.raw_steps[StepCounter.step_index].nil?
21
+ # https://github.com/calabash/calabash-android/issues/58#issuecomment-6745642
22
+ if scenario.respond_to? :raw_steps
23
+ StepCounter.step_line = scenario.raw_steps[StepCounter.step_index].line unless scenario.raw_steps[StepCounter.step_index].nil?
24
+ else
25
+ StepCounter.step_line = StepCounter.step_line + 1
26
+ end
11
27
  end
12
28
 
13
29
  StepCounter = Class.new
@@ -200,7 +200,7 @@ module Operations
200
200
  filename_prefix = FeatureNameMemory.feature_name.gsub(/\s+/, '_').downcase
201
201
  begin
202
202
  Timeout.timeout(30) do
203
- file_name = "#{path}/#{filename_prefix}_#{StepCounter.step_line}.png"
203
+ file_name = "#{path}/#{filename_prefix}_#{FeatureNameMemory.invocation}_#{StepCounter.step_line}.png"
204
204
  image = http("/screenshot")
205
205
  open(file_name ,"wb") { |file|
206
206
  file.write(image)
@@ -29,4 +29,16 @@ Then /^I don't see "([^\"]*)"$/ do |text|
29
29
  performAction('assert_text', text, false) #second param indicated that the text should _not_ be found
30
30
  end
31
31
 
32
+ # This step is more of an example or macro to be used within your own custom steps
33
+ # Generally, assert_view_property takes 3 args, but for if 'property'='compoundDrawables', the next arg should be 'left'/'right'/'top'/'bottom', followed by the expected drawable ID.
34
+ # @param view_id - the name of the view, eg: R.my_view_id
35
+ # @param property - eg: 'visibility' (visible/invisible/gone), 'drawable' (expected drawable ID)
36
+ Then /^the view with id "([^\"]*)" should have property "([^\"]*)" = "([^\"]*)"$/ do | view_id, property, value |
37
+ # get_view_property is also available: performAction( 'get_view_property', 'my_view', 'visibility')
38
+ performAction( 'assert_view_property', view_id, property, value )
39
+ end
32
40
 
41
+ Then /^the "([^\"]*)" activity should be open$/ do | expected_activity |
42
+ actual_activity = performAction('get_activity_name')['message']
43
+ raise "The current activity is #{actual_activity}" unless( actual_activity == expected_activity || actual_activity == expected_activity + 'Activity' )
44
+ end
@@ -0,0 +1,40 @@
1
+ # By default "get_list_item_text" returns an array of arrays of text for each entry in the first ListView
2
+ # The "get_list_item_text" action also supports:
3
+ # (all items of 2nd list) <code>performAction( 'get_list_item_text', '2' )</code>
4
+ # (1st item of 2nd list) <code>performAction( 'get_list_item_text', '2' , '1' )</code>
5
+ Then /^I should see following list:$/ do | expected_table |
6
+ result = performAction('get_list_item_text')
7
+ response_table = result['bonusInformation']
8
+ response_table.each_with_index do | row_data, index |
9
+ row_data = JSON.parse( row_data )
10
+ response_table[index] = row_data
11
+ end
12
+
13
+ expected_table.diff!(response_table)
14
+ end
15
+
16
+ # Note: This step is currently intended as more of an example rather than a rock-solid, well-tested step.
17
+ # (The server implementation works well for me, but my test steps that use it are application-specific)
18
+ # Similarly to the "get_list_item_text" action, the "get_list_item_properties" action defaults to
19
+ # all rows of the first ListView, but can be instructed to target a specific row (or all rows) of a specific ListView.
20
+ # Example TextView row: {"id":"title", "text":"My Title", "color":0, "background":0, "compoundDrawables":["left"]}
21
+ # Example ViewGroup row: {"children":[{"id":"title", "text":"My Title"}, {"id":"subtitle", "text":"Second line"}]}
22
+ # Example TableLayout row: {"cells":[{"column":0, "id":"colA", "text": "This is a Test"}]}
23
+ Then /^The "([^\"]*)" for row (\d+) should be "([^\"]*)"$/ do | view_id, row, value |
24
+ response = performAction( 'get_list_item_properties', '1' , row )['bonusInformation']
25
+ response = JSON.parse( response[0] )
26
+
27
+ if( response['children'] )
28
+ found_id = false
29
+ response['children'] each do | view |
30
+ if( view['id'] == view_id )
31
+ raise "Text is #{view['text']}, expected #{value}" unless( view['text'] == value )
32
+ found_id = true
33
+ end
34
+ end
35
+ raise "Could not find view with ID: #{view_id}" unless( found_id )
36
+ else
37
+ raise "ID is #{response['id']}, expected #{view_id}" unless( response['id'] == view_id )
38
+ raise "Text is #{response['text']}, expected #{view_id}" unless( response['text'] == value )
39
+ end
40
+ end
@@ -0,0 +1,61 @@
1
+ When /^I centre the map at (-?\d+\.\d+), (-?\d+\.\d+)$/ do | lat, lon |
2
+ performAction('set_map_center', lat, lon)
3
+ end
4
+
5
+ When /^I pan the map to (-?\d+\.\d+), (-?\d+\.\d+)$/ do | lat, lon |
6
+ performAction('pan_map_to', lat, lon)
7
+ performAction('wait', 1)
8
+ end
9
+
10
+ When /^(?:I )?set the map zoom level to (\d+)$/ do | zoom |
11
+ performAction('set_map_zoom', zoom)
12
+ sleep(0.2)
13
+ end
14
+
15
+ When /^(?:I )?zoom (in|out) on the map$/ do | zoom |
16
+ performAction('set_map_zoom', zoom)
17
+ sleep(0.2)
18
+ end
19
+
20
+ Then /^the map zoom level should be (\d+)$/ do | zoom |
21
+ result = performAction('get_map_zoom')
22
+ raise StandardError.new( "The map's zoom level should be #{zoom} but is #{result['message']}" ) unless zoom.eql?( result['message'] )
23
+ end
24
+
25
+ When /^I tap the map marker "([^\"]*)"$/ do | marker_title |
26
+ performAction('tap_map_marker_by_title', marker_title, 60000)
27
+ end
28
+
29
+ When /^I double tap the map marker "([^\"]*)"$/ do | marker_title |
30
+ performAction('tap_map_marker_by_title', marker_title, 60000)
31
+ sleep(0.4)
32
+ performAction('tap_map_marker_by_title', marker_title, 100)
33
+ end
34
+
35
+ When /^I tap away from the markers$/ do
36
+ performAction('tap_map_away_from_markers')
37
+ end
38
+
39
+ Then /^I should see the following markers:$/ do | marker_table |
40
+ verify_map_markers( marker_table )
41
+ end
42
+
43
+ Then /^the map should be centred at (-?\d+\.\d+), (-?\d+\.\d+)$/ do | lat, lon |
44
+ result = performAction('get_map_center')
45
+ bonus_info = result['bonusInformation']
46
+ actual_lat = bonus_info[0].to_f
47
+ actual_lon = bonus_info[1].to_f
48
+ lat = lat.to_f
49
+ lon = lon.to_f
50
+ tol = 0.00001
51
+ if( (lat - actual_lat).abs > tol || (lon - actual_lon).abs > tol )
52
+ raise StandardError.new( "The map should have been centred on: #{lat},#{lon} but was actually centred on #{bonus_info.inspect}" )
53
+ end
54
+ end
55
+
56
+ Then /^the map marker "([^\"]*)" should be highlighted$/ do | marker_title |
57
+ result = performAction('get_map_marker', marker_title)
58
+ result = result['message']
59
+ result = JSON.parse( result )
60
+ raise StandardError.new( "The marker '#{marker_title}' was found, but is not highlighted" ) unless result['focused']
61
+ end
@@ -23,10 +23,19 @@ Then /^I select "([^\"]*)" from the menu$/ do |item|
23
23
  performAction('select_from_menu', item)
24
24
  end
25
25
 
26
+ Then /^I select tab number (\d+)$/ do | tab |
27
+ performAction('select_tab', tab)
28
+ end
29
+
30
+ # @param - the "tag" associated with the tab, or the text within the tab label
31
+ Then /^I select the "([^\"]*)" tab$/ do | tab |
32
+ performAction('select_tab', tab)
33
+ end
34
+
26
35
  Then /^I scroll down$/ do
27
36
  performAction('scroll_down')
28
37
  end
29
38
 
30
39
  Then /^I scroll up$/ do
31
40
  performAction('scroll_up')
32
- end
41
+ end
@@ -64,3 +64,8 @@ end
64
64
  Then /^I wait up to (\d+) seconds for the "([^\"]*)" screen to appear$/ do |timeout, text|
65
65
  performAction('wait_for_screen', text, timeout)
66
66
  end
67
+
68
+ # @param - the "tag" associated with the tab, or the text within the tab label
69
+ Then /^I wait for the "([^\"]*)" tab to appear$/ do | tab |
70
+ performAction('wait_for_tab', tab)
71
+ end
@@ -1,6 +1,6 @@
1
1
  module Calabash
2
2
  module Android
3
- VERSION = "0.2.11"
4
- FRAMEWORK_VERSION = "0.2.11"
3
+ VERSION = "0.2.12"
4
+ FRAMEWORK_VERSION = "0.2.12"
5
5
  end
6
6
  end
@@ -1,13 +1,13 @@
1
1
  <?xml version="1.0" encoding="utf-8"?>
2
2
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
3
  package="#TESTED_APP_PACKAGE#.test"
4
- android:versionCode="1"
5
- android:versionName="1.0">
4
+ android:versionCode="3"
5
+ android:versionName="0.3.0">
6
6
  <application android:label="instrumentation_backend">
7
-
8
- <uses-library android:name="android.test.runner" />
7
+ <uses-library android:name="android.test.runner" />
8
+ <uses-library android:name="com.google.android.maps" android:required="false" />
9
9
  </application>
10
10
  <uses-sdk android:minSdkVersion="4" />
11
11
  <instrumentation android:targetPackage="#TESTED_APP_PACKAGE#" android:name="sh.calaba.instrumentationbackend.CalabashInstrumentationTestRunner" />
12
12
  <uses-permission android:name="android.permission.INTERNET" />
13
- </manifest>
13
+ </manifest>
@@ -89,6 +89,10 @@
89
89
  <target name="-prepare.testserver" description="Makes sure the testserver matches the tested application by looking at its manifest file">
90
90
  <copy todir="${staging.dir}">
91
91
  <fileset dir="instrumentation-backend"/>
92
+ </copy>
93
+ <copy todir="${staging.dir}/libs">
94
+ <!-- There may be a better way of doing this, but it works -->
95
+ <fileset dir="${env.ANDROID_HOME}/add-ons/addon-google_apis-google-${android.api.level}/libs"/>
92
96
  </copy>
93
97
  <copy todir="${staging.dir}/assets">
94
98
  <fileset dir="${calabashjs.dir}"/>
@@ -1,13 +1,14 @@
1
1
  <?xml version="1.0" encoding="utf-8"?>
2
2
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
3
  package="sh.calaba.test"
4
- android:versionCode="1"
5
- android:versionName="1.0" >
4
+ android:versionCode="3"
5
+ android:versionName="0.3.0" >
6
6
 
7
7
  <uses-sdk android:minSdkVersion="6" />
8
8
 
9
9
  <application android:label="CalabashTestServer" >
10
10
  <uses-library android:name="android.test.runner" />
11
+ <uses-library android:name="com.google.android.maps" android:required="false" />
11
12
  </application>
12
13
 
13
14
  <instrumentation
@@ -18,4 +19,4 @@
18
19
  <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
19
20
  <uses-permission android:name="android.permission.INTERNET" />
20
21
 
21
- </manifest>
22
+ </manifest>
@@ -8,4 +8,4 @@
8
8
  # project structure.
9
9
 
10
10
  # Project target.
11
- target=android-15
11
+ target=Google Inc.:Google APIs:15
@@ -0,0 +1,328 @@
1
+ package com.jayway.android.robotium.solo;
2
+
3
+ import java.util.ArrayList;
4
+ import java.util.List;
5
+
6
+ import android.app.Instrumentation;
7
+ import android.graphics.PixelFormat;
8
+ import android.graphics.Point;
9
+ import android.graphics.Rect;
10
+ import android.graphics.Region;
11
+ import android.os.SystemClock;
12
+ import android.util.Log;
13
+
14
+ import com.google.android.maps.GeoPoint;
15
+ import com.google.android.maps.ItemizedOverlay;
16
+ import com.google.android.maps.MapView;
17
+ import com.google.android.maps.Overlay;
18
+ import com.google.android.maps.OverlayItem;
19
+ import com.google.android.maps.Projection;
20
+
21
+ /**
22
+ * @author Nicholas Albion
23
+ */
24
+ public class MapViewUtils {
25
+ private final Instrumentation inst;
26
+ private final ViewFetcher viewFetcher;
27
+ private final Sleeper sleeper;
28
+ private final Waiter waiter;
29
+
30
+ /**
31
+ * Constructs this object.
32
+ *
33
+ * @param inst the {@code Instrumentation} instance.
34
+ * @param viewFetcher the {@code ViewFetcher} instance.
35
+ * @param sleeper the {@code Sleeper} instance
36
+ */
37
+ public MapViewUtils( Instrumentation inst, ViewFetcher viewFetcher, Sleeper sleeper, Waiter waiter ) {
38
+ this.inst = inst;
39
+ this.viewFetcher = viewFetcher;
40
+ this.sleeper = sleeper;
41
+ this.waiter = waiter;
42
+ }
43
+
44
+ private MapView getMapView() {
45
+ return waiter.waitForAndGetView( 0, MapView.class );
46
+ // final ArrayList<View> viewList = RobotiumUtils.removeInvisibleViews(viewFetcher.getAllViews(true));
47
+ // ArrayList<MapView> maps = RobotiumUtils.filterViews( MapView.class, viewList );
48
+ // if( maps.size() == 0 ) {
49
+ // return null;
50
+ // }
51
+ // return maps.get(0);
52
+ }
53
+
54
+ /**
55
+ * @param lat
56
+ * @param lon
57
+ */
58
+ public void setCenter( double lat, double lon ) {
59
+ MapView mapView = getMapView();
60
+ mapView.getController().setCenter( new GeoPoint((int)(lat * 1E6), (int)(lon * 1E6)) );
61
+ }
62
+
63
+ public double[] getMapCenter() {
64
+ MapView mapView = getMapView();
65
+ GeoPoint center = mapView.getMapCenter();
66
+ return new double[] { center.getLatitudeE6() / 1E6, center.getLongitudeE6() / 1E6 };
67
+ }
68
+
69
+ /**
70
+ * @param lat
71
+ * @param lon
72
+ */
73
+ public void panTo( double lat, double lon ) {
74
+ MapView mapView = getMapView();
75
+ mapView.getController().animateTo( new GeoPoint((int)(lat * 1E6), (int)(lon * 1E6)) );
76
+ }
77
+
78
+ public int getZoom() {
79
+ MapView mapView = getMapView();
80
+ return mapView.getZoomLevel();
81
+ }
82
+
83
+ public int setZoom( int zoomLevel ) {
84
+ MapView mapView = getMapView();
85
+ mapView.getController().stopAnimation(true);
86
+ return mapView.getController().setZoom( zoomLevel );
87
+ }
88
+
89
+ public boolean zoomIn() {
90
+ MapView mapView = getMapView();
91
+ return mapView.getController().zoomIn();
92
+ }
93
+
94
+ public boolean zoomOut() {
95
+ MapView mapView = getMapView();
96
+ return mapView.getController().zoomOut();
97
+ }
98
+
99
+ /**
100
+ * @return [top, right, bottom, left] in decimal degrees
101
+ */
102
+ public List<String> getBounds() {
103
+ MapView mapView = getMapView();
104
+ GeoPoint center = mapView.getMapCenter();
105
+ int latCtr = center.getLatitudeE6();
106
+ int lonCtr = center.getLongitudeE6();
107
+ int latSpan = mapView.getLatitudeSpan() >> 1;
108
+ int lonSpan = mapView.getLongitudeSpan() >> 1;
109
+
110
+ Log.i("MapView", "latSpan: " + latSpan);
111
+
112
+ ArrayList<String> bounds = new ArrayList<String>(4);
113
+ bounds.add( Double.toString( (latCtr + latSpan) / 1E6 ) );
114
+ bounds.add( Double.toString( (lonCtr + lonSpan) / 1E6 ) );
115
+ bounds.add( Double.toString( (latCtr - latSpan) / 1E6 ) );
116
+ bounds.add( Double.toString( (lonCtr - lonSpan) / 1E6 ) );
117
+ return bounds;
118
+ }
119
+
120
+ /**
121
+ * @return A list of JSON strings representing the markers.
122
+ * @see #getMarkerItem(String)
123
+ * @see MapViewUtils#toString(OverlayItem, boolean)
124
+ */
125
+ public List<String> getMarkerItems() {
126
+ ArrayList<String> markers = new ArrayList<String>();
127
+
128
+ MapView mapView = getMapView();
129
+ for( Overlay overlay : mapView.getOverlays() ) {
130
+ if( overlay instanceof ItemizedOverlay ) {
131
+ @SuppressWarnings("rawtypes")
132
+ ItemizedOverlay markerOverlay = ((ItemizedOverlay)overlay);
133
+ int noOfMarkers = markerOverlay.size();
134
+ int lastFocused = markerOverlay.getLastFocusedIndex();
135
+
136
+ Log.i("MapView", "Overlay " + markerOverlay + " has " + noOfMarkers + " markers");
137
+ markers.ensureCapacity( markers.size() + noOfMarkers );
138
+ for( int i = 0; i < noOfMarkers; i++ ) {
139
+ OverlayItem item = markerOverlay.getItem(i);
140
+ String str = toString( item, lastFocused == i );
141
+ markers.add( str );
142
+ }
143
+ }
144
+ }
145
+
146
+ Log.i("MapView", "Sending response with " + markers.size() + " markers");
147
+ return markers;
148
+ }
149
+
150
+ /**
151
+ * @param title
152
+ * @return null or (for example) {"latitude":-33.123456, "longitude":151.123456, "title":"My Marker", "snippet":"More Info about my marker"}
153
+ */
154
+ public String getMarkerItem( String title ) {
155
+ ArrayList<String> markers = new ArrayList<String>();
156
+
157
+ MapView mapView = getMapView();
158
+ for( Overlay overlay : mapView.getOverlays() ) {
159
+ if( overlay instanceof ItemizedOverlay ) {
160
+ @SuppressWarnings("rawtypes")
161
+ ItemizedOverlay markerOverlay = ((ItemizedOverlay)overlay);
162
+ int noOfMarkers = markerOverlay.size();
163
+ int lastFocused = markerOverlay.getLastFocusedIndex();
164
+
165
+ Log.i("MapView", "Overlay " + markerOverlay + " has " + noOfMarkers + " markers");
166
+ markers.ensureCapacity( markers.size() + noOfMarkers );
167
+ for( int i = 0; i < noOfMarkers; i++ ) {
168
+ OverlayItem item = markerOverlay.getItem(i);
169
+
170
+ if( title.equals(item.getTitle()) ) {
171
+ return toString( item, lastFocused == i );
172
+ }
173
+ }
174
+ }
175
+ }
176
+ return null;
177
+ }
178
+
179
+ /**
180
+ * @param title
181
+ * @param timeout in ms
182
+ * @return true if the marker was found and tapped upon
183
+ */
184
+ public boolean tapMarkerItem( String title, long timeout ) {
185
+ final long endTime = SystemClock.uptimeMillis() + timeout;
186
+ Log.i("TapMarker", "Looking for marker '" + title + "'");
187
+
188
+ while( SystemClock.uptimeMillis() < endTime ) {
189
+ MapView mapView = getMapView();
190
+ for( Overlay overlay : mapView.getOverlays() ) {
191
+ if( overlay instanceof ItemizedOverlay ) {
192
+ @SuppressWarnings("rawtypes")
193
+ ItemizedOverlay markerOverlay = ((ItemizedOverlay)overlay);
194
+ int noOfMarkers = markerOverlay.size();
195
+ for( int i = 0; i < noOfMarkers; i++ ) {
196
+ OverlayItem item = markerOverlay.getItem(i);
197
+ String itemTitle = item.getTitle();
198
+ // Log.i("TapMarker", " item title: " + itemTitle);
199
+ if( title.equals( itemTitle ) ) {
200
+ markerOverlay.onTap( item.getPoint(), mapView );
201
+ return true;
202
+ }
203
+ }
204
+ }
205
+ }
206
+
207
+ Log.i("TapMarker", "Could not find marker '" + title + "', try again in a moment");
208
+ sleeper.sleep();
209
+ }
210
+
211
+ Log.i("TapMarker", "Nope, could not find marker '" + title + "'");
212
+ return false;
213
+ }
214
+
215
+ /**
216
+ * @param step - number of pixels to step when searching for an empty piece of screen
217
+ * @return true if it was possible to tap on the screen without tapping on a marker
218
+ */
219
+ @SuppressWarnings("rawtypes")
220
+ public boolean tapAwayFromMarkerItems( int step ) {
221
+ MapView mapView = getMapView();
222
+ Projection proj = mapView.getProjection();
223
+ ItemizedOverlay overlayToTap = null;
224
+ Point markerPoint = new Point();
225
+ int w = mapView.getWidth();
226
+ int h = mapView.getHeight();
227
+
228
+ for( int x = 0; x < w; x += step ) {
229
+ NEXT_Y:
230
+ for( int y = 0; y < h; y += step ) {
231
+ boolean tappedMarkerAtPoint = false;
232
+ for( Overlay overlay : mapView.getOverlays() ) {
233
+ if( overlay instanceof ItemizedOverlay ) {
234
+ ItemizedOverlay markerOverlay = ((ItemizedOverlay)overlay);
235
+ // if( markerOverlay.onTap( geoPoint, mapView ) ) {
236
+ // tappedMarkerAtPoint = true;
237
+ // break;
238
+ // }
239
+ int noOfMarkers = markerOverlay.size();
240
+ for( int i = 0; i < noOfMarkers; i++ ) {
241
+ OverlayItem item = markerOverlay.getItem(i);
242
+ proj.toPixels( item.getPoint(), markerPoint );
243
+ Rect markerBounds = item.getMarker(0).getBounds();
244
+ markerBounds.offset( markerPoint.x, markerPoint.y );
245
+
246
+ Log.d("TapAwayFromMarkers", "markerBounds: " + markerBounds);
247
+ if( markerBounds.contains(x, y) ) {
248
+ Log.d("TapAwayFromMarkers", "Tapping at " + x + ", " + y + " would tap on " + item.getTitle());
249
+ continue NEXT_Y;
250
+ }
251
+ }
252
+ overlayToTap = markerOverlay;
253
+ }
254
+ }
255
+
256
+ if( tappedMarkerAtPoint == false && overlayToTap != null ) {
257
+ Log.i("TapAwayFromMarkers", "Tapping away from markers at " + x + ", " + y);
258
+ GeoPoint geoPoint = proj.fromPixels(x, y);
259
+ overlayToTap.onTap(geoPoint, mapView);
260
+ return true;
261
+ }
262
+ }
263
+ }
264
+
265
+ Log.i("TapMarker", "Nope, could not avoid tapping on any markers");
266
+ return false;
267
+ }
268
+
269
+ /**
270
+ * @param item
271
+ * @param isLastFocused
272
+ * @return eg: {"latitude":-33.123456, "longitude":151.123456, "title":"My Marker", "snippet":"More Info about my marker"}
273
+ */
274
+ private String toString( OverlayItem item, boolean isLastFocused ) {
275
+ GeoPoint point = item.getPoint();
276
+ StringBuilder str = new StringBuilder("{\"latitude\":\"").append(Double.toString( point.getLatitudeE6() / 1E6 ))
277
+ .append("\", \"longitude\":\"").append( Double.toString( point.getLongitudeE6() / 1E6 ))
278
+ .append("\", \"title\":\"").append( item.getTitle().replaceAll("\"", "\\\"") );
279
+ String snippet = item.getSnippet();
280
+ if( snippet != null && snippet.length() != 0 ) {
281
+ str.append("\", \"snippet\":\"").append( snippet.replaceAll("\"", "\\\"") );
282
+ }
283
+ if( isLastFocused ) {
284
+ str.append("\", \"focused\":true");
285
+ } else {
286
+ str.append("\"");
287
+ }
288
+
289
+ appendTransparency( str, item );
290
+
291
+ str.append("}");
292
+ return str.toString();
293
+ }
294
+
295
+ private void appendTransparency( StringBuilder str, OverlayItem item ) {
296
+ StringBuilder transparencyStr = new StringBuilder();
297
+ appendTransparency( transparencyStr, item, "default", 0 );
298
+ appendTransparency( transparencyStr, item, "focused", OverlayItem.ITEM_STATE_FOCUSED_MASK );
299
+ appendTransparency( transparencyStr, item, "pressed", OverlayItem.ITEM_STATE_PRESSED_MASK );
300
+ appendTransparency( transparencyStr, item, "selected", OverlayItem.ITEM_STATE_SELECTED_MASK );
301
+
302
+ if( transparencyStr.length() != 0 ) {
303
+ transparencyStr.deleteCharAt( transparencyStr.length() - 1 );
304
+ str.append(", \"transparency\":{").append(transparencyStr).append("}");
305
+ }
306
+ }
307
+
308
+ private void appendTransparency( StringBuilder str, OverlayItem item, String stateName, int state ) {
309
+ switch( item.getMarker( OverlayItem.ITEM_STATE_FOCUSED_MASK ).getOpacity() ) {
310
+ case PixelFormat.UNKNOWN: break;
311
+ case PixelFormat.TRANSPARENT:
312
+ str.append("\"").append(stateName).append("\":\"transparent\",");
313
+ return;
314
+ case PixelFormat.TRANSLUCENT:
315
+ str.append("\"").append(stateName).append("\":\"translucent\",");
316
+ return;
317
+ case PixelFormat.OPAQUE:
318
+ str.append("\"").append(stateName).append("\":\"opaque\",");
319
+ return;
320
+ }
321
+
322
+
323
+ Region region = item.getMarker(state).getTransparentRegion();
324
+ if( region != null ) {
325
+ str.append("\"").append(stateName).append("\":true,");
326
+ }
327
+ }
328
+ }