calabash-android 0.2.11 → 0.2.12

Sign up to get free protection for your applications and to get access to all the features.
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
+ }